---
title: Consuming Streams
description: Print LLM chunks, observe tool events, and read final results from CrewAI streams.
icon: square-terminal
mode: "wide"
---

## Overview

Use this guide when you want to subscribe to a CrewAI stream and print or route frames as they arrive.

The basic pattern is:

```python
stream = flow.stream_events(inputs={"topic": "AI agents"})

with stream:
    for frame in stream:
        ...

result = stream.result
```

Always consume the stream before reading `stream.result`.

## Print LLM Output

If you only care about text generated by LLM calls, subscribe to the `llm` projection and print `frame.content`:

```python
stream = flow.stream_events(inputs={"topic": "AI agents"})

with stream:
    for frame in stream.llm:
        print(frame.content, end="", flush=True)

print()
result = stream.result
```

`frame.content` is an empty string for frames that do not carry printable text, so this is also safe:

```python
with flow.stream_events(inputs={"topic": "AI agents"}) as stream:
    for frame in stream.events:
        if frame.channel == "llm" and frame.content:
            print(frame.content, end="", flush=True)

result = stream.result
```

## Print Tool Activity

Tool events arrive on the `tools` channel. Use `frame.type` to distinguish starts, finishes, and errors.

```python
with flow.stream_events(inputs={"topic": "AI agents"}) as stream:
    for frame in stream.events:
        if frame.channel == "llm" and frame.content:
            print(frame.content, end="", flush=True)

        if frame.channel == "tools" and frame.type == "tool_usage_started":
            print(f"\nTool started: {frame.event.get('tool_name')}")

        if frame.channel == "tools" and frame.type == "tool_usage_finished":
            print(f"\nTool finished: {frame.event.get('tool_name')}")

result = stream.result
```

`frame.event` is the structured payload for the source event. Use it for metadata such as tool names, arguments, message roles, and runtime identifiers.

## Watch Flow Progress

Flow lifecycle and method execution frames arrive on the `flow` channel:

```python
with flow.stream_events(inputs={"topic": "AI agents"}) as stream:
    for frame in stream.flow:
        print(frame.type, frame.namespace)

result = stream.result
```

Use this when you want a progress log instead of token-level output.

## Interleave Selected Channels

Use `interleave()` when you want a subset of channels while preserving their relative order:

```python
with flow.stream_events(inputs={"topic": "AI agents"}) as stream:
    for frame in stream.interleave(["llm", "tools"]):
        if frame.channel == "llm":
            print(frame.content, end="", flush=True)
        elif frame.type == "tool_usage_started":
            print(f"\nTool: {frame.event.get('tool_name')}")

result = stream.result
```

## Stream a Direct LLM Call

Direct `llm.call(...)` returns the final assembled result. To stream a direct LLM call, use `llm.stream_events(...)`:

```python
from crewai import LLM


llm = LLM(model="gpt-4o-mini")
stream = llm.stream_events("Explain streaming in one sentence.")

with stream:
    for frame in stream.llm:
        print(frame.content, end="", flush=True)

print()
result = stream.result
```

## Stream a Conversational Turn

Conversational Flows expose `stream_turn()` for one user message:

```python
stream = flow.stream_turn(
    "What can you help me with?",
    session_id="session-1",
)

with stream:
    for frame in stream.interleave(["llm", "messages"]):
        if frame.channel == "llm":
            print(frame.content, end="", flush=True)
        elif frame.channel == "messages":
            print(f"\n{frame.event.get('role')}: {frame.event.get('content')}")

reply = stream.result
```

## Async Consumers

Async streams use the same channel projections:

```python
stream = flow.astream(inputs={"topic": "AI agents"})

async with stream:
    async for frame in stream.llm:
        print(frame.content, end="", flush=True)

result = stream.result
```

## Cleanup

Use the stream as a context manager when possible. If a client disconnects or you stop consuming early, close the stream:

```python
stream = flow.stream_events(inputs={"topic": "AI agents"})

try:
    for frame in stream.events:
        print(frame.content, end="", flush=True)
finally:
    if not stream.is_exhausted:
        stream.close()
```

For async streams, call `await stream.aclose()`.

## See Also

- [Streaming](/edge/en/concepts/streaming)
- [Streaming Runtime Contract](/edge/en/learn/streaming-runtime-contract)
- [Streaming Flow Execution](/edge/en/learn/streaming-flow-execution)
- [Streaming Crew Execution](/edge/en/learn/streaming-crew-execution)
