From 6cc9ea2538be4ad6681bb6b597b8529847bc10da Mon Sep 17 00:00:00 2001 From: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:27:18 -0700 Subject: [PATCH] fix(cost-map): retarget mistral-medium-latest to Medium 3.5 and add date-pinned aliases (#31373) * fix(cost-map): retarget mistral-medium-latest to Medium 3.5 and add date-pinned aliases Mistral repointed the rolling mistral-medium-latest alias from Medium 3.1 to Medium 3.5, but the static cost map still carried Medium 3.1 specs, showing wrong pricing/context in the model hub and undercharging spend by about 3.75x (LIT-3883). Update mistral/mistral-medium-latest to Medium 3.5 ($1.50/$7.50 per 1M, 256K context, reasoning + vision), add the bare date-pinned aliases mistral/mistral-medium-2604 (Medium 3.5) and mistral/mistral-medium-2508 (Medium 3.1) that match Mistral's real API model ids, and add supports_reasoning to mistral/mistral-medium-3-5. Apply every change to both model_prices_and_context_window.json and the bundled litellm/model_prices_and_context_window_backup.json so the two stay in sync, and extend the regression tests to lock the resolved get_model_info values and the main/backup parity for all touched models. * test(cost-map): force local cost map in mistral-medium-latest resolution test get_model_info reads litellm.model_cost, which is fetched from the remote main branch at import time when LITELLM_LOCAL_MODEL_COST_MAP is unset. Until this PR lands on main, that remote map still carries the pre-merge Medium 3.1 pricing, so the assertion was only passing when the remote fetch happened to fail and fell back to the bundled backup. Force the local cost map (the same fixture pattern the other get_model_info tests use) so the alias resolution is verified deterministically against the in-repo file. --- ...odel_prices_and_context_window_backup.json | 36 +++++++- model_prices_and_context_window.json | 36 +++++++- .../test_mistral_medium_3_5_model_metadata.py | 87 ++++++++++++++----- 3 files changed, 134 insertions(+), 25 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 1393c683860..d44fc654a56 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -25793,7 +25793,7 @@ "supports_response_schema": true, "supports_tool_choice": true }, - "mistral/mistral-medium-latest": { + "mistral/mistral-medium-2508": { "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 131072, @@ -25801,12 +25801,45 @@ "max_tokens": 131072, "mode": "chat", "output_cost_per_token": 2e-06, + "source": "https://mistral.ai/news/mistral-medium-3", "supports_assistant_prefill": true, "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true }, + "mistral/mistral-medium-2604": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "mistral", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 7.5e-06, + "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "mistral/mistral-medium-latest": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "mistral", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 7.5e-06, + "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "mistral/mistral-medium-3-1-2508": { "input_cost_per_token": 4e-07, "litellm_provider": "mistral", @@ -25833,6 +25866,7 @@ "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", "supports_assistant_prefill": true, "supports_function_calling": true, + "supports_reasoning": true, "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 9867874cb65..a174c1b5efd 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -25965,7 +25965,7 @@ "supports_response_schema": true, "supports_tool_choice": true }, - "mistral/mistral-medium-latest": { + "mistral/mistral-medium-2508": { "input_cost_per_token": 4e-07, "litellm_provider": "mistral", "max_input_tokens": 131072, @@ -25973,12 +25973,45 @@ "max_tokens": 131072, "mode": "chat", "output_cost_per_token": 2e-06, + "source": "https://mistral.ai/news/mistral-medium-3", "supports_assistant_prefill": true, "supports_function_calling": true, "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true }, + "mistral/mistral-medium-2604": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "mistral", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 7.5e-06, + "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "mistral/mistral-medium-latest": { + "input_cost_per_token": 1.5e-06, + "litellm_provider": "mistral", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 7.5e-06, + "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "mistral/mistral-medium-3-1-2508": { "input_cost_per_token": 4e-07, "litellm_provider": "mistral", @@ -26005,6 +26038,7 @@ "source": "https://docs.mistral.ai/models/model-cards/mistral-medium-3-5-26-04", "supports_assistant_prefill": true, "supports_function_calling": true, + "supports_reasoning": true, "supports_response_schema": true, "supports_tool_choice": true, "supports_vision": true diff --git a/tests/test_litellm/test_mistral_medium_3_5_model_metadata.py b/tests/test_litellm/test_mistral_medium_3_5_model_metadata.py index 496b0276b87..7cc05d6e30a 100644 --- a/tests/test_litellm/test_mistral_medium_3_5_model_metadata.py +++ b/tests/test_litellm/test_mistral_medium_3_5_model_metadata.py @@ -3,19 +3,45 @@ from pathlib import Path import pytest +import litellm from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider +REPO_ROOT = Path(__file__).parents[2] +MAIN_PATH = REPO_ROOT / "model_prices_and_context_window.json" +BACKUP_PATH = REPO_ROOT / "litellm" / "model_prices_and_context_window_backup.json" -@pytest.mark.parametrize("model", ["mistral/mistral-medium-3-5"]) -def test_mistral_medium_3_5_model_info(model): - json_path = Path(__file__).parents[2] / "model_prices_and_context_window.json" - with open(json_path) as f: - model_cost = json.load(f) +MEDIUM_3_5_MODELS = ( + "mistral/mistral-medium-3-5", + "mistral/mistral-medium-2604", + "mistral/mistral-medium-latest", +) - info = model_cost.get(model) - assert ( - info is not None - ), f"{model} not found in model_prices_and_context_window.json" +SYNCED_MODELS = MEDIUM_3_5_MODELS + ( + "mistral/mistral-medium-2508", + "mistral/mistral-medium-3-1-2508", +) + + +def _load(path): + with open(path) as f: + return json.load(f) + + +@pytest.fixture +def local_model_cost_map(monkeypatch): + """Force get_model_info to resolve against the in-repo cost map instead of the + remote one fetched at import time, which still carries the pre-merge pricing.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + litellm.get_model_info.cache_clear() + yield + litellm.get_model_info.cache_clear() + + +@pytest.mark.parametrize("model", MEDIUM_3_5_MODELS) +def test_medium_3_5_specs(model): + info = _load(MAIN_PATH).get(model) + assert info is not None, f"{model} missing from model_prices_and_context_window.json" assert info["litellm_provider"] == "mistral" assert info["mode"] == "chat" @@ -27,10 +53,11 @@ def test_mistral_medium_3_5_model_info(model): assert info["max_output_tokens"] == 262144 assert info["max_tokens"] == 262144 + assert info["supports_reasoning"] is True + assert info["supports_vision"] is True assert info["supports_function_calling"] is True assert info["supports_response_schema"] is True assert info["supports_tool_choice"] is True - assert info["supports_vision"] is True assert info["supports_assistant_prefill"] is True routed_model, provider, _, _ = get_llm_provider(model=model) @@ -38,18 +65,32 @@ def test_mistral_medium_3_5_model_info(model): assert provider == "mistral" -def test_mistral_medium_3_5_backup_matches_main(): - """Ensure the bundled model cost map stays in sync with the canonical file.""" - repo_root = Path(__file__).parents[2] - main_path = repo_root / "model_prices_and_context_window.json" - backup_path = repo_root / "litellm" / "model_prices_and_context_window_backup.json" +def test_mistral_medium_latest_resolves_to_medium_3_5(local_model_cost_map): + """LIT-3883: the -latest alias was retargeted to Medium 3.5; get_model_info must + return the 3.5 pricing/context/reasoning, not the stale Medium 3.1 values.""" + info = litellm.get_model_info(model="mistral/mistral-medium-latest") - with open(main_path) as f: - main_cost = json.load(f) - with open(backup_path) as f: - backup_cost = json.load(f) + assert info["input_cost_per_token"] == 1.5e-06 + assert info["output_cost_per_token"] == 7.5e-06 + assert info["max_input_tokens"] == 262144 + assert info["supports_reasoning"] is True - for model in ("mistral/mistral-medium-3-5",): - assert backup_cost.get(model) == main_cost.get( - model - ), f"{model} differs between main and backup model cost maps" + +def test_mistral_medium_2508_keeps_medium_3_1_specs(): + """The date-pinned 2508 alias is Medium 3.1 and must not inherit 3.5 pricing.""" + info = _load(MAIN_PATH).get("mistral/mistral-medium-2508") + assert info is not None, "mistral/mistral-medium-2508 missing from cost map" + + assert info["input_cost_per_token"] == 4e-07 + assert info["output_cost_per_token"] == 2e-06 + assert info["max_input_tokens"] == 131072 + assert info.get("supports_reasoning") is not True + + +@pytest.mark.parametrize("model", SYNCED_MODELS) +def test_backup_matches_main(model): + """Ensure the bundled (backup) cost map stays in sync with the canonical file.""" + main_cost = _load(MAIN_PATH) + backup_cost = _load(BACKUP_PATH) + + assert backup_cost.get(model) == main_cost.get(model), f"{model} differs between main and backup model cost maps"