Fix LLM tracing for LLMContext

This commit is contained in:
Mark Backman
2025-11-11 14:31:55 -05:00
parent 54e8d29615
commit d37eabf57f
2 changed files with 46 additions and 27 deletions

View File

@@ -1316,6 +1316,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Fixed an issue with OpenTelemetry where tracing wasn't correctly displaying
LLM completions and tools when using the universal LLMContext.
- Fixed an RTVI issue that was causing frames to be pushed before pipeline was - Fixed an RTVI issue that was causing frames to be pushed before pipeline was
properly initialized. properly initialized.

View File

@@ -23,6 +23,8 @@ if TYPE_CHECKING:
from opentelemetry import context as context_api from opentelemetry import context as context_api
from opentelemetry import trace from opentelemetry import trace
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.utils.tracing.service_attributes import ( from pipecat.utils.tracing.service_attributes import (
add_gemini_live_span_attributes, add_gemini_live_span_attributes,
add_llm_span_attributes, add_llm_span_attributes,
@@ -382,43 +384,57 @@ def traced_llm(func: Optional[Callable] = None, *, name: Optional[str] = None) -
# Replace push_frame to capture output # Replace push_frame to capture output
self.push_frame = traced_push_frame self.push_frame = traced_push_frame
# Detect if we're using Google's service # Get messages for logging
is_google_service = "google" in service_class_name.lower() # For OpenAILLMContext: use context's own get_messages_for_logging() method
# For LLMContext: use adapter's get_messages_for_logging() which returns
# Try to get messages based on service type # messages in provider's native format with sensitive data sanitized
messages = None messages = None
serialized_messages = None serialized_messages = None
# TODO: Revisit once we unify the messages across services if isinstance(context, OpenAILLMContext):
if is_google_service: # OpenAILLMContext and subclasses have their own method
# Handle Google service specifically messages = context.get_messages_for_logging()
if hasattr(context, "get_messages_for_logging"): elif isinstance(context, LLMContext):
messages = context.get_messages_for_logging() # Universal LLMContext - use adapter for provider-native format
else: if hasattr(self, "get_llm_adapter"):
# Handle other services like OpenAI adapter = self.get_llm_adapter()
if hasattr(context, "get_messages"): messages = adapter.get_messages_for_logging(context)
messages = context.get_messages() elif hasattr(context, "get_messages"):
elif hasattr(context, "messages"): # Fallback for unknown context types
messages = context.messages messages = context.get_messages()
elif hasattr(context, "messages"):
messages = context.messages
# Serialize messages if available # Serialize messages if available
if messages: if messages:
try: serialized_messages = json.dumps(messages)
serialized_messages = json.dumps(messages)
except Exception as e:
serialized_messages = f"Error serializing messages: {str(e)}"
# Get tools, system message, etc. based on the service type # Get tools
tools = getattr(context, "tools", None) # For OpenAILLMContext: tools may need adapter conversion if set
# For LLMContext: use adapter's from_standard_tools() to convert ToolsSchema
tools = None
serialized_tools = None serialized_tools = None
tool_count = 0 tool_count = 0
if tools: if isinstance(context, OpenAILLMContext):
try: # OpenAILLMContext: tools property handles adapter conversion internally
serialized_tools = json.dumps(tools) tools = context.tools
tool_count = len(tools) if isinstance(tools, list) else 1 elif isinstance(context, LLMContext):
except Exception as e: # Universal LLMContext - use adapter to convert ToolsSchema
serialized_tools = f"Error serializing tools: {str(e)}" if hasattr(self, "get_llm_adapter") and hasattr(context, "tools"):
adapter = self.get_llm_adapter()
tools = adapter.from_standard_tools(context.tools)
elif hasattr(context, "tools"):
# Fallback for unknown context types
tools = context.tools
# Serialize and count tools if available
# Check if tools is not None and not NOT_GIVEN (using attribute check as fallback)
if tools is not None and not (
hasattr(tools, "__name__") and tools.__name__ == "NOT_GIVEN"
):
serialized_tools = json.dumps(tools)
tool_count = len(tools) if isinstance(tools, list) else 1
# Handle system message for different services # Handle system message for different services
system_message = None system_message = None