mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix: address req changes
This commit is contained in:
parent
778c6e1574
commit
94d4a4dce0
4 changed files with 127 additions and 21 deletions
|
|
@ -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 {},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue