mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
Move validate_and_fix_openai_messages tests to correct directory
Per CONTRIBUTING.md, tests for litellm/utils.py belong in tests/test_litellm/test_utils.py, not tests/local_testing/.
This commit is contained in:
parent
cac6b88c54
commit
37e52ed164
2 changed files with 104 additions and 114 deletions
|
|
@ -1,114 +0,0 @@
|
|||
"""
|
||||
Tests for validate_and_fix_openai_messages in litellm/utils.py
|
||||
|
||||
Covers the fix for https://github.com/BerriAI/litellm/issues/25880:
|
||||
Pydantic Message objects with tool_calls should not trigger
|
||||
PydanticSerializationUnexpectedValue warnings.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.types.utils import (
|
||||
ChatCompletionMessageToolCall,
|
||||
Function,
|
||||
Message,
|
||||
)
|
||||
from litellm.utils import validate_and_fix_openai_messages
|
||||
|
||||
|
||||
def _make_tool_call_message():
|
||||
"""Helper: build an assistant Message with a single tool_call."""
|
||||
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():
|
||||
"""Regression: pydantic Messages with tool_calls must not emit warnings."""
|
||||
msg = _make_tool_call_message()
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("error", message="Pydantic serializer warnings")
|
||||
# Should not raise
|
||||
result = validate_and_fix_openai_messages(
|
||||
[
|
||||
{"role": "user", "content": "hi"},
|
||||
msg,
|
||||
{"role": "tool", "tool_call_id": "toolu_1", "content": "3"},
|
||||
]
|
||||
)
|
||||
|
||||
# Basic sanity: all messages come back as dicts
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 3
|
||||
for m in result:
|
||||
assert isinstance(m, dict)
|
||||
|
||||
# The assistant message should have tool_calls serialized as dicts
|
||||
assistant_msg = result[1]
|
||||
assert assistant_msg["role"] == "assistant"
|
||||
assert isinstance(assistant_msg["tool_calls"], list)
|
||||
assert isinstance(assistant_msg["tool_calls"][0], dict)
|
||||
assert assistant_msg["tool_calls"][0]["function"]["name"] == "add"
|
||||
|
||||
|
||||
def test_original_message_not_mutated():
|
||||
"""The original pydantic Message should not be mutated by validation."""
|
||||
msg = _make_tool_call_message()
|
||||
original_tc = msg.tool_calls[0]
|
||||
|
||||
validate_and_fix_openai_messages(
|
||||
[
|
||||
{"role": "user", "content": "hi"},
|
||||
msg,
|
||||
{"role": "tool", "tool_call_id": "toolu_1", "content": "3"},
|
||||
]
|
||||
)
|
||||
|
||||
# tool_calls on the original message should still be the pydantic model
|
||||
assert isinstance(msg.tool_calls[0], ChatCompletionMessageToolCall)
|
||||
assert msg.tool_calls[0] is original_tc
|
||||
|
||||
|
||||
def test_dict_messages_still_work():
|
||||
"""Plain dict messages should still be processed correctly."""
|
||||
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_gets_default():
|
||||
"""Messages without a role should default to 'assistant'."""
|
||||
result = validate_and_fix_openai_messages(
|
||||
[{"content": "some content"}]
|
||||
)
|
||||
assert result[0]["role"] == "assistant"
|
||||
|
||||
|
||||
def test_dict_messages_not_mutated_in_place():
|
||||
"""Original dict messages should not be mutated."""
|
||||
original = {"role": "user", "content": "test"}
|
||||
validate_and_fix_openai_messages([original])
|
||||
# Should still be the same dict content
|
||||
assert original == {"role": "user", "content": "test"}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import warnings
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
|
@ -18,6 +19,9 @@ from litellm.types.utils import (
|
|||
LlmProviders,
|
||||
ModelResponseStream,
|
||||
StreamingChoices,
|
||||
ChatCompletionMessageToolCall,
|
||||
Function,
|
||||
Message,
|
||||
)
|
||||
from litellm.utils import (
|
||||
ProviderConfigManager,
|
||||
|
|
@ -27,6 +31,7 @@ from litellm.utils import (
|
|||
get_llm_provider,
|
||||
get_optional_params_image_gen,
|
||||
is_cached_message,
|
||||
validate_and_fix_openai_messages,
|
||||
)
|
||||
|
||||
# Adds the parent directory to the system path
|
||||
|
|
@ -3868,3 +3873,102 @@ class TestValidateAndFixThinkingParam:
|
|||
validate_and_fix_thinking_param(thinking=thinking)
|
||||
assert "budgetTokens" in thinking
|
||||
assert "budget_tokens" not in thinking
|
||||
|
||||
|
||||
# --- Tests for validate_and_fix_openai_messages (issue #25880) ---
|
||||
|
||||
|
||||
def _make_tool_call_message():
|
||||
"""Helper: build an assistant Message with a single tool_call."""
|
||||
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():
|
||||
"""Regression: pydantic Messages with tool_calls must not emit warnings."""
|
||||
msg = _make_tool_call_message()
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("error", message="Pydantic serializer warnings")
|
||||
# Should not raise
|
||||
result = validate_and_fix_openai_messages(
|
||||
[
|
||||
{"role": "user", "content": "hi"},
|
||||
msg,
|
||||
{"role": "tool", "tool_call_id": "toolu_1", "content": "3"},
|
||||
]
|
||||
)
|
||||
|
||||
# Basic sanity: all messages come back as dicts
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 3
|
||||
for m in result:
|
||||
assert isinstance(m, dict)
|
||||
|
||||
# The assistant message should have tool_calls serialized as dicts
|
||||
assistant_msg = result[1]
|
||||
assert assistant_msg["role"] == "assistant"
|
||||
assert isinstance(assistant_msg["tool_calls"], list)
|
||||
assert isinstance(assistant_msg["tool_calls"][0], dict)
|
||||
assert assistant_msg["tool_calls"][0]["function"]["name"] == "add"
|
||||
|
||||
|
||||
def test_original_message_not_mutated():
|
||||
"""The original pydantic Message should not be mutated by validation."""
|
||||
msg = _make_tool_call_message()
|
||||
original_tc = msg.tool_calls[0]
|
||||
|
||||
validate_and_fix_openai_messages(
|
||||
[
|
||||
{"role": "user", "content": "hi"},
|
||||
msg,
|
||||
{"role": "tool", "tool_call_id": "toolu_1", "content": "3"},
|
||||
]
|
||||
)
|
||||
|
||||
# tool_calls on the original message should still be the pydantic model
|
||||
assert isinstance(msg.tool_calls[0], ChatCompletionMessageToolCall)
|
||||
assert msg.tool_calls[0] is original_tc
|
||||
|
||||
|
||||
def test_dict_messages_still_work():
|
||||
"""Plain dict messages should still be processed correctly."""
|
||||
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_gets_default():
|
||||
"""Messages without a role should default to 'assistant'."""
|
||||
result = validate_and_fix_openai_messages(
|
||||
[{"content": "some content"}]
|
||||
)
|
||||
assert result[0]["role"] == "assistant"
|
||||
|
||||
|
||||
def test_dict_messages_not_mutated_in_place():
|
||||
"""Original dict messages should not be mutated."""
|
||||
original = {"role": "user", "content": "test"}
|
||||
validate_and_fix_openai_messages([original])
|
||||
# Should still be the same dict content
|
||||
assert original == {"role": "user", "content": "test"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue