Streaming tool calls and fine-grained tool streaming
Status: DOCUMENTED · LIVE_VERIFIED 2026-09-18 (b1 buffered, b2 eager_input_streaming:true, b3 legacy header; claude-haiku-4-5-20251001).
Sources: Fine-grained tool streaming · Streaming messages · Server tools — streaming.
Last verified: 2026-09-18.
Event sequence for a tool_use block (live b1, tool_choice: any)
message_start usage.input_tokens=676
content_block_start [0] {"type":"tool_use","id":"toolu_01VJ5…","name":"get_weather","input":{},"caller":{"type":"direct"}}
ping
content_block_delta [0] {"type":"input_json_delta","partial_json":""}
content_block_delta [0] {"type":"input_json_delta","partial_json":"{\"locat"}
content_block_delta [0] {"type":"input_json_delta","partial_json":"ion\": \""}
content_block_delta [0] {"type":"input_json_delta","partial_json":"San Fra"}
content_block_delta [0] {"type":"input_json_delta","partial_json":"ncisco,"}
content_block_delta [0] {"type":"input_json_delta","partial_json":" CA\"}"}
content_block_stop [0]
message_delta {"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"container":null} usage.output_tokens=41
message_stopAccumulate partial_json strings and json.loads at content_block_stop. input: {} in content_block_start is a placeholder. message_delta.delta carries stop_details and container fields (live; container is set when code execution ran).
Fine-grained (eager) streaming
Set "eager_input_streaming": true on a custom tool (per tool; false keeps buffered streaming even when the legacy header is present). No beta header needed since 2026-02-05; the legacy anthropic-beta: fine-grained-tool-streaming-2025-05-14 still works and enables it for tools that leave the field unset (b3 produced the same fragments as b2). The header is rejected together with computer_toolset_20260801 / browser_toolset_20260801 entries.
| Mode | Fragments seen live (same prompt) | Guarantees |
|---|---|---|
| buffered (default) | 6 short chunks, each key/value validated before emission; delays between events | accumulated string is valid JSON |
| eager | 4 longer chunks ({"location": "San, Francisco, CA, "}) |
no server-side buffering or validation → may be partial/invalid JSON, especially at stop_reason: max_tokens; guard the parse and return {"INVALID_JSON": raw} as an is_error tool_result |
Server tools stream the same way: server_tool_use gets input_json_deltas, the *_tool_result block arrives whole in a single content_block_start (no deltas; pause while the tool runs, ping keepalives ~30 s). Toolset members always arrive as one complete input_json_delta.
Examples: examples/anthropic/tools/fine-grained-streaming/basic.{sh,py}. Test: tests/anthropic/test_tools.py::test_streaming_input_json_delta.