Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/providers/anthropic.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,29 @@ foreach ($stream as $event) {
}
```

### Fine-grained tool streaming

Anthropic’s [fine-grained tool streaming](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/fine-grained-tool-streaming) lets tool-use input arrive incrementally (with lower latency for large arguments) instead of waiting for a complete JSON payload. Enable it on each tool that should use this behavior with the `eager_input_streaming` provider option:

```php
use Prism\Prism\Facades\Prism;
use Prism\Prism\Tool;

$weatherTool = Tool::as('get_weather')
->for('Get current weather for a location')
->withStringParameter('location', 'The city and state')
->withProviderOptions(['eager_input_streaming' => true])
->using(fn (string $location): string => "Weather in {$location}: 72°F, sunny");

$stream = Prism::text()
->using('anthropic', 'claude-sonnet-4-5-20250929')
->withTools([$weatherTool])
->withPrompt('What is the weather in San Francisco?')
->asStream();
```

Use this together with a streaming entrypoint (`asStream()`, `asEventStreamResponse()`, etc.). While the model is still emitting a tool call, streamed input may be partial JSON; only treat tool arguments as final once the stream indicates the tool input is complete.

For complete streaming documentation including Vercel Data Protocol and WebSocket broadcasting, see [Streaming Output](/core-concepts/streaming-output).

## Documents
Expand Down
1 change: 1 addition & 0 deletions src/Providers/Anthropic/Maps/ToolMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static function map(array $tools): array
],
'cache_control' => self::normalizeCacheControl($tool),
'strict' => (bool) $tool->providerOptions('strict'),
'eager_input_streaming' => (bool) $tool->providerOptions('eager_input_streaming'),
]);
}, $tools);
}
Expand Down
42 changes: 42 additions & 0 deletions tests/Providers/Anthropic/ToolMapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,45 @@
'ephemeral',
AnthropicCacheType::Ephemeral->value,
]);

it('sets eager_input_streaming when eager_input_streaming provider option is true on tool', function (): void {
$tool = (new Tool)
->as('search')
->for('Searching the web')
->withStringParameter('query', 'the detailed search query')
->using(fn (): string => '[Search results]')
->withProviderOptions(['eager_input_streaming' => true]);

expect(ToolMap::map([$tool]))->toBe([[
'name' => 'search',
'description' => 'Searching the web',
'input_schema' => [
'type' => 'object',
'properties' => [
'query' => [
'description' => 'the detailed search query',
'type' => 'string',
],
],
'required' => ['query'],
],
'eager_input_streaming' => true,
]]);
});

it('omits eager_input_streaming when eager_input_streaming provider option is omitted or false', function (?bool $eagerInputStreaming): void {
$tool = (new Tool)
->as('search')
->for('Searching the web')
->withStringParameter('query', 'the detailed search query')
->using(fn (): string => '[Search results]');

if ($eagerInputStreaming !== null) {
$tool = $tool->withProviderOptions(['eager_input_streaming' => $eagerInputStreaming]);
}

expect(ToolMap::map([$tool])[0])->not->toHaveKey('eager_input_streaming');
})->with([
'omitted' => [null],
'false' => [false],
]);