mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(langfuse_otel): use safe_dumps in set_messages to handle pydantic Message objects
Calling json.dumps() directly on the messages list failed with 'Object of type Message is not JSON serializable' when users passed litellm.Message (a pydantic BaseModel) objects instead of plain dicts. Replace json.dumps() with safe_dumps() which recursively calls model_dump() on any pydantic BaseModel instances before serializing. Fixes #26977 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
934ecdca78
commit
f5dc440753
2 changed files with 100 additions and 1 deletions
|
|
@ -14,6 +14,7 @@ from litellm.integrations.opentelemetry_utils.base_otel_llm_obs_attributes impor
|
|||
BaseLLMObsOTELAttributes,
|
||||
safe_set_attribute,
|
||||
)
|
||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||
from litellm.types.llms.openai import HttpxBinaryResponseContent, ResponsesAPIResponse
|
||||
from litellm.types.utils import (
|
||||
EmbeddingResponse,
|
||||
|
|
@ -96,7 +97,7 @@ class LangfuseLLMObsOTELAttributes(BaseLLMObsOTELAttributes):
|
|||
prompt["tools"] = tools
|
||||
|
||||
input = prompt
|
||||
safe_set_attribute(span, "langfuse.observation.input", json.dumps(input))
|
||||
safe_set_attribute(span, "langfuse.observation.input", safe_dumps(input))
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
Tests for LangfuseLLMObsOTELAttributes, specifically verifying that pydantic
|
||||
Message objects in the messages list are correctly serialized without raising
|
||||
TypeError. Regression test for:
|
||||
https://github.com/BerriAI/litellm/issues/26977
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../.."))
|
||||
|
||||
from litellm.integrations.langfuse.langfuse_otel_attributes import (
|
||||
LangfuseLLMObsOTELAttributes,
|
||||
)
|
||||
from litellm.types.utils import Message
|
||||
|
||||
|
||||
def test_set_messages_with_pydantic_message_objects():
|
||||
"""
|
||||
Regression test for issue #26977:
|
||||
set_messages should not raise 'Object of type Message is not JSON serializable'
|
||||
when messages is a list of litellm.Message (pydantic) objects.
|
||||
"""
|
||||
span = MagicMock()
|
||||
kwargs = {
|
||||
"messages": [Message(role="user", content="hello")],
|
||||
"optional_params": {},
|
||||
}
|
||||
|
||||
# Should not raise TypeError
|
||||
LangfuseLLMObsOTELAttributes.set_messages(span, kwargs)
|
||||
|
||||
# Verify that set_attribute was called with a valid JSON string
|
||||
span.set_attribute.assert_called_once()
|
||||
key, value = span.set_attribute.call_args[0]
|
||||
assert key == "langfuse.observation.input"
|
||||
|
||||
# The value must be parseable JSON
|
||||
parsed = json.loads(value)
|
||||
assert "messages" in parsed
|
||||
assert parsed["messages"][0]["role"] == "user"
|
||||
assert parsed["messages"][0]["content"] == "hello"
|
||||
|
||||
|
||||
def test_set_messages_with_dict_messages():
|
||||
"""
|
||||
Ensure that plain dict messages still work correctly after the fix.
|
||||
"""
|
||||
span = MagicMock()
|
||||
kwargs = {
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
"optional_params": {},
|
||||
}
|
||||
|
||||
LangfuseLLMObsOTELAttributes.set_messages(span, kwargs)
|
||||
|
||||
span.set_attribute.assert_called_once()
|
||||
key, value = span.set_attribute.call_args[0]
|
||||
assert key == "langfuse.observation.input"
|
||||
|
||||
parsed = json.loads(value)
|
||||
assert parsed["messages"][0]["role"] == "user"
|
||||
|
||||
|
||||
def test_set_messages_with_tools():
|
||||
"""
|
||||
Ensure tools are included in the serialized input and pydantic messages
|
||||
are handled correctly together.
|
||||
"""
|
||||
span = MagicMock()
|
||||
kwargs = {
|
||||
"messages": [Message(role="user", content="what's the weather?")],
|
||||
"optional_params": {
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"parameters": {"type": "object"},
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
LangfuseLLMObsOTELAttributes.set_messages(span, kwargs)
|
||||
|
||||
span.set_attribute.assert_called_once()
|
||||
key, value = span.set_attribute.call_args[0]
|
||||
parsed = json.loads(value)
|
||||
assert "tools" in parsed
|
||||
assert parsed["tools"][0]["function"]["name"] == "get_weather"
|
||||
assert parsed["messages"][0]["role"] == "user"
|
||||
Loading…
Add table
Reference in a new issue