This commit is contained in:
emme1t 2026-11-05 19:35:01 +00:00 • committed by GitHub
commit 5bae31d500
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 6 deletions

View file

@ -307,8 +307,13 @@ def _spend_metadata(logging_obj: "LiteLLMLoggingObj") -> Mapping[str, object]:
rebuilt logging object has no request headers to derive them from.
"""
litellm_params: Final = _OBJECT_DICT.validate_python(logging_obj.model_call_details.get("litellm_params") or {})
metadata: Final = _OBJECT_MAPPING.validate_python(
get_litellm_metadata_from_kwargs(kwargs=logging_obj.model_call_details)
)
if metadata.get("tags") and not isinstance(metadata.get("tags"), list):
return metadata
return {
**_OBJECT_MAPPING.validate_python(get_litellm_metadata_from_kwargs(kwargs=logging_obj.model_call_details)),
**metadata,
**_request_tags(litellm_params),
}

View file

@ -7573,9 +7573,9 @@ class StandardLoggingPayloadSetup:
)
request_tags: Final = next(
(
cast(list[str], tags) # cast-ok: tags are caller-provided
for tags in (metadata.get("tags"), litellm_metadata.get("tags"))
if isinstance(tags, list) and tags
cast(list[str], tags) # cast-ok: preserve existing caller-provided list elements
for tag_metadata in (metadata, litellm_metadata)
if isinstance(tags := tag_metadata.get("tags"), list) and tags
),
(),
)

View file

@ -484,13 +484,14 @@ async def test_schedule_registers_an_agent_only_create_that_names_no_model():
@pytest.mark.asyncio
async def test_schedule_registers_a_create_whose_tags_are_a_string_and_keeps_them_as_sent():
@pytest.mark.parametrize("metadata_field", ["metadata", "litellm_metadata"])
async def test_schedule_registers_a_create_whose_tags_are_a_string_and_keeps_them_as_sent(metadata_field):
"""
The regression: storing the create's context read string tags as a list
and raised, so a create sent with a tags string answered with an error
after the provider had already started the interaction.
"""
logging_obj = _logging_obj(litellm_params={"metadata": _create_metadata(tags="team-a,nightly-batch")})
logging_obj = _logging_obj(litellm_params={metadata_field: _create_metadata(tags="team-a,nightly-batch")})
store = InMemoryBackgroundSettlementStore()
task = await maybe_schedule_background_interaction_cost_polling(

View file

@ -3675,6 +3675,58 @@ def test_get_request_tags_preserves_non_string_metadata_tags():
assert 7 in tags
@pytest.mark.parametrize("metadata_key", ["metadata", "litellm_metadata"])
@pytest.mark.parametrize("invalid_tags", ["my-tag", 7, True, {"tag": "value"}, ("my-tag",), None, ""])
def test_standard_logging_payload_ignores_non_list_tags(
logging_obj: LitellmLogging, metadata_key: str, invalid_tags: object
) -> None:
timestamp: Final = datetime.datetime(2026, 1, 1)
payload: Final = get_standard_logging_object_payload(
kwargs={
"litellm_call_id": "invalid-tag-request",
"model": "test-model",
"messages": [],
"litellm_params": {
metadata_key: {"tags": invalid_tags},
"proxy_server_request": {"headers": {"user-agent": "litellm/1.0.0"}},
},
},
init_response_obj={},
start_time=timestamp,
end_time=timestamp,
logging_obj=logging_obj,
status="success",
)
assert payload is not None
assert payload["litellm_call_id"] == "invalid-tag-request"
assert payload["request_tags"] == ["User-Agent: litellm", "User-Agent: litellm/1.0.0"]
@pytest.mark.parametrize("invalid_tags", ["my-tag", 7, {"tag": "value"}, ("my-tag",), [], None])
def test_get_request_tags_uses_valid_fallback_tags(invalid_tags: object) -> None:
fallback_tags: Final = ["fallback-tag"]
tags: Final = StandardLoggingPayloadSetup.get_request_tags(
litellm_params={
"metadata": {"tags": invalid_tags},
"litellm_metadata": {"tags": fallback_tags},
},
proxy_server_request={"headers": {"user-agent": "litellm/1.0.0"}},
)
assert tags == ["fallback-tag", "User-Agent: litellm", "User-Agent: litellm/1.0.0"]
assert fallback_tags == ["fallback-tag"]
def test_get_request_tags_skips_unused_malformed_metadata() -> None:
tags: Final = StandardLoggingPayloadSetup.get_request_tags(
litellm_params={"metadata": {"tags": ["primary-tag"]}, "litellm_metadata": "unused"},
proxy_server_request={},
)
assert tags == ["primary-tag"]
def test_get_request_tags_from_metadata_and_litellm_metadata():
"""
Test that _get_request_tags correctly picks tags from both 'metadata' and 'litellm_metadata'.