@@ -1173,3 +1173,360 @@ async def test_parse_openai_completion_raises_empty_model_output_error():
11731173 await provider ._parse_openai_completion (completion , tools = None )
11741174 finally :
11751175 await provider .terminate ()
1176+
1177+
1178+ @pytest .mark .asyncio
1179+ async def test_query_filters_empty_assistant_message_without_tool_calls (monkeypatch ):
1180+ """Test that empty assistant messages without tool_calls are filtered out."""
1181+ provider = _make_provider ()
1182+ try :
1183+ captured_kwargs = {}
1184+
1185+ async def fake_create (** kwargs ):
1186+ captured_kwargs .update (kwargs )
1187+ return ChatCompletion .model_validate (
1188+ {
1189+ "id" : "chatcmpl-test" ,
1190+ "object" : "chat.completion" ,
1191+ "created" : 0 ,
1192+ "model" : "gpt-4o-mini" ,
1193+ "choices" : [
1194+ {
1195+ "index" : 0 ,
1196+ "message" : {
1197+ "role" : "assistant" ,
1198+ "content" : "ok" ,
1199+ },
1200+ "finish_reason" : "stop" ,
1201+ }
1202+ ],
1203+ "usage" : {
1204+ "prompt_tokens" : 1 ,
1205+ "completion_tokens" : 1 ,
1206+ "total_tokens" : 2 ,
1207+ },
1208+ }
1209+ )
1210+
1211+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1212+
1213+ payloads = {
1214+ "model" : "gpt-4o-mini" ,
1215+ "messages" : [
1216+ {"role" : "user" , "content" : "hello" },
1217+ {"role" : "assistant" , "content" : "" }, # Should be filtered
1218+ {"role" : "user" , "content" : "world" },
1219+ ],
1220+ }
1221+
1222+ await provider ._query (payloads = payloads , tools = None )
1223+
1224+ # The empty assistant message should be filtered out
1225+ messages = captured_kwargs ["messages" ]
1226+ assert len (messages ) == 2
1227+ assert messages [0 ] == {"role" : "user" , "content" : "hello" }
1228+ assert messages [1 ] == {"role" : "user" , "content" : "world" }
1229+ finally :
1230+ await provider .terminate ()
1231+
1232+
1233+ @pytest .mark .asyncio
1234+ async def test_query_filters_null_content_assistant_message_without_tool_calls (
1235+ monkeypatch ,
1236+ ):
1237+ """Test that assistant messages with null content and no tool_calls are filtered."""
1238+ provider = _make_provider ()
1239+ try :
1240+ captured_kwargs = {}
1241+
1242+ async def fake_create (** kwargs ):
1243+ captured_kwargs .update (kwargs )
1244+ return ChatCompletion .model_validate (
1245+ {
1246+ "id" : "chatcmpl-test" ,
1247+ "object" : "chat.completion" ,
1248+ "created" : 0 ,
1249+ "model" : "gpt-4o-mini" ,
1250+ "choices" : [
1251+ {
1252+ "index" : 0 ,
1253+ "message" : {
1254+ "role" : "assistant" ,
1255+ "content" : "ok" ,
1256+ },
1257+ "finish_reason" : "stop" ,
1258+ }
1259+ ],
1260+ "usage" : {
1261+ "prompt_tokens" : 1 ,
1262+ "completion_tokens" : 1 ,
1263+ "total_tokens" : 2 ,
1264+ },
1265+ }
1266+ )
1267+
1268+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1269+
1270+ payloads = {
1271+ "model" : "gpt-4o-mini" ,
1272+ "messages" : [
1273+ {"role" : "user" , "content" : "hello" },
1274+ {"role" : "assistant" , "content" : None }, # Should be filtered
1275+ {"role" : "user" , "content" : "world" },
1276+ ],
1277+ }
1278+
1279+ await provider ._query (payloads = payloads , tools = None )
1280+
1281+ # The null content assistant message should be filtered out
1282+ messages = captured_kwargs ["messages" ]
1283+ assert len (messages ) == 2
1284+ assert messages [0 ] == {"role" : "user" , "content" : "hello" }
1285+ assert messages [1 ] == {"role" : "user" , "content" : "world" }
1286+ finally :
1287+ await provider .terminate ()
1288+
1289+
1290+ @pytest .mark .asyncio
1291+ async def test_query_converts_empty_content_to_none_with_tool_calls (monkeypatch ):
1292+ """Test that empty content with tool_calls is converted to None (OpenAI spec)."""
1293+ provider = _make_provider ()
1294+ try :
1295+ captured_kwargs = {}
1296+
1297+ async def fake_create (** kwargs ):
1298+ captured_kwargs .update (kwargs )
1299+ return ChatCompletion .model_validate (
1300+ {
1301+ "id" : "chatcmpl-test" ,
1302+ "object" : "chat.completion" ,
1303+ "created" : 0 ,
1304+ "model" : "gpt-4o-mini" ,
1305+ "choices" : [
1306+ {
1307+ "index" : 0 ,
1308+ "message" : {
1309+ "role" : "assistant" ,
1310+ "content" : "ok" ,
1311+ },
1312+ "finish_reason" : "stop" ,
1313+ }
1314+ ],
1315+ "usage" : {
1316+ "prompt_tokens" : 1 ,
1317+ "completion_tokens" : 1 ,
1318+ "total_tokens" : 2 ,
1319+ },
1320+ }
1321+ )
1322+
1323+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1324+
1325+ payloads = {
1326+ "model" : "gpt-4o-mini" ,
1327+ "messages" : [
1328+ {"role" : "user" , "content" : "hello" },
1329+ {
1330+ "role" : "assistant" ,
1331+ "content" : "" ,
1332+ "tool_calls" : [
1333+ {
1334+ "id" : "call-123" ,
1335+ "type" : "function" ,
1336+ "function" : {"name" : "test" , "arguments" : "{}" },
1337+ }
1338+ ],
1339+ },
1340+ {"role" : "user" , "content" : "world" },
1341+ ],
1342+ }
1343+
1344+ await provider ._query (payloads = payloads , tools = None )
1345+
1346+ # The assistant message with tool_calls should be kept but content set to None
1347+ messages = captured_kwargs ["messages" ]
1348+ assert len (messages ) == 3
1349+ assert messages [1 ]["role" ] == "assistant"
1350+ assert messages [1 ]["content" ] is None
1351+ assert messages [1 ]["tool_calls" ] is not None
1352+ finally :
1353+ await provider .terminate ()
1354+
1355+
1356+ @pytest .mark .asyncio
1357+ async def test_query_keeps_valid_assistant_message_with_content (monkeypatch ):
1358+ """Test that valid assistant messages with content are kept."""
1359+ provider = _make_provider ()
1360+ try :
1361+ captured_kwargs = {}
1362+
1363+ async def fake_create (** kwargs ):
1364+ captured_kwargs .update (kwargs )
1365+ return ChatCompletion .model_validate (
1366+ {
1367+ "id" : "chatcmpl-test" ,
1368+ "object" : "chat.completion" ,
1369+ "created" : 0 ,
1370+ "model" : "gpt-4o-mini" ,
1371+ "choices" : [
1372+ {
1373+ "index" : 0 ,
1374+ "message" : {
1375+ "role" : "assistant" ,
1376+ "content" : "ok" ,
1377+ },
1378+ "finish_reason" : "stop" ,
1379+ }
1380+ ],
1381+ "usage" : {
1382+ "prompt_tokens" : 1 ,
1383+ "completion_tokens" : 1 ,
1384+ "total_tokens" : 2 ,
1385+ },
1386+ }
1387+ )
1388+
1389+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1390+
1391+ payloads = {
1392+ "model" : "gpt-4o-mini" ,
1393+ "messages" : [
1394+ {"role" : "user" , "content" : "hello" },
1395+ {"role" : "assistant" , "content" : "response" },
1396+ {"role" : "user" , "content" : "world" },
1397+ ],
1398+ }
1399+
1400+ await provider ._query (payloads = payloads , tools = None )
1401+
1402+ # All messages should be kept
1403+ messages = captured_kwargs ["messages" ]
1404+ assert len (messages ) == 3
1405+ assert messages [1 ] == {"role" : "assistant" , "content" : "response" }
1406+ finally :
1407+ await provider .terminate ()
1408+
1409+
1410+ @pytest .mark .asyncio
1411+ async def test_query_keeps_assistant_message_with_tool_calls_and_none_content (
1412+ monkeypatch ,
1413+ ):
1414+ """Test that assistant messages with tool_calls and None content are kept."""
1415+ provider = _make_provider ()
1416+ try :
1417+ captured_kwargs = {}
1418+
1419+ async def fake_create (** kwargs ):
1420+ captured_kwargs .update (kwargs )
1421+ return ChatCompletion .model_validate (
1422+ {
1423+ "id" : "chatcmpl-test" ,
1424+ "object" : "chat.completion" ,
1425+ "created" : 0 ,
1426+ "model" : "gpt-4o-mini" ,
1427+ "choices" : [
1428+ {
1429+ "index" : 0 ,
1430+ "message" : {
1431+ "role" : "assistant" ,
1432+ "content" : "ok" ,
1433+ },
1434+ "finish_reason" : "stop" ,
1435+ }
1436+ ],
1437+ "usage" : {
1438+ "prompt_tokens" : 1 ,
1439+ "completion_tokens" : 1 ,
1440+ "total_tokens" : 2 ,
1441+ },
1442+ }
1443+ )
1444+
1445+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1446+
1447+ payloads = {
1448+ "model" : "gpt-4o-mini" ,
1449+ "messages" : [
1450+ {"role" : "user" , "content" : "hello" },
1451+ {
1452+ "role" : "assistant" ,
1453+ "content" : None ,
1454+ "tool_calls" : [
1455+ {
1456+ "id" : "call-123" ,
1457+ "type" : "function" ,
1458+ "function" : {"name" : "test" , "arguments" : "{}" },
1459+ }
1460+ ],
1461+ },
1462+ {"role" : "user" , "content" : "world" },
1463+ ],
1464+ }
1465+
1466+ await provider ._query (payloads = payloads , tools = None )
1467+
1468+ # The assistant message with tool_calls should be kept
1469+ messages = captured_kwargs ["messages" ]
1470+ assert len (messages ) == 3
1471+ assert messages [1 ]["role" ] == "assistant"
1472+ assert messages [1 ]["content" ] is None
1473+ assert messages [1 ]["tool_calls" ] is not None
1474+ finally :
1475+ await provider .terminate ()
1476+
1477+
1478+ @pytest .mark .asyncio
1479+ async def test_query_does_not_filter_user_or_system_messages (monkeypatch ):
1480+ """Test that user and system messages are not affected by the filter."""
1481+ provider = _make_provider ()
1482+ try :
1483+ captured_kwargs = {}
1484+
1485+ async def fake_create (** kwargs ):
1486+ captured_kwargs .update (kwargs )
1487+ return ChatCompletion .model_validate (
1488+ {
1489+ "id" : "chatcmpl-test" ,
1490+ "object" : "chat.completion" ,
1491+ "created" : 0 ,
1492+ "model" : "gpt-4o-mini" ,
1493+ "choices" : [
1494+ {
1495+ "index" : 0 ,
1496+ "message" : {
1497+ "role" : "assistant" ,
1498+ "content" : "ok" ,
1499+ },
1500+ "finish_reason" : "stop" ,
1501+ }
1502+ ],
1503+ "usage" : {
1504+ "prompt_tokens" : 1 ,
1505+ "completion_tokens" : 1 ,
1506+ "total_tokens" : 2 ,
1507+ },
1508+ }
1509+ )
1510+
1511+ monkeypatch .setattr (provider .client .chat .completions , "create" , fake_create )
1512+
1513+ payloads = {
1514+ "model" : "gpt-4o-mini" ,
1515+ "messages" : [
1516+ {"role" : "system" , "content" : "" }, # Empty system message
1517+ {"role" : "user" , "content" : "" }, # Empty user message
1518+ {"role" : "assistant" , "content" : "" }, # Should be filtered
1519+ {"role" : "user" , "content" : "hello" },
1520+ ],
1521+ }
1522+
1523+ await provider ._query (payloads = payloads , tools = None )
1524+
1525+ # Only assistant message should be filtered
1526+ messages = captured_kwargs ["messages" ]
1527+ assert len (messages ) == 3
1528+ assert messages [0 ] == {"role" : "system" , "content" : "" }
1529+ assert messages [1 ] == {"role" : "user" , "content" : "" }
1530+ assert messages [2 ] == {"role" : "user" , "content" : "hello" }
1531+ finally :
1532+ await provider .terminate ()
0 commit comments