mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
Merge f58deb3286 into f2118fc767
This commit is contained in:
commit
ed788f5f75
2 changed files with 96 additions and 3 deletions
|
|
@ -8921,14 +8921,23 @@ def convert_list_message_to_dict(
|
|||
return [dict(cleanup_none_field_in_message(message=_as_message_value(message))) for message in messages]
|
||||
|
||||
|
||||
def validate_and_fix_openai_messages(messages: list):
|
||||
def validate_and_fix_openai_messages(
|
||||
messages: list[Any], # mutable-ok: caller passes arbitrary message dicts
|
||||
):
|
||||
"""
|
||||
Ensures all messages are valid OpenAI chat completion messages.
|
||||
|
||||
Handles missing role for assistant messages.
|
||||
"""
|
||||
new_messages: Final = []
|
||||
new_messages: Final[list[AllMessageValues]] = [] # mutable-ok: list is populated by appending, name never rebound
|
||||
for message in messages:
|
||||
# Copy before mutating: assigning dicts into a pydantic Message's
|
||||
# tool_calls triggers PydanticSerializationUnexpectedValue warnings on
|
||||
# the later model_dump(), and it rewrites the caller's objects.
|
||||
if isinstance(message, BaseModel):
|
||||
message = message.model_dump(exclude_none=True)
|
||||
elif isinstance(message, dict):
|
||||
message = message.copy()
|
||||
if not message.get("role"):
|
||||
message["role"] = "assistant"
|
||||
if message.get("tool_calls"):
|
||||
|
|
@ -8941,7 +8950,9 @@ def validate_and_fix_openai_messages(messages: list):
|
|||
),
|
||||
)
|
||||
cleaned_message = cleanup_none_field_in_message(message=convert_msg_to_dict)
|
||||
new_messages.append(cleaned_message)
|
||||
new_messages.append(
|
||||
cast(AllMessageValues, cleaned_message)
|
||||
) # cast-ok: cleanup_none_field_in_message return satisfies AllMessageValues
|
||||
return validate_chat_completion_user_messages(messages=new_messages)
|
||||
|
||||
|
||||
|
|
|
|||
82
tests/unit/test_validate_and_fix_openai_messages.py
Normal file
82
tests/unit/test_validate_and_fix_openai_messages.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""Tests for validate_and_fix_openai_messages (issue #25880)."""
|
||||
|
||||
import warnings
|
||||
|
||||
from litellm.types.utils import ChatCompletionMessageToolCall, Function, Message
|
||||
from litellm.utils import validate_and_fix_openai_messages
|
||||
|
||||
|
||||
def _make_tool_call_message() -> Message:
|
||||
tc = ChatCompletionMessageToolCall(
|
||||
id="toolu_1",
|
||||
type="function",
|
||||
function=Function(name="add", arguments='{"a":1,"b":2}'),
|
||||
)
|
||||
return Message(content="", role="assistant", tool_calls=[tc])
|
||||
|
||||
|
||||
def test_no_pydantic_serialization_warning_with_tool_calls():
|
||||
msg = _make_tool_call_message()
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"error",
|
||||
message=".*serialized value may not be as expected.*",
|
||||
category=UserWarning,
|
||||
)
|
||||
result = validate_and_fix_openai_messages(
|
||||
[
|
||||
{"role": "user", "content": "hi"},
|
||||
msg,
|
||||
{"role": "tool", "tool_call_id": "toolu_1", "content": "3"},
|
||||
]
|
||||
)
|
||||
|
||||
assert len(result) == 3
|
||||
assert all(isinstance(m, dict) for m in result)
|
||||
assistant_msg = result[1]
|
||||
assert assistant_msg["role"] == "assistant"
|
||||
assert isinstance(assistant_msg["tool_calls"][0], dict)
|
||||
assert assistant_msg["tool_calls"][0]["function"]["name"] == "add"
|
||||
|
||||
|
||||
def test_original_message_not_mutated():
|
||||
msg = _make_tool_call_message()
|
||||
original_tc = msg.tool_calls[0]
|
||||
|
||||
validate_and_fix_openai_messages([{"role": "user", "content": "hi"}, msg])
|
||||
|
||||
assert isinstance(msg.tool_calls[0], ChatCompletionMessageToolCall)
|
||||
assert msg.tool_calls[0] is original_tc
|
||||
|
||||
|
||||
def test_dict_messages_with_tool_calls_still_work():
|
||||
messages = [
|
||||
{"role": "user", "content": "hello"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {"name": "greet", "arguments": "{}"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_1", "content": "hi"},
|
||||
]
|
||||
|
||||
result = validate_and_fix_openai_messages(messages)
|
||||
|
||||
assert len(result) == 3
|
||||
assert result[1]["tool_calls"][0]["function"]["name"] == "greet"
|
||||
|
||||
|
||||
def test_missing_role_defaults_to_assistant_without_mutating_input():
|
||||
original = {"content": "some content"}
|
||||
|
||||
result = validate_and_fix_openai_messages([original])
|
||||
|
||||
assert result[0]["role"] == "assistant"
|
||||
assert original == {"content": "some content"}
|
||||
Loading…
Add table
Reference in a new issue