mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-17 04:34:25 +00:00
test(langfuse): guard parent precedence and default root behavior
Lock in that the trace-id fix only anchors the otherwise-random root span: a traceparent header and an explicit proxy parent span both keep control of the trace identity over metadata.trace_id, and without any trace id each request still gets its own independent random root trace.
This commit is contained in:
parent
9d2063a169
commit
afe1f1329d
1 changed files with 74 additions and 15 deletions
|
|
@ -13,7 +13,7 @@ from litellm.integrations.opentelemetry import OpenTelemetryConfig
|
|||
from litellm.types.llms.openai import ResponsesAPIResponse
|
||||
|
||||
|
||||
def _export_langfuse_generation_span(metadata, *, generation_name="gen", model="doubao"):
|
||||
def _export_generation_span_for_kwargs(kwargs, *, generation_name):
|
||||
"""Run a success event through a real (in-memory) Langfuse OTEL exporter and
|
||||
return the finished generation span, so trace-id tests assert against the
|
||||
actually-exported OTEL span context rather than a mock."""
|
||||
|
|
@ -22,14 +22,6 @@ def _export_langfuse_generation_span(metadata, *, generation_name="gen", model="
|
|||
config=OpenTelemetryConfig(exporter=exporter, skip_set_global=True),
|
||||
callback_name="langfuse_otel",
|
||||
)
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {"generation_name": generation_name, **metadata}
|
||||
},
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"model": model,
|
||||
"call_type": "acompletion",
|
||||
}
|
||||
response_obj = {
|
||||
"id": "chatcmpl-test",
|
||||
"choices": [{"message": {"role": "assistant", "content": "ok"}}],
|
||||
|
|
@ -47,6 +39,20 @@ def _export_langfuse_generation_span(metadata, *, generation_name="gen", model="
|
|||
)
|
||||
|
||||
|
||||
def _export_langfuse_generation_span(
|
||||
metadata, *, generation_name="gen", model="doubao"
|
||||
):
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {"generation_name": generation_name, **metadata}
|
||||
},
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"model": model,
|
||||
"call_type": "acompletion",
|
||||
}
|
||||
return _export_generation_span_for_kwargs(kwargs, generation_name=generation_name)
|
||||
|
||||
|
||||
class TestLangfuseOtelIntegration:
|
||||
def test_get_langfuse_otel_config_with_required_env_vars(self):
|
||||
"""Test that config is created correctly with required environment variables."""
|
||||
|
|
@ -286,9 +292,9 @@ class TestLangfuseOtelIntegration:
|
|||
import hashlib
|
||||
|
||||
trace_id = "global_20260225143025_ord2468"
|
||||
expected_otel_trace_id = hashlib.sha256(trace_id.encode("utf-8")).digest()[
|
||||
:16
|
||||
].hex()
|
||||
expected_otel_trace_id = (
|
||||
hashlib.sha256(trace_id.encode("utf-8")).digest()[:16].hex()
|
||||
)
|
||||
generation_name = "ishaan-test-generation"
|
||||
exporter = InMemorySpanExporter()
|
||||
logger = LangfuseOtelLogger(
|
||||
|
|
@ -329,7 +335,9 @@ class TestLangfuseOtelIntegration:
|
|||
)
|
||||
|
||||
assert generation_span.attributes.get("langfuse.trace.id") == trace_id
|
||||
assert format(generation_span.context.trace_id, "032x") == expected_otel_trace_id
|
||||
assert (
|
||||
format(generation_span.context.trace_id, "032x") == expected_otel_trace_id
|
||||
)
|
||||
|
||||
def test_valid_hex_trace_id_used_directly(self):
|
||||
"""A ``metadata.trace_id`` that is already a valid 32-hex OTEL id must be
|
||||
|
|
@ -349,8 +357,7 @@ class TestLangfuseOtelIntegration:
|
|||
{"trace_id": "123e4567-e89b-12d3-a456-426614174000"}
|
||||
)
|
||||
assert (
|
||||
format(span.context.trace_id, "032x")
|
||||
== "123e4567e89b12d3a456426614174000"
|
||||
format(span.context.trace_id, "032x") == "123e4567e89b12d3a456426614174000"
|
||||
)
|
||||
|
||||
def test_existing_trace_id_takes_precedence_over_trace_id(self):
|
||||
|
|
@ -378,6 +385,58 @@ class TestLangfuseOtelIntegration:
|
|||
assert span.name == "my-gen"
|
||||
assert format(span.context.trace_id, "032x") == expected
|
||||
|
||||
def test_traceparent_header_wins_over_metadata_trace_id(self):
|
||||
"""Distributed tracing must not regress: a traceparent header keeps
|
||||
control of the trace identity even when metadata.trace_id is present."""
|
||||
parent_trace_hex = "11111111111111111111111111111111"
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {
|
||||
"generation_name": "gen",
|
||||
"trace_id": "global_20260225143025_ord2468",
|
||||
},
|
||||
"proxy_server_request": {
|
||||
"headers": {
|
||||
"traceparent": f"00-{parent_trace_hex}-2222222222222222-01"
|
||||
}
|
||||
},
|
||||
},
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"model": "doubao",
|
||||
"call_type": "acompletion",
|
||||
}
|
||||
span = _export_generation_span_for_kwargs(kwargs, generation_name="gen")
|
||||
assert format(span.context.trace_id, "032x") == parent_trace_hex
|
||||
|
||||
def test_explicit_parent_span_wins_over_metadata_trace_id(self):
|
||||
"""An explicit proxy parent span keeps control of the trace identity even
|
||||
when metadata.trace_id is present (the common proxy nesting case)."""
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
|
||||
parent = TracerProvider().get_tracer("test").start_span("parent")
|
||||
parent_trace_hex = format(parent.get_span_context().trace_id, "032x")
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {
|
||||
"generation_name": "gen",
|
||||
"trace_id": "global_20260225143025_ord2468",
|
||||
"litellm_parent_otel_span": parent,
|
||||
},
|
||||
},
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
"model": "doubao",
|
||||
"call_type": "acompletion",
|
||||
}
|
||||
span = _export_generation_span_for_kwargs(kwargs, generation_name="gen")
|
||||
assert format(span.context.trace_id, "032x") == parent_trace_hex
|
||||
|
||||
def test_no_trace_id_keeps_independent_random_root_spans(self):
|
||||
"""Without a metadata trace id the default behavior is unchanged: each
|
||||
request gets its own random root trace, not a shared anchored one."""
|
||||
first = _export_langfuse_generation_span({})
|
||||
second = _export_langfuse_generation_span({})
|
||||
assert first.context.trace_id != second.context.trace_id
|
||||
|
||||
def test_set_langfuse_specific_attributes_with_content(self):
|
||||
"""Test that _set_langfuse_specific_attributes correctly sets observation.output with regular content response."""
|
||||
from litellm.types.integrations.langfuse_otel import LangfuseSpanAttributes
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue