mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(logging): fall back to litellm_metadata when metadata is empty (#36105)
get_litellm_params returned metadata=None whenever only litellm_metadata was supplied, which overwrote the fallback function_setup had already applied and left litellm_params["metadata"] empty. On the /v1/responses completion-transformation bridge, used by every provider without a native Responses API config, and on /v1/messages, that discarded the caller's trace fields a second time after the proxy had promoted them. Resolve metadata to a copy of litellm_metadata when metadata is empty, guarding on isinstance because the proxy leaves an unparseable litellm_metadata string in place and a null metadata would otherwise suppress the backfill and break the merge. update_from_kwargs copies rather than aliases for the same reason: on these routes it is handed the caller's provider-bound dict and would otherwise write user_api_key_auth into it.
This commit is contained in:
parent
f4f59ec4c3
commit
f3f72c4574
4 changed files with 80 additions and 4 deletions
|
|
@ -115,8 +115,11 @@ def get_litellm_params(
|
|||
litellm_request_debug: bool | None = None,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
_litellm_metadata_dict: Final = litellm_metadata if isinstance(litellm_metadata, dict) else None
|
||||
resolved_metadata: Final = _litellm_metadata_dict.copy() if not metadata and _litellm_metadata_dict else metadata
|
||||
|
||||
# Derive litellm_session_id / litellm_trace_id from metadata when not provided (call chaining)
|
||||
_meta: Final = metadata or {}
|
||||
_meta: Final = resolved_metadata or {}
|
||||
if litellm_session_id is None:
|
||||
litellm_session_id = _meta.get("session_id") or _meta.get("trace_id")
|
||||
if litellm_trace_id is None:
|
||||
|
|
@ -139,7 +142,7 @@ def get_litellm_params(
|
|||
"model_alias_map": model_alias_map,
|
||||
"completion_call_id": completion_call_id,
|
||||
"aembedding": aembedding,
|
||||
"metadata": metadata,
|
||||
"metadata": resolved_metadata,
|
||||
"model_info": model_info,
|
||||
"proxy_server_request": proxy_server_request,
|
||||
"preset_cache_key": preset_cache_key,
|
||||
|
|
|
|||
|
|
@ -585,8 +585,8 @@ class Logging(LiteLLMLoggingBaseClass):
|
|||
"""
|
||||
base_litellm_params: Final[dict[str, Any]] = {}
|
||||
|
||||
if "metadata" in kwargs:
|
||||
base_litellm_params["metadata"] = kwargs["metadata"]
|
||||
if isinstance(kwargs.get("metadata"), dict):
|
||||
base_litellm_params["metadata"] = kwargs["metadata"].copy()
|
||||
if "litellm_metadata" in kwargs and isinstance(kwargs["litellm_metadata"], dict):
|
||||
base_litellm_params["litellm_metadata"] = kwargs["litellm_metadata"]
|
||||
if "metadata" not in base_litellm_params:
|
||||
|
|
|
|||
|
|
@ -162,3 +162,56 @@ class TestGetLitellmParamsDataResidency:
|
|||
api_base="https://eu.api.openai.com/v1",
|
||||
)
|
||||
assert result["data_residency"] is None
|
||||
|
||||
|
||||
class TestMetadataFallsBackToLitellmMetadata:
|
||||
def test_metadata_falls_back_to_litellm_metadata_when_absent(self):
|
||||
result = get_litellm_params(litellm_metadata={"trace_id": "trace-1"})
|
||||
assert result["metadata"] == {"trace_id": "trace-1"}
|
||||
assert result["litellm_metadata"] == {"trace_id": "trace-1"}
|
||||
|
||||
def test_empty_metadata_falls_back_to_litellm_metadata(self):
|
||||
result = get_litellm_params(metadata={}, litellm_metadata={"trace_id": "trace-1"})
|
||||
assert result["metadata"] == {"trace_id": "trace-1"}
|
||||
|
||||
def test_metadata_wins_when_both_present(self):
|
||||
result = get_litellm_params(
|
||||
metadata={"trace_id": "from-metadata"},
|
||||
litellm_metadata={"trace_id": "from-litellm-metadata"},
|
||||
)
|
||||
assert result["metadata"] == {"trace_id": "from-metadata"}
|
||||
|
||||
@pytest.mark.parametrize("bad_value", ["not-json-a-string", 12345, ["a"], True])
|
||||
def test_non_dict_litellm_metadata_is_ignored(self, bad_value):
|
||||
result = get_litellm_params(litellm_metadata=bad_value)
|
||||
assert result["metadata"] is None
|
||||
|
||||
def test_metadata_stays_none_without_litellm_metadata(self):
|
||||
result = get_litellm_params(api_key="test-key")
|
||||
assert result["metadata"] is None
|
||||
|
||||
def test_session_and_trace_id_derived_from_litellm_metadata(self):
|
||||
result = get_litellm_params(
|
||||
litellm_metadata={"trace_id": "trace-1", "session_id": "session-1"},
|
||||
)
|
||||
assert result["litellm_session_id"] == "session-1"
|
||||
assert result["litellm_trace_id"] == "trace-1"
|
||||
|
||||
def test_explicit_session_and_trace_id_are_not_overridden(self):
|
||||
result = get_litellm_params(
|
||||
litellm_session_id="explicit-session",
|
||||
litellm_trace_id="explicit-trace",
|
||||
litellm_metadata={"trace_id": "trace-1", "session_id": "session-1"},
|
||||
)
|
||||
assert result["litellm_session_id"] == "explicit-session"
|
||||
assert result["litellm_trace_id"] == "explicit-trace"
|
||||
|
||||
def test_litellm_metadata_fallback_is_copied_not_aliased(self):
|
||||
litellm_metadata = {"trace_id": "trace-1"}
|
||||
|
||||
result = get_litellm_params(litellm_metadata=litellm_metadata)
|
||||
|
||||
assert result["metadata"] == litellm_metadata
|
||||
assert result["metadata"] is not litellm_metadata
|
||||
result["metadata"].pop("trace_id")
|
||||
assert litellm_metadata == {"trace_id": "trace-1"}
|
||||
|
|
|
|||
|
|
@ -526,6 +526,26 @@ class TestUpdateFromKwargs:
|
|||
)
|
||||
assert logging_obj.litellm_params["litellm_call_id"] == "call-empty"
|
||||
|
||||
@pytest.mark.parametrize("caller_metadata", [None, "not-a-dict", 42])
|
||||
def test_non_dict_caller_metadata_does_not_break_the_merge(self, logging_obj, caller_metadata):
|
||||
logging_obj.update_from_kwargs(
|
||||
kwargs={"metadata": caller_metadata, "litellm_metadata": {"user_api_key_hash": "hashed"}},
|
||||
litellm_params={"metadata": {"user_api_key_hash": "hashed", "litellm_api_version": "1.0"}},
|
||||
)
|
||||
|
||||
assert logging_obj.litellm_params["metadata"]["user_api_key_hash"] == "hashed"
|
||||
|
||||
def test_does_not_mutate_caller_metadata_dict(self, logging_obj):
|
||||
caller_metadata: dict = {}
|
||||
|
||||
logging_obj.update_from_kwargs(
|
||||
kwargs={"metadata": caller_metadata, "litellm_metadata": {"user_api_key_hash": "hashed"}},
|
||||
litellm_params={"metadata": {"user_api_key_hash": "hashed", "litellm_api_version": "1.0"}},
|
||||
)
|
||||
|
||||
assert caller_metadata == {}
|
||||
assert logging_obj.litellm_params["metadata"]["user_api_key_hash"] == "hashed"
|
||||
|
||||
|
||||
def test_logging_prevent_double_logging(logging_obj):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue