fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""Langfuse OTLP attribute mapper.
|
|
|
|
Langfuse ingests OTLP spans and reads from its own vendor namespace
|
|
(``langfuse.observation.*``, ``langfuse.trace.*``). Compose this mapper after
|
|
``GenAIMapper`` to send canonical + Langfuse-flavored spans simultaneously.
|
|
|
|
Every attribute is declared as a ``key -> extractor`` table entry (one callable
|
|
per mapping operation): ``_LLM_CALL_ATTRS`` for scalars and ``_BLOB_ATTRS`` for
|
|
the JSON-serialized payloads. ``_llm_call`` just applies both tables.
|
|
"""
|
|
|
|
import json
|
|
from typing import Callable
|
|
|
|
from litellm.integrations.otel.mappers.base import AttributeMap, AttrValue, SpanData
|
|
from litellm.integrations.otel.mappers.utils import (
|
|
collect,
|
|
json_if,
|
|
output_messages,
|
|
serialize_messages,
|
|
)
|
|
from litellm.integrations.otel.model.payloads import (
|
|
LLMCallSpanData,
|
|
LLMRequestParams,
|
|
LLMUsage,
|
|
)
|
|
|
|
|
|
class LangfuseMapper:
|
|
|
|
_LLM_CALL_ATTRS: dict[str, Callable[[LLMCallSpanData], AttrValue | None]] = {
|
|
"langfuse.observation.type": lambda d: "generation",
|
|
"langfuse.observation.model.name": lambda d: d.request_model or None,
|
|
"langfuse.observation.metadata.provider": lambda d: d.provider or None,
|
|
"langfuse.observation.id": lambda d: d.identity.call_id or None,
|
|
"langfuse.trace.metadata.team_id": lambda d: d.identity.team_id or None,
|
|
"langfuse.trace.metadata.team_alias": lambda d: d.identity.team_alias or None,
|
|
}
|
|
|
|
# Sub-tables folded into their respective JSON blobs.
|
|
_MODEL_PARAMS: dict[str, Callable[[LLMRequestParams], AttrValue | None]] = {
|
|
"temperature": lambda rp: rp.temperature,
|
|
"top_p": lambda rp: rp.top_p,
|
|
"max_tokens": lambda rp: rp.max_tokens,
|
|
"frequency_penalty": lambda rp: rp.frequency_penalty,
|
|
"presence_penalty": lambda rp: rp.presence_penalty,
|
|
"seed": lambda rp: rp.seed,
|
|
}
|
|
_USAGE_FIELDS: dict[str, Callable[[LLMUsage], AttrValue | None]] = {
|
|
"input": lambda u: u.input_tokens,
|
|
"output": lambda u: u.output_tokens,
|
|
"total": lambda u: u.total_tokens,
|
|
}
|
|
|
|
# JSON-payload attributes: each builder returns the serialized blob or None.
|
|
_BLOB_ATTRS: dict[str, Callable[[LLMCallSpanData], AttrValue | None]] = {
|
|
"langfuse.observation.model.parameters": lambda d: json_if(
|
|
collect(LangfuseMapper._MODEL_PARAMS, d.request_params)
|
|
),
|
|
"langfuse.observation.input": lambda d: serialize_messages(d.messages_in),
|
|
"langfuse.observation.output": lambda d: serialize_messages(output_messages(d)),
|
|
"langfuse.observation.usage_details": lambda d: json_if(
|
|
collect(LangfuseMapper._USAGE_FIELDS, d.usage)
|
|
),
|
|
"langfuse.observation.cost_details": lambda d: (
|
|
json.dumps({"total": d.response_cost})
|
|
if d.response_cost is not None
|
|
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),
|
|
}
|