109 lines
3.1 KiB
Python
109 lines
3.1 KiB
Python
|
|
"""
|
||
|
|
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, Optional, Union
|
||
|
|
|
||
|
|
from pydantic import BaseModel
|
||
|
|
from typing_extensions import override
|
||
|
|
|
||
|
|
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", 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
|
||
|
|
def set_response_output_messages(span: "Span", response_obj):
|
||
|
|
safe_set_attribute(
|
||
|
|
span,
|
||
|
|
"langfuse.observation.output",
|
||
|
|
get_output_content_by_type(response_obj),
|
||
|
|
)
|