From 38bc855235e5c442219a808ca4b301ee73c37675 Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 18 Sep 2026 02:09:04 +0000 Subject: [PATCH 1/3] test(cost_map): stop pinning supports_reasoning absent on the openrouter o1 entry Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../litellm_core_utils/test_fallback_generalizations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py b/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py index f7793286c7a..91e43cba825 100644 --- a/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py +++ b/tests/test_litellm/litellm_core_utils/test_fallback_generalizations.py @@ -951,7 +951,6 @@ def test_shipped_openai_reasoning_rule_loses_to_mapped_entries(shipped_cost_map) [ ("azure/us/o1-2024-12-17", "azure", True), ("github_copilot/gpt-5", "github_copilot", None), - ("openrouter/openai/o1", "openrouter", None), ("perplexity/openai/gpt-5.4-mini", "perplexity", None), ], ) From 6933ca2337899093bc8c1dc1d5b0b44237eb7955 Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 18 Sep 2026 02:13:44 +0000 Subject: [PATCH 2/3] fix(model_prices): add cache-read pricing to Mistral chat models missing it Mistral bills cached prompt tokens at 10% of the input price for every model, but twelve active mistral/ chat rows had no cache_read_input_token_cost, so the cost calculator billed their cache hits at zero. Adds the derived rate to those rows in both registry copies and a registry invariant test that fails when an active priced Mistral chat row drops the field or drifts from the 10% ratio Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ...odel_prices_and_context_window_backup.json | 12 ++++++++ model_prices_and_context_window.json | 12 ++++++++ .../test_litellm/test_model_prices_schema.py | 28 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b78d3becbf5..355e2b90e96 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -36785,6 +36785,7 @@ "supports_tool_choice": true }, "mistral/codestral-mamba-latest": { + "cache_read_input_token_cost": 2.5e-08, "input_cost_per_token": 2.5e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36842,6 +36843,7 @@ "supports_tool_choice": true }, "mistral/devstral-small-latest": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36871,6 +36873,7 @@ "supports_tool_choice": true }, "mistral/devstral-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36885,6 +36888,7 @@ "supports_tool_choice": true }, "mistral/devstral-medium-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36986,6 +36990,7 @@ "source": "https://docs.mistral.ai/models/mistral-embed-23-12" }, "mistral/mistral-medium-3": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 1.5e-06, "litellm_provider": "mistral", "max_input_tokens": 262144, @@ -37034,6 +37039,7 @@ "supports_audio_output": true }, "mistral/voxtral-small-2507": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_second": 6.666666666666667e-05, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", @@ -37049,6 +37055,7 @@ "supports_tool_choice": true }, "mistral/voxtral-small-latest": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_second": 6.666666666666667e-05, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", @@ -37554,6 +37561,7 @@ "supports_vision": true }, "mistral/mistral-small": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", "max_input_tokens": 32000, @@ -37680,6 +37688,7 @@ "supports_vision": true }, "mistral/mistral-tiny": { + "cache_read_input_token_cost": 2.5e-08, "input_cost_per_token": 2.5e-07, "litellm_provider": "mistral", "max_input_tokens": 32000, @@ -37718,6 +37727,7 @@ "supports_tool_choice": true }, "mistral/open-mistral-nemo": { + "cache_read_input_token_cost": 3e-08, "input_cost_per_token": 3e-07, "litellm_provider": "mistral", "max_input_tokens": 128000, @@ -37803,6 +37813,7 @@ "supports_vision": true }, "mistral/pixtral-large-latest": { + "cache_read_input_token_cost": 2e-07, "input_cost_per_token": 2e-06, "litellm_provider": "mistral", "max_input_tokens": 128000, @@ -62790,6 +62801,7 @@ "supports_tool_choice": true }, "mistral/mistral-code-agent-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b78d3becbf5..355e2b90e96 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -36785,6 +36785,7 @@ "supports_tool_choice": true }, "mistral/codestral-mamba-latest": { + "cache_read_input_token_cost": 2.5e-08, "input_cost_per_token": 2.5e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36842,6 +36843,7 @@ "supports_tool_choice": true }, "mistral/devstral-small-latest": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36871,6 +36873,7 @@ "supports_tool_choice": true }, "mistral/devstral-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36885,6 +36888,7 @@ "supports_tool_choice": true }, "mistral/devstral-medium-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, @@ -36986,6 +36990,7 @@ "source": "https://docs.mistral.ai/models/mistral-embed-23-12" }, "mistral/mistral-medium-3": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 1.5e-06, "litellm_provider": "mistral", "max_input_tokens": 262144, @@ -37034,6 +37039,7 @@ "supports_audio_output": true }, "mistral/voxtral-small-2507": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_second": 6.666666666666667e-05, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", @@ -37049,6 +37055,7 @@ "supports_tool_choice": true }, "mistral/voxtral-small-latest": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_second": 6.666666666666667e-05, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", @@ -37554,6 +37561,7 @@ "supports_vision": true }, "mistral/mistral-small": { + "cache_read_input_token_cost": 1e-08, "input_cost_per_token": 1e-07, "litellm_provider": "mistral", "max_input_tokens": 32000, @@ -37680,6 +37688,7 @@ "supports_vision": true }, "mistral/mistral-tiny": { + "cache_read_input_token_cost": 2.5e-08, "input_cost_per_token": 2.5e-07, "litellm_provider": "mistral", "max_input_tokens": 32000, @@ -37718,6 +37727,7 @@ "supports_tool_choice": true }, "mistral/open-mistral-nemo": { + "cache_read_input_token_cost": 3e-08, "input_cost_per_token": 3e-07, "litellm_provider": "mistral", "max_input_tokens": 128000, @@ -37803,6 +37813,7 @@ "supports_vision": true }, "mistral/pixtral-large-latest": { + "cache_read_input_token_cost": 2e-07, "input_cost_per_token": 2e-06, "litellm_provider": "mistral", "max_input_tokens": 128000, @@ -62790,6 +62801,7 @@ "supports_tool_choice": true }, "mistral/mistral-code-agent-latest": { + "cache_read_input_token_cost": 4e-08, "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 256000, diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index e562797fbe8..94a9afe72c6 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -274,3 +274,31 @@ def test_every_bedrock_openai_gpt_row_advertises_xhigh(prices: dict): and "xhigh" not in (resolve_supported_reasoning_efforts(entry, deployment_is_mapped=True) or ()) ] assert missing == [] + + +def is_active_priced_mistral_chat_row(name: str, entry: dict) -> bool: + return ( + name.startswith("mistral/") + and entry.get("mode") == "chat" + and entry.get("deprecation_date") is None + and (entry.get("input_cost_per_token") or 0) > 0 + ) + + +def test_active_mistral_chat_rows_price_cache_reads_below_input(prices: dict): + """A Mistral chat row without a cache-read rate bills cached prompt tokens at zero, so every + active priced row must carry one, and it must be cheaper than a fresh input token. Mistral + bills cached tokens at 10% of the input price for every model (docs.mistral.ai/studio/ + conversations/advanced/prompt-caching, read 2026-09-18), so the ratio is checked as well.""" + drifted: Final = [ + f"{name}: cache_read={entry.get('cache_read_input_token_cost')} input={entry['input_cost_per_token']}" + for name, entry in prices.items() + if isinstance(entry, dict) + and is_active_priced_mistral_chat_row(name, entry) + and not ( + isinstance(entry.get("cache_read_input_token_cost"), float) + and 0 < entry["cache_read_input_token_cost"] < entry["input_cost_per_token"] + and entry["cache_read_input_token_cost"] == pytest.approx(entry["input_cost_per_token"] / 10) + ) + ] + assert drifted == [] From 1bc4509bf047d93c881b00fea9f442d055d34289 Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 18 Sep 2026 02:22:06 +0000 Subject: [PATCH 3/3] test(model_prices): type the Mistral cache-read helpers and check the backup registry too Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../test_litellm/test_model_prices_schema.py | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py index 94a9afe72c6..2f9b11a16b7 100644 --- a/tests/test_litellm/test_model_prices_schema.py +++ b/tests/test_litellm/test_model_prices_schema.py @@ -3,6 +3,7 @@ from __future__ import annotations import importlib.util import json import re +from collections.abc import Mapping from pathlib import Path from types import MappingProxyType from typing import Final @@ -276,29 +277,40 @@ def test_every_bedrock_openai_gpt_row_advertises_xhigh(prices: dict): assert missing == [] -def is_active_priced_mistral_chat_row(name: str, entry: dict) -> bool: +def is_active_priced_mistral_chat_row(name: str, entry: Mapping[str, object]) -> bool: + input_cost: Final = entry.get("input_cost_per_token") return ( name.startswith("mistral/") and entry.get("mode") == "chat" and entry.get("deprecation_date") is None - and (entry.get("input_cost_per_token") or 0) > 0 + and isinstance(input_cost, (int, float)) + and input_cost > 0 ) -def test_active_mistral_chat_rows_price_cache_reads_below_input(prices: dict): +def cache_read_is_tenth_of_input(entry: Mapping[str, object]) -> bool: + cache_read: Final = entry.get("cache_read_input_token_cost") + input_cost: Final = entry.get("input_cost_per_token") + return ( + isinstance(cache_read, float) + and isinstance(input_cost, (int, float)) + and 0 < cache_read < input_cost + and cache_read == pytest.approx(input_cost / 10) + ) + + +@pytest.mark.parametrize("path", (PRICES_PATH, BACKUP_PRICES_PATH), ids=("main", "backup")) +def test_active_mistral_chat_rows_price_cache_reads_below_input(path: Path): """A Mistral chat row without a cache-read rate bills cached prompt tokens at zero, so every active priced row must carry one, and it must be cheaper than a fresh input token. Mistral bills cached tokens at 10% of the input price for every model (docs.mistral.ai/studio/ conversations/advanced/prompt-caching, read 2026-09-18), so the ratio is checked as well.""" + rows: Mapping[str, object] = json.loads(path.read_text()) drifted: Final = [ - f"{name}: cache_read={entry.get('cache_read_input_token_cost')} input={entry['input_cost_per_token']}" - for name, entry in prices.items() + f"{name}: cache_read={entry.get('cache_read_input_token_cost')} input={entry.get('input_cost_per_token')}" + for name, entry in rows.items() if isinstance(entry, dict) and is_active_priced_mistral_chat_row(name, entry) - and not ( - isinstance(entry.get("cache_read_input_token_cost"), float) - and 0 < entry["cache_read_input_token_cost"] < entry["input_cost_per_token"] - and entry["cache_read_input_token_cost"] == pytest.approx(entry["input_cost_per_token"] / 10) - ) + and not cache_read_is_tenth_of_input(entry) ] assert drifted == []