@@ -1308,3 +1308,107 @@ def test_openai_gpt_5_codex_reasoning():
13081308 print ("response: " , response )
13091309 for chunk in response :
13101310 print ("chunk: " , chunk )
1311+
1312+
1313+ # Tests moved from test_streaming_n_with_tools.py
1314+ # Regression test for: https://github.com/BerriAI/litellm/issues/8977
1315+ @pytest .mark .parametrize ("model" , ["gpt-4o" , "gpt-4-turbo" ])
1316+ @pytest .mark .asyncio
1317+ async def test_streaming_tool_calls_with_n_greater_than_1 (model ):
1318+ """
1319+ Test that the index field in a choice object is correctly populated
1320+ when using streaming mode with n>1 and tool calls.
1321+
1322+ Regression test for: https://github.com/BerriAI/litellm/issues/8977
1323+ """
1324+ tools = [
1325+ {
1326+ "type" : "function" ,
1327+ "function" : {
1328+ "strict" : True ,
1329+ "name" : "get_current_weather" ,
1330+ "description" : "Get the current weather in a given location" ,
1331+ "parameters" : {
1332+ "type" : "object" ,
1333+ "properties" : {
1334+ "location" : {
1335+ "type" : "string" ,
1336+ "description" : "The city and state, e.g. San Francisco, CA" ,
1337+ },
1338+ "unit" : {
1339+ "type" : "string" ,
1340+ "enum" : ["celsius" , "fahrenheit" ],
1341+ },
1342+ },
1343+ "required" : ["location" , "unit" ],
1344+ "additionalProperties" : False ,
1345+ },
1346+ },
1347+ }
1348+ ]
1349+
1350+ response = litellm .completion (
1351+ model = model ,
1352+ messages = [
1353+ {
1354+ "role" : "user" ,
1355+ "content" : "What is the weather in San Francisco?" ,
1356+ },
1357+ ],
1358+ tools = tools ,
1359+ stream = True ,
1360+ n = 3 ,
1361+ )
1362+
1363+ # Collect all chunks and their indices
1364+ indices_seen = []
1365+ for chunk in response :
1366+ assert len (chunk .choices ) == 1 , "Each streaming chunk should have exactly 1 choice"
1367+ assert hasattr (chunk .choices [0 ], "index" ), "Choice should have an index attribute"
1368+ index = chunk .choices [0 ].index
1369+ indices_seen .append (index )
1370+
1371+ # Verify that we got chunks with different indices (0, 1, 2 for n=3)
1372+ unique_indices = set (indices_seen )
1373+ assert unique_indices == {0 , 1 , 2 }, f"Should have indices 0, 1, 2 for n=3, got { unique_indices } "
1374+
1375+ print (f"✓ Test passed: streaming with n=3 and tool calls correctly populates index field" )
1376+ print (f" Indices seen: { indices_seen } " )
1377+ print (f" Unique indices: { unique_indices } " )
1378+
1379+
1380+ @pytest .mark .parametrize ("model" , ["gpt-4o" ])
1381+ @pytest .mark .asyncio
1382+ async def test_streaming_content_with_n_greater_than_1 (model ):
1383+ """
1384+ Test that the index field is correctly populated for regular content streaming
1385+ (not tool calls) with n>1.
1386+ """
1387+ response = litellm .completion (
1388+ model = model ,
1389+ messages = [
1390+ {
1391+ "role" : "user" ,
1392+ "content" : "Say hello in one word" ,
1393+ },
1394+ ],
1395+ stream = True ,
1396+ n = 2 ,
1397+ max_tokens = 10 ,
1398+ )
1399+
1400+ # Collect all chunks and their indices
1401+ indices_seen = []
1402+ for chunk in response :
1403+ assert len (chunk .choices ) == 1 , "Each streaming chunk should have exactly 1 choice"
1404+ assert hasattr (chunk .choices [0 ], "index" ), "Choice should have an index attribute"
1405+ index = chunk .choices [0 ].index
1406+ indices_seen .append (index )
1407+
1408+ # Verify that we got chunks with different indices (0, 1 for n=2)
1409+ unique_indices = set (indices_seen )
1410+ assert unique_indices == {0 , 1 }, f"Should have indices 0, 1 for n=2, got { unique_indices } "
1411+
1412+ print (f"✓ Test passed: streaming with n=2 and regular content correctly populates index field" )
1413+ print (f" Indices seen: { indices_seen } " )
1414+ print (f" Unique indices: { unique_indices } " )
0 commit comments