Files
2026-07-13 13:39:38 +08:00

64 lines
2.8 KiB
Python

from __future__ import annotations
import traceback
from datetime import datetime, timezone
from typing import TYPE_CHECKING
from opentelemetry import trace
from . import trace_types
if TYPE_CHECKING:
from ..metrics import RealtimeModelMetrics
def record_exception(span: trace.Span, exception: Exception) -> None:
span.record_exception(exception)
span.set_status(trace.Status(trace.StatusCode.ERROR, str(exception)))
# set the exception in span attributes in case the exception event is not rendered
span.set_attributes(
{
trace_types.ATTR_EXCEPTION_TYPE: exception.__class__.__name__,
trace_types.ATTR_EXCEPTION_MESSAGE: str(exception),
trace_types.ATTR_EXCEPTION_TRACE: traceback.format_exc(),
}
)
def record_realtime_metrics(span: trace.Span, ev: RealtimeModelMetrics) -> None:
model_name = ev.metadata.model_name if ev.metadata else None
model_provider = ev.metadata.model_provider if ev.metadata else None
attrs: dict[str, str | int] = {
trace_types.ATTR_GEN_AI_OPERATION_NAME: "chat",
trace_types.ATTR_GEN_AI_PROVIDER_NAME: model_provider or "unknown",
trace_types.ATTR_GEN_AI_REQUEST_MODEL: model_name or "unknown",
trace_types.ATTR_REALTIME_MODEL_METRICS: ev.model_dump_json(),
trace_types.ATTR_GEN_AI_USAGE_INPUT_TOKENS: ev.input_tokens,
trace_types.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS: ev.output_tokens,
trace_types.ATTR_GEN_AI_USAGE_INPUT_TEXT_TOKENS: ev.input_token_details.text_tokens,
trace_types.ATTR_GEN_AI_USAGE_INPUT_AUDIO_TOKENS: ev.input_token_details.audio_tokens,
trace_types.ATTR_GEN_AI_USAGE_INPUT_CACHED_TOKENS: ev.input_token_details.cached_tokens,
trace_types.ATTR_GEN_AI_USAGE_OUTPUT_TEXT_TOKENS: ev.output_token_details.text_tokens,
trace_types.ATTR_GEN_AI_USAGE_OUTPUT_AUDIO_TOKENS: ev.output_token_details.audio_tokens,
}
if ev.ttft != -1:
completion_start_time = ev.timestamp + ev.ttft
# This attribute is used by LangFuse to calculate "time to first token metric"
# in same way we calculate in livekit (ttft = first_token_timestamp - ev.timestamp)
# So providing it explicitly here so we can graph and search by ttft.
# Must be provided as UTC isoformat string for LangFuse
completion_start_time_utc = datetime.fromtimestamp(
completion_start_time, tz=timezone.utc
).isoformat()
attrs[trace_types.ATTR_LANGFUSE_COMPLETION_START_TIME] = completion_start_time_utc
if span.is_recording():
span.set_attributes(attrs)
else:
from .traces import tracer
# create a dedicated child span for orphaned metrics
with trace.use_span(span):
with tracer.start_span("realtime_metrics") as child:
child.set_attributes(attrs)