Skip to content

Commit 396ab80

Browse files
Fix index field not populated in streaming mode with n>1 and tool calls (#15962)
* fix index tool calling in streaming * moved test to llm translation --------- Co-authored-by: Ishaan Jaffer <[email protected]>
1 parent 07d2a27 commit 396ab80

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

litellm/litellm_core_utils/streaming_handler.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,14 @@ def is_chunk_non_empty(
734734
"function_call" in completion_obj
735735
and completion_obj["function_call"] is not None
736736
)
737+
or (
738+
"tool_calls" in model_response.choices[0].delta
739+
and model_response.choices[0].delta["tool_calls"] is not None
740+
)
741+
or (
742+
"function_call" in model_response.choices[0].delta
743+
and model_response.choices[0].delta["function_call"] is not None
744+
)
737745
or (
738746
"reasoning_content" in model_response.choices[0].delta
739747
and model_response.choices[0].delta.reasoning_content is not None

tests/llm_translation/test_openai.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)