Page history
fine-grained-tool-streaming
agents-and-tools/tool-use/fine-grained-tool-streaming
History
agents-and-tools/tool-use/fine-grained-tool-streaming Changed · +1 / -1 lines
from line 18
All models support fine-grained tool streaming on the Claude API, [Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-in-amazon-bedrock), [Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](https://platform.claude.com/docs/en/build-with-claude/claude-in-microsoft-foundry). To use it, set `eager_input_streaming` to `true` on any user-defined tool where you want fine-grained streaming enabled, and enable streaming on your request. -The `eager_input_streaming` field is optional. Setting it to `true` turns on fine-grained streaming for that tool, and omitting it gives you standard buffered streaming, in which the API buffers and validates each parameter value before streaming it back. The exception is a request that still sends the legacy `fine-grained-tool-streaming-2025-05-14` beta header, which turns fine-grained streaming on for tools that leave the field unset. The per-tool field replaces that header, and an explicit `false` keeps buffered streaming for a tool even when a request still sends it. See [Tool reference](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference) for the field definition. +The `eager_input_streaming` field is optional. Setting it to `true` turns on fine-grained streaming for that tool, and omitting it gives you standard buffered streaming, in which the API buffers and validates each parameter value before streaming it back. The exception is a request that still sends the legacy `fine-grained-tool-streaming-2025-05-14` beta header, which turns fine-grained streaming on for tools that leave the field unset. The per-tool field replaces that header, and an explicit `false` keeps buffered streaming for a tool even when a request still sends it. The legacy header cannot be combined with a [computer use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool) or [browser use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/browser-use-tool) toolset entry: the API rejects a request that sends both, so remove the header and set `eager_input_streaming` on the user-defined tools that need it. See [Tool reference](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference) for the field definition. The following example turns on fine-grained streaming for a `make_file` tool and asks Claude for a long poem, so the tool input is large enough to watch it stream in:
agents-and-tools/tool-use/fine-grained-tool-streaming First recorded · 928 lines, first recorded
## How to use fine-grained tool streaming ## Accumulating tool input deltas ## Handling invalid JSON in tool responses ## Next steps
The first capture of this source. The page was already there, and this is what it said.
---
title: Fine-grained tool streaming
url: https://platform.claude.com/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming
description: Stream tool inputs without server-side JSON buffering for latency-sensitive applications.
---
<Note>
For how zero data retention (ZDR) applies to this feature, see [API and data retention](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention).
</Note>
Fine-grained tool streaming delivers a tool's input to your client as Claude generates it, without server-side buffering or JSON validation. Skipping the buffering step reduces the time to the first fragment of a large parameter, such as a document or a block of code, and the fragments arrive through the same [Streaming messages](https://platform.claude.com/docs/en/build-with-claude/streaming) events as standard tool use.
<Warning>
Because the API does not buffer or validate a tool's input before streaming it, you might receive partial or invalid JSON. A response that ends with the [stop reason](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons) `max_tokens` can also cut a parameter off midway. Accumulate the fragments, guard the parse, and see [Handling invalid JSON in tool responses](https://platform.claude.com/docs/en/agents-and-tools/tool-use/fine-grained-tool-streaming#handling-invalid-json-in-tool-responses) for how to return unparseable input to Claude.
</Warning>
## How to use fine-grained tool streaming
All models support fine-grained tool streaming on the Claude API, [Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-in-amazon-bedrock), [Claude Platform on AWS](https://platform.claude.com/docs/en/build-with-claude/claude-platform-on-aws), [Google Cloud](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai), and [Microsoft Foundry](https://platform.claude.com/docs/en/build-with-claude/claude-in-microsoft-foundry). To use it, set `eager_input_streaming` to `true` on any user-defined tool where you want fine-grained streaming enabled, and enable streaming on your request.
The `eager_input_streaming` field is optional. Setting it to `true` turns on fine-grained streaming for that tool, and omitting it gives you standard buffered streaming, in which the API buffers and validates each parameter value before streaming it back. The exception is a request that still sends the legacy `fine-grained-tool-streaming-2025-05-14` beta header, which turns fine-grained streaming on for tools that leave the field unset. The per-tool field replaces that header, and an explicit `false` keeps buffered streaming for a tool even when a request still sends it. See [Tool reference](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference) for the field definition.
The following example turns on fine-grained streaming for a `make_file` tool and asks Claude for a long poem, so the tool input is large enough to watch it stream in:
<CodeGroup>
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5",
"max_tokens": 65536,
"tools": [
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": true,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to"
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file"
}
},
"required": ["filename", "lines_of_text"]
}
}
],
"messages": [
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?"
}
],
"stream": true
}'
```
```bash CLI
ant messages create --stream --format jsonl <<'YAML' |
model: claude-opus-5
max_tokens: 65536
tools:
- name: make_file
description: Write text to a file
eager_input_streaming: true
input_schema:
type: object
properties:
filename:
type: string
description: The filename to write text to
lines_of_text:
type: array
description: An array of lines of text to write to the file
required:
- filename
- lines_of_text
messages:
- role: user
content: Can you write a long poem and make a file called poem.txt?
YAML
jq -rj 'select(.delta.type == "input_json_delta") | .delta.partial_json'
```
```python Python
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=65536,
model="claude-opus-5",
tools=[
{
"name": "make_file",
"description": "Write text to a file",
"eager_input_streaming": True,
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "The filename to write text to",
},
"lines_of_text": {
"type": "array",
"description": "An array of lines of text to write to the file",
},
},
"required": ["filename", "lines_of_text"],
},
}
],
messages=[
{
"role": "user",
"content": "Can you write a long poem and make a file called poem.txt?",
}
],
) as stream:
for event in stream:
if event.type == "input_json":
print(event.partial_json, end="", flush=True)
final_message = stream.get_final_message()
print()
for block in final_message.content:
if block.type == "tool_use":
print(f"Complete tool input: {block.input}")
```
```typescript TypeScript
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-5",
max_tokens: 65536,
tools: [
{
name: "make_file",
description: "Write text to a file",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {
filename: {
type: "string",
description: "The filename to write text to"
},
lines_of_text: {
type: "array",
description: "An array of lines of text to write to the file"
}
},
required: ["filename", "lines_of_text"]
}
}
],
messages: [
{
role: "user",
content: "Can you write a long poem and make a file called poem.txt?"
}
]
});
stream.on("inputJson", (partialJson) => {
process.stdout.write(partialJson);
});
const message = await stream.finalMessage();
console.log();
for (const block of message.content) {
if (block.type === "tool_use") {
console.log("Complete tool input:", block.input);
}
}
```
```csharp C#
AnthropicClient client = new();
MessageCreateParams parameters = new()
{
Model = Model.ClaudeOpus5,
MaxTokens = 65536,
Tools =
[
new Tool
{
Name = "make_file",
Description = "Write text to a file",
EagerInputStreaming = true,
InputSchema = new InputSchema
{
Properties = new Dictionary<string, JsonElement>
{
["filename"] = JsonSerializer.SerializeToElement(
new { type = "string", description = "The filename to write text to" }
),
["lines_of_text"] = JsonSerializer.SerializeToElement(
new { type = "array", description = "An array of lines of text to write to the file" }
),
},
Required = ["filename", "lines_of_text"],
},
},
],
Messages =
[
new()
{
Role = Role.User,
Content = "Can you write a long poem and make a file called poem.txt?",
},
],
};
// The C# example assembles the input itself: content block index -> accumulated JSON
var toolInputs = new Dictionary<long, StringBuilder>();
await foreach (var streamEvent in client.Messages.CreateStreaming(parameters))
{
if (
streamEvent.TryPickContentBlockStart(out var start)
&& start.ContentBlock.TryPickToolUse(out _)
)
{
toolInputs[start.Index] = new StringBuilder();
}
else if (
streamEvent.TryPickContentBlockDelta(out var delta)
&& delta.Delta.TryPickInputJson(out var inputJson)
)
{
Console.Write(inputJson.PartialJson);
toolInputs[delta.Index].Append(inputJson.PartialJson);
}
}
Console.WriteLine();
foreach (var accumulatedInput in toolInputs.Values)
{
Console.WriteLine($"Complete tool input: {accumulatedInput}");
}
```
```go Go
client := anthropic.NewClient()
makeFileTool := anthropic.ToolParam{
Name: "make_file",
Description: anthropic.String("Write text to a file"),
EagerInputStreaming: anthropic.Bool(true),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"filename": map[string]any{
"type": "string",
"description": "The filename to write text to",
},
"lines_of_text": map[string]any{
"type": "array",
"description": "An array of lines of text to write to the file",
},
},
Required: []string{"filename", "lines_of_text"},
},
}
stream := client.Messages.NewStreaming(context.Background(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 65536,
Tools: []anthropic.ToolUnionParam{{OfTool: &makeFileTool}},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock(
"Can you write a long poem and make a file called poem.txt?",
)),
},
})
message := anthropic.Message{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
panic(err)
}
if delta, ok := event.AsAny().(anthropic.ContentBlockDeltaEvent); ok {
if inputJSON, ok := delta.Delta.AsAny().(anthropic.InputJSONDelta); ok {
fmt.Print(inputJSON.PartialJSON)
}
}
}
if err := stream.Err(); err != nil {
panic(err)
}
Cut at 300 lines.