mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
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:
parent
3829fa3014
commit
93afde8605
2 changed files with 78 additions and 0 deletions
|
|
@ -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(
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue