44capability matrix is documented behaviour rather than schema. When OpenAI ships
55a new model family, the registry in this file should be updated to match.
66
7+ The canonical source for reasoning-effort behaviour is the docstring on the
8+ ``Reasoning.effort`` parameter in ``src/openai/types/shared/reasoning.py``.
9+
710Example:
811 >>> from openai import get_model_capabilities
912 >>> caps = get_model_capabilities("gpt-5.4-mini")
1013 >>> caps.supports_reasoning
1114 True
1215 >>> caps.reasoning_effort_options
13- ('none', 'minimal', ' low', 'medium', 'high', 'xhigh')
16+ ('none', 'low', 'medium', 'high', 'xhigh')
1417 >>> get_model_capabilities("gpt-4.1").supports_reasoning
1518 False
1619"""
1720
1821from __future__ import annotations
1922
23+ import re
2024from typing import Any , Tuple , Optional
2125from dataclasses import dataclass
2226
@@ -81,28 +85,43 @@ def _caps(
8185# ---------------------------------------------------------------------------
8286# Family registry.
8387#
84- # Entries are matched by longest-prefix against the model string, with chat /
85- # search variants checked via the suffix test in `get_model_capabilities`.
88+ # Entries are matched by longest *segment* prefix against the model string
89+ # (i.e. the registered prefix must either equal the model exactly or be
90+ # followed by a `-`), with chat / search variants checked via the suffix test
91+ # in `get_model_capabilities`.
8692#
8793# When OpenAI ships a new family, add an entry here. Order within this tuple
8894# does not matter; the lookup picks the longest matching prefix.
8995# ---------------------------------------------------------------------------
9096
91- # Effort scales reused across families.
97+ # Effort scales reused across families. These mirror the prose in
98+ # `src/openai/types/shared/reasoning.py`:
99+ #
100+ # - All models *before* gpt-5.1 default to medium and do NOT support `none`.
101+ # gpt-5 base accepts `minimal/low/medium/high`.
102+ # - gpt-5.1 supports `none/low/medium/high` (no `minimal`).
103+ # - `xhigh` is supported for models *after* gpt-5.1-codex-max, i.e.
104+ # gpt-5.2 onward, on top of the gpt-5.1 effort scale.
105+ # - gpt-5-pro defaults to and only supports `high`.
92106_EFFORT_O_SERIES : Tuple [ReasoningEffort , ...] = ("low" , "medium" , "high" )
93- _EFFORT_GPT5 : Tuple [ReasoningEffort , ...] = ("minimal" , "low" , "medium" , "high" )
94- _EFFORT_GPT5_1 : Tuple [ReasoningEffort , ...] = ("none" , "minimal" , "low" , "medium" , "high" )
95- _EFFORT_GPT5_4 : Tuple [ReasoningEffort , ...] = ("none" , "minimal" , "low" , "medium" , "high" , "xhigh" )
107+ _EFFORT_GPT5_BASE : Tuple [ReasoningEffort , ...] = ("minimal" , "low" , "medium" , "high" )
108+ _EFFORT_GPT5_1 : Tuple [ReasoningEffort , ...] = ("none" , "low" , "medium" , "high" )
109+ _EFFORT_GPT5_2_PLUS : Tuple [ReasoningEffort , ...] = ("none" , "low" , "medium" , "high" , "xhigh" )
110+ _EFFORT_GPT5_PRO : Tuple [ReasoningEffort , ...] = ("high" ,)
96111
97112
98113_FAMILIES : Tuple [ModelCapabilities , ...] = (
99114 # gpt-5.x reasoning models. Temperature is rejected unless you use a
100115 # `-chat-latest` variant or set `reasoning_effort="none"`.
101- _caps ("gpt-5.4" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_4 ),
102- _caps ("gpt-5.3" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_1 ),
103- _caps ("gpt-5.2" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_1 ),
116+ #
117+ # gpt-5-pro is a high-only reasoning model and must be registered as its
118+ # own family so longest-prefix matching beats the generic `gpt-5`.
119+ _caps ("gpt-5-pro" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_PRO ),
120+ _caps ("gpt-5.4" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_2_PLUS ),
121+ _caps ("gpt-5.3" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_2_PLUS ),
122+ _caps ("gpt-5.2" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_2_PLUS ),
104123 _caps ("gpt-5.1" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_1 ),
105- _caps ("gpt-5" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5 ),
124+ _caps ("gpt-5" , supports_temperature = False , supports_reasoning = True , reasoning_effort_options = _EFFORT_GPT5_BASE ),
106125 # Classic chat families.
107126 _caps ("gpt-4.1" , supports_temperature = True , supports_reasoning = False , reasoning_effort_options = None ),
108127 _caps ("gpt-4o" , supports_temperature = True , supports_reasoning = False , reasoning_effort_options = None ),
@@ -142,9 +161,22 @@ def _caps(
142161)
143162
144163
145- # Suffixes that override family defaults. A model ending in one of these is
146- # treated as a non-reasoning chat variant regardless of its family.
147- _CHAT_VARIANT_SUFFIXES : Tuple [str , ...] = ("-chat-latest" , "-search-preview" )
164+ # Matches `*-chat-latest` and `*-search-preview` (with an optional trailing
165+ # `-YYYY-MM-DD` snapshot date), e.g. `gpt-4o-search-preview-2025-03-11`.
166+ # These variants behave like classic chat models regardless of family.
167+ _CHAT_VARIANT_RE = re .compile (r"-(?:chat-latest|search-preview)(?:-\d{4}-\d{2}-\d{2})?$" )
168+
169+
170+ def _matches_family (model : str , family : str ) -> bool :
171+ """Match ``model`` against a family prefix at a segment boundary.
172+
173+ A model matches when it equals the family exactly or extends it with a
174+ ``-`` separator. This prevents collisions like ``gpt-5.10`` being
175+ misclassified as ``gpt-5.1``.
176+ """
177+ if model == family :
178+ return True
179+ return model .startswith (family + "-" )
148180
149181
150182def get_model_capabilities (model : str ) -> Optional [ModelCapabilities ]:
@@ -155,11 +187,17 @@ def get_model_capabilities(model: str) -> Optional[ModelCapabilities]:
155187 decide which controls to render) but is only as fresh as this module's
156188 registry. New model families need a corresponding entry here.
157189
190+ Matching is segment-aware: the registered prefix must either equal the
191+ model exactly or be followed by a ``-`` separator. ``"gpt-5.10"`` will
192+ therefore *not* match the ``gpt-5.1`` family and ``"o1-previewed"`` will
193+ not match ``o1-preview``; both fall through to ``None`` so callers treat
194+ them as unknown.
195+
158196 Args:
159197 model: A model identifier such as ``"gpt-5.4-mini"`` or
160198 ``"gpt-4o-2024-08-06"``. Date suffixes and size variants
161- (``-mini``, ``-nano``) are handled automatically by longest-prefix
162- matching.
199+ (``-mini``, ``-nano``, ``-pro`` ) are handled automatically by
200+ longest-prefix matching.
163201
164202 Returns:
165203 A :class:`ModelCapabilities` describing the model, or ``None`` if no
@@ -169,11 +207,15 @@ def get_model_capabilities(model: str) -> Optional[ModelCapabilities]:
169207
170208 Example:
171209 >>> get_model_capabilities("gpt-5.4-mini").reasoning_effort_options
172- ('none', 'minimal', ' low', 'medium', 'high', 'xhigh')
210+ ('none', 'low', 'medium', 'high', 'xhigh')
173211 >>> get_model_capabilities("gpt-5-chat-latest").supports_temperature
174212 True
175213 >>> get_model_capabilities("gpt-5").supports_temperature
176214 False
215+ >>> get_model_capabilities("gpt-5-pro").reasoning_effort_options
216+ ('high',)
217+ >>> get_model_capabilities("gpt-5.10") is None
218+ True
177219 >>> get_model_capabilities("nonexistent-model") is None
178220 True
179221 """
@@ -184,11 +226,12 @@ def get_model_capabilities(model: str) -> Optional[ModelCapabilities]:
184226 if not isinstance (candidate , str ) or not candidate :
185227 return None
186228
187- # Longest matching prefix wins so that "gpt-5.4" beats "gpt-5", and "o1-pro"
188- # beats "o1".
229+ # Longest matching family wins so that "gpt-5.4" beats "gpt-5", and
230+ # "o1-pro" beats "o1". Segment boundary check rejects things like
231+ # "gpt-5.10" claiming to be "gpt-5.1".
189232 best : Optional [ModelCapabilities ] = None
190233 for entry in _FAMILIES :
191- if not candidate . startswith ( entry .family ):
234+ if not _matches_family ( candidate , entry .family ):
192235 continue
193236 if best is None or len (entry .family ) > len (best .family ):
194237 best = entry
@@ -199,8 +242,10 @@ def get_model_capabilities(model: str) -> Optional[ModelCapabilities]:
199242 # Chat / search variants override family defaults: gpt-5-chat-latest is a
200243 # non-reasoning model even though gpt-5* normally is one. We still report
201244 # the family so callers can group e.g. "gpt-5.2-chat-latest" with
202- # "gpt-5.2".
203- if any (candidate .endswith (suffix ) for suffix in _CHAT_VARIANT_SUFFIXES ):
245+ # "gpt-5.2". The regex tolerates a trailing date snapshot like
246+ # `-2025-03-11` so dated variants like `gpt-4o-search-preview-2025-03-11`
247+ # are recognized too.
248+ if _CHAT_VARIANT_RE .search (candidate ):
204249 return ModelCapabilities (
205250 family = best .family ,
206251 supports_temperature = True ,
0 commit comments