@@ -180,6 +180,23 @@ def test_format_request_tool_message():
180180 assert tru_result == exp_result
181181
182182
183+ def test_format_request_tool_message_single_text_returns_string ():
184+ """Test that single text content is returned as string for model compatibility."""
185+ tool_result = {
186+ "content" : [{"text" : '{"result": "success"}' }],
187+ "status" : "success" ,
188+ "toolUseId" : "c1" ,
189+ }
190+
191+ tru_result = OpenAIModel .format_request_tool_message (tool_result )
192+ exp_result = {
193+ "content" : '{"result": "success"}' ,
194+ "role" : "tool" ,
195+ "tool_call_id" : "c1" ,
196+ }
197+ assert tru_result == exp_result
198+
199+
183200def test_split_tool_message_images_with_image ():
184201 """Test that images are extracted from tool messages."""
185202 tool_message = {
@@ -441,7 +458,7 @@ def test_format_request_messages(system_prompt):
441458 ],
442459 },
443460 {
444- "content" : [{ "text" : "4" , "type" : "text" }] ,
461+ "content" : "4" ,
445462 "role" : "tool" ,
446463 "tool_call_id" : "c1" ,
447464 },
@@ -1397,3 +1414,122 @@ def test_format_request_filters_location_source_document(model, caplog):
13971414 assert len (formatted_content ) == 1
13981415 assert formatted_content [0 ]["type" ] == "text"
13991416 assert "Location sources are not supported by OpenAI" in caplog .text
1417+
1418+
1419+ def test_format_request_messages_with_tool_calls_no_content ():
1420+ """Test that assistant messages with only tool calls are included and have no content field."""
1421+ messages = [
1422+ {"role" : "user" , "content" : [{"text" : "Use the calculator" }]},
1423+ {
1424+ "role" : "assistant" ,
1425+ "content" : [
1426+ {
1427+ "toolUse" : {
1428+ "input" : {"expression" : "2+2" },
1429+ "name" : "calculator" ,
1430+ "toolUseId" : "c1" ,
1431+ },
1432+ },
1433+ ],
1434+ },
1435+ ]
1436+
1437+ tru_result = OpenAIModel .format_request_messages (messages )
1438+
1439+ exp_result = [
1440+ {"role" : "user" , "content" : [{"text" : "Use the calculator" , "type" : "text" }]},
1441+ {
1442+ "role" : "assistant" ,
1443+ "tool_calls" : [
1444+ {
1445+ "function" : {"arguments" : '{"expression": "2+2"}' , "name" : "calculator" },
1446+ "id" : "c1" ,
1447+ "type" : "function" ,
1448+ }
1449+ ],
1450+ },
1451+ ]
1452+ assert tru_result == exp_result
1453+
1454+
1455+ def test_format_request_messages_multiple_tool_calls_with_images ():
1456+ """Test that multiple tool calls with image results are formatted correctly.
1457+
1458+ OpenAI requires all tool response messages to immediately follow the assistant
1459+ message with tool_calls, before any other messages. When tools return images,
1460+ the images are moved to user messages, but these must come after ALL tool messages.
1461+ """
1462+ messages = [
1463+ {"role" : "user" , "content" : [{"text" : "Run the tools" }]},
1464+ {
1465+ "role" : "assistant" ,
1466+ "content" : [
1467+ {"toolUse" : {"input" : {}, "name" : "tool1" , "toolUseId" : "call_1" }},
1468+ {"toolUse" : {"input" : {}, "name" : "tool2" , "toolUseId" : "call_2" }},
1469+ ],
1470+ },
1471+ {
1472+ "role" : "user" ,
1473+ "content" : [
1474+ {
1475+ "toolResult" : {
1476+ "toolUseId" : "call_1" ,
1477+ "content" : [{"image" : {"format" : "png" , "source" : {"bytes" : b"img1" }}}],
1478+ "status" : "success" ,
1479+ }
1480+ },
1481+ {
1482+ "toolResult" : {
1483+ "toolUseId" : "call_2" ,
1484+ "content" : [{"image" : {"format" : "png" , "source" : {"bytes" : b"img2" }}}],
1485+ "status" : "success" ,
1486+ }
1487+ },
1488+ ],
1489+ },
1490+ ]
1491+
1492+ tru_result = OpenAIModel .format_request_messages (messages )
1493+
1494+ image_placeholder = (
1495+ "Tool successfully returned an image. The image is being provided in the following user message."
1496+ )
1497+ exp_result = [
1498+ {"role" : "user" , "content" : [{"text" : "Run the tools" , "type" : "text" }]},
1499+ {
1500+ "role" : "assistant" ,
1501+ "tool_calls" : [
1502+ {"function" : {"arguments" : "{}" , "name" : "tool1" }, "id" : "call_1" , "type" : "function" },
1503+ {"function" : {"arguments" : "{}" , "name" : "tool2" }, "id" : "call_2" , "type" : "function" },
1504+ ],
1505+ },
1506+ {
1507+ "role" : "tool" ,
1508+ "tool_call_id" : "call_1" ,
1509+ "content" : [{"type" : "text" , "text" : image_placeholder }],
1510+ },
1511+ {
1512+ "role" : "tool" ,
1513+ "tool_call_id" : "call_2" ,
1514+ "content" : [{"type" : "text" , "text" : image_placeholder }],
1515+ },
1516+ {
1517+ "role" : "user" ,
1518+ "content" : [
1519+ {
1520+ "image_url" : {"detail" : "auto" , "format" : "image/png" , "url" : "data:image/png;base64,aW1nMQ==" },
1521+ "type" : "image_url" ,
1522+ }
1523+ ],
1524+ },
1525+ {
1526+ "role" : "user" ,
1527+ "content" : [
1528+ {
1529+ "image_url" : {"detail" : "auto" , "format" : "image/png" , "url" : "data:image/png;base64,aW1nMg==" },
1530+ "type" : "image_url" ,
1531+ }
1532+ ],
1533+ },
1534+ ]
1535+ assert tru_result == exp_result
0 commit comments