fix(cost-map): bill web search on the azure_ai Claude rows that were missing a search price

azure_ai/claude-opus-4-5, sonnet-4-5, sonnet-4-6 and mythos-5-1 had no
search_context_cost_per_query while their Anthropic rows bill $0.01 per
query, so web searches through Azure on them cost $0. Copy the price and
mark them supports_web_search in both catalog copies, and add a test that
keeps every azure_ai Claude row's search price in step with its bare row.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerry 2026-11-10 18:44:25 +00:00
parent 2624f7caea
commit 34eac7ce54
3 changed files with 71 additions and 0 deletions

View file

@ -3921,6 +3921,11 @@
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 2.5e-05,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -3932,6 +3937,7 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 4096,
"source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
},
@ -4088,6 +4094,11 @@
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
@ -4105,6 +4116,7 @@
"supports_vision": true,
"supports_xhigh_reasoning_effort": true,
"supports_max_reasoning_effort": false,
"supports_web_search": true,
"prompt_cache_min_tokens": 512,
"source": "https://learn.microsoft.com/en-us/azure/foundry/concepts/model-lifecycle-retirement"
},
@ -4229,6 +4241,11 @@
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 1.5e-05,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -4239,6 +4256,7 @@
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 1024,
"source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
},
@ -4290,6 +4308,11 @@
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 1.5e-05,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -4302,6 +4325,7 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 1024,
"source": "https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/claude-models"
},

View file

@ -3921,6 +3921,11 @@
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 2.5e-05,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -3932,6 +3937,7 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 4096,
"source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
},
@ -4088,6 +4094,11 @@
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
@ -4105,6 +4116,7 @@
"supports_vision": true,
"supports_xhigh_reasoning_effort": true,
"supports_max_reasoning_effort": false,
"supports_web_search": true,
"prompt_cache_min_tokens": 512,
"source": "https://learn.microsoft.com/en-us/azure/foundry/concepts/model-lifecycle-retirement"
},
@ -4229,6 +4241,11 @@
"max_input_tokens": 200000,
"max_output_tokens": 64000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 1.5e-05,
"supports_assistant_prefill": true,
"supports_computer_use": true,
@ -4239,6 +4256,7 @@
"supports_response_schema": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 1024,
"source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
},
@ -4290,6 +4308,11 @@
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"mode": "chat",
"search_context_cost_per_query": {
"search_context_size_high": 0.01,
"search_context_size_low": 0.01,
"search_context_size_medium": 0.01
},
"output_cost_per_token": 1.5e-05,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -4302,6 +4325,7 @@
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true,
"supports_web_search": true,
"prompt_cache_min_tokens": 1024,
"source": "https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/claude-models"
},

View file

@ -1050,6 +1050,29 @@ def test_bedrock_gpt_oss_vision_is_explicitly_disabled_for_the_router_gate() ->
assert litellm.utils.is_vision_explicitly_disabled("bedrock_mantle/openai.gpt-oss-20b") is True
@pytest.mark.parametrize("path", (PRICES_PATH, BACKUP_PRICES_PATH), ids=("main", "backup"))
def test_azure_ai_claude_rows_bill_web_search_like_their_anthropic_rows(path: Path) -> None:
prices: Final[dict[str, JsonValue]] = TypeAdapter(dict[str, JsonValue]).validate_json(path.read_bytes())
pairs: Final = {
key: (row, prices[key.removeprefix("azure_ai/")])
for key, row in prices.items()
if key.startswith("azure_ai/claude") and isinstance(row, dict) and key.removeprefix("azure_ai/") in prices
}
priced_pairs: Final = {
key: (row, bare)
for key, (row, bare) in pairs.items()
if isinstance(bare, dict) and bare.get("search_context_cost_per_query")
}
assert priced_pairs
drifted: Final = tuple(
key
for key, (row, bare) in priced_pairs.items()
if row.get("search_context_cost_per_query") != bare["search_context_cost_per_query"]
or row.get("supports_web_search") is not True
)
assert drifted == (), drifted
@pytest.mark.parametrize("path", (PRICES_PATH, BACKUP_PRICES_PATH), ids=("main", "backup"))
def test_azure_ai_claude_rows_priced_for_web_search_say_they_support_it(path: Path) -> None:
prices: Final[dict[str, JsonValue]] = TypeAdapter(dict[str, JsonValue]).validate_json(path.read_bytes())