This commit is contained in:
Chun Shi 2026-09-22 21:53:30 -07:00 • committed by GitHub
commit 7d206dd830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 1 deletions

View file

@ -327,7 +327,14 @@ class LangFuseLogger:
verbose_logger.warning("Overwriting Langfuse `%s` from request header", trace_param_key)
else:
verbose_logger.debug("Found Langfuse `%s` in request header", trace_param_key)
metadata[trace_param_key] = proxy_headers.get(metadata_param_key)
header_value = proxy_headers.get(metadata_param_key)
if trace_param_key == "tags" and isinstance(header_value, str):
# Langfuse tags are a list. Parse the comma-separated header string
# here, so the shared litellm_params metadata never holds a raw
# string: the router's tag handling reuses that dict across retry
# attempts and crashes on a string during retry/fallback (#38927).
header_value = [tag.strip() for tag in header_value.split(",") if tag.strip()]
metadata[trace_param_key] = header_value
return metadata

View file

@ -1799,3 +1799,63 @@ def test_langfuse_deployment_environment_fallback_never_raises(monkeypatch, env_
langfuse_host="https://test.langfuse.com",
)
assert logger.langfuse_environment == expected
def test_tags_header_is_parsed_into_a_list():
"""A ``langfuse_tags`` header arrives as one comma-separated string."""
metadata: dict = {}
litellm_params = {
"metadata": metadata,
"proxy_server_request": {"headers": {"langfuse_tags": "org:acme, agent:support"}},
}
returned = LangFuseLogger.add_metadata_from_header(litellm_params, metadata)
assert returned["tags"] == ["org:acme", "agent:support"]
def test_tags_header_never_leaves_a_string_in_shared_metadata():
"""``add_metadata_from_header`` mutates the caller's metadata dict in place, and the
router reuses that dict across retry attempts: a raw header string in ``tags``
crashes ``Router._update_kwargs_with_deployment`` with ``'str' object has no
attribute 'append'`` on any retry/fallback deployment that has
``litellm_credential_name`` (https://github.com/BerriAI/litellm/issues/38927)."""
metadata: dict = {}
litellm_params = {
"metadata": metadata,
"proxy_server_request": {"headers": {"langfuse_tags": "org:acme,agent:support"}},
}
returned = LangFuseLogger.add_metadata_from_header(litellm_params, metadata)
assert returned is metadata
assert metadata["tags"] == ["org:acme", "agent:support"]
# What the router's "CREDENTIAL NAME AS TAG" branch does on the next attempt:
metadata["tags"].append("Credential: my-credential")
assert "Credential: my-credential" in metadata["tags"]
def test_tags_already_in_metadata_pass_through_unchanged():
"""Body-supplied tags are already a list; only header ingestion needs parsing."""
metadata: dict = {"tags": ["pre:set"]}
litellm_params = {
"metadata": metadata,
"proxy_server_request": {"headers": {}},
}
returned = LangFuseLogger.add_metadata_from_header(litellm_params, metadata)
assert returned["tags"] == ["pre:set"]
def test_other_langfuse_headers_stay_raw_strings():
"""Only ``tags`` needs list parsing; other steering headers keep their raw value."""
metadata: dict = {}
litellm_params = {
"metadata": metadata,
"proxy_server_request": {"headers": {"langfuse_existing_trace_id": "trace-123"}},
}
LangFuseLogger.add_metadata_from_header(litellm_params, metadata)
assert metadata["existing_trace_id"] == "trace-123"