From e08989dd8fc5acf9ad4d7db8ab23fcf7c46c3481 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Fri, 20 Feb 2026 17:16:36 -0800 Subject: [PATCH] Fix credential name tag injection by moving to Router level The previous approach tried to inject litellm_credential_name as a tag in get_standard_logging_object_payload, but the credential name was never available in litellm_params because the Logging object is created by the proxy BEFORE the Router selects a deployment. The credential name only exists in the deployment's litellm_params, which is resolved later. This fix injects the credential name as a tag in Router._update_kwargs_with_deployment(), right alongside the existing deployment-level tags mechanism. This ensures the credential name flows through the normal metadata.tags pipeline. Co-Authored-By: Claude Opus 4.6 --- .../litellm_core_utils/get_litellm_params.py | 1 - litellm/litellm_core_utils/litellm_logging.py | 5 - litellm/router.py | 10 + .../test_get_litellm_params.py | 9 - .../test_litellm_logging.py | 199 ------------------ tests/test_litellm/test_router.py | 77 +++++++ 6 files changed, 87 insertions(+), 214 deletions(-) diff --git a/litellm/litellm_core_utils/get_litellm_params.py b/litellm/litellm_core_utils/get_litellm_params.py index 986782b2dfa..36a8dfdb5a6 100644 --- a/litellm/litellm_core_utils/get_litellm_params.py +++ b/litellm/litellm_core_utils/get_litellm_params.py @@ -32,7 +32,6 @@ _OPTIONAL_KWARGS_KEYS = frozenset({ "aws_bedrock_runtime_endpoint", "tpm", "rpm", - "litellm_credential_name", }) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index c3e1642d72b..76e37010109 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -5076,11 +5076,6 @@ def get_standard_logging_object_payload( litellm_params=litellm_params, proxy_server_request=proxy_server_request ) - # Inject credential name as tag for spend tracking - credential_name = litellm_params.get("litellm_credential_name") - if credential_name and credential_name not in request_tags: - request_tags.append(credential_name) - # cleanup timestamps ( start_time_float, diff --git a/litellm/router.py b/litellm/router.py index b1337159e57..69e3e994dcd 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2019,6 +2019,16 @@ class Router: merged_tags.append(tag) kwargs[metadata_variable_name]["tags"] = merged_tags + ## CREDENTIAL NAME AS TAG + credential_name = deployment.get("litellm_params", {}).get( + "litellm_credential_name" + ) + if credential_name: + existing_tags = kwargs[metadata_variable_name].get("tags") or [] + if credential_name not in existing_tags: + existing_tags.append(credential_name) + kwargs[metadata_variable_name]["tags"] = existing_tags + kwargs["model_info"] = model_info kwargs["timeout"] = self._get_timeout( diff --git a/tests/test_litellm/litellm_core_utils/test_get_litellm_params.py b/tests/test_litellm/litellm_core_utils/test_get_litellm_params.py index 77e2c1bd37f..b39943b3e49 100644 --- a/tests/test_litellm/litellm_core_utils/test_get_litellm_params.py +++ b/tests/test_litellm/litellm_core_utils/test_get_litellm_params.py @@ -126,12 +126,3 @@ class TestGetLitellmParamsExplicitFields: result = get_litellm_params(no_log=True) assert result["no-log"] is True - def test_litellm_credential_name_captured(self): - """litellm_credential_name should be captured via _OPTIONAL_KWARGS_KEYS.""" - result = get_litellm_params(litellm_credential_name="my-credential") - assert result["litellm_credential_name"] == "my-credential" - - def test_litellm_credential_name_absent(self): - """When litellm_credential_name is not passed, it should not appear.""" - result = get_litellm_params() - assert "litellm_credential_name" not in result diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index 35fd710f910..3cc91869289 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -1357,202 +1357,3 @@ def test_get_error_information_error_code_priority(): assert result["error_class"] == "NoCodeException" -def test_credential_name_injected_as_tag(): - """ - Test that litellm_credential_name from litellm_params is injected into - request_tags in the standard logging payload. - - In the real flow, litellm_credential_name is captured into litellm_params - by get_litellm_params() via _OPTIONAL_KWARGS_KEYS. - """ - from litellm.litellm_core_utils.litellm_logging import ( - get_standard_logging_object_payload, - Logging, - ) - from datetime import datetime - - logging_obj = Logging( - model="gpt-4o", - messages=[{"role": "user", "content": "Hello"}], - stream=False, - call_type="completion", - start_time=datetime.now(), - litellm_call_id="test-cred-tag", - function_id="test-function", - ) - - mock_response = { - "id": "chatcmpl-123", - "object": "chat.completion", - "model": "gpt-4o", - "usage": { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30, - }, - "choices": [ - { - "index": 0, - "message": {"role": "assistant", "content": "Hi!"}, - "finish_reason": "stop", - } - ], - } - - kwargs = { - "model": "gpt-4o", - "messages": [{"role": "user", "content": "Hello"}], - "response_cost": 0.001, - "custom_llm_provider": "openai", - "litellm_params": { - "litellm_credential_name": "my-openai-credential", - }, - } - - start_time = datetime.now() - end_time = datetime.now() - - payload = get_standard_logging_object_payload( - kwargs=kwargs, - init_response_obj=mock_response, - start_time=start_time, - end_time=end_time, - logging_obj=logging_obj, - status="success", - ) - - assert payload is not None - assert "my-openai-credential" in payload["request_tags"] - - -def test_credential_name_not_injected_when_absent(): - """ - Test that when litellm_credential_name is not in litellm_params, - request_tags are unchanged. - """ - from litellm.litellm_core_utils.litellm_logging import ( - get_standard_logging_object_payload, - Logging, - ) - from datetime import datetime - - logging_obj = Logging( - model="gpt-4o", - messages=[{"role": "user", "content": "Hello"}], - stream=False, - call_type="completion", - start_time=datetime.now(), - litellm_call_id="test-no-cred", - function_id="test-function", - ) - - mock_response = { - "id": "chatcmpl-456", - "object": "chat.completion", - "model": "gpt-4o", - "usage": { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30, - }, - "choices": [ - { - "index": 0, - "message": {"role": "assistant", "content": "Hi!"}, - "finish_reason": "stop", - } - ], - } - - kwargs = { - "model": "gpt-4o", - "messages": [{"role": "user", "content": "Hello"}], - "response_cost": 0.001, - "custom_llm_provider": "openai", - } - - start_time = datetime.now() - end_time = datetime.now() - - payload = get_standard_logging_object_payload( - kwargs=kwargs, - init_response_obj=mock_response, - start_time=start_time, - end_time=end_time, - logging_obj=logging_obj, - status="success", - ) - - assert payload is not None - # No credential-related tags should be present - for tag in payload["request_tags"]: - assert tag.startswith("User-Agent:") - - -def test_credential_name_not_duplicated_in_tags(): - """ - Test that if the credential name already exists in the tags list, - it is not duplicated. - """ - from litellm.litellm_core_utils.litellm_logging import ( - get_standard_logging_object_payload, - Logging, - ) - from datetime import datetime - - logging_obj = Logging( - model="gpt-4o", - messages=[{"role": "user", "content": "Hello"}], - stream=False, - call_type="completion", - start_time=datetime.now(), - litellm_call_id="test-dup-cred", - function_id="test-function", - ) - - mock_response = { - "id": "chatcmpl-789", - "object": "chat.completion", - "model": "gpt-4o", - "usage": { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30, - }, - "choices": [ - { - "index": 0, - "message": {"role": "assistant", "content": "Hi!"}, - "finish_reason": "stop", - } - ], - } - - kwargs = { - "model": "gpt-4o", - "messages": [{"role": "user", "content": "Hello"}], - "response_cost": 0.001, - "custom_llm_provider": "openai", - "litellm_params": { - "litellm_credential_name": "my-openai-credential", - "metadata": {"tags": ["my-openai-credential", "other-tag"]}, - }, - } - - start_time = datetime.now() - end_time = datetime.now() - - payload = get_standard_logging_object_payload( - kwargs=kwargs, - init_response_obj=mock_response, - start_time=start_time, - end_time=end_time, - logging_obj=logging_obj, - status="success", - ) - - assert payload is not None - credential_count = payload["request_tags"].count("my-openai-credential") - assert credential_count == 1, ( - f"Expected credential name once, found {credential_count} times" - ) diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index c55b26ca39c..f0e754cee82 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -2186,3 +2186,80 @@ def test_update_kwargs_with_deployment_merge_tools_request_overrides_tool_choice # Request tool_choice should be preserved (merged tools still applied) assert kwargs["tool_choice"] == "none" + + +def test_credential_name_injected_as_tag(): + """ + Test that litellm_credential_name from deployment litellm_params + is injected as a tag into metadata during _update_kwargs_with_deployment. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "xai-model", + "litellm_params": { + "model": "xai/grok-4-1-fast", + "litellm_credential_name": "xAI", + }, + } + ], + ) + + kwargs: dict = {"metadata": {"tags": ["A.101"]}} + deployment = router.get_deployment_by_model_group_name( + model_group_name="xai-model" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + assert "xAI" in kwargs["metadata"]["tags"] + assert "A.101" in kwargs["metadata"]["tags"] + + +def test_credential_name_not_duplicated_in_tags(): + """ + Test that if the credential name already exists in the tags list, + it is not duplicated. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "xai-model", + "litellm_params": { + "model": "xai/grok-4-1-fast", + "litellm_credential_name": "xAI", + }, + } + ], + ) + + kwargs: dict = {"metadata": {"tags": ["xAI", "A.101"]}} + deployment = router.get_deployment_by_model_group_name( + model_group_name="xai-model" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + assert kwargs["metadata"]["tags"].count("xAI") == 1 + + +def test_credential_name_not_injected_when_absent(): + """ + Test that when no litellm_credential_name is set, tags are unchanged. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "gpt-model", + "litellm_params": { + "model": "gpt-4o", + }, + } + ], + ) + + kwargs: dict = {"metadata": {"tags": ["A.101"]}} + deployment = router.get_deployment_by_model_group_name( + model_group_name="gpt-model" + ) + router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + + assert kwargs["metadata"]["tags"] == ["A.101"]