fix: address req changes

This commit is contained in:
Harshit28j 2026-03-20 18:44:23 +05:30
parent 778c6e1574
commit 94d4a4dce0
4 changed files with 127 additions and 21 deletions

View file

@ -590,19 +590,25 @@ class Logging(LiteLLMLoggingBaseClass):
kwargs["litellm_metadata"], dict
):
base_litellm_params["litellm_metadata"] = kwargs["litellm_metadata"]
if "metadata" not in base_litellm_params:
base_litellm_params["metadata"] = kwargs["litellm_metadata"].copy()
else:
# Merge litellm_metadata into metadata without overwriting existing
# keys so API key fields are visible to callbacks even when
# Anthropic's native metadata is present (/v1/messages).
for key, value in kwargs["litellm_metadata"].items():
if key not in base_litellm_params["metadata"]:
base_litellm_params["metadata"][key] = value
if litellm_params:
base_litellm_params.update(litellm_params)
# Merge litellm_metadata into metadata AFTER .update(litellm_params) so
# the merge isn't silently overwritten. This ensures API key fields
# (user_api_key_hash, etc.) are visible to callbacks even when the
# request uses "litellm_metadata" (e.g. /v1/messages from Claude Code).
if "litellm_metadata" in kwargs and isinstance(
kwargs["litellm_metadata"], dict
):
if "metadata" not in base_litellm_params:
base_litellm_params["metadata"] = dict(kwargs["litellm_metadata"])
else:
base_litellm_params["metadata"] = dict(base_litellm_params["metadata"])
for key, value in kwargs["litellm_metadata"].items():
if key not in base_litellm_params["metadata"]:
base_litellm_params["metadata"][key] = value
self.update_environment_variables(
litellm_params=base_litellm_params,
optional_params=optional_params or {},

View file

@ -1155,12 +1155,15 @@ def function_setup( # noqa: PLR0915
# populate litellm_params["metadata"] so callbacks (e.g. Langfuse) that
# read API key info from litellm_params["metadata"] see the fields.
if not litellm_params.get("metadata"):
litellm_params["metadata"] = kwargs["litellm_metadata"].copy()
litellm_params["metadata"] = dict(kwargs["litellm_metadata"])
else:
# Merge litellm_metadata into metadata without overwriting existing
# keys. This ensures API key fields (user_api_key_hash, etc.) are
# visible to callbacks even when Anthropic's native metadata field
# is present in /v1/messages requests from Claude Code.
litellm_params["metadata"] = dict(
litellm_params["metadata"]
) # don't mutate caller's dict
for key, value in kwargs["litellm_metadata"].items():
if key not in litellm_params["metadata"]:
litellm_params["metadata"][key] = value

View file

@ -4,7 +4,6 @@ Tests for get_litellm_params and related helpers.
Ensures backward compatibility after sparse kwargs extraction optimization.
"""
import pytest
from litellm.litellm_core_utils.get_litellm_params import (
_OPTIONAL_KWARGS_KEYS,
@ -126,3 +125,54 @@ class TestGetLitellmParamsExplicitFields:
result = get_litellm_params(no_log=True)
assert result["no-log"] is True
class TestGetLitellmParamsLitellmMetadataMerge:
"""Verify litellm_metadata is merged into metadata for callback visibility."""
def test_merge_when_no_metadata(self):
"""When only litellm_metadata is provided, it becomes metadata."""
lm_meta = {"user_api_key_hash": "hashed-abc", "team_id": "t-1"}
result = get_litellm_params(litellm_metadata=lm_meta)
assert result["metadata"] == lm_meta
# Must be a copy, not the same object
assert result["metadata"] is not lm_meta
def test_merge_preserves_existing_metadata_keys(self):
"""Existing metadata keys are not overwritten by litellm_metadata."""
metadata = {"user_api_key": "sk-real", "trace_id": "t-1"}
lm_meta = {
"user_api_key_hash": "hashed-abc",
"user_api_key": "should-not-overwrite",
}
result = get_litellm_params(metadata=metadata, litellm_metadata=lm_meta)
assert result["metadata"]["user_api_key"] == "sk-real"
assert result["metadata"]["trace_id"] == "t-1"
assert result["metadata"]["user_api_key_hash"] == "hashed-abc"
def test_merge_does_not_mutate_caller_metadata(self):
"""The caller's original metadata dict must not be mutated."""
original_metadata = {"user_api_key": "sk-real"}
lm_meta = {"user_api_key_hash": "hashed-abc"}
result = get_litellm_params(
metadata=original_metadata, litellm_metadata=lm_meta
)
assert "user_api_key_hash" not in original_metadata
assert result["metadata"] is not original_metadata
def test_merge_does_not_mutate_caller_litellm_metadata(self):
"""The caller's original litellm_metadata dict must not be mutated."""
lm_meta = {"user_api_key_hash": "hashed-abc"}
original_keys = set(lm_meta.keys())
get_litellm_params(litellm_metadata=lm_meta)
assert set(lm_meta.keys()) == original_keys
def test_no_merge_when_litellm_metadata_is_none(self):
"""When litellm_metadata is None, metadata is returned as-is."""
metadata = {"user_api_key": "sk-real"}
result = get_litellm_params(metadata=metadata, litellm_metadata=None)
assert result["metadata"] == metadata

View file

@ -33,7 +33,7 @@ def test_get_masked_api_base(logging_obj):
api_base = "https://api.openai.com/v1"
masked_api_base = logging_obj._get_masked_api_base(api_base)
assert masked_api_base == "https://api.openai.com/v1"
assert type(masked_api_base) == str
assert isinstance(masked_api_base, str)
def test_sentry_sample_rate():
@ -222,16 +222,55 @@ class TestUpdateFromKwargs:
assert logging_obj.litellm_params["metadata"] == lm_meta
def test_no_backfill_when_metadata_already_present(self, logging_obj):
metadata = {"user_api_key": "sk-real"}
lm_meta = {"model_info": {"id": "deploy-1"}}
def test_merges_litellm_metadata_into_existing_metadata(self, logging_obj):
"""When both metadata and litellm_metadata exist, litellm_metadata keys
should be merged into metadata without overwriting existing keys."""
metadata = {"user_api_key": "sk-real", "trace_id": "t-1"}
lm_meta = {
"user_api_key_hash": "hashed-abc",
"user_api_key": "should-not-overwrite",
}
kwargs = {"metadata": metadata, "litellm_metadata": lm_meta}
logging_obj.update_from_kwargs(kwargs=kwargs)
assert logging_obj.litellm_params["metadata"] == metadata
result_meta = logging_obj.litellm_params["metadata"]
# Existing key preserved
assert result_meta["user_api_key"] == "sk-real"
assert result_meta["trace_id"] == "t-1"
# New key merged from litellm_metadata
assert result_meta["user_api_key_hash"] == "hashed-abc"
# litellm_metadata also stored
assert logging_obj.litellm_params["litellm_metadata"] == lm_meta
def test_merge_does_not_mutate_original_kwargs_metadata(self, logging_obj):
"""The merge must not mutate the caller's original metadata dict."""
original_metadata = {"user_api_key": "sk-real"}
lm_meta = {"user_api_key_hash": "hashed-abc", "team_id": "t-1"}
kwargs = {"metadata": original_metadata, "litellm_metadata": lm_meta}
logging_obj.update_from_kwargs(kwargs=kwargs)
# Original dict should be untouched
assert "user_api_key_hash" not in original_metadata
assert "team_id" not in original_metadata
def test_merge_survives_litellm_params_update(self, logging_obj):
"""When caller passes litellm_params with its own metadata, litellm_metadata
keys should still be merged in (merge happens after .update())."""
caller_metadata = {"from_caller": True}
lm_meta = {"user_api_key_hash": "hashed-xyz"}
kwargs = {"litellm_metadata": lm_meta}
logging_obj.update_from_kwargs(
kwargs=kwargs,
litellm_params={"metadata": caller_metadata, "litellm_call_id": "x"},
)
result_meta = logging_obj.litellm_params["metadata"]
assert result_meta["from_caller"] is True
assert result_meta["user_api_key_hash"] == "hashed-xyz"
def test_caller_litellm_params_win_over_kwargs(self, logging_obj):
"""Explicit litellm_params from the caller should override auto-extracted values."""
kwargs = {"metadata": {"from_kwargs": True}}
@ -241,7 +280,7 @@ class TestUpdateFromKwargs:
litellm_params={"metadata": {"from_caller": True}, "litellm_call_id": "x"},
)
assert logging_obj.litellm_params["metadata"] == {"from_caller": True}
assert logging_obj.litellm_params["metadata"]["from_caller"] is True
def test_custom_pricing_detected_via_litellm_metadata(self, logging_obj):
"""Custom pricing in litellm_metadata.model_info should set custom_pricing flag."""
@ -1901,8 +1940,8 @@ def test_function_setup_litellm_metadata_populates_metadata():
def test_function_setup_metadata_takes_precedence_over_litellm_metadata():
"""
Test that when BOTH metadata and litellm_metadata are present (e.g., user sets
Anthropic API metadata AND proxy adds litellm_metadata), metadata is used as
litellm_params["metadata"] and litellm_metadata is stored separately.
Anthropic API metadata AND proxy adds litellm_metadata), existing metadata keys
are preserved and litellm_metadata keys are merged in without overwriting.
"""
import litellm
@ -1916,6 +1955,7 @@ def test_function_setup_metadata_takes_precedence_over_litellm_metadata():
"litellm_metadata": {
"user_api_key_hash": "sk-hashed-xyz",
"user_api_key_team_id": "team-xyz",
"user_id": "should-not-overwrite",
},
}
@ -1928,16 +1968,23 @@ def test_function_setup_metadata_takes_precedence_over_litellm_metadata():
litellm_params = logging_obj.model_call_details.get("litellm_params", {})
# When both are present, metadata should be the explicit "metadata" dict
# Existing metadata key preserved (not overwritten by litellm_metadata)
metadata = litellm_params.get("metadata")
assert metadata is not None
assert metadata.get("user_id") == "anthropic-user-id"
# litellm_metadata should be preserved separately for merge_litellm_metadata()
# litellm_metadata keys merged into metadata
assert metadata.get("user_api_key_hash") == "sk-hashed-xyz"
assert metadata.get("user_api_key_team_id") == "team-xyz"
# litellm_metadata should be preserved separately
litellm_metadata = litellm_params.get("litellm_metadata")
assert litellm_metadata is not None
assert litellm_metadata.get("user_api_key_hash") == "sk-hashed-xyz"
# metadata must be a copy — mutating it must not affect the original kwargs dict
assert metadata is not kwargs["metadata"]
def test_function_setup_empty_metadata_falls_back_to_litellm_metadata():
"""