fix(proxy): drop cost-map metadata echoed back on model save

Filter unchanged cost-map fields from model-info save echoes while preserving edited overrides.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-09-19 09:03:19 +00:00
parent 5fc510a6fd
commit 52e1732fc9
3 changed files with 157 additions and 2 deletions

View file

@ -137,7 +137,12 @@ from litellm.types.router import (
updateDeployment,
updateLiteLLMParams,
)
from litellm.types.utils import echoed_cost_map_pricing_fields, without_server_derived_pricing
from litellm.types.utils import (
COST_MAP_LOOKUP_KEY,
echoed_cost_map_fields,
echoed_cost_map_pricing_fields,
without_server_derived_pricing,
)
from litellm.utils import get_utc_datetime
if TYPE_CHECKING:
@ -871,6 +876,16 @@ def _ptu_priced_deployment(model_params: Deployment) -> Deployment:
)
def _cost_map_entry(model_info: Mapping[str, object]) -> Mapping[str, object]:
key: Final = model_info.get(COST_MAP_LOOKUP_KEY)
if not isinstance(key, str):
return MappingProxyType({})
try:
return MappingProxyType(dict(litellm.get_model_info(model=key)))
except Exception:
return MappingProxyType({})
def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> PrismaCompatibleUpdateDBModel:
if updated_patch.model_info is not None:
_raise_if_ptu_cost_attribution_disabled(updated_patch.model_info.model_dump(exclude_none=True))
@ -893,7 +908,17 @@ def update_db_model(db_model: Deployment, updated_patch: updateDeployment) -> Pr
# update model info
if updated_patch.model_info:
merged_model_info.update(without_server_derived_pricing(updated_patch.model_info.model_dump(exclude_none=True)))
incoming_model_info: Final = updated_patch.model_info.model_dump(exclude_none=True)
echoed_fields: Final = echoed_cost_map_fields(incoming_model_info, _cost_map_entry(incoming_model_info))
merged_model_info.update(
MappingProxyType(
dict(
(k, v)
for k, v in without_server_derived_pricing(incoming_model_info).items()
if k not in echoed_fields
)
)
)
# Honor explicit-null clears LAST, after both merges, so a model_info blob a client
# passes through cannot silently undo a litellm_params clear via .update().

View file

@ -3758,6 +3758,18 @@ def echoed_cost_map_pricing_fields(model_info: Mapping[str, Any]) -> tuple[str,
return tuple(sorted(k for k in model_info if is_server_derived_pricing_key(k)))
def echoed_cost_map_fields(model_info: Mapping[str, Any], cost_map_entry: Mapping[str, Any]) -> tuple[str, ...]:
"""Fields a ``/model/info`` echo copied from the cost map unchanged.
Only ``litellm.get_model_info`` emits ``key``, so a blob carrying it is an echo of that
response. Anything in it that still equals the resolved cost-map entry is a display value
nobody typed; a value the operator edited differs and stays a real override.
"""
if COST_MAP_LOOKUP_KEY not in model_info:
return ()
return tuple(sorted(k for k, v in model_info.items() if k in cost_map_entry and cost_map_entry[k] == v))
def pricing_override_fields(*sources: Mapping[str, Any]) -> tuple[str, ...]:
return tuple(
sorted(

View file

@ -3949,6 +3949,124 @@ class TestModelInfoServerDerivedPricingFilter:
assert written["access_groups"] == ["prod"]
class TestModelInfoCostMapEchoFilter:
"""LIT-5534. ``/model/info`` fills a deployment's ``model_info`` from the cost map (context
limits, mode, provider, supported params, capability flags), and the Admin UI edit form sends
that whole blob back on any save. Only values that still equal the cost-map entry are the
echo; a value the operator changed is a real override and stays."""
def test_echoed_cost_map_metadata_is_not_persisted(self):
import litellm
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
entry = litellm.get_model_info("gpt-5.6")
echo = {**entry, "id": "dep-echo-0", "db_model": True, "access_groups": ["prod"]}
db_model = Deployment(
model_name="gpt-5.6",
litellm_params=LiteLLM_Params(model="openai/gpt-5.6"),
model_info=ModelInfo(id="dep-echo-0"),
)
result = update_db_model(
db_model=db_model,
updated_patch=updateDeployment(model_info=ModelInfo(**echo)),
)
info = json.loads(result["model_info"])
assert info["access_groups"] == ["prod"]
assert set(info).isdisjoint(entry)
assert "max_input_tokens" not in info and "mode" not in info and "supports_vision" not in info, (
"cost-map metadata must not be persisted from an unchanged /model/info echo"
)
def test_an_edited_value_survives_the_echo_filter(self):
import litellm
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
entry = litellm.get_model_info("gpt-5.6")
echo = {
**entry,
"id": "dep-echo-1",
"db_model": True,
"access_groups": ["prod"],
"max_input_tokens": entry["max_input_tokens"] + 1,
"mode": "completion" if entry["mode"] != "completion" else "chat",
}
db_model = Deployment(
model_name="gpt-5.6",
litellm_params=LiteLLM_Params(model="openai/gpt-5.6"),
model_info=ModelInfo(id="dep-echo-1"),
)
result = update_db_model(
db_model=db_model,
updated_patch=updateDeployment(model_info=ModelInfo(**echo)),
)
info = json.loads(result["model_info"])
assert info["max_input_tokens"] == echo["max_input_tokens"]
assert info["mode"] == echo["mode"]
assert "litellm_provider" not in info
assert "supported_openai_params" not in info
def test_metadata_without_a_cost_map_key_is_persisted(self):
import litellm
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
from litellm.types.utils import echoed_cost_map_fields
entry = litellm.get_model_info("gpt-5.6")
assert echoed_cost_map_fields({"max_input_tokens": entry["max_input_tokens"]}, entry) == ()
db_model = Deployment(
model_name="gpt-5.6",
litellm_params=LiteLLM_Params(model="openai/gpt-5.6"),
model_info=ModelInfo(id="dep-echo-2"),
)
result = update_db_model(
db_model=db_model,
updated_patch=updateDeployment(
model_info=ModelInfo(
id="dep-echo-2",
max_input_tokens=entry["max_input_tokens"],
mode=entry["mode"],
)
),
)
info = json.loads(result["model_info"])
assert info["max_input_tokens"] == entry["max_input_tokens"]
assert info["mode"] == entry["mode"]
def test_a_stored_mode_survives_an_echoed_save(self):
import litellm
from litellm.proxy.management_endpoints.model_management_endpoints import update_db_model
from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo
entry = litellm.get_model_info("gpt-5.6")
db_model = Deployment(
model_name="gpt-5.6",
litellm_params=LiteLLM_Params(model="openai/gpt-5.6"),
model_info=ModelInfo(id="dep-echo-3", mode=entry["mode"]),
)
echo = {**entry, "id": "dep-echo-3", "db_model": True, "access_groups": ["prod"]}
result = update_db_model(
db_model=db_model,
updated_patch=updateDeployment(model_info=ModelInfo(**echo)),
)
info = json.loads(result["model_info"])
assert info["mode"] == entry["mode"]
assert "max_input_tokens" not in info
class TestUpdateDBModelClearCacheControlInjectionPoints:
def test_explicit_null_removes_stored_injection_points(self):
from litellm.proxy.management_endpoints.model_management_endpoints import (