fix(cost_map): fill missing Claude capability fields from the family baseline

Mapped Claude entries on Anthropic, Azure Foundry, Bedrock and Vertex now get
supports_parallel_function_calling and supports_system_messages from the
claude-family-baseline rule when the entry leaves them out. Values set on an
entry still win
This commit is contained in:
ryan-crabbe-berri 2026-11-02 22:38:35 -08:00
parent 4573fa1f31
commit 203f692b0b
3 changed files with 60 additions and 5 deletions

View file

@ -63749,7 +63749,8 @@
{
"name": "claude-family-baseline",
"pattern": "claude-[a-z]+-\\d+(?:[-.]\\d+)?(?:-\\d{8})?",
"description": "Any Claude family-major id with an optional minor and an optional 8-digit date suffix, under any provider namespace (bare, bedrock-dotted, vertex, databricks, ...), so bare majors like claude-newfamily-5 get the same baseline as claude-newfamily-5-1. Carries the model-family facts every Claude shares; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number.",
"fill_missing_for_providers": ["anthropic", "azure_ai", "bedrock", "bedrock_converse", "vertex_ai-anthropic_models"],
"description": "Any Claude family-major id with an optional minor and an optional 8-digit date suffix, under any provider namespace (bare, bedrock-dotted, vertex, databricks, ...), so bare majors like claude-newfamily-5 get the same baseline as claude-newfamily-5-1. Carries the model-family facts every Claude shares; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number. On mapped Claude entries from Anthropic, Azure Foundry, Bedrock and Vertex it only fills the keys an entry leaves out, so the entry's own values always win.",
"model_info": {
"mode": "chat",
"max_input_tokens": 200000,

View file

@ -63749,7 +63749,8 @@
{
"name": "claude-family-baseline",
"pattern": "claude-[a-z]+-\\d+(?:[-.]\\d+)?(?:-\\d{8})?",
"description": "Any Claude family-major id with an optional minor and an optional 8-digit date suffix, under any provider namespace (bare, bedrock-dotted, vertex, databricks, ...), so bare majors like claude-newfamily-5 get the same baseline as claude-newfamily-5-1. Carries the model-family facts every Claude shares; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number.",
"fill_missing_for_providers": ["anthropic", "azure_ai", "bedrock", "bedrock_converse", "vertex_ai-anthropic_models"],
"description": "Any Claude family-major id with an optional minor and an optional 8-digit date suffix, under any provider namespace (bare, bedrock-dotted, vertex, databricks, ...), so bare majors like claude-newfamily-5 get the same baseline as claude-newfamily-5-1. Carries the model-family facts every Claude shares; it carries no pricing, so cost stays on the standard unpriced behavior rather than a guessed number. On mapped Claude entries from Anthropic, Azure Foundry, Bedrock and Vertex it only fills the keys an entry leaves out, so the entry's own values always win.",
"model_info": {
"mode": "chat",
"max_input_tokens": 200000,

View file

@ -9,7 +9,8 @@ resolution (get_model_info) including the shipped rules in the bundled cost map.
import json
import logging
from collections.abc import Callable
import re
from collections.abc import Callable, Mapping
from pathlib import Path
from typing import Final
@ -914,11 +915,11 @@ def test_shipped_claude_thinking_rules_backfill_only_anthropic(shipped_cost_map)
assert info.get("supports_adaptive_thinking") is None
assert info.get("supports_legacy_thinking") is None
assert info.get("max_input_tokens") is None
assert match_fill_missing_generalizations("claude-sonnet-4-6", "anthropic") == {
assert match_fill_missing_generalizations("claude-sonnet-4-6", "anthropic").items() >= {
"supports_adaptive_thinking": True,
"supports_legacy_thinking": True,
"supports_tool_search": True,
}
}.items()
assert match_fill_missing_generalizations("claude-sonnet-4-6", "perplexity") is None
@ -961,3 +962,55 @@ def test_shipped_tool_search_rule_fills_mapped_claude_entries_without_flag(shipp
assert match_fill_missing_generalizations("claude-opus-5", "bedrock")["supports_tool_search"] is True
assert match_fill_missing_generalizations("claude-opus-5", "azure_ai")["supports_tool_search"] is True
assert match_fill_missing_generalizations("claude-opus-5", "perplexity") is None
CLAUDE_BASELINE_FILL_PROVIDERS: Final = ("anthropic", "azure_ai", "bedrock", "bedrock_converse", "vertex_ai-anthropic_models")
def _claude_baseline() -> Mapping[str, object]:
return next(rule for rule in get_fallback_generalization_rules() if rule["name"] == "claude-family-baseline")
def _mapped_claude_chat_entries() -> tuple[tuple[str, Mapping[str, object]], ...]:
pattern: Final = re.compile(_claude_baseline()["pattern"], re.IGNORECASE)
return tuple(
(key, entry)
for key, entry in litellm.model_cost.items()
if isinstance(entry, dict) and entry.get("mode") == "chat" and pattern.search(key) is not None
)
def _fields_overriding_baseline(entry: Mapping[str, object], baseline: Mapping[str, object]) -> frozenset[str]:
return frozenset(field for field in entry.keys() & baseline.keys() if entry[field] != baseline[field])
@pytest.mark.parametrize("provider", CLAUDE_BASELINE_FILL_PROVIDERS)
def test_shipped_claude_baseline_fills_only_missing_keys_on_mapped_entries(shipped_cost_map, provider):
"""A mapped Claude entry from a first-party host gets each baseline key it leaves out,
while every baseline key the entry sets keeps the entry's own value."""
baseline: Final = _claude_baseline()["model_info"]
entries: Final = tuple((key, entry) for key, entry in _mapped_claude_chat_entries() if entry["litellm_provider"] == provider)
assert any(baseline.keys() - entry.keys() for _, entry in entries)
assert any(_fields_overriding_baseline(entry, baseline) for _, entry in entries)
for key, entry in entries:
info = litellm.get_model_info(key)
assert info["key"] == key
assert {field: info[field] for field in baseline} == {
field: entry.get(field, value) for field, value in baseline.items()
}, key
def test_shipped_claude_baseline_leaves_other_providers_entries_unfilled(shipped_cost_map):
baseline: Final = _claude_baseline()["model_info"]
entries: Final = tuple(
(key, entry)
for key, entry in _mapped_claude_chat_entries()
if entry["litellm_provider"] not in CLAUDE_BASELINE_FILL_PROVIDERS
)
assert any("supports_parallel_function_calling" not in entry for _, entry in entries)
for key, entry in entries:
info = litellm.get_model_info(key)
missing = baseline.keys() - entry.keys()
assert {field: info.get(field) for field in missing} == dict.fromkeys(missing), key