---
title: Streams
description: Understand stream events and aggregation.
type: conceptual
summary: Learn how provider events become an aggregated assistant message.
---

# Streams



`ai.stream` turns provider events into a stateful `Stream` object. Iteration
returns events; the stream also keeps the in-progress assistant message.

## Stream lifecycle

```python
async with ai.stream(model, messages, tools=tools) as stream:
    async for event in stream:
        ...

message = stream.message
```

On entry, the framework prepares message history and creates a provider request.
On exit, it closes the underlying async generator.

## Event aggregation

`Stream.__anext__` receives one provider event, updates `stream.message`, and
then returns a copy of the event with the current message attached.

```python
async for event in stream:
    print(event.kind, event.message.text)
```

## Text and reasoning blocks

Text and reasoning use start, delta, and end events. Deltas append to the part
with the same block ID:

```python
if isinstance(event, ai.events.TextDelta):
    print(event.chunk, end="", flush=True)
elif isinstance(event, ai.events.ReasoningDelta):
    record_reasoning(event.chunk)
```

## Tool-call blocks

Function tool calls stream as `ToolStart`, `ToolDelta`, and `ToolEnd`.
`ToolEnd.tool_call` is the complete `ToolCallPart`:

```python
if isinstance(event, ai.events.ToolEnd):
    call = event.tool_call
    print(call.tool_name, call.tool_args)
```

## Built-in tool blocks

Provider-executed tools use separate built-in events. The host does not execute
these calls:

```python
if isinstance(event, ai.events.BuiltinToolResult):
    print(event.result.tool_name, event.result.result)
```

## File events

Generated files arrive as `FileEvent` and become `FilePart` values on the
assistant message:

```python
if isinstance(event, ai.events.FileEvent):
    print(event.media_type, event.filename)
```

## Usage and provider metadata

Usage and provider metadata are latest-wins fields. Events may carry them, and
the stream copies usage onto the assistant message:

```python
if event.usage is not None:
    print(event.usage)
```

## Replay streams

When the last message has `replay=True`, `ai.stream` does not call the provider.
It emits synthetic replay tool-end events from the existing assistant message
so resume flows can dispatch the same tool calls again.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)