Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/phpaisdk/core/llms.txt

Use this file to discover all available pages before exploring further.

Stream wraps the provider’s raw streaming generator and gives you three ways to consume a streaming text generation: iterate text chunks directly, inspect every typed stream part via parts(), or register lifecycle hooks that fire automatically as the stream is drained. All three approaches accumulate state internally, so calling result() or run() after any of them returns a fully-populated TextResult.

Methods

chunks()
Generator<int, string>
Yields each text delta string as it arrives from the model. Only TextDeltaPart values are surfaced; all other part types (tool calls, finish signals, metadata) are silently consumed and accumulated in the internal StreamState. Use this when you only need the raw text and do not care about other event types.
parts()
Generator<int, StreamPart>
Yields every StreamPart emitted by the provider, including text deltas, tool call events, reasoning deltas, finish signals, and provider metadata. This is the most detailed consumption path. If an ErrorPart arrives and no onError hook is registered, the wrapped Throwable is re-thrown from inside the generator.
getIterator()
Generator<int, StreamPart>
Implements IteratorAggregate, delegating to parts(). Allows the Stream object to be used directly in a foreach loop without calling parts() explicitly.
run()
TextResult
Drains the entire stream — consuming all parts, firing all registered hooks — and returns the accumulated TextResult. Use this when you want the complete result without manually iterating.
result()
TextResult
Returns the TextResult assembled from the accumulated StreamState. Call this after you have already iterated the stream (via chunks(), parts(), or foreach). Calling result() before the stream is fully consumed will return a partial result.
onChunk(callable $callback)
self
Registers a callback that is invoked for each text delta as it arrives. The callback receives the delta string: fn(string $text): void. Returns $this for fluent chaining.
callback
callable
required
A callable with the signature fn(string $text): void. Receives the incremental text string from each TextDeltaPart.
onFinish(callable $callback)
self
Registers a callback that is invoked once after the stream fully completes and all parts have been consumed. The callback receives the final TextResult: fn(TextResult $result): void. Returns $this for fluent chaining.
callback
callable
required
A callable with the signature fn(TextResult $result): void.
onError(callable $callback)
self
Registers a callback that is invoked when an ErrorPart arrives in the stream. The callback receives the wrapped Throwable: fn(Throwable $e): void. When this hook is registered, the error is consumed and the stream continues; without it, the exception is re-thrown. Returns $this for fluent chaining.
callback
callable
required
A callable with the signature fn(Throwable $e): void.
onToolCall(callable $callback)
self
Registers a callback that is invoked once after the stream finishes for each fully assembled ToolCall. Tool call argument fragments are accumulated across ToolCallStartPart and ToolCallDeltaPart events and decoded into complete ToolCall objects before the callback fires. Returns $this for fluent chaining.
callback
callable
required
A callable with the signature fn(ToolCall $call): void.

Stream part types

Every value yielded by parts() is an instance of StreamPart. Use instanceof checks to branch on the specific type.
TextDeltaPart
StreamPart
Carries an incremental fragment of the model’s text output.
ReasoningDeltaPart
StreamPart
Carries an incremental fragment of the model’s internal reasoning or extended thinking text. Only emitted by reasoning-capable models when reasoning is enabled.
ToolCallStartPart
StreamPart
Emitted once when a streamed tool call begins. Carries the slot index and the tool’s identity. Argument JSON arrives in subsequent ToolCallDeltaPart events tied to the same index.
ToolCallDeltaPart
StreamPart
A fragment of a streamed tool call’s argument JSON. Fragments are accumulated by StreamState per slot index and decoded once the stream finishes, ensuring valid JSON even when the provider splits argument data across many events.
FinishPart
StreamPart
Emitted at the end of the stream to signal that generation is complete. Carries the final finish reason and token usage for the turn.
ErrorPart
StreamPart
Wraps a Throwable that occurred during streaming. If an onError hook is registered, the hook is called and the stream continues; otherwise the exception is re-thrown from the generator.
ProviderMetadataPart
StreamPart
Carries provider-specific metadata emitted during the stream. Multiple parts may arrive for the same provider; StreamState merges them by provider key using array_merge.

Code examples

Iterating text chunks

$stream = $ai->stream('Write a haiku about lazy evaluation.');

foreach ($stream->chunks() as $chunk) {
    echo $chunk;
    flush();
}

// Access the full result after iteration
$result = $stream->result();
echo "\n\nFinish reason: " . $result->finishReason->value;
echo "\nTotal tokens: " . $result->usage->totalTokens;

Inspecting all stream parts

use AiSdk\Streaming\TextDeltaPart;
use AiSdk\Streaming\ToolCallStartPart;
use AiSdk\Streaming\ToolCallDeltaPart;
use AiSdk\Streaming\ReasoningDeltaPart;
use AiSdk\Streaming\FinishPart;
use AiSdk\Streaming\ErrorPart;
use AiSdk\Streaming\ProviderMetadataPart;

$stream = $ai->stream('What is the weather in Dublin?');

foreach ($stream->parts() as $part) {
    if ($part instanceof TextDeltaPart) {
        echo $part->text;
        flush();
    } elseif ($part instanceof ReasoningDeltaPart) {
        // Reasoning-capable models only
        error_log('[reasoning] ' . $part->text);
    } elseif ($part instanceof ToolCallStartPart) {
        echo "\n[Tool call starting: {$part->name} (id: {$part->id})]\n";
    } elseif ($part instanceof ToolCallDeltaPart) {
        // Argument fragments accumulate in StreamState automatically
    } elseif ($part instanceof FinishPart) {
        echo "\n[Finished: {$part->reason->value}, tokens: {$part->usage->totalTokens}]\n";
    } elseif ($part instanceof ProviderMetadataPart) {
        error_log('[metadata:' . $part->provider . '] ' . json_encode($part->metadata));
    }
}

Using lifecycle hooks

use AiSdk\ToolCall;

$result = $ai->stream('Summarise the top PHP news this week.')
    ->onChunk(function (string $text): void {
        echo $text;
        flush();
    })
    ->onToolCall(function (ToolCall $call): void {
        echo "\n[Tool executed: {$call->name}]\n";
    })
    ->onError(function (Throwable $e): void {
        error_log('Stream error: ' . $e->getMessage());
    })
    ->onFinish(function (\AiSdk\Results\TextResult $result): void {
        error_log(sprintf(
            'Stream finished. Tokens: %d in / %d out.',
            $result->usage->inputTokens,
            $result->usage->outputTokens,
        ));
    })
    ->run(); // Drains the stream and fires all hooks

echo "\n\nFull text:\n" . $result->text;

Build docs developers (and LLMs) love