Merge pull request #22981 from BerriAI/litellm_reasoning-effort-dict-normalization

feat(openai): normalize reasoning_effort dict to string for chat completion API
This commit is contained in:
Sameer Kankute 2026-03-07 00:37:07 +05:30 committed by GitHub
commit 11e2bdbaec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 241 additions and 83 deletions

View File

@ -1,6 +1,6 @@
"""Support for OpenAI gpt-5 model family."""
from typing import Optional
from typing import Optional, Union
import litellm
from litellm.utils import _supports_factory
@ -8,6 +8,23 @@ from litellm.utils import _supports_factory
from .gpt_transformation import OpenAIGPTConfig
def _normalize_reasoning_effort_for_chat_completion(
value: Union[str, dict, None],
) -> Optional[str]:
"""Convert reasoning_effort to the string format expected by OpenAI chat completion API.
The chat completion API expects a simple string: 'none', 'low', 'medium', 'high', or 'xhigh'.
Config/deployments may pass the Responses API format: {'effort': 'high', 'summary': 'detailed'}.
"""
if value is None:
return None
if isinstance(value, str):
return value
if isinstance(value, dict) and "effort" in value:
return value["effort"]
return None
class OpenAIGPT5Config(OpenAIGPTConfig):
"""Configuration for gpt-5 models including GPT-5-Codex variants.
@ -47,6 +64,12 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
model_name = model.split("/")[-1]
return model_name.startswith("gpt-5.2") or model_name.startswith("gpt-5.4")
@classmethod
def is_model_gpt_5_4_model(cls, model: str) -> bool:
"""Check if the model is a gpt-5.4 variant (including pro)."""
model_name = model.split("/")[-1]
return model_name.startswith("gpt-5.4")
@classmethod
def _supports_reasoning_effort_level(cls, model: str, level: str) -> bool:
"""Check if the model supports a specific reasoning_effort level.
@ -127,10 +150,20 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
drop_params=drop_params,
)
reasoning_effort = (
# Normalize reasoning_effort: chat completion API expects a string, not a dict
# (e.g. {'effort': 'high', 'summary': 'detailed'} -> 'high')
raw_reasoning_effort = (
non_default_params.get("reasoning_effort")
or optional_params.get("reasoning_effort")
)
normalized = _normalize_reasoning_effort_for_chat_completion(raw_reasoning_effort)
if raw_reasoning_effort is not None and normalized is not None:
if "reasoning_effort" in non_default_params:
non_default_params["reasoning_effort"] = normalized
if "reasoning_effort" in optional_params:
optional_params["reasoning_effort"] = normalized
reasoning_effort = normalized or raw_reasoning_effort
if reasoning_effort is not None and reasoning_effort == "xhigh":
if not self._supports_reasoning_effort_level(model, "xhigh"):
if litellm.drop_params or drop_params:
@ -152,6 +185,17 @@ class OpenAIGPT5Config(OpenAIGPTConfig):
"max_tokens"
)
# gpt-5.4: function calls not supported when reasoning_effort != "none"
# Drop reasoning_effort when tools are present (small minority of volume)
if self.is_model_gpt_5_4_model(model):
has_tools = bool(
non_default_params.get("tools") or optional_params.get("tools")
)
if has_tools and reasoning_effort not in (None, "none"):
non_default_params.pop("reasoning_effort", None)
optional_params.pop("reasoning_effort", None)
reasoning_effort = None
# gpt-5.1/5.2 support logprobs, top_p, top_logprobs only when reasoning_effort="none"
supports_none = self._supports_reasoning_effort_level(model, "none")
if supports_none:

View File

@ -20935,16 +20935,22 @@
},
"gpt-5.4": {
"cache_read_input_token_cost": 2.5e-07,
"cache_read_input_token_cost_above_272k_tokens": 5e-07,
"cache_read_input_token_cost_priority": 5e-07,
"cache_read_input_token_cost_above_272k_tokens_priority": 1e-06,
"input_cost_per_token": 2.5e-06,
"input_cost_per_token_above_272k_tokens": 5e-06,
"input_cost_per_token_priority": 5e-06,
"input_cost_per_token_above_272k_tokens_priority": 1e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"output_cost_per_token_above_272k_tokens": 2.25e-05,
"output_cost_per_token_priority": 2.25e-05,
"output_cost_per_token_above_272k_tokens_priority": 3.375e-05,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -20973,16 +20979,22 @@
},
"gpt-5.4-2026-03-05": {
"cache_read_input_token_cost": 2.5e-07,
"cache_read_input_token_cost_above_272k_tokens": 5e-07,
"cache_read_input_token_cost_priority": 5e-07,
"cache_read_input_token_cost_above_272k_tokens_priority": 1e-06,
"input_cost_per_token": 2.5e-06,
"input_cost_per_token_above_272k_tokens": 5e-06,
"input_cost_per_token_priority": 5e-06,
"input_cost_per_token_above_272k_tokens_priority": 1e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"output_cost_per_token_above_272k_tokens": 2.25e-05,
"output_cost_per_token_priority": 2.25e-05,
"output_cost_per_token_above_272k_tokens_priority": 3.375e-05,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -21005,84 +21017,26 @@
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_service_tier": true,
"supports_vision": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true
},
"gpt-5.4-pro": {
"cache_read_input_token_cost": 2e-06,
"input_cost_per_token": 2e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"output_cost_per_token": 0.00012,
"supported_endpoints": [
"/v1/responses"
],
"supported_modalities": [
"text",
"image"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
},
"gpt-5.4-pro-2026-03-05": {
"cache_read_input_token_cost": 2e-06,
"input_cost_per_token": 2e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"output_cost_per_token": 0.00012,
"supported_endpoints": [
"/v1/responses"
],
"supported_modalities": [
"text",
"image"
],
"supported_output_modalities": [
"text"
],
"supports_function_calling": true,
"supports_native_streaming": true,
"supports_parallel_function_calling": true,
"supports_pdf_input": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
"supports_vision": true
},
"gpt-5.4-pro": {
"cache_read_input_token_cost": 3e-06,
"cache_read_input_token_cost_above_272k_tokens": 6e-06,
"cache_read_input_token_cost_priority": 6e-06,
"cache_read_input_token_cost_above_272k_tokens_priority": 1.2e-05,
"input_cost_per_token": 3e-05,
"input_cost_per_token_above_272k_tokens": 6e-05,
"input_cost_per_token_priority": 6e-05,
"input_cost_per_token_above_272k_tokens_priority": 1.2e-04,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.8e-04,
"output_cost_per_token_above_272k_tokens": 2.7e-04,
"output_cost_per_token_priority": 2.7e-04,
"output_cost_per_token_above_272k_tokens_priority": 4.05e-04,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -21112,16 +21066,22 @@
},
"gpt-5.4-pro-2026-03-05": {
"cache_read_input_token_cost": 3e-06,
"cache_read_input_token_cost_above_272k_tokens": 6e-06,
"cache_read_input_token_cost_priority": 6e-06,
"cache_read_input_token_cost_above_272k_tokens_priority": 1.2e-05,
"input_cost_per_token": 3e-05,
"input_cost_per_token_above_272k_tokens": 6e-05,
"input_cost_per_token_priority": 6e-05,
"input_cost_per_token_above_272k_tokens_priority": 1.2e-04,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.8e-04,
"output_cost_per_token_above_272k_tokens": 2.7e-04,
"output_cost_per_token_priority": 2.7e-04,
"output_cost_per_token_above_272k_tokens_priority": 4.05e-04,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",

View File

@ -183,12 +183,16 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
float
] # OpenAI priority service tier pricing
cache_read_input_token_cost_above_200k_tokens: Optional[float]
cache_read_input_token_cost_above_272k_tokens: Optional[float]
input_cost_per_character: Optional[float] # only for vertex ai models
input_cost_per_audio_token: Optional[float]
input_cost_per_token_above_128k_tokens: Optional[float] # only for vertex ai models
input_cost_per_token_above_200k_tokens: Optional[
float
] # only for vertex ai gemini-2.5-pro models
input_cost_per_token_above_272k_tokens: Optional[
float
] # GPT-5.4/5.4-pro: prompts >272K priced at 2x input
input_cost_per_character_above_128k_tokens: Optional[
float
] # only for vertex ai models
@ -213,6 +217,9 @@ class ModelInfoBase(ProviderSpecificModelInfo, total=False):
output_cost_per_token_above_200k_tokens: Optional[
float
] # only for vertex ai gemini-2.5-pro models
output_cost_per_token_above_272k_tokens: Optional[
float
] # GPT-5.4/5.4-pro: prompts >272K priced at 1.5x output
output_cost_per_character_above_128k_tokens: Optional[
float
] # only for vertex ai models

View File

@ -5639,6 +5639,9 @@ def _get_model_info_helper( # noqa: PLR0915
cache_read_input_token_cost_above_200k_tokens=_model_info.get(
"cache_read_input_token_cost_above_200k_tokens", None
),
cache_read_input_token_cost_above_272k_tokens=_model_info.get(
"cache_read_input_token_cost_above_272k_tokens", None
),
cache_read_input_token_cost_flex=_model_info.get(
"cache_read_input_token_cost_flex", None
),
@ -5657,6 +5660,9 @@ def _get_model_info_helper( # noqa: PLR0915
input_cost_per_token_above_200k_tokens=_model_info.get(
"input_cost_per_token_above_200k_tokens", None
),
input_cost_per_token_above_272k_tokens=_model_info.get(
"input_cost_per_token_above_272k_tokens", None
),
input_cost_per_query=_model_info.get("input_cost_per_query", None),
input_cost_per_second=_model_info.get("input_cost_per_second", None),
input_cost_per_audio_token=_model_info.get(
@ -5703,6 +5709,9 @@ def _get_model_info_helper( # noqa: PLR0915
output_cost_per_token_above_200k_tokens=_model_info.get(
"output_cost_per_token_above_200k_tokens", None
),
output_cost_per_token_above_272k_tokens=_model_info.get(
"output_cost_per_token_above_272k_tokens", None
),
output_cost_per_second=_model_info.get("output_cost_per_second", None),
output_cost_per_video_per_second=_model_info.get(
"output_cost_per_video_per_second", None

View File

@ -20632,6 +20632,7 @@
"supports_tool_choice": true,
"supports_service_tier": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": false
},
@ -20670,6 +20671,7 @@
"supports_tool_choice": true,
"supports_service_tier": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": false
},
@ -20707,6 +20709,7 @@
"supports_system_messages": true,
"supports_tool_choice": false,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": false
},
@ -20746,9 +20749,9 @@
"supports_tool_choice": true,
"supports_service_tier": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_web_search": true
"supports_xhigh_reasoning_effort": true
},
"gpt-5.2-2025-12-11": {
"cache_read_input_token_cost": 1.75e-07,
@ -20786,9 +20789,9 @@
"supports_tool_choice": true,
"supports_service_tier": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": true,
"supports_xhigh_reasoning_effort": true,
"supports_web_search": true
"supports_xhigh_reasoning_effort": true
},
"gpt-5.2-chat-latest": {
"cache_read_input_token_cost": 1.75e-07,
@ -20823,9 +20826,9 @@
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": false,
"supports_web_search": true
"supports_xhigh_reasoning_effort": false
},
"gpt-5.3-chat-latest": {
"cache_read_input_token_cost": 1.75e-07,
@ -20860,9 +20863,9 @@
"supports_system_messages": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"supports_none_reasoning_effort": false,
"supports_xhigh_reasoning_effort": false,
"supports_web_search": true
"supports_xhigh_reasoning_effort": false
},
"gpt-5.2-pro": {
"input_cost_per_token": 2.1e-05,
@ -20932,16 +20935,22 @@
},
"gpt-5.4": {
"cache_read_input_token_cost": 2.5e-07,
"cache_read_input_token_cost_above_272k_tokens": 5e-07,
"cache_read_input_token_cost_priority": 5e-07,
"cache_read_input_token_cost_above_272k_tokens_priority": 1e-06,
"input_cost_per_token": 2.5e-06,
"input_cost_per_token_above_272k_tokens": 5e-06,
"input_cost_per_token_priority": 5e-06,
"input_cost_per_token_above_272k_tokens_priority": 1e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"output_cost_per_token_above_272k_tokens": 2.25e-05,
"output_cost_per_token_priority": 2.25e-05,
"output_cost_per_token_above_272k_tokens_priority": 3.375e-05,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -20970,16 +20979,22 @@
},
"gpt-5.4-2026-03-05": {
"cache_read_input_token_cost": 2.5e-07,
"cache_read_input_token_cost_above_272k_tokens": 5e-07,
"cache_read_input_token_cost_priority": 5e-07,
"cache_read_input_token_cost_above_272k_tokens_priority": 1e-06,
"input_cost_per_token": 2.5e-06,
"input_cost_per_token_above_272k_tokens": 5e-06,
"input_cost_per_token_priority": 5e-06,
"input_cost_per_token_above_272k_tokens_priority": 1e-05,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.5e-05,
"output_cost_per_token_above_272k_tokens": 2.25e-05,
"output_cost_per_token_priority": 2.25e-05,
"output_cost_per_token_above_272k_tokens_priority": 3.375e-05,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -21006,16 +21021,22 @@
},
"gpt-5.4-pro": {
"cache_read_input_token_cost": 3e-06,
"cache_read_input_token_cost_above_272k_tokens": 6e-06,
"cache_read_input_token_cost_priority": 6e-06,
"cache_read_input_token_cost_above_272k_tokens_priority": 1.2e-05,
"input_cost_per_token": 3e-05,
"input_cost_per_token_above_272k_tokens": 6e-05,
"input_cost_per_token_priority": 6e-05,
"input_cost_per_token_above_272k_tokens_priority": 1.2e-04,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.8e-04,
"output_cost_per_token_above_272k_tokens": 2.7e-04,
"output_cost_per_token_priority": 2.7e-04,
"output_cost_per_token_above_272k_tokens_priority": 4.05e-04,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",
@ -21045,16 +21066,22 @@
},
"gpt-5.4-pro-2026-03-05": {
"cache_read_input_token_cost": 3e-06,
"cache_read_input_token_cost_above_272k_tokens": 6e-06,
"cache_read_input_token_cost_priority": 6e-06,
"cache_read_input_token_cost_above_272k_tokens_priority": 1.2e-05,
"input_cost_per_token": 3e-05,
"input_cost_per_token_above_272k_tokens": 6e-05,
"input_cost_per_token_priority": 6e-05,
"input_cost_per_token_above_272k_tokens_priority": 1.2e-04,
"litellm_provider": "openai",
"max_input_tokens": 1050000,
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "responses",
"mode": "chat",
"output_cost_per_token": 1.8e-04,
"output_cost_per_token_above_272k_tokens": 2.7e-04,
"output_cost_per_token_priority": 2.7e-04,
"output_cost_per_token_above_272k_tokens_priority": 4.05e-04,
"supported_endpoints": [
"/v1/chat/completions",
"/v1/batch",

View File

@ -295,6 +295,32 @@ def test_generic_cost_per_token_above_200k_tokens():
)
def test_generic_cost_per_token_gpt54_above_272k_tokens():
"""GPT-5.4/5.4-pro: prompts >272K input tokens priced at 2x input, 1.5x output."""
model = "gpt-5.4"
custom_llm_provider = "openai"
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
litellm.model_cost = litellm.get_model_cost_map(url="")
model_cost_map = litellm.model_cost[model]
prompt_tokens = 273000 # Above 272K threshold
completion_tokens = 1000
usage = Usage(
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
)
prompt_cost, completion_cost = generic_cost_per_token(
model=model,
usage=usage,
custom_llm_provider=custom_llm_provider,
)
expected_prompt = model_cost_map["input_cost_per_token_above_272k_tokens"] * prompt_tokens
expected_completion = model_cost_map["output_cost_per_token_above_272k_tokens"] * completion_tokens
assert round(prompt_cost, 10) == round(expected_prompt, 10)
assert round(completion_cost, 10) == round(expected_completion, 10)
def test_generic_cost_per_token_anthropic_prompt_caching():
model = "claude-sonnet-4@20250514"
usage = Usage(

View File

@ -324,6 +324,81 @@ def test_gpt5_4_pro_allows_reasoning_effort_xhigh(config: OpenAIConfig):
assert params["reasoning_effort"] == "xhigh"
def test_gpt5_normalizes_reasoning_effort_dict_to_string(config: OpenAIConfig):
"""Chat completion API expects reasoning_effort as a string, not a dict.
Config/deployments may pass Responses API format: {'effort': 'high', 'summary': 'detailed'}.
"""
params = config.map_openai_params(
non_default_params={"reasoning_effort": {"effort": "high", "summary": "detailed"}},
optional_params={},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == "high"
def test_gpt5_normalizes_reasoning_effort_dict_from_optional_params(config: OpenAIConfig):
"""reasoning_effort dict in optional_params (e.g. from model config) is normalized."""
params = config.map_openai_params(
non_default_params={},
optional_params={"reasoning_effort": {"effort": "medium", "summary": "detailed"}},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == "medium"
def test_gpt5_4_drops_reasoning_effort_when_tools_present(config: OpenAIConfig):
"""gpt-5.4: function calls not supported with reasoning_effort != 'none'. Drop reasoning_effort."""
tools = [{"type": "function", "function": {"name": "test", "description": "test"}}]
params = config.map_openai_params(
non_default_params={"reasoning_effort": "high", "tools": tools},
optional_params={},
model="gpt-5.4",
drop_params=False,
)
assert "reasoning_effort" not in params
assert params["tools"] == tools
def test_gpt5_4_keeps_reasoning_effort_when_no_tools(config: OpenAIConfig):
"""reasoning_effort is kept when tools are not present."""
params = config.map_openai_params(
non_default_params={"reasoning_effort": "high"},
optional_params={},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == "high"
def test_gpt5_4_keeps_reasoning_effort_none_with_tools(config: OpenAIConfig):
"""reasoning_effort='none' is kept when tools are present."""
tools = [{"type": "function", "function": {"name": "test", "description": "test"}}]
params = config.map_openai_params(
non_default_params={"reasoning_effort": "none", "tools": tools},
optional_params={},
model="gpt-5.4",
drop_params=False,
)
assert params["reasoning_effort"] == "none"
assert params["tools"] == tools
def test_gpt5_2_keeps_reasoning_effort_with_tools(config: OpenAIConfig):
"""gpt-5.2: reasoning_effort drop only applies to gpt-5.4, not gpt-5.2."""
tools = [{"type": "function", "function": {"name": "test", "description": "test"}}]
params = config.map_openai_params(
non_default_params={"reasoning_effort": "high", "tools": tools},
optional_params={},
model="gpt-5.2",
drop_params=False,
)
assert params["reasoning_effort"] == "high"
assert params["tools"] == tools
def test_gpt5_4_pro_rejects_non_default_temperature(config: OpenAIConfig):
with pytest.raises(litellm.utils.UnsupportedParamsError):
config.map_openai_params(

View File

@ -513,6 +513,8 @@ def validate_model_cost_values(model_data, exceptions=None):
"output_cost_per_token_above_128k_tokens",
"input_cost_per_token_above_200k_tokens",
"output_cost_per_token_above_200k_tokens",
"input_cost_per_token_above_272k_tokens",
"output_cost_per_token_above_272k_tokens",
"input_cost_per_character_above_128k_tokens",
"output_cost_per_character_above_128k_tokens",
"input_cost_per_image_above_128k_tokens",
@ -603,6 +605,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"cache_creation_input_token_cost_above_200k_tokens": {"type": "number"},
"cache_read_input_token_cost": {"type": "number"},
"cache_read_input_token_cost_above_200k_tokens": {"type": "number"},
"cache_read_input_token_cost_above_272k_tokens": {"type": "number"},
"cache_creation_input_token_cost_above_1hr_above_200k_tokens": {"type": "number"},
"cache_read_input_audio_token_cost": {"type": "number"},
"cache_read_input_token_cost_per_audio_token": {"type": "number"},
@ -618,16 +621,20 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"input_cost_per_image_above_128k_tokens": {"type": "number"},
"input_cost_per_image_token": {"type": "number"},
"input_cost_per_token_above_200k_tokens": {"type": "number"},
"input_cost_per_token_above_272k_tokens": {"type": "number"},
"cache_read_input_token_cost_flex": {"type": "number"},
"cache_read_input_token_cost_priority": {"type": "number"},
"cache_read_input_token_cost_above_200k_tokens_priority": {"type": "number"},
"cache_read_input_token_cost_above_272k_tokens_priority": {"type": "number"},
"input_cost_per_token_flex": {"type": "number"},
"input_cost_per_token_priority": {"type": "number"},
"input_cost_per_token_above_200k_tokens_priority": {"type": "number"},
"input_cost_per_token_above_272k_tokens_priority": {"type": "number"},
"input_cost_per_audio_token_priority": {"type": "number"},
"output_cost_per_token_flex": {"type": "number"},
"output_cost_per_token_priority": {"type": "number"},
"output_cost_per_token_above_200k_tokens_priority": {"type": "number"},
"output_cost_per_token_above_272k_tokens_priority": {"type": "number"},
"input_cost_per_pixel": {"type": "number"},
"input_cost_per_query": {"type": "number"},
"input_cost_per_request": {"type": "number"},
@ -694,6 +701,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"output_cost_per_token": {"type": "number"},
"output_cost_per_token_above_128k_tokens": {"type": "number"},
"output_cost_per_token_above_200k_tokens": {"type": "number"},
"output_cost_per_token_above_272k_tokens": {"type": "number"},
"output_cost_per_image_above_1024_and_1024_pixels": {"type": "number"},
"output_cost_per_image_above_1024_and_1024_pixels_and_premium_image": {
"type": "number"
@ -732,6 +740,8 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"supports_web_search": {"type": "boolean"},
"supports_url_context": {"type": "boolean"},
"supports_reasoning": {"type": "boolean"},
"supports_none_reasoning_effort": {"type": "boolean"},
"supports_xhigh_reasoning_effort": {"type": "boolean"},
"supports_service_tier": {"type": "boolean"},
"supports_preset": {"type": "boolean"},
"tool_use_system_prompt_tokens": {"type": "number"},