Skip to content

Commit ae3931f

Browse files
FIx #14158
1 parent 359780b commit ae3931f

File tree

2 files changed

+237
-8
lines changed

2 files changed

+237
-8
lines changed

litellm/llms/oci/chat/transformation.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,14 @@ def adapt_messages_to_generic_oci_standard(
772772
tool_calls = message.get("tool_calls")
773773
tool_call_id = message.get("tool_call_id")
774774

775-
if role in ["system", "user", "assistant"] and content is not None:
775+
if role == "assistant" and tool_calls is not None:
776+
if not isinstance(tool_calls, list):
777+
raise Exception("Prop `tool_calls` must be a list of tool calls")
778+
new_messages.append(
779+
adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls)
780+
)
781+
782+
elif role in ["system", "user", "assistant"] and content is not None:
776783
if not isinstance(content, (str, list)):
777784
raise Exception(
778785
"Prop `content` must be a string or a list of content items"
@@ -781,13 +788,6 @@ def adapt_messages_to_generic_oci_standard(
781788
adapt_messages_to_generic_oci_standard_content_message(role, content)
782789
)
783790

784-
elif role == "assistant" and tool_calls is not None:
785-
if not isinstance(tool_calls, list):
786-
raise Exception("Prop `tool_calls` must be a list of tool calls")
787-
new_messages.append(
788-
adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls)
789-
)
790-
791791
elif role == "tool":
792792
if not isinstance(tool_call_id, str):
793793
raise Exception("Prop `tool_call_id` is required and must be a string")
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import pytest
2+
from litellm.llms.oci.chat.transformation import adapt_messages_to_generic_oci_standard
3+
4+
def test_adapt_messages_with_empty_content_and_tool_calls():
5+
"""Test that assistant messages with empty content and tool_calls are processed correctly."""
6+
# Arrange
7+
messages_with_empty_content = [
8+
{"role": "user", "content": "Tell me the weather in Tokyo."},
9+
{
10+
"role": "assistant",
11+
"content": "", # Empty string
12+
"tool_calls": [
13+
{
14+
"id": "call_test_empty",
15+
"type": "function",
16+
"function": {
17+
"name": "get_weather",
18+
"arguments": '{"city": "Tokyo"}'
19+
}
20+
}
21+
]
22+
},
23+
{
24+
"role": "tool",
25+
"content": '{"weather": "Sunny", "temperature": "25°C"}',
26+
"tool_call_id": "call_test_empty"
27+
}
28+
]
29+
30+
# Act
31+
result = adapt_messages_to_generic_oci_standard(messages_with_empty_content)
32+
33+
# Assert
34+
assert len(result) == 3
35+
36+
# Check user message
37+
assert result[0].role == "USER"
38+
assert result[0].content[0].type == "TEXT"
39+
assert result[0].content[0].text == "Tell me the weather in Tokyo."
40+
41+
# Check assistant message with tool_calls (should prioritize tool_calls over empty content)
42+
assert result[1].role == "ASSISTANT"
43+
assert result[1].toolCalls is not None
44+
assert len(result[1].toolCalls) == 1
45+
assert result[1].toolCalls[0].id == "call_test_empty"
46+
assert result[1].toolCalls[0].name == "get_weather"
47+
48+
# Check tool response message
49+
assert result[2].role == "TOOL" # Tool responses have TOOL role, not USER
50+
assert result[2].content[0].type == "TEXT"
51+
assert "weather" in result[2].content[0].text
52+
assert result[2].toolCallId == "call_test_empty" # Tool call ID is in separate field
53+
54+
def test_adapt_messages_with_none_content_and_tool_calls():
55+
"""Test that assistant messages with None content and tool_calls are processed correctly."""
56+
# Arrange
57+
messages_with_none_content = [
58+
{"role": "user", "content": "Tell me the weather in Tokyo."},
59+
{
60+
"role": "assistant",
61+
"content": None, # None value
62+
"tool_calls": [
63+
{
64+
"id": "call_test_none",
65+
"type": "function",
66+
"function": {
67+
"name": "get_weather",
68+
"arguments": '{"city": "Tokyo"}'
69+
}
70+
}
71+
]
72+
},
73+
{
74+
"role": "tool",
75+
"content": '{"weather": "Sunny", "temperature": "25°C"}',
76+
"tool_call_id": "call_test_none"
77+
}
78+
]
79+
80+
# Act
81+
result = adapt_messages_to_generic_oci_standard(messages_with_none_content)
82+
83+
# Assert
84+
assert len(result) == 3
85+
86+
# Check assistant message prioritizes tool_calls over None content
87+
assert result[1].role == "ASSISTANT"
88+
assert result[1].toolCalls is not None
89+
assert len(result[1].toolCalls) == 1
90+
assert result[1].toolCalls[0].id == "call_test_none"
91+
92+
def test_adapt_messages_with_tool_calls_only():
93+
"""Test that assistant messages with only tool_calls (no content field) are processed correctly."""
94+
# Arrange
95+
messages_no_content = [
96+
{"role": "user", "content": "Tell me the weather in Tokyo."},
97+
{
98+
"role": "assistant",
99+
# No content field at all
100+
"tool_calls": [
101+
{
102+
"id": "call_test_no_content",
103+
"type": "function",
104+
"function": {
105+
"name": "get_weather",
106+
"arguments": '{"city": "Tokyo"}'
107+
}
108+
}
109+
]
110+
},
111+
{
112+
"role": "tool",
113+
"content": '{"weather": "Sunny", "temperature": "25°C"}',
114+
"tool_call_id": "call_test_no_content"
115+
}
116+
]
117+
118+
# Act
119+
result = adapt_messages_to_generic_oci_standard(messages_no_content)
120+
121+
# Assert
122+
assert len(result) == 3
123+
124+
# Check assistant message processes tool_calls correctly
125+
assert result[1].role == "ASSISTANT"
126+
assert result[1].toolCalls is not None
127+
assert len(result[1].toolCalls) == 1
128+
assert result[1].toolCalls[0].id == "call_test_no_content"
129+
130+
def test_adapt_messages_with_content_only():
131+
"""Test that assistant messages with only content (no tool_calls) are processed correctly."""
132+
# Arrange
133+
messages_content_only = [
134+
{"role": "user", "content": "Hello"},
135+
{
136+
"role": "assistant",
137+
"content": "Hello! How can I help you today?"
138+
}
139+
]
140+
141+
# Act
142+
result = adapt_messages_to_generic_oci_standard(messages_content_only)
143+
144+
# Assert
145+
assert len(result) == 2
146+
147+
# Check assistant message with content only
148+
assert result[1].role == "ASSISTANT"
149+
assert result[1].content[0].type == "TEXT"
150+
assert result[1].content[0].text == "Hello! How can I help you today?"
151+
assert result[1].toolCalls is None
152+
153+
def test_adapt_messages_tool_id_tracking():
154+
"""Test that tool call IDs are properly tracked for validation."""
155+
# Arrange
156+
messages = [
157+
{"role": "user", "content": "Test"},
158+
{
159+
"role": "assistant",
160+
"tool_calls": [
161+
{
162+
"id": "call_123",
163+
"type": "function",
164+
"function": {
165+
"name": "test_func",
166+
"arguments": '{"param": "value"}'
167+
}
168+
}
169+
]
170+
},
171+
{
172+
"role": "tool",
173+
"content": "Result",
174+
"tool_call_id": "call_123"
175+
}
176+
]
177+
178+
# Act
179+
result = adapt_messages_to_generic_oci_standard(messages)
180+
181+
# Assert
182+
# Tool call should be processed and ID should be available for validation
183+
assert result[1].toolCalls[0].id == "call_123"
184+
185+
# Tool response should reference the same ID
186+
tool_response_text = result[2].content[0].text
187+
# Tool response text is just the content, tool_call_id is separate
188+
assert tool_response_text == "Result" # The actual content
189+
assert result[2].toolCallId == "call_123" # Tool call ID is in separate field
190+
191+
def test_adapt_messages_multiple_tool_calls():
192+
"""Test that multiple tool calls in a single message are processed correctly."""
193+
# Arrange
194+
messages = [
195+
{"role": "user", "content": "Test multiple tools"},
196+
{
197+
"role": "assistant",
198+
"content": "",
199+
"tool_calls": [
200+
{
201+
"id": "call_1",
202+
"type": "function",
203+
"function": {
204+
"name": "func1",
205+
"arguments": '{"param": "value1"}'
206+
}
207+
},
208+
{
209+
"id": "call_2",
210+
"type": "function",
211+
"function": {
212+
"name": "func2",
213+
"arguments": '{"param": "value2"}'
214+
}
215+
}
216+
]
217+
}
218+
]
219+
220+
# Act
221+
result = adapt_messages_to_generic_oci_standard(messages)
222+
223+
# Assert
224+
assert len(result) == 2
225+
assert result[1].role == "ASSISTANT"
226+
assert len(result[1].toolCalls) == 2
227+
assert result[1].toolCalls[0].id == "call_1"
228+
assert result[1].toolCalls[1].id == "call_2"
229+

0 commit comments

Comments
 (0)