Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions livekit-agents/livekit/agents/llm/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .._exceptions import APIConnectionError, APIError
from ..log import logger
from ..metrics import LLMMetrics
from ..telemetry import trace_types, tracer, utils as telemetry_utils
from ..telemetry import _chat_ctx_to_otel_events, trace_types, tracer, utils as telemetry_utils
from ..types import (
DEFAULT_API_CONNECT_OPTIONS,
NOT_GIVEN,
Expand Down Expand Up @@ -169,7 +169,11 @@ def __init__(
)

async def _traceable_main_task() -> None:
with tracer.start_as_current_span(self._llm_request_span_name, end_on_exit=False):
with tracer.start_as_current_span(
self._llm_request_span_name, end_on_exit=False
) as span:
for name, attributes in _chat_ctx_to_otel_events(self._chat_ctx):
span.add_event(name, attributes)
await self._main_task()

self._task = asyncio.create_task(_traceable_main_task(), name="LLM._main_task")
Expand Down
9 changes: 8 additions & 1 deletion livekit-agents/livekit/agents/telemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from . import http_server, metrics, trace_types, utils
from .traces import _setup_cloud_tracer, _upload_session_report, set_tracer_provider, tracer
from .traces import (
_chat_ctx_to_otel_events,
_setup_cloud_tracer,
_upload_session_report,
set_tracer_provider,
tracer,
)

__all__ = [
"tracer",
Expand All @@ -10,6 +16,7 @@
"utils",
"_setup_cloud_tracer",
"_upload_session_report",
"_chat_ctx_to_otel_events",
]

# Cleanup docs of unexported modules
Expand Down
45 changes: 43 additions & 2 deletions livekit-agents/livekit/agents/telemetry/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.trace import Span, TraceFlags, Tracer
from opentelemetry.util._decorator import _agnosticcontextmanager
from opentelemetry.util.types import AttributeValue
from opentelemetry.util.types import Attributes, AttributeValue

from livekit import api
from livekit.protocol import agent_pb, metrics as proto_metrics

from ..log import logger
from . import trace_types

if TYPE_CHECKING:
from ..llm import ChatItem
from ..llm import ChatContext, ChatItem
from ..voice.report import SessionReport


Expand Down Expand Up @@ -206,6 +207,46 @@ def __call__(self) -> dict[str, str]:
root.addHandler(handler)


def _chat_ctx_to_otel_events(chat_ctx: ChatContext) -> list[tuple[str, Attributes]]:
role_to_event = {
"system": trace_types.EVENT_GEN_AI_SYSTEM_MESSAGE,
"user": trace_types.EVENT_GEN_AI_USER_MESSAGE,
"assistant": trace_types.EVENT_GEN_AI_ASSISTANT_MESSAGE,
}

events: list[tuple[str, Attributes]] = []
for item in chat_ctx.items:
if item.type == "message" and (event_name := role_to_event.get(item.role)):
# only support text content for now
events.append((event_name, {"content": item.text_content or ""}))
elif item.type == "function_call":
events.append(
(
trace_types.EVENT_GEN_AI_ASSISTANT_MESSAGE,
{
"role": "assistant",
"tool_calls": [
json.dumps(
{
"function": {"name": item.name, "arguments": item.arguments},
"id": item.call_id,
"type": "function",
}
)
],
},
)
)
elif item.type == "function_call_output":
events.append(
(
trace_types.EVENT_GEN_AI_TOOL_MESSAGE,
{"content": item.output, "name": item.name, "id": item.call_id},
)
)
return events


def _to_proto_chat_item(item: ChatItem) -> dict: # agent_pb.agent_session.ChatContext.ChatItem:
item_pb = agent_pb.agent_session.ChatContext.ChatItem()

Expand Down