mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
173 lines
7.1 KiB
Python
173 lines
7.1 KiB
Python
"""
|
|
Validate Claude Sonnet 5 model configuration entries.
|
|
|
|
Sonnet 5 ships with the gen-5 adaptive-thinking profile (adaptive thinking
|
|
always on, no extended thinking, ``effort`` defaults to ``high``), so it must
|
|
mirror the sampling-param and prefill restrictions that Fable 5 / Opus 4.8 carry
|
|
rather than the older Sonnet 4.6 behavior. The cost-map entries are also what
|
|
populate ``litellm.anthropic_models`` at import, which is what lets a bare
|
|
``claude-sonnet-5`` name resolve to the ``anthropic`` provider (and match an
|
|
``anthropic/*`` wildcard deployment).
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm.constants import BEDROCK_CONVERSE_MODELS
|
|
from litellm.litellm_core_utils.get_model_cost_map import GetModelCostMap
|
|
|
|
REPO_ROOT = os.path.join(os.path.dirname(__file__), "../..")
|
|
|
|
ALL_SONNET_5_VARIANTS = (
|
|
"claude-sonnet-5",
|
|
"anthropic.claude-sonnet-5",
|
|
"global.anthropic.claude-sonnet-5",
|
|
"us.anthropic.claude-sonnet-5",
|
|
"eu.anthropic.claude-sonnet-5",
|
|
"au.anthropic.claude-sonnet-5",
|
|
"jp.anthropic.claude-sonnet-5",
|
|
"vertex_ai/claude-sonnet-5",
|
|
"vertex_ai/claude-sonnet-5@default",
|
|
"azure_ai/claude-sonnet-5",
|
|
)
|
|
|
|
|
|
def _load_root_cost_map() -> dict:
|
|
json_path = os.path.join(REPO_ROOT, "model_prices_and_context_window.json")
|
|
with open(json_path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
|
|
def test_sonnet_5_pricing_and_capabilities():
|
|
model_data = _load_root_cost_map()
|
|
|
|
expected_providers = {
|
|
"claude-sonnet-5": "anthropic",
|
|
"anthropic.claude-sonnet-5": "bedrock_converse",
|
|
"vertex_ai/claude-sonnet-5": "vertex_ai-anthropic_models",
|
|
"azure_ai/claude-sonnet-5": "azure_ai",
|
|
}
|
|
|
|
for model_name, provider in expected_providers.items():
|
|
assert model_name in model_data, f"Missing model entry: {model_name}"
|
|
info = model_data[model_name]
|
|
|
|
assert info["litellm_provider"] == provider
|
|
assert info["mode"] == "chat"
|
|
assert info["max_input_tokens"] == 1000000
|
|
assert info["max_output_tokens"] == 128000
|
|
assert info["max_tokens"] == 128000
|
|
|
|
# Introductory Sonnet 5 pricing through 2026-08-31: $2 / $10 per MTok,
|
|
# with the 1.25x cache-write and 0.1x cache-read multipliers. On
|
|
# 2026-09-01 flip these five fields back to the sticker rate, here and
|
|
# in both cost-map JSON files (all ten claude-sonnet-5 entries):
|
|
# input_cost_per_token: 3e-06
|
|
# output_cost_per_token: 1.5e-05
|
|
# cache_creation_input_token_cost: 3.75e-06
|
|
# cache_creation_input_token_cost_above_1hr: 6e-06
|
|
# cache_read_input_token_cost: 3e-07
|
|
# Regional Bedrock profiles (us./eu./au./jp.) stay at 1.1x those values:
|
|
# 3.3e-06 / 1.65e-05 / 4.125e-06 / 6.6e-06 / 3.3e-07 (see
|
|
# test_sonnet_5_bedrock_regional_pricing below).
|
|
assert info["input_cost_per_token"] == 2e-06
|
|
assert info["output_cost_per_token"] == 1e-05
|
|
assert info["cache_creation_input_token_cost"] == 2.5e-06
|
|
assert info["cache_creation_input_token_cost_above_1hr"] == 4e-06
|
|
assert info["cache_read_input_token_cost"] == 2e-07
|
|
|
|
# gen-5 adaptive-thinking profile: effort-driven, no sampling params, no
|
|
# assistant prefill.
|
|
assert info["supports_adaptive_thinking"] is True
|
|
assert info["supports_reasoning"] is True
|
|
assert info["supports_sampling_params"] is False
|
|
assert info["supports_assistant_prefill"] is False
|
|
|
|
assert info["supports_function_calling"] is True
|
|
assert info["supports_prompt_caching"] is True
|
|
assert info["supports_tool_choice"] is True
|
|
assert info["supports_vision"] is True
|
|
|
|
|
|
def test_sonnet_5_bedrock_regional_pricing():
|
|
"""Global/base endpoints use base pricing; the us./eu./au./jp. regional
|
|
cross-region inference profiles carry a 10% premium."""
|
|
model_data = _load_root_cost_map()
|
|
|
|
base_pricing = {
|
|
"input_cost_per_token": 2e-06,
|
|
"output_cost_per_token": 1e-05,
|
|
"cache_creation_input_token_cost": 2.5e-06,
|
|
"cache_creation_input_token_cost_above_1hr": 4e-06,
|
|
"cache_read_input_token_cost": 2e-07,
|
|
}
|
|
regional_pricing = {
|
|
"input_cost_per_token": 2.2e-06,
|
|
"output_cost_per_token": 1.1e-05,
|
|
"cache_creation_input_token_cost": 2.75e-06,
|
|
"cache_creation_input_token_cost_above_1hr": 4.4e-06,
|
|
"cache_read_input_token_cost": 2.2e-07,
|
|
}
|
|
|
|
expected = {
|
|
"anthropic.claude-sonnet-5": base_pricing,
|
|
"global.anthropic.claude-sonnet-5": base_pricing,
|
|
"us.anthropic.claude-sonnet-5": regional_pricing,
|
|
"eu.anthropic.claude-sonnet-5": regional_pricing,
|
|
"au.anthropic.claude-sonnet-5": regional_pricing,
|
|
"jp.anthropic.claude-sonnet-5": regional_pricing,
|
|
}
|
|
|
|
for model_name, pricing in expected.items():
|
|
assert model_name in model_data, f"Missing model entry: {model_name}"
|
|
info = model_data[model_name]
|
|
assert info["litellm_provider"] == "bedrock_converse"
|
|
assert info["bedrock_output_config_effort_ceiling"] == "xhigh"
|
|
for key, value in pricing.items():
|
|
assert info[key] == value, f"{model_name}.{key} = {info[key]}, want {value}"
|
|
|
|
|
|
def test_sonnet_5_present_in_bundled_backup():
|
|
"""The bundled backup is the runtime fallback (and what tests load with
|
|
``LITELLM_LOCAL_MODEL_COST_MAP=True``); it must carry the same entries as the
|
|
root cost map, otherwise the model resolves on one path but not the other."""
|
|
backup = GetModelCostMap.load_local_model_cost_map()
|
|
for model_name in ALL_SONNET_5_VARIANTS:
|
|
assert model_name in backup, f"Missing from backup cost map: {model_name}"
|
|
|
|
|
|
def test_sonnet_5_registered_for_bedrock_converse():
|
|
assert "anthropic.claude-sonnet-5" in BEDROCK_CONVERSE_MODELS
|
|
|
|
|
|
def test_sonnet_5_provider_resolves_via_model_info(local_model_cost_map):
|
|
"""Regression: ``claude-sonnet-5`` must resolve to provider ``anthropic``.
|
|
|
|
Before the cost-map entry existed, the model was unknown to LiteLLM, so it
|
|
could not be tied to the ``anthropic`` provider and an ``anthropic/*``
|
|
wildcard deployment would not match it."""
|
|
info = litellm.get_model_info(model="claude-sonnet-5")
|
|
assert info["litellm_provider"] == "anthropic"
|
|
assert info["max_input_tokens"] == 1000000
|
|
assert info["max_output_tokens"] == 128000
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"cost_map",
|
|
[_load_root_cost_map(), GetModelCostMap.load_local_model_cost_map()],
|
|
ids=["root", "bundled_backup"],
|
|
)
|
|
def test_sonnet_5_all_variants_carry_adaptive_thinking_flag(cost_map):
|
|
"""Every Sonnet 5 entry must advertise ``supports_adaptive_thinking``.
|
|
|
|
Adaptive-thinking detection is cost-map driven, so a single variant missing
|
|
the flag silently sends the legacy ``thinking.type='enabled'`` shape and the
|
|
provider 400s. This guards against a future variant being added without it."""
|
|
variants = [k for k in cost_map if "claude-sonnet-5" in k]
|
|
assert variants, "no claude-sonnet-5 entries found in cost map"
|
|
missing = [k for k in variants if cost_map[k].get("supports_adaptive_thinking") is not True]
|
|
assert not missing, f"missing supports_adaptive_thinking: {missing}"
|