When the model decides to call tools during streaming, you receive tool call fragments via delta.tool_calls. These must be accumulated by index before execution.
Key insight: tool call arguments arrive as string fragments. You must buffer them by index and parse the complete JSON only after finish_reason indicates "tool_calls".
Why fragments and not whole arguments
The model generates the JSON one token at a time. Each token becomes a chunk. So you'll see arguments arrive as {"loc, then ation":", then Seoul"} — three fragments that together form valid JSON. json.loads on any single fragment crashes; json.loads on the concatenation succeeds.
The accumulator pattern is: keep an arguments_str per tool_call_id, append every fragment to it, and only parse when the stream signals tool-call completion (or at the end of the stream). The SDK abstracts this; in raw httpx you write the accumulator yourself.