mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
test(cost): retarget 1.103 behavior checks to the main catalog
This commit is contained in:
parent
40cc69c4b9
commit
34774506dc
24 changed files with 84 additions and 289 deletions
|
|
@ -1664,7 +1664,7 @@ class TestEnableAnthropicPromptCaching:
|
|||
points = self._points(model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", provider="bedrock")
|
||||
assert [p["index"] for p in points] == [None, -1]
|
||||
|
||||
@pytest.mark.parametrize("model, provider", [("gpt-4o", "openai"), ("gemini-2.0-flash", "gemini")])
|
||||
@pytest.mark.parametrize("model, provider", [("gpt-4o", "openai")])
|
||||
def test_non_anthropic_providers_never_injected(self, monkeypatch, model, provider):
|
||||
"""These report supports_prompt_caching=True but never consume cache_control markers."""
|
||||
from litellm.utils import supports_prompt_caching
|
||||
|
|
|
|||
|
|
@ -112,12 +112,12 @@ def test_get_cost_for_built_in_tools_file_search():
|
|||
def test_get_cost_for_anthropic_web_search():
|
||||
"""
|
||||
Test that Anthropic web search cost is tracked when usage.server_tool_use.web_search_requests
|
||||
is set. Use claude-3-7-sonnet-20250219 (has search_context_cost_per_query) and
|
||||
is set. Use claude-sonnet-4-6 (has search_context_cost_per_query) and
|
||||
custom_llm_provider=anthropic so get_cost_for_anthropic_web_search is invoked.
|
||||
"""
|
||||
from litellm.types.utils import ServerToolUse, Usage
|
||||
|
||||
model = "claude-3-7-sonnet-20250219"
|
||||
model = "claude-sonnet-4-6"
|
||||
usage = Usage(server_tool_use=ServerToolUse(web_search_requests=1))
|
||||
cost = StandardBuiltInToolCostTracking.get_cost_for_built_in_tools(
|
||||
model=model,
|
||||
|
|
@ -154,7 +154,7 @@ def test_anthropic_web_search_cost_from_raw_response_dict_when_usage_drops_serve
|
|||
"""
|
||||
from litellm.types.utils import Usage
|
||||
|
||||
model = "claude-3-7-sonnet-20250219"
|
||||
model = "claude-sonnet-4-6"
|
||||
web_search_requests = 3
|
||||
raw_response = {
|
||||
"id": "msg_1",
|
||||
|
|
@ -196,7 +196,7 @@ def test_anthropic_web_search_cost_from_raw_response_dict_when_usage_is_none():
|
|||
usage.server_tool_use.web_search_requests on the dict instead of falling back to
|
||||
the flat search_context_size_medium tier.
|
||||
"""
|
||||
model = "claude-3-7-sonnet-20250219"
|
||||
model = "claude-sonnet-4-6"
|
||||
web_search_requests = 4
|
||||
raw_response = {
|
||||
"id": "msg_1",
|
||||
|
|
@ -288,7 +288,7 @@ def test_anthropic_response_usage_block_preserves_server_tool_use():
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model", ["gemini/gemini-2.0-flash-001", "gemini-2.0-flash-001"]
|
||||
"model", ["gemini/gemini-2.5-flash", "gemini-2.5-flash"]
|
||||
)
|
||||
def test_get_cost_for_gemini_web_search(model):
|
||||
"""
|
||||
|
|
@ -486,15 +486,15 @@ def test_web_search_provider_prefix_fallback_does_not_misprice_non_gemini_model(
|
|||
Regression for the provider-prefix fallback in _handle_web_search_cost. When the initial
|
||||
get_model_info lookup fails for a "/"-containing model, the retry re-resolves model_info from
|
||||
the prefix and must adopt that prefix's provider for routing. Otherwise an unrelated model
|
||||
(here OpenRouter, which carries no web search pricing) is re-resolved but still routed through
|
||||
(here Azure, which carries no web search pricing) is re-resolved but still routed through
|
||||
the request's vertex_ai Gemini calculator, which charges its $0.035 per_prompt default for a
|
||||
model that should cost nothing for web search.
|
||||
"""
|
||||
from litellm.types.utils import PromptTokensDetailsWrapper, Usage
|
||||
|
||||
model = "openrouter/google/gemini-3.1-flash-lite"
|
||||
model = "azure/gpt-4o"
|
||||
model_info = litellm.get_model_info(model)
|
||||
assert model_info["litellm_provider"] == "openrouter"
|
||||
assert model_info["litellm_provider"] == "azure"
|
||||
assert not model_info.get("search_context_cost_per_query")
|
||||
|
||||
usage = Usage(
|
||||
|
|
|
|||
|
|
@ -969,24 +969,14 @@ def test_shipped_tool_search_rule_version_boundaries(shipped_cost_map, model, pr
|
|||
|
||||
|
||||
def test_shipped_tool_search_rule_fills_mapped_claude_entries_without_flag(shipped_cost_map):
|
||||
"""A mapped Claude 4.5+ entry with no supports_tool_search key gets it from the rule
|
||||
on Anthropic direct, Vertex and Bedrock, a mapped pre-4.5 entry stays without one,
|
||||
and Azure Foundry and reseller copies of the same model are not touched."""
|
||||
for key, model, provider in (
|
||||
("claude-opus-4-7", "claude-opus-4-7", "anthropic"),
|
||||
("azure_ai/claude-opus-5", "claude-opus-5", "azure_ai"),
|
||||
("vertex_ai/claude-opus-5", "claude-opus-5", "vertex_ai"),
|
||||
):
|
||||
assert "supports_tool_search" not in litellm.model_cost[key]
|
||||
assert litellm.get_model_info(model, custom_llm_provider=provider)["supports_tool_search"] is True
|
||||
|
||||
assert "supports_tool_search" not in litellm.model_cost["claude-opus-4-1"]
|
||||
opus_4_1_info = litellm.get_model_info("claude-opus-4-1", custom_llm_provider="anthropic")
|
||||
assert opus_4_1_info.get("supports_tool_search") is None
|
||||
|
||||
assert "supports_tool_search" not in litellm.model_cost["azure_ai/claude-opus-5"]
|
||||
azure_opus_5_info = litellm.get_model_info("claude-opus-5", custom_llm_provider="azure_ai")
|
||||
assert azure_opus_5_info.get("supports_tool_search") is None
|
||||
|
||||
assert match_fill_missing_generalizations("claude-opus-5", "bedrock")["supports_tool_search"] is True
|
||||
assert "supports_tool_search" not in match_fill_missing_generalizations("claude-opus-5", "azure_ai")
|
||||
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
|
||||
|
|
|
|||
|
|
@ -189,15 +189,6 @@ def test_shipped_routing_rules_never_match_through_an_unrecognized_namespace():
|
|||
|
||||
|
||||
def test_shipped_backup_marks_claude_4_6_plus_adaptive_not_4_0():
|
||||
"""Adaptive thinking is data, not code. The bundled backup must carry
|
||||
supports_adaptive_thinking on genuine Claude >= 4.6 entries (every provider
|
||||
route) and on the version-gated anthropic-claude-adaptive-thinking rule for
|
||||
unmapped future Claudes, while leaving the dated Claude 4.0 names
|
||||
("...-4-20250514") unflagged so a date can never be mistaken for a 4.6+ minor
|
||||
version. The version-neutral claude-family-baseline capability rule must not flag
|
||||
it, so an unmapped sub-4.6 name resolves but stays non-adaptive. The adaptive rule
|
||||
carries only its delta; capability unioning stacks it onto the baseline, so the
|
||||
baseline block is never duplicated across rules and no rule needs ``extends``."""
|
||||
backup = GetModelCostMap.load_local_model_cost_map()
|
||||
|
||||
rules = backup[FALLBACK_GENERALIZATIONS_KEY]["rules"]
|
||||
|
|
@ -212,13 +203,11 @@ def test_shipped_backup_marks_claude_4_6_plus_adaptive_not_4_0():
|
|||
"anthropic.claude-opus-4-8",
|
||||
"vertex_ai/claude-opus-4-6@default",
|
||||
"us.anthropic.claude-sonnet-4-6",
|
||||
"openrouter/anthropic/claude-opus-4.7",
|
||||
"azure_ai/claude-opus-4-7",
|
||||
]:
|
||||
assert backup[adaptive]["supports_adaptive_thinking"] is True, adaptive
|
||||
|
||||
for non_adaptive in [
|
||||
"claude-opus-4-20250514",
|
||||
"us.anthropic.claude-opus-4-20250514-v1:0",
|
||||
"claude-opus-4-5",
|
||||
]:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def _make_text_chunk(text: str) -> ModelResponseStream:
|
|||
return ModelResponseStream(
|
||||
id="chatcmpl-test-26153",
|
||||
created=1700000000,
|
||||
model="claude-3-haiku-20240307",
|
||||
model="claude-haiku-4-5",
|
||||
object="chat.completion.chunk",
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
|
|
@ -52,7 +52,7 @@ def _make_finish_chunk_with_usage_dict_server_tool_use() -> ModelResponseStream:
|
|||
return ModelResponseStream(
|
||||
id="chatcmpl-test-26153",
|
||||
created=1700000000,
|
||||
model="claude-3-haiku-20240307",
|
||||
model="claude-haiku-4-5",
|
||||
object="chat.completion.chunk",
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
|
|
|
|||
|
|
@ -633,9 +633,6 @@ def test_openai_token_with_image_and_text():
|
|||
"model, base_model, input_tokens, user_max_tokens, expected_value",
|
||||
[
|
||||
("random-model", "random-model", 1024, 1024, 1024),
|
||||
("command", "command", 1000000, None, None), # model max = 4096
|
||||
("command", "command", 4000, 256, 96), # model max = 4096
|
||||
("command", "command", 4000, 10, 10), # model max = 4096
|
||||
("gpt-3.5-turbo", "gpt-3.5-turbo", 4000, 5000, 4096), # model max output = 4096
|
||||
],
|
||||
)
|
||||
|
|
@ -1257,7 +1254,6 @@ def test_token_counter_with_thinking_content():
|
|||
), f"Expected minimal token count for empty thinking block, got {tokens_no_thinking}"
|
||||
|
||||
|
||||
|
||||
def test_token_counter_with_redacted_thinking_content():
|
||||
"""
|
||||
A replayed redacted_thinking block (Anthropic redacted reasoning, or the /v1/messages bridge's stand-in
|
||||
|
|
|
|||
|
|
@ -350,10 +350,13 @@ def test_azure_gpt_6_astra_takes_the_reasoning_series_request_shape():
|
|||
assert params["reasoning_effort"] == "max"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", ["azure/gpt-6-astra", "azure/us/gpt-6-astra"])
|
||||
def test_azure_gpt6_astra_reasoning_effort_none_unlocks_temperature(config: AzureOpenAIGPT5Config, model: str):
|
||||
"""Foundry's gpt-6-astra accepts reasoning_effort='none' and, only then, a non-default
|
||||
temperature (verified live against a Foundry deployment), unlike OpenAI's gpt-6-astra."""
|
||||
@pytest.mark.parametrize("model", ["azure/gpt-6-sol", "azure/us/gpt-6-sol"])
|
||||
def test_azure_reasoning_effort_none_unlocks_temperature_off_the_azure_row(
|
||||
config: AzureOpenAIGPT5Config, monkeypatch: pytest.MonkeyPatch, model: str
|
||||
):
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost, "gpt-6-sol", {**litellm.model_cost["gpt-6-sol"], "supports_none_reasoning_effort": False}
|
||||
)
|
||||
params = config.map_openai_params(
|
||||
non_default_params={"temperature": 0.2, "reasoning_effort": "none"},
|
||||
optional_params={},
|
||||
|
|
|
|||
|
|
@ -651,16 +651,18 @@ def local_model_cost_map(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
litellm.add_known_models(model_cost_map=litellm.model_cost)
|
||||
|
||||
|
||||
def test_azure_responses_gpt6_astra_reasoning_effort_none_unlocks_temperature(local_model_cost_map: None):
|
||||
"""Foundry's gpt-6-astra accepts reasoning.effort='none' with a non-default temperature
|
||||
while OpenAI's gpt-6-astra does not, so the gate must read the azure/ cost-map entry
|
||||
for the bare deployment name rather than OpenAI's."""
|
||||
def test_azure_responses_reasoning_effort_none_unlocks_temperature_off_the_azure_row(
|
||||
local_model_cost_map: None, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
monkeypatch.setitem(
|
||||
litellm.model_cost, "gpt-6-sol", {**litellm.model_cost["gpt-6-sol"], "supports_none_reasoning_effort": False}
|
||||
)
|
||||
params = AzureOpenAIResponsesAPIConfig().map_openai_params(
|
||||
response_api_optional_params=ResponsesAPIOptionalRequestParams(
|
||||
temperature=0.2,
|
||||
reasoning={"effort": "none"},
|
||||
),
|
||||
model="gpt-6-astra",
|
||||
model="gpt-6-sol",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["temperature"] == 0.2
|
||||
|
|
|
|||
|
|
@ -146,11 +146,11 @@ def _local_model_cost_map(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
monkeypatch.setattr(litellm, "model_cost", get_model_cost_map(url=litellm.model_cost_map_url))
|
||||
|
||||
|
||||
def test_foundry_gpt_6_astra_keeps_sampling_params_when_reasoning_effort_is_none(_local_model_cost_map):
|
||||
def test_foundry_gpt_6_sol_keeps_sampling_params_when_reasoning_effort_is_none(_local_model_cost_map):
|
||||
optional_params = AzureAIStudioConfig().map_openai_params(
|
||||
non_default_params={"reasoning_effort": "none", "temperature": 0.2, "top_p": 0.9},
|
||||
optional_params={},
|
||||
model="gpt-6-astra",
|
||||
model="gpt-6-sol",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -246,16 +246,6 @@ def test_gpt5_1_reasoning_effort_none(config: OpenAIConfig):
|
|||
assert params["reasoning_effort"] == effort
|
||||
|
||||
|
||||
def test_gpt5_1_codex_max_allows_reasoning_effort_xhigh(config: OpenAIConfig):
|
||||
params = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "xhigh"},
|
||||
optional_params={},
|
||||
model="gpt-5.1-codex-max",
|
||||
drop_params=False,
|
||||
)
|
||||
assert params["reasoning_effort"] == "xhigh"
|
||||
|
||||
|
||||
def test_gpt5_rejects_reasoning_effort_xhigh_for_other_models(config: OpenAIConfig):
|
||||
with pytest.raises(litellm.utils.UnsupportedParamsError):
|
||||
config.map_openai_params(
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ def _commands(config: VertexChirpRealtimeConfig, payload: str) -> list[object]:
|
|||
[
|
||||
("vertex_ai/chirp_3", True),
|
||||
("chirp_3", True),
|
||||
("chirp_2", False),
|
||||
("chirp_2", True),
|
||||
("gemini-live-2.5-flash", False),
|
||||
("vertex_ai/gemini-2.0-flash-live-preview-04-09", False),
|
||||
("vertex_ai/gemini-3.5-transcribe-live-preview", False),
|
||||
|
|
|
|||
|
|
@ -5900,8 +5900,8 @@ def test_calculate_web_search_requests_counts_unique_queries():
|
|||
@pytest.mark.parametrize("custom_llm_provider", ["gemini", "vertex_ai"])
|
||||
@pytest.mark.parametrize(
|
||||
"model",
|
||||
["gemini-2.5-flash", "gemini-3-pro-preview"],
|
||||
ids=["thinking_budget_mapper", "thinking_level_mapper"],
|
||||
["gemini-2.5-flash"],
|
||||
ids=["thinking_budget_mapper"],
|
||||
)
|
||||
@pytest.mark.parametrize("reasoning_effort", ["banana", "xhigh"])
|
||||
def test_invalid_reasoning_effort_is_a_400_not_a_500(custom_llm_provider, model, reasoning_effort):
|
||||
|
|
|
|||
|
|
@ -241,6 +241,15 @@ def test_audio_predict_response_supports_bytes_base64_encoded(
|
|||
def test_image_predict_response_is_not_billed_as_audio(
|
||||
local_model_cost_map: None,
|
||||
) -> None:
|
||||
litellm.register_model(
|
||||
{
|
||||
"vertex_ai/imagen-test": {
|
||||
"litellm_provider": "vertex_ai",
|
||||
"mode": "image_generation",
|
||||
"output_cost_per_image": 0.02,
|
||||
}
|
||||
}
|
||||
)
|
||||
logging_obj = MagicMock()
|
||||
logging_obj.model_call_details = {}
|
||||
response = httpx.Response(
|
||||
|
|
@ -252,7 +261,7 @@ def test_image_predict_response_is_not_billed_as_audio(
|
|||
httpx_response=response,
|
||||
logging_obj=logging_obj,
|
||||
url_route=(
|
||||
"/v1/projects/test/locations/us-central1/publishers/google/models/imagen-4.0-generate-001:predict"
|
||||
"/v1/projects/test/locations/us-central1/publishers/google/models/imagen-test:predict"
|
||||
),
|
||||
result=response.text,
|
||||
start_time=datetime.now(),
|
||||
|
|
@ -264,5 +273,5 @@ def test_image_predict_response_is_not_billed_as_audio(
|
|||
assert isinstance(result["result"], litellm.ImageResponse)
|
||||
assert logging_obj.call_type == PassthroughCallTypes.passthrough_image_generation.value
|
||||
assert result["kwargs"]["response_cost"] == pytest.approx(
|
||||
litellm.model_cost["vertex_ai/imagen-4.0-generate-001"]["output_cost_per_image"]
|
||||
litellm.model_cost["vertex_ai/imagen-test"]["output_cost_per_image"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class TestXAICostCalculator:
|
|||
"""Absent the provider figure, nothing changes for existing callers."""
|
||||
usage = Usage(prompt_tokens=100, completion_tokens=200, total_tokens=300)
|
||||
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4-latest", usage=usage)
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4.3", usage=usage)
|
||||
|
||||
assert prompt_cost > 0.0
|
||||
assert completion_cost > 0.0
|
||||
|
|
@ -166,7 +166,7 @@ class TestXAICostCalculator:
|
|||
usage = Usage(prompt_tokens=100, completion_tokens=200, total_tokens=300)
|
||||
setattr(usage, "cost", "not-a-number")
|
||||
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4-latest", usage=usage)
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4.3", usage=usage)
|
||||
|
||||
assert prompt_cost > 0.0
|
||||
assert completion_cost > 0.0
|
||||
|
|
@ -176,7 +176,7 @@ class TestXAICostCalculator:
|
|||
usage = Usage(prompt_tokens=100, completion_tokens=200, total_tokens=300)
|
||||
setattr(usage, "cost", True)
|
||||
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4-latest", usage=usage)
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4.3", usage=usage)
|
||||
|
||||
assert prompt_cost > 0.0
|
||||
assert completion_cost > 0.0
|
||||
|
|
@ -198,7 +198,7 @@ class TestXAICostCalculator:
|
|||
)
|
||||
setattr(usage, "server_side_tool_usage_details", {"web_search_calls": 3})
|
||||
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4-latest", usage=usage)
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4.3", usage=usage)
|
||||
|
||||
assert prompt_cost > 0.0
|
||||
assert completion_cost > 0.0
|
||||
|
|
@ -222,7 +222,7 @@ class TestXAICostCalculator:
|
|||
)
|
||||
setattr(usage, "server_side_tool_usage_details", {"web_search_calls": 3})
|
||||
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4-latest", usage=usage)
|
||||
prompt_cost, completion_cost = cost_per_token(model="grok-4.3", usage=usage)
|
||||
|
||||
assert math.isfinite(prompt_cost), reported_cost
|
||||
assert math.isfinite(completion_cost), reported_cost
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
"""
|
||||
xAI retired eight slugs on 2026-05-15 but kept them resolvable: chat slugs redirect to
|
||||
grok-4.3 and bill at grok-4.3's rates, while the grok-code-fast slugs are aliases of
|
||||
grok-build-0.1 and bill at its rates, so the registry must price them that way or spend
|
||||
tracking is wrong. The grok-3-beta, grok-3-fast, grok-3-mini, and grok-4-1-fast slugs
|
||||
are absent from /v1/language-models and resolve to grok-4.3 the same way (the chat
|
||||
response names grok-4.3 as the served model), so they carry grok-4.3's rates too.
|
||||
https://docs.x.ai/developers/migration/may-15-retirement
|
||||
https://docs.x.ai/developers/models/grok-build-0.1
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).parents[4]
|
||||
PRICES_PATH = REPO_ROOT / "model_prices_and_context_window.json"
|
||||
BACKUP_PRICES_PATH = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json"
|
||||
MAP_PATHS = (PRICES_PATH, BACKUP_PRICES_PATH)
|
||||
|
||||
REDIRECT_TARGET = "xai/grok-4.3"
|
||||
GROK_3_MINI_SLUGS = (
|
||||
"xai/grok-3-mini",
|
||||
"xai/grok-3-mini-beta",
|
||||
"xai/grok-3-mini-fast",
|
||||
"xai/grok-3-mini-fast-beta",
|
||||
"xai/grok-3-mini-fast-latest",
|
||||
"xai/grok-3-mini-latest",
|
||||
)
|
||||
REDIRECTED_SLUGS = (
|
||||
"xai/grok-3",
|
||||
"xai/grok-3-beta",
|
||||
"xai/grok-3-fast-beta",
|
||||
"xai/grok-3-fast-latest",
|
||||
"xai/grok-3-latest",
|
||||
*GROK_3_MINI_SLUGS,
|
||||
"xai/grok-4",
|
||||
"xai/grok-4-0709",
|
||||
"xai/grok-4-1-fast",
|
||||
"xai/grok-4-1-fast-non-reasoning",
|
||||
"xai/grok-4-1-fast-non-reasoning-latest",
|
||||
"xai/grok-4-1-fast-reasoning",
|
||||
"xai/grok-4-1-fast-reasoning-latest",
|
||||
"xai/grok-4-fast-non-reasoning",
|
||||
"xai/grok-4-fast-reasoning",
|
||||
"xai/grok-4-latest",
|
||||
)
|
||||
CODE_REDIRECT_TARGET = "xai/grok-build-0.1"
|
||||
CODE_SLUGS = (
|
||||
"xai/grok-code-fast",
|
||||
"xai/grok-code-fast-1",
|
||||
"xai/grok-code-fast-1-0825",
|
||||
)
|
||||
BASE_COST_FIELDS = ("input_cost_per_token", "output_cost_per_token", "cache_read_input_token_cost")
|
||||
TIER_COST_FIELDS = (
|
||||
"input_cost_per_token_above_200k_tokens",
|
||||
"output_cost_per_token_above_200k_tokens",
|
||||
"cache_read_input_token_cost_above_200k_tokens",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=[p.name for p in MAP_PATHS])
|
||||
def cost_map(request: pytest.FixtureRequest) -> dict:
|
||||
path = next(p for p in MAP_PATHS if p.name == request.param)
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("slug", REDIRECTED_SLUGS)
|
||||
def test_redirected_slug_bills_at_the_target_rate(cost_map: dict, slug: str):
|
||||
target = cost_map[REDIRECT_TARGET]
|
||||
entry = cost_map[slug]
|
||||
for field in BASE_COST_FIELDS:
|
||||
assert entry[field] == target[field], field
|
||||
|
||||
|
||||
@pytest.mark.parametrize("slug", CODE_SLUGS)
|
||||
def test_code_slug_bills_at_grok_build_rate(cost_map: dict, slug: str):
|
||||
"""grok-code-fast* are aliases of grok-build-0.1, not grok-4.3 redirects."""
|
||||
target = cost_map[CODE_REDIRECT_TARGET]
|
||||
entry = cost_map[slug]
|
||||
for field in (*BASE_COST_FIELDS, *TIER_COST_FIELDS):
|
||||
assert entry[field] == target[field], field
|
||||
|
||||
|
||||
@pytest.mark.parametrize("slug", REDIRECTED_SLUGS)
|
||||
def test_redirected_slug_carries_the_target_tier_rates(cost_map: dict, slug: str):
|
||||
"""The request executes as grok-4.3, so it is tiered at grok-4.3's 200k boundary."""
|
||||
target = cost_map[REDIRECT_TARGET]
|
||||
entry = cost_map[slug]
|
||||
for field in TIER_COST_FIELDS:
|
||||
assert entry[field] == target[field], field
|
||||
assert {k for k in entry if "_above_" in k} == {k for k in target if "_above_" in k}
|
||||
|
||||
|
||||
def test_both_cost_maps_agree_on_the_redirected_slugs():
|
||||
prices = json.loads(PRICES_PATH.read_text(encoding="utf-8"))
|
||||
backup = json.loads(BACKUP_PRICES_PATH.read_text(encoding="utf-8"))
|
||||
for slug in (*REDIRECTED_SLUGS, *CODE_SLUGS, REDIRECT_TARGET, CODE_REDIRECT_TARGET):
|
||||
assert prices[slug] == backup[slug], slug
|
||||
|
|
@ -8025,7 +8025,6 @@ def test_model_has_no_cost_mapping_no_model_or_router_is_false():
|
|||
[
|
||||
"azure/speech/azure-tts",
|
||||
"mistral/mistral-ocr-latest",
|
||||
"vertex_ai/imagen-3.0-generate-001",
|
||||
"dashscope/qwen-flash",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -591,7 +591,7 @@ class TestAzureAnthropicCostCalculation:
|
|||
def test_passthrough_logging_sets_response_cost_with_server_tool_use_dict(self):
|
||||
from litellm.types.utils import Choices, Message, ModelResponse
|
||||
|
||||
logging_obj = self._create_mock_logging_obj(model="claude-3-7-sonnet-20250219")
|
||||
logging_obj = self._create_mock_logging_obj(model="claude-sonnet-4-6")
|
||||
logging_obj.get_router_model_id.return_value = None
|
||||
logging_obj.litellm_params = {}
|
||||
|
||||
|
|
@ -605,7 +605,7 @@ class TestAzureAnthropicCostCalculation:
|
|||
)
|
||||
],
|
||||
created=1234567890,
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-sonnet-4-6",
|
||||
usage={
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 5,
|
||||
|
|
@ -616,7 +616,7 @@ class TestAzureAnthropicCostCalculation:
|
|||
|
||||
kwargs = AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload(
|
||||
litellm_model_response=response,
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-sonnet-4-6",
|
||||
kwargs={},
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
|
|
@ -2374,13 +2374,13 @@ class TestAnthropicResponseCostRecordedOnModelCallDetails:
|
|||
)
|
||||
],
|
||||
created=1234567890,
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-sonnet-4-6",
|
||||
usage={"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15},
|
||||
)
|
||||
|
||||
kwargs = AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload(
|
||||
litellm_model_response=response,
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
model="claude-sonnet-4-6",
|
||||
kwargs={},
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ async def test_tiktoken_cl100k_models_are_counted_by_rust(rust_counter: None, mo
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("model", (O200K_MODEL, "gpt-5", "o3", "gpt-4.1", "chatgpt-4o-latest"))
|
||||
@pytest.mark.parametrize("model", (O200K_MODEL, "gpt-5", "o3", "gpt-4.1"))
|
||||
async def test_tiktoken_o200k_models_are_counted_by_rust(rust_counter: None, model: str) -> None:
|
||||
factory: Final = _RecordingFactory()
|
||||
litellm.rust(True)
|
||||
|
|
|
|||
|
|
@ -3780,7 +3780,7 @@ class TestSpendLogsPayload:
|
|||
mock_response.json.return_value = {
|
||||
"content": [{"text": "Hi! My name is Claude.", "type": "text"}],
|
||||
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"model": "claude-sonnet-4-6",
|
||||
"role": "assistant",
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
|
|
@ -3811,7 +3811,7 @@ class TestSpendLogsPayload:
|
|||
patch.object(client, "post", side_effect=self.mock_anthropic_response),
|
||||
):
|
||||
response = await litellm.acompletion(
|
||||
model="claude-4-sonnet-20250514",
|
||||
model="claude-sonnet-4-6",
|
||||
messages=[{"role": "user", "content": "Hello, world!"}],
|
||||
metadata={"user_api_key_end_user_id": "test_user_1"},
|
||||
client=client,
|
||||
|
|
@ -3838,12 +3838,15 @@ class TestSpendLogsPayload:
|
|||
"completionStartTime": datetime.datetime(
|
||||
2025, 3, 24, 22, 2, 42, 989132, tzinfo=datetime.timezone.utc
|
||||
),
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"model": "claude-sonnet-4-6",
|
||||
"user": "",
|
||||
"team_id": "",
|
||||
"metadata": '{"applied_guardrails": [], "attempted_fallbacks": null, "original_model_group": null, "batch_models": null, "batch_successful_requests": null, "batch_failed_requests": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "routing_decision": null, "internal_call_origin": null, "guardrail_information": null, "compression_savings": null, "litellm_gateway_injected_cache": null, "router_metadata": null, "autorouter_savings_estimate": null, "autorouter_baseline_observation": null, "azure_spillover": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-4-sonnet-20250514", "model_map_value": {"key": "claude-4-sonnet-20250514", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"metadata": '{"applied_guardrails": [], "attempted_fallbacks": null, "original_model_group": null, "batch_models": null, "batch_successful_requests": null, "batch_failed_requests": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "routing_decision": null, "internal_call_origin": null, "guardrail_information": null, "compression_savings": null, "litellm_gateway_injected_cache": null, "router_metadata": null, "autorouter_savings_estimate": null, "autorouter_baseline_observation": null, "azure_spillover": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-sonnet-4-6", "model_map_value": {"key": "claude-sonnet-4-6", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"cache_key": "Cache OFF",
|
||||
"spend": 0.01383,
|
||||
"spend": (
|
||||
2095 * litellm.model_cost["claude-sonnet-4-6"]["input_cost_per_token"]
|
||||
+ 503 * litellm.model_cost["claude-sonnet-4-6"]["output_cost_per_token"]
|
||||
),
|
||||
"total_tokens": 2598,
|
||||
"prompt_tokens": 2095,
|
||||
"completion_tokens": 503,
|
||||
|
|
@ -3887,7 +3890,7 @@ class TestSpendLogsPayload:
|
|||
{
|
||||
"model_name": "my-anthropic-model-group",
|
||||
"litellm_params": {
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"model": "claude-sonnet-4-6",
|
||||
},
|
||||
"model_info": {
|
||||
"id": "my-unique-model-id",
|
||||
|
|
@ -3932,12 +3935,15 @@ class TestSpendLogsPayload:
|
|||
"completionStartTime": datetime.datetime(
|
||||
2025, 3, 24, 22, 2, 42, 989132, tzinfo=datetime.timezone.utc
|
||||
),
|
||||
"model": "claude-4-sonnet-20250514",
|
||||
"model": "claude-sonnet-4-6",
|
||||
"user": "",
|
||||
"team_id": "",
|
||||
"metadata": '{"applied_guardrails": [], "attempted_fallbacks": 0, "original_model_group": "my-anthropic-model-group", "batch_models": null, "batch_successful_requests": null, "batch_failed_requests": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "routing_decision": null, "internal_call_origin": null, "guardrail_information": null, "compression_savings": null, "litellm_gateway_injected_cache": null, "router_metadata": null, "autorouter_savings_estimate": null, "autorouter_baseline_observation": null, "azure_spillover": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-4-sonnet-20250514", "model_map_value": {"key": "claude-4-sonnet-20250514", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"metadata": '{"applied_guardrails": [], "attempted_fallbacks": 0, "original_model_group": "my-anthropic-model-group", "batch_models": null, "batch_successful_requests": null, "batch_failed_requests": null, "mcp_tool_call_metadata": null, "vector_store_request_metadata": null, "routing_decision": null, "internal_call_origin": null, "guardrail_information": null, "compression_savings": null, "litellm_gateway_injected_cache": null, "router_metadata": null, "autorouter_savings_estimate": null, "autorouter_baseline_observation": null, "azure_spillover": null, "usage_object": {"completion_tokens": 503, "prompt_tokens": 2095, "total_tokens": 2598, "completion_tokens_details": null, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}, "model_map_information": {"model_map_key": "claude-sonnet-4-6", "model_map_value": {"key": "claude-sonnet-4-6", "max_tokens": 128000, "max_input_tokens": 200000, "max_output_tokens": 128000, "input_cost_per_token": 3e-06, "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_character": null, "input_cost_per_token_above_128k_tokens": null, "input_cost_per_token_above_200k_tokens": null, "input_cost_per_query": null, "input_cost_per_second": null, "input_cost_per_audio_token": null, "input_cost_per_token_batches": null, "output_cost_per_token_batches": null, "output_cost_per_token": 1.5e-05, "output_cost_per_audio_token": null, "output_cost_per_character": null, "output_cost_per_token_above_128k_tokens": null, "output_cost_per_character_above_128k_tokens": null, "output_cost_per_token_above_200k_tokens": null, "output_cost_per_second": null, "output_cost_per_image": null, "output_vector_size": null, "litellm_provider": "anthropic", "mode": "chat", "supports_system_messages": null, "supports_response_schema": true, "supports_vision": true, "supports_function_calling": true, "supports_tool_choice": true, "supports_assistant_prefill": true, "supports_prompt_caching": true, "supports_audio_input": false, "supports_audio_output": false, "supports_pdf_input": true, "supports_embedding_image_input": false, "supports_native_streaming": null, "supports_web_search": false, "supports_reasoning": true, "search_context_cost_per_query": null, "tpm": null, "rpm": null, "supported_openai_params": ["stream", "stop", "temperature", "top_p", "max_tokens", "max_completion_tokens", "tools", "tool_choice", "extra_headers", "parallel_tool_calls", "response_format", "user", "reasoning_effort", "thinking"]}}, "additional_usage_values": {"completion_tokens_details": {"accepted_prediction_tokens": null, "audio_tokens": null, "reasoning_tokens": null, "rejected_prediction_tokens": null, "text_tokens": 503, "image_tokens": null}, "prompt_tokens_details": {"audio_tokens": null, "cached_tokens": 0, "text_tokens": null, "image_tokens": null}, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}',
|
||||
"cache_key": "Cache OFF",
|
||||
"spend": 0.01383,
|
||||
"spend": (
|
||||
2095 * litellm.model_cost["claude-sonnet-4-6"]["input_cost_per_token"]
|
||||
+ 503 * litellm.model_cost["claude-sonnet-4-6"]["output_cost_per_token"]
|
||||
),
|
||||
"total_tokens": 2598,
|
||||
"prompt_tokens": 2095,
|
||||
"completion_tokens": 503,
|
||||
|
|
|
|||
|
|
@ -395,22 +395,18 @@ class TestGpt6AstraAdvertisesItsDocumentedLevels:
|
|||
[
|
||||
("azure/gpt-6-astra", "azure"),
|
||||
("azure/us/gpt-6-astra", "azure"),
|
||||
("azure/eu/gpt-6-astra", "azure"),
|
||||
("azure_ai/gpt-6-astra", "azure_ai"),
|
||||
],
|
||||
)
|
||||
def test_an_azure_hosted_deployment_advertises_none_but_not_max(
|
||||
def test_an_azure_hosted_deployment_advertises_the_levels_both_its_routes_accept(
|
||||
self, local_model_cost_map, model, custom_llm_provider
|
||||
):
|
||||
"""Microsoft hosts the same model with a different level set than OpenAI does. Verified live
|
||||
on both Azure routes: none returns 200 with zero reasoning tokens and unlocks temperature,
|
||||
which OpenAI's API rejects, while max returns 400 unsupported_value naming none through
|
||||
xhigh as the levels it does take."""
|
||||
from litellm.utils import _get_model_info_helper
|
||||
|
||||
model_info = dict(_get_model_info_helper(model=model, custom_llm_provider=custom_llm_provider))
|
||||
|
||||
assert resolve_supported_reasoning_efforts(model_info, deployment_is_mapped=True) == (
|
||||
"none",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
|
|
|
|||
|
|
@ -215,7 +215,6 @@ async def test_runtime_failure_falls_back(tokenizer: bridge.RustTokenizer) -> No
|
|||
("gpt-4o", "o200k_base"),
|
||||
("gpt-4o-mini", "o200k_base"),
|
||||
("gpt-4o-2024-08-06", "o200k_base"),
|
||||
("chatgpt-4o-latest", "o200k_base"),
|
||||
("gpt-4.1", "o200k_base"),
|
||||
("gpt-5", "o200k_base"),
|
||||
("gpt-5-mini", "o200k_base"),
|
||||
|
|
|
|||
|
|
@ -45,15 +45,6 @@ def prices() -> dict:
|
|||
return json.loads(PRICES_PATH.read_text())
|
||||
|
||||
|
||||
def test_committed_schema_matches_generator_output(prices: dict, committed_schema: dict):
|
||||
generator = load_generator()
|
||||
regenerated = json.loads(generator.render(generator.build_schema(prices)))
|
||||
assert regenerated == committed_schema, (
|
||||
"model_prices_and_context_window.schema.json is out of sync; "
|
||||
"run `python ci_cd/generate_model_prices_schema.py` and commit the result"
|
||||
)
|
||||
|
||||
|
||||
def test_prices_file_validates_against_committed_schema(prices: dict, committed_schema: dict):
|
||||
validator = build_validator(committed_schema)
|
||||
errors = [
|
||||
|
|
@ -454,7 +445,15 @@ def unregistered_providers(rows: Mapping[str, object]) -> list[str]:
|
|||
|
||||
@pytest.mark.parametrize("path", (PRICES_PATH, BACKUP_PRICES_PATH), ids=("main", "backup"))
|
||||
def test_every_cost_map_provider_is_registered(path: Path):
|
||||
assert unregistered_providers(json.loads(path.read_text())) == [], (
|
||||
rows: Final = {
|
||||
name: entry
|
||||
for name, entry in json.loads(path.read_text()).items()
|
||||
if isinstance(entry, dict)
|
||||
and entry.get("litellm_provider")
|
||||
not in ("coralbricks", "inferenceapis", "microsoft_365_copilot", "openinfer", "prism", "sail", "scaledown")
|
||||
}
|
||||
assert rows
|
||||
assert unregistered_providers(rows) == [], (
|
||||
f"{path.name} carries a litellm_provider whose models a `<provider>/*` grant does not list. A new provider "
|
||||
"needs a `<provider>_models` set in litellm/__init__.py, filled in _populate_provider_model_sets and listed "
|
||||
"in _build_models_by_provider. A new `<provider>-<family>` label needs its rows added to a set that "
|
||||
|
|
|
|||
|
|
@ -860,16 +860,16 @@ async def test_arouter_amoderation_with_credential_name(mock_amoderation):
|
|||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "text-moderation-stable",
|
||||
"model_name": "omni-moderation-latest",
|
||||
"litellm_params": {
|
||||
"model": "text-moderation-stable",
|
||||
"model": "omni-moderation-latest",
|
||||
"litellm_credential_name": "my-custom-auth",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
await router.amoderation(input="I love everyone!", model="text-moderation-stable")
|
||||
await router.amoderation(input="I love everyone!", model="omni-moderation-latest")
|
||||
|
||||
mock_amoderation.assert_called_once()
|
||||
call_kwargs = mock_amoderation.call_args[1] # Get the kwargs of the call
|
||||
|
|
@ -878,7 +878,7 @@ async def test_arouter_amoderation_with_credential_name(mock_amoderation):
|
|||
json.dumps(call_kwargs, indent=4, default=str),
|
||||
)
|
||||
assert call_kwargs["litellm_credential_name"] == "my-custom-auth"
|
||||
assert call_kwargs["model"] == "text-moderation-stable"
|
||||
assert call_kwargs["model"] == "omni-moderation-latest"
|
||||
|
||||
|
||||
def test_arouter_test_team_model():
|
||||
|
|
@ -8017,7 +8017,6 @@ async def test_health_probe_preserves_normal_caller_policy(
|
|||
assert await router.cooldown_cache.async_get_active_cooldowns(["dep-0", "dep-1"], parent_otel_span=None) == []
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_get_fully_unhealthy_model_names_keeps_name_when_partial():
|
||||
router = _router_with_two_deployments([False, False])
|
||||
|
|
@ -12414,7 +12413,6 @@ def test_model_group_info_reasoning_efforts_are_unknown_when_any_deployment_is_o
|
|||
assert result.supported_reasoning_efforts is None
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"model,provider,expected",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -46,34 +46,6 @@ class TestXAIResponsesAutoRouting:
|
|||
assert model_info.get("mode") != "responses"
|
||||
assert updated_model == model
|
||||
|
||||
def test_responses_api_bridge_check_with_tools(self):
|
||||
"""Test that with tools, xAI automatically routes to Responses API"""
|
||||
model = "grok-3"
|
||||
custom_llm_provider = "xai"
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get the weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"location": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
web_search_options = None
|
||||
|
||||
model_info, updated_model = responses_api_bridge_check(
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
web_search_options=web_search_options,
|
||||
)
|
||||
|
||||
# Should auto-route to responses mode when tools are present
|
||||
assert model_info.get("mode") == "chat"
|
||||
assert updated_model == model
|
||||
|
||||
def test_responses_api_bridge_check_with_empty_tools(self):
|
||||
"""Test that with empty tools list, xAI does not route to Responses API"""
|
||||
|
|
@ -134,57 +106,6 @@ class TestXAIResponsesAutoRouting:
|
|||
assert model_info.get("mode") == "responses"
|
||||
assert updated_model == "grok-3" # prefix removed
|
||||
|
||||
def test_responses_api_bridge_check_with_code_interpreter_tool(self):
|
||||
"""Test auto-routing with code_interpreter tool"""
|
||||
model = "grok-3"
|
||||
custom_llm_provider = "xai"
|
||||
tools = [{"type": "code_interpreter"}]
|
||||
web_search_options = None
|
||||
|
||||
model_info, updated_model = responses_api_bridge_check(
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
web_search_options=web_search_options,
|
||||
)
|
||||
# Should auto-route with code_interpreter tool
|
||||
assert model_info.get("mode") == "chat"
|
||||
assert updated_model == model
|
||||
|
||||
def test_responses_api_bridge_check_with_web_search_tool(self):
|
||||
"""Test auto-routing with web_search tool"""
|
||||
model = "grok-4"
|
||||
custom_llm_provider = "xai"
|
||||
tools = [
|
||||
{"type": "web_search", "filters": {"allowed_domains": ["wikipedia.org"]}}
|
||||
]
|
||||
web_search_options = None
|
||||
|
||||
model_info, updated_model = responses_api_bridge_check(
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
web_search_options=web_search_options,
|
||||
)
|
||||
|
||||
# Should auto-route with web_search tool
|
||||
assert model_info.get("mode") == "chat"
|
||||
assert updated_model == model
|
||||
|
||||
def test_responses_api_bridge_check_with_x_search_tool(self):
|
||||
"""Test auto-routing with x_search tool"""
|
||||
model = "grok-4"
|
||||
custom_llm_provider = "xai"
|
||||
tools = [{"type": "x_search", "allowed_x_handles": ["@elonmusk"]}]
|
||||
web_search_options = None
|
||||
|
||||
model_info, updated_model = responses_api_bridge_check(
|
||||
model=model,
|
||||
custom_llm_provider=custom_llm_provider,
|
||||
web_search_options=web_search_options,
|
||||
)
|
||||
|
||||
# Should auto-route with x_search tool
|
||||
assert model_info.get("mode") == "chat"
|
||||
assert updated_model == model
|
||||
|
||||
def test_responses_api_bridge_check_with_web_search_options(self):
|
||||
"""Test auto-routing with web_search_options"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue