Why parallel tool calls merge in an OpenAI-compatible stream.

Check whether the stream and client disagree about the index for each tool call.

If a streaming agent turns two tool calls into one malformed call—or runs neither—check the raw tool_calls[].index values before changing the prompt or model. A client that groups chunks by this field can merge calls when it is absent and the parser defaults every call to index 0.

Distinguish the two index fields

Chat Completions has an outer choices[].index for the selected response choice and a separate delta.tool_calls[].index for each tool call. They identify different things. The OpenAI streaming reference defines the tool-call index inside each tool_calls entry; it is the value a parser can use to accumulate that call's partial name and arguments.

The cited report shows one client keying partial calls by index and defaulting a missing index to zero. If your client does the same, distinct IDs may not prevent both argument strings from being appended to one slot. A client that groups by ID may behave differently, so inspect the exact SDK or framework that consumes the stream.

Compare the original stream

Capture the complete SSE body rather than only printing delta.content. In the excerpt below, choices[0].index is present, but each nested tool call has an ID and no index. The IDs and arguments are fictional; this short example illustrates the missing field and is not a live provider response.

data: {"choices":[{"index":0,"delta":{"tool_calls":[{"id":"call_a","type":"function","function":{"name":"lookup","arguments":"{\"city\":\"Paris\"}"}}]},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{"tool_calls":[{"id":"call_b","type":"function","function":{"name":"lookup","arguments":"{\"city\":\"Tokyo\"}"}}]},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

For the same request, compare a streamed response with stream: false. If the non-streaming response contains two separate calls but the streamed client produces one concatenated argument string, the difference localizes the failure to the streaming compatibility path. It does not by itself prove whether the server response or client adapter is responsible.

Check the client path

  1. Keep the response status, model label, client and framework versions, whether stream was enabled, and the complete tool-call deltas.
  2. Compare the outer choice index, every tool-call ID, every nested tool-call index, function name and accumulated arguments.
  3. Check the final finish_reason. The OpenAI reference lists tool_calls when a tool was called; one current compatibility report also records stop on a response that contains tool calls.
  4. Try one tool at a time, or disable streaming for that request if the client supports it. If the problem disappears, keep the test result with the report; these workarounds do not identify which layer needs a fix.

Update the affected client or adapter when a fix is available. Do not infer that the model lacks tool support from a parser failure, and do not blindly replay a call when the first attempt may already have triggered a side effect.

Check repeated indexes too

A different failure can occur when one SSE event contains multiple partial entries with the same tool-call index. The OpenAI Python SDK issue below records an accumulator that kept the first indexed list unchanged, then appended later fragments to only one physical list position. Its maintainers merged a fix in PR #3425 on 10 September 2026. Check whether the installed SDK release includes that fix before attributing the malformed arguments to a model or endpoint.

The local tool-call SSE inspector groups the pasted fragments by index and flags missing or repeated indexes. It does not execute functions or send the capture anywhere.

When the function name arrives later

A partial tool-call delta can contain an ID and argument fragments before function.name appears. In one Vercel AI issue, the client rejected the early delta with “Expected 'function.name' to be a string” before a later delta supplied the name. The issue was closed with a maintainer fix that buffers compatible-provider tool-call deltas until the name arrives; it is one reported client case, not evidence that every SDK fails this way.

Check the complete stream before classifying the call. The local inspector assembles a late name by index and flags it so you can check whether your SDK validates too early. If the capture reaches finish_reason or [DONE] without any function name, the call cannot be identified; if the capture is still partial, the name may simply not have arrived yet.

One reported compatibility case

An Agno report opened on 19 October 2026 describes OpenAIChat.parse_tool_calls defaulting an absent index to zero. It reports concatenated arguments for parallel calls from a Gemini OpenAI-compatible endpoint and a final finish_reason: "stop". This is a concrete report for one client path, not evidence that every Gemini model, provider or OpenAI-compatible client behaves this way.

Google's compatibility documentation shows streaming and function-calling examples for its OpenAI-compatible endpoint. Those examples establish that both features are documented; they do not claim that every client/framework combination has identical streaming behavior. Reproduce the issue against the exact model, endpoint and library versions you use.

Sources and next steps

To share the issue with a provider, redact keys and private prompt data from the capture, then use the provider support report checklist. For other stream endings, see stream stalls and missing finish reasons.

Sources checked 22 October 2026. No API key or live model request was used.

Exact symptom wording and source

Use the quoted text to locate the relevant check. Providers and clients can phrase the same failure differently; compare the complete response before applying a fix.

  • Reported text: Parallel tool_calls merged; arguments concatenated

    One reported parser path defaults a missing tool_calls[].index to zero. This report does not establish how other clients or models behave.

    every call goes into slot 0: the arguments are concatenated

    Agno #11148: open client report (19 October 2026)