fix(langfuse_otel_attributes.py): log tools and other optional params

This commit is contained in:
Krrish Dholakia 2025-10-22 14:58:01 -07:00
parent 75bee1f274
commit b2da4bdac2
3 changed files with 91 additions and 16 deletions

View file

@ -20,12 +20,14 @@ class ArizeOTELAttributes(BaseLLMObsOTELAttributes):
@staticmethod
@override
def set_messages(span: "Span", messages: List[Dict[str, Any]]):
def set_messages(span: "Span", kwargs: Dict[str, Any]):
from litellm.integrations._types.open_inference import (
MessageAttributes,
SpanAttributes,
)
messages = kwargs.get("messages")
# for /chat/completions
# https://docs.arize.com/arize/large-language-models/tracing/semantic-conventions
if messages:
@ -205,9 +207,7 @@ def set_attributes(
SpanAttributes.OPENINFERENCE_SPAN_KIND,
OpenInferenceSpanKindValues.LLM.value,
)
messages = kwargs.get("messages")
attributes.set_messages(span, messages)
attributes.set_messages(span, kwargs)
# Capture tools (function definitions) used in the LLM call.
tools = optional_params.get("tools")

View file

@ -1,25 +1,104 @@
import json
from typing import TYPE_CHECKING, Any, Dict, List
"""
If the LLM Obs has any specific attributes to log request or response, we can add them here.
Relevant Issue: https://github.com/BerriAI/litellm/issues/13764
"""
import json
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from numpy import isin
from pydantic import BaseModel
from typing_extensions import override
import litellm
from litellm.integrations.opentelemetry_utils.base_otel_llm_obs_attributes import (
BaseLLMObsOTELAttributes,
safe_set_attribute,
)
from litellm.types.llms.openai import HttpxBinaryResponseContent, ResponsesAPIResponse
from litellm.types.utils import (
EmbeddingResponse,
ImageResponse,
ModelResponse,
RerankResponse,
TextCompletionResponse,
TranscriptionResponse,
)
if TYPE_CHECKING:
from opentelemetry.trace import Span
def get_output_content_by_type(
response_obj: Union[
None,
dict,
EmbeddingResponse,
ModelResponse,
TextCompletionResponse,
ImageResponse,
TranscriptionResponse,
RerankResponse,
HttpxBinaryResponseContent,
ResponsesAPIResponse,
list,
],
kwargs: Optional[Dict[str, Any]] = None,
) -> str:
"""
Extract output content from response objects based on their type.
This utility function handles the type-specific logic for converting
various response objects into appropriate output formats for Langfuse logging.
Args:
response_obj: The response object returned by the function
kwargs: Optional keyword arguments containing call_type and other metadata
Returns:
The formatted output content suitable for Langfuse logging, or None
"""
if response_obj is None:
return ""
kwargs = kwargs or {}
call_type = kwargs.get("call_type", None)
# Embedding responses - no output content
if call_type == "embedding" or isinstance(response_obj, EmbeddingResponse):
return "embedding-output"
# Binary/Speech responses
if isinstance(response_obj, HttpxBinaryResponseContent):
return "speech-output"
if isinstance(response_obj, BaseModel):
return response_obj.model_dump_json()
if response_obj and (
isinstance(response_obj, dict) or isinstance(response_obj, list)
):
return json.dumps(response_obj)
else:
return ""
class LangfuseLLMObsOTELAttributes(BaseLLMObsOTELAttributes):
@staticmethod
@override
def set_messages(span: "Span", messages: List[Dict[str, Any]]):
safe_set_attribute(
span, "langfuse.observation.input", json.dumps({"messages": messages})
)
def set_messages(span: "Span", kwargs: Dict[str, Any]):
prompt = {"messages": kwargs.get("messages")}
optional_params = kwargs.get("optional_params", {})
functions = optional_params.get("functions")
tools = optional_params.get("tools")
if functions is not None:
prompt["functions"] = functions
if tools is not None:
prompt["tools"] = tools
input = prompt
safe_set_attribute(span, "langfuse.observation.input", json.dumps(input))
@staticmethod
@override
@ -27,9 +106,5 @@ class LangfuseLLMObsOTELAttributes(BaseLLMObsOTELAttributes):
safe_set_attribute(
span,
"langfuse.observation.output",
(
response_obj.model_dump_json()
if isinstance(response_obj, BaseModel)
else response_obj
),
get_output_content_by_type(response_obj),
)

View file

@ -7,7 +7,7 @@ if TYPE_CHECKING:
class BaseLLMObsOTELAttributes(ABC):
@staticmethod
def set_messages(span: "Span", messages: List[Dict[str, Any]]):
def set_messages(span: "Span", kwargs: Dict[str, Any]):
pass
@staticmethod