fix(router): strip provider/ prefix on litellm.model_cost lookup

Greptile P1 on #30383: `litellm.model_cost` keys are bare (e.g.
`claude-haiku-4-5-20251001`), but `litellm_params.model` carries the
user-facing `<provider>/<model>` form that dashboard `/model/new`
produces (e.g. `anthropic/claude-haiku-4-5-20251001`). A raw
`model_cost.get(litellm_params.model)` misses for every dashboard-
added Anthropic / Gemini / Cohere / Mistral deployment, so the entire
backfill silently no-ops for the most common user-facing model-name
shape — defeating the PR's stated goal.

Fix: try raw lookup first (preserves the `bedrock/...` and other
existing `provider/model` keys in the static map), fall back to
`get_llm_provider(model=...)` which strips the prefix and returns
the same canonical bare name `get_model_info` / the cost calculator
already use.

Tests:
- `test_provider_prefixed_model_resolves_to_canonical`: asserts that
  `anthropic/claude-haiku-4-5-20251001` (the prefixed form) backfills
  cache rates from the bare canonical entry, with an explicit premise
  guard that fails loud if the static map ever starts carrying
  `anthropic/...` keys.

6/6 pass locally.
This commit is contained in:
songkuan-zheng 2026-06-16 05:02:59 +00:00
parent 7159b539e8
commit a56f4dadb3
2 changed files with 66 additions and 3 deletions

View file

@ -7946,10 +7946,27 @@ class Router:
(those that have a static model_cost entry). Mutates
``model_info_dict`` in place.
"""
bare_model_name = litellm_params.get("model")
if not bare_model_name:
model_param = litellm_params.get("model")
if not model_param:
return
canonical = litellm.model_cost.get(bare_model_name)
# litellm.model_cost keys are mostly bare (e.g. "claude-3-opus-20240229")
# while `litellm_params.model` carries the user-facing
# `<provider>/<model>` shape (e.g. "anthropic/claude-3-opus-20240229").
# Try the raw value first to cover entries that DO keep the slash
# (bedrock uses a `.` separator already, but some "provider/model"
# keys exist for non-OpenAI shapes); fall back to the
# `get_llm_provider` split, which is the same canonical normalization
# used by `get_model_info` and the cost calculator.
canonical = litellm.model_cost.get(model_param)
if canonical is None:
try:
bare_model_name, _, _, _ = get_llm_provider(model=model_param)
if bare_model_name and bare_model_name != model_param:
canonical = litellm.model_cost.get(bare_model_name)
except Exception:
# Don't let an unknown-provider parsing exception break
# deployment creation. No canonical -> no backfill.
pass
if not canonical:
return
for field in CustomPricingLiteLLMParams.model_fields.keys():

View file

@ -206,3 +206,49 @@ def test_backfill_cost_fields_from_canonical_direct_call():
assert (
model_info_dict.get(field) == canonical[field]
), f"{field} should have been backfilled from canonical entry"
def test_provider_prefixed_model_resolves_to_canonical():
"""Regression for greptile P1 on #30383: dashboard /model/new stores
the user-facing model as `anthropic/claude-haiku-4-5-20251001` in
`litellm_params.model`, but `litellm.model_cost` keys are bare
(no `provider/` prefix). A raw `model_cost.get(litellm_params.model)`
misses; backfill must fall back to `get_llm_provider` to strip the
prefix, otherwise the entire fix silently no-ops for the most
common user-facing model-name shape.
"""
deployment_uuid = "backfill-prefixed-uuid"
litellm.model_cost.pop(deployment_uuid, None)
# Verify the premise: prefixed lookup misses, bare lookup hits
assert litellm.model_cost.get(f"anthropic/{KNOWN_MODEL}") is None, (
"test premise broken — litellm.model_cost now contains "
"`anthropic/...` keys; review whether backfill stripping is "
"still required"
)
assert (
litellm.model_cost.get(KNOWN_MODEL) is not None
), "bundled JSON should contain bare KNOWN_MODEL"
# litellm_params.model carries the user-facing `provider/model` form
_build_router(deployment_uuid=deployment_uuid, model=f"anthropic/{KNOWN_MODEL}")
entry = litellm.model_cost.get(deployment_uuid, {})
canonical = litellm.model_cost[KNOWN_MODEL]
# Cache rates must have been backfilled from the bare canonical entry
backfilled_at_least_one = False
for field in (
"cache_creation_input_token_cost",
"cache_read_input_token_cost",
):
if canonical.get(field) is not None:
assert entry.get(field) == canonical[field], (
f"{field} must be backfilled from canonical bare entry "
f"when litellm_params.model has a provider/ prefix; "
f"got entry={entry.get(field)!r}, canonical={canonical[field]!r}"
)
backfilled_at_least_one = True
assert backfilled_at_least_one, (
"test setup expected canonical to carry at least one cache rate "
"to backfill; bundled JSON may have changed"
)