From eb66bc14d94dc841bbe9bc1c4584ab7505a13456 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sun, 18 Oct 2026 08:41:05 -0700 Subject: [PATCH] fix(cost-map): fill a model registered before the remote map lands from the remote entry A Router built while the first download is still failing writes an empty cost map entry for a bare model the bundled map does not know. The import merge skipped every key written after import, so that model never got its remote provider and its deployment stayed unresolved after the retry landed. Fields written since import still win, and the remote entry fills the rest. --- .../litellm_core_utils/get_model_cost_map.py | 7 ++++- .../test_get_model_cost_map.py | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/get_model_cost_map.py b/litellm/litellm_core_utils/get_model_cost_map.py index 0fc2be58b2d..69a19ffe6bf 100644 --- a/litellm/litellm_core_utils/get_model_cost_map.py +++ b/litellm/litellm_core_utils/get_model_cost_map.py @@ -852,6 +852,11 @@ def _merge_into_import_map(fetch: _ImportFetch, finalized: Mapping[str, object]) for key, entry in remote if key not in fetch.loaded and key not in fetch.bundled } + added_since_import: Final = { + key: _merged_entry(current, entry, frozenset(current.keys())) + for key, entry in remote + if key not in fetch.loaded and _is_cost_entry(current := fetch.bundled.get(key)) and _is_cost_entry(entry) + } dropped: Final = tuple( key for key in fetch.loaded.keys() - finalized.keys() @@ -859,7 +864,7 @@ def _merge_into_import_map(fetch: _ImportFetch, finalized: Mapping[str, object]) ) for live_entry, entry in updated_in_place: live_entry.update(entry) - fetch.bundled.update({**replaced, **added}) + fetch.bundled.update({**replaced, **added, **added_since_import}) for key in dropped: fetch.bundled.pop(key, None) GetModelCostMap._snapshot_loaded_catalog( # pyright: ignore[reportPrivateUsage] # same module diff --git a/tests/unit/litellm_core_utils/test_get_model_cost_map.py b/tests/unit/litellm_core_utils/test_get_model_cost_map.py index d618266209c..cc62fbf4eae 100644 --- a/tests/unit/litellm_core_utils/test_get_model_cost_map.py +++ b/tests/unit/litellm_core_utils/test_get_model_cost_map.py @@ -1304,6 +1304,36 @@ def test_a_router_built_before_the_remote_map_lands_prices_its_models_from_the_r assert litellm.get_model_info("openai/gpt-4o")["input_cost_per_token"] == remote_price +def test_a_router_built_before_the_remote_map_lands_serves_a_bare_model_only_the_remote_map_knows( + isolated_litellm_cost_state, monkeypatch +): + import litellm + from litellm.litellm_core_utils.get_model_cost_map import load_model_cost_map_at_import + + client, retry_started, release = _rate_limited_then_held_client(_remote_map_with("claude-remote-only-test")) + try: + monkeypatch.setattr( + litellm, "model_cost", load_model_cost_map_at_import(url=_URL, client=client, sleep=lambda _: None) + ) + assert retry_started.wait(timeout=10) + router = litellm.Router( + model_list=[ + { + "model_name": "remote-only", + "litellm_params": {"model": "claude-remote-only-test", "api_key": "sk-test"}, + } + ], + ignore_invalid_deployments=True, + ) + served_before_the_remote_map = len(router.model_list) + finally: + _finish_fetch(release) + + assert served_before_the_remote_map == 0 + assert [deployment["model_name"] for deployment in router.model_list] == ["remote-only"] + assert litellm.get_model_info("claude-remote-only-test")["litellm_provider"] == "anthropic" + + def test_a_partial_edit_made_before_the_remote_map_lands_keeps_only_the_fields_it_changed( isolated_litellm_cost_state, monkeypatch ):