fix(proxy): report null cost for unpriced deployments on the /model/info id lookup

GET /model/info?litellm_model_id= went through _get_proxy_model_info, a copy of _enrich_model_info_with_litellm_data that missed the unpriced check, so the same deployment read as null in the list and as 0 on the id lookup. The id lookup now delegates to the shared helper, so the two paths cannot drift again
This commit is contained in:
ryan-crabbe-berri 2026-09-18 13:08:55 -07:00
parent 170581e710
commit d37320f8cf
2 changed files with 51 additions and 39 deletions

View file

@ -15057,45 +15057,7 @@ def _translate_model_name_for_response(model: dict) -> dict:
def _get_proxy_model_info(model: dict) -> dict:
# provided model_info in config.yaml
model_info: Final = model.get("model_info", {})
# read litellm model_prices_and_context_window.json to get the following:
# input_cost_per_token, output_cost_per_token, max_tokens
litellm_model_info = get_litellm_model_info(model=model)
# 2nd pass on the model, try seeing if we can find model in litellm model_cost map
if litellm_model_info == {}:
# use litellm_param model_name to get model_info
litellm_params = model.get("litellm_params", {})
litellm_model = litellm_params.get("model", None)
try:
litellm_model_info = litellm.get_model_info(model=litellm_model)
except Exception:
litellm_model_info = {}
# 3rd pass on the model, try seeing if we can find model but without the "/" in model cost map
if litellm_model_info == {}:
# use litellm_param model_name to get model_info
litellm_params = model.get("litellm_params", {})
litellm_model = litellm_params.get("model", None)
split_model: Final = litellm_model.split("/")
if len(split_model) > 0:
litellm_model = split_model[-1]
try:
litellm_model_info = litellm.get_model_info(model=litellm_model, custom_llm_provider=split_model[0])
except Exception:
litellm_model_info = {}
discovered_model_info: Final = (
llm_router.get_discovered_model_info(model_info.get("id")) if llm_router is not None else MappingProxyType({})
)
for k, v in MappingProxyType({**litellm_model_info, **discovered_model_info}).items():
if k not in model_info or (model_info[k] is None and k in discovered_model_info):
model_info[k] = v
model["model_info"] = model_info
# don't return the llm credentials
model = remove_sensitive_info_from_deployment(deployment_dict=model, excluded_keys={"litellm_credential_name"})
return _translate_model_name_for_response(model)
return _translate_model_name_for_response(_enrich_model_info_with_litellm_data(model=model, llm_router=llm_router))
def _model_info_json_response(data: Sequence[Mapping[str, object]] | Mapping[str, object]) -> Response:

View file

@ -323,6 +323,56 @@ def test_model_info_reports_null_cost_for_unpriced_deployment_and_zero_for_decla
assert input_cost > 0 and output_cost > 0
def test_model_info_id_lookup_reports_the_same_cost_as_the_list(
client: TestClient,
auth_as: Callable[[], AbstractContextManager[object]],
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``GET /model/info?litellm_model_id=`` must agree with the ``GET /model/info`` list, so an
unpriced deployment cannot read as null in the list and as free on the id lookup."""
monkeypatch.setattr(litellm, "model_cost", copy.deepcopy(litellm.model_cost))
declared: Final = {"input_cost_per_token": 0.001, "output_cost_per_token": 0.002}
free: Final = {"input_cost_per_token": 0, "output_cost_per_token": 0}
router: Final = litellm.Router(
model_list=[
{
"model_name": name,
"litellm_params": {"model": f"openai/{name}", "api_key": "x", "api_base": "http://vllm", **costs},
"model_info": {"id": f"{name}-id"},
}
for name, costs in (("vllm-unpriced", {}), ("vllm-free", free), ("vllm-priced", declared))
]
)
monkeypatch.setattr(proxy_server, "llm_router", router)
monkeypatch.setattr(proxy_server, "llm_model_list", router.get_model_list())
monkeypatch.setattr(proxy_server, "user_model", None)
def costs_of(response: httpx.Response) -> dict[str, tuple[object, object]]:
assert response.status_code == 200, response.text
return {
row["model_info"]["id"]: (
row["model_info"].get("input_cost_per_token"),
row["model_info"].get("output_cost_per_token"),
)
for row in response.json()["data"]
}
with auth_as():
listed: Final = costs_of(client.get("/model/info"))
by_id: Final = {
model_id: costs_of(client.get("/model/info", params={"litellm_model_id": model_id}))[model_id]
for model_id in listed
}
assert listed == {
"vllm-unpriced-id": (None, None),
"vllm-free-id": (0, 0),
"vllm-priced-id": (declared["input_cost_per_token"], declared["output_cost_per_token"]),
}
assert by_id == listed
_invalidate_model_cost_lowercase_map()
def test_v1_model_info_star_wildcard_filter_keeps_provider_expansion(monkeypatch):
from litellm.proxy._types import SpecialModelNames, UserAPIKeyAuth
from litellm.proxy.auth import model_checks