"""Langtrace attribute mapper. Produces Langtrace's attribute vocabulary so a span can be ingested by a Langtrace backend. Compose it alongside other mappers like any other vocabulary. Scalar attributes are declared as a flat ``key -> extractor`` table (one lambda per mapping operation); the prompt/completion blobs are serialized as a tail. """ from typing import Callable from litellm.integrations.otel.mappers.base import AttributeMap, AttrValue, SpanData from litellm.integrations.otel.mappers.utils import ( collect, json_or_none, output_messages, ) from litellm.integrations.otel.model.payloads import LLMCallSpanData class LangtraceMapper: _LLM_CALL_ATTRS: dict[str, Callable[[LLMCallSpanData], AttrValue | None]] = { "gen_ai.operation.name": lambda d: "chat", "langtrace.service.name": lambda d: d.provider or None, "llm.model": lambda d: d.request_model or None, "gen_ai.response.model": lambda d: d.response_model or None, "gen_ai.response_id": lambda d: d.response_id or None, "gen_ai.system_fingerprint": lambda d: d.system_fingerprint or None, "llm.temperature": lambda d: d.request_params.temperature, "llm.top_p": lambda d: d.request_params.top_p, "llm.top_k": lambda d: d.request_params.top_k, "llm.max_tokens": lambda d: d.request_params.max_tokens, "llm.frequency_penalty": lambda d: d.request_params.frequency_penalty, "llm.presence_penalty": lambda d: d.request_params.presence_penalty, "llm.stream": lambda d: d.is_streaming, "llm.token.counts.prompt": lambda d: d.usage.input_tokens, "llm.token.counts.completion": lambda d: d.usage.output_tokens, "llm.token.counts.total": lambda d: d.usage.total_tokens, } _BLOB_ATTRS: dict[str, Callable[[LLMCallSpanData], AttrValue | None]] = { "llm.prompts": lambda d: ( json_or_none(list(d.messages_in)) if d.messages_in else None ), "llm.completions": lambda d: ( json_or_none(output_messages(d)) if d.choices_out else None ), } def map(self, data: SpanData) -> AttributeMap: match data: case LLMCallSpanData(): return self._llm_call(data) case _: return {} @classmethod def _llm_call(cls, data: LLMCallSpanData) -> AttributeMap: return { **collect(cls._LLM_CALL_ATTRS, data), **collect(cls._BLOB_ATTRS, data), }