feat(proxy): add x-litellm-model-name response header with deployment model string (#33698)

The proxy already returns x-litellm-model-id (the deployment id) and x-litellm-model-group (the requested model-group alias), but never surfaces the concrete underlying model that served the request; the router rewrites the response model field to the group alias, so callers had no way to read the actual deployment model like anthropic/claude-haiku-4-5. Expose it as x-litellm-model-name, sourced from the deployment recorded in litellm_params metadata.

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-07-17 20:29:42 -07:00 • committed by GitHub
parent 3829fa3014
commit 93afde8605
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 0 deletions

View file

@ -925,9 +925,12 @@ class ProxyBaseLLMRequestProcessing:
# If conversion fails, use original spend
pass
model_name = ProxyBaseLLMRequestProcessing._get_deployment_model_name(litellm_logging_obj)
headers = {
"x-litellm-call-id": call_id,
"x-litellm-model-id": model_id,
"x-litellm-model-name": model_name,
"x-litellm-cache-key": cache_key,
"x-litellm-model-api-base": (
api_base.split("?")[0] if api_base else None
@ -1396,6 +1399,27 @@ class ProxyBaseLLMRequestProcessing:
model_id = model_info.get("id", "") or ""
return model_id
@staticmethod
def _get_deployment_model_name(
litellm_logging_obj: LiteLLMLoggingObj | None,
) -> str | None:
"""Extract the underlying deployment model string (e.g. ``azure/gpt-4o``).
The router rewrites the response ``model`` field to the model-group alias
the client requested, so neither the response body nor the existing
headers expose the concrete deployment model. The router records it under
``litellm_params`` metadata as ``deployment``, so read it back from there.
"""
litellm_params = getattr(litellm_logging_obj, "litellm_params", None)
if not isinstance(litellm_params, dict):
return None
for key in ("litellm_metadata", "metadata"):
metadata = litellm_params.get(key, {}) or {}
deployment = metadata.get("deployment")
if deployment:
return deployment
return None
@staticmethod
def _response_cost_from_logging_obj(
*,

View file

@ -200,6 +200,60 @@ def test_get_custom_headers_without_model_id():
assert headers["x-litellm-model-id"] in [None, ""]
class _FakeLoggingObj:
def __init__(self, litellm_params):
self.litellm_params = litellm_params
self.litellm_call_id = "test-call-id"
@pytest.mark.parametrize("metadata_key", ["metadata", "litellm_metadata"])
def test_get_custom_headers_includes_deployment_model_name(metadata_key):
"""
x-litellm-model-name should expose the underlying deployment model string,
which the router records under litellm_params[metadata]["deployment"].
"""
mock_user_api_key_dict = MagicMock()
mock_user_api_key_dict.tpm_limit = 1000
mock_user_api_key_dict.rpm_limit = 100
logging_obj = _FakeLoggingObj(
litellm_params={metadata_key: {"deployment": "azure/gpt-4o-2024-08-06"}}
)
headers = ProxyBaseLLMRequestProcessing.get_custom_headers(
user_api_key_dict=mock_user_api_key_dict,
model_id="deployment-uuid",
request_data={},
hidden_params={},
litellm_logging_obj=logging_obj,
)
assert headers["x-litellm-model-name"] == "azure/gpt-4o-2024-08-06"
assert headers["x-litellm-model-id"] == "deployment-uuid"
def test_get_custom_headers_omits_model_name_when_deployment_missing():
"""
Without a deployment model string, x-litellm-model-name must not be emitted
(rather than leaking an empty/None value).
"""
mock_user_api_key_dict = MagicMock()
mock_user_api_key_dict.tpm_limit = 1000
mock_user_api_key_dict.rpm_limit = 100
logging_obj = _FakeLoggingObj(litellm_params={"metadata": {}})
headers = ProxyBaseLLMRequestProcessing.get_custom_headers(
user_api_key_dict=mock_user_api_key_dict,
model_id="deployment-uuid",
request_data={},
hidden_params={},
litellm_logging_obj=logging_obj,
)
assert "x-litellm-model-name" not in headers
def test_get_custom_headers_with_empty_string_model_id():
"""
Test that get_custom_headers handles empty string model_id correctly.