mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(otel): stamp gen_ai.input/output.messages on v2 spans (#30548)
The canonical GenAI mapper's _LLM_CALL_ATTRS table had no extractors for gen_ai.input.messages or gen_ai.output.messages, so V2 LLM spans never carried prompt or completion content even when capture was enabled via OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=span_and_event. The request and response bodies were already captured onto LLMCallSpanData.messages_in and choices_out, but the mapper never read them. Add the two extractors, serializing messages_in and output_messages(d) through serialize_messages so the keys are omitted when content capture is off and the spans stay sparse. Resolves LIT-3788
This commit is contained in:
parent
902122a06b
commit
b8b0d458af
2 changed files with 51 additions and 1 deletions
|
|
@ -10,7 +10,12 @@ table: one lambda per mapping operation, applied against the typed span data.
|
|||
from typing import Callable
|
||||
|
||||
from litellm.integrations.otel.mappers.base import AttributeMap, AttrValue, SpanData
|
||||
from litellm.integrations.otel.mappers.utils import collect, drop_none
|
||||
from litellm.integrations.otel.mappers.utils import (
|
||||
collect,
|
||||
drop_none,
|
||||
output_messages,
|
||||
serialize_messages,
|
||||
)
|
||||
from litellm.integrations.otel.model.payloads import (
|
||||
GuardrailSpanData,
|
||||
LLMCallSpanData,
|
||||
|
|
@ -47,6 +52,8 @@ class GenAIMapper:
|
|||
else None
|
||||
),
|
||||
GenAI.REQUEST_SEED: lambda d: d.request_params.seed,
|
||||
GenAI.INPUT_MESSAGES: lambda d: serialize_messages(d.messages_in),
|
||||
GenAI.OUTPUT_MESSAGES: lambda d: serialize_messages(output_messages(d)),
|
||||
GenAI.RESPONSE_MODEL: lambda d: d.response_model,
|
||||
GenAI.RESPONSE_ID: lambda d: d.response_id,
|
||||
GenAI.RESPONSE_FINISH_REASONS: lambda d: (
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
baggage helpers, metrics, the typed coercion helpers, mapper branches, span-name
|
||||
builders, and the registry validator's failure paths. Needs the OTel SDK."""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("opentelemetry")
|
||||
|
|
@ -225,6 +227,47 @@ def test_genai_mapper_all_request_params():
|
|||
assert attrs["server.port"] == 443
|
||||
|
||||
|
||||
def test_genai_mapper_stamps_input_output_messages():
|
||||
data = LLMCallSpanData(
|
||||
operation=GenAIOperation.CHAT,
|
||||
provider="openai",
|
||||
request_model="gpt-4o",
|
||||
response_model="gpt-4o-2024",
|
||||
response_id="resp_1",
|
||||
request_params=LLMRequestParams(),
|
||||
usage=LLMUsage(),
|
||||
finish_reasons=("stop",),
|
||||
error=None,
|
||||
response_cost=None,
|
||||
server=None,
|
||||
identity=RequestIdentity(call_id="c1"),
|
||||
messages_in=(
|
||||
{"role": "system", "content": "Be concise."},
|
||||
{"role": "user", "content": "What's the weather?"},
|
||||
),
|
||||
choices_out=(
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {"role": "assistant", "content": "Sunny."},
|
||||
},
|
||||
),
|
||||
)
|
||||
attrs = GenAIMapper().map(data)
|
||||
assert json.loads(attrs[GenAI.INPUT_MESSAGES]) == [
|
||||
{"role": "system", "content": "Be concise."},
|
||||
{"role": "user", "content": "What's the weather?"},
|
||||
]
|
||||
assert json.loads(attrs[GenAI.OUTPUT_MESSAGES]) == [
|
||||
{"role": "assistant", "content": "Sunny."}
|
||||
]
|
||||
|
||||
|
||||
def test_genai_mapper_omits_messages_when_content_not_captured():
|
||||
attrs = GenAIMapper().map(_full_llm_call())
|
||||
assert GenAI.INPUT_MESSAGES not in attrs
|
||||
assert GenAI.OUTPUT_MESSAGES not in attrs
|
||||
|
||||
|
||||
def test_genai_mapper_cost_breakdown():
|
||||
from litellm.integrations.otel.model.semconv import LiteLLM
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue