Unify flow streaming frame items

This commit is contained in:
lorenzejay
2026-06-29 14:50:17 -07:00
parent a48f45c917
commit 90b06a4523
15 changed files with 739 additions and 220 deletions

View File

@@ -28,6 +28,8 @@ frame.timestamp # event timestamp
frame.parent_id # parent event id, when available
frame.previous_id # previous event id, when available
frame.data # event payload
frame.event # alias for frame.data
frame.content # printable text for token-like frames, otherwise ""
```
The `channel` field is the fastest way to route frames in consumers:
@@ -45,7 +47,7 @@ The `channel` field is the fastest way to route frames in consumers:
## Stream a Flow
Use `stream_events()` to run a Flow and iterate over all frames:
Set `stream=True` on a Flow to make `kickoff()` return a stream session:
```python
from crewai.flow import Flow, start
@@ -57,18 +59,22 @@ class ReportFlow(Flow):
return "done"
flow = ReportFlow()
stream = flow.stream_events()
flow = ReportFlow(stream=True)
stream = flow.kickoff()
with stream:
for frame in stream.events:
print(frame.seq, frame.channel, frame.type, frame.data)
for chunk in stream:
print(chunk.content, end="", flush=True)
if chunk.type == "tool_usage_started":
print(chunk.event["tool_name"])
result = stream.result
```
You must consume the stream before reading `stream.result`. Accessing the result early raises a `RuntimeError` so consumers do not accidentally treat a partial run as complete.
You can also call `flow.stream_events(...)` directly when you want streaming for a single invocation without setting `stream=True` on the Flow instance.
## Filter by Channel
`StreamSession` exposes channel projections that preserve global frame order within the selected channel:
@@ -78,7 +84,7 @@ stream = flow.stream_events()
with stream:
for frame in stream.llm:
print(frame.data.get("chunk", ""), end="", flush=True)
print(frame.content, end="", flush=True)
result = stream.result
```
@@ -105,8 +111,8 @@ flow = ReportFlow()
stream = flow.astream()
async with stream:
async for frame in stream.events:
print(frame.channel, frame.type)
async for chunk in stream.events:
print(chunk.channel, chunk.type, chunk.content)
result = stream.result
```
@@ -115,14 +121,14 @@ The async session has the same projections as the sync session.
## Stream a Direct LLM Call
`llm.call(...)` still returns the final assembled result. Use `llm.stream_call(...)` when you want to iterate over chunks as they arrive:
`llm.call(...)` still returns the final assembled result. Use `llm.stream_events(...)` when you want to iterate over chunks as they arrive while keeping the structured event payload:
```python
from crewai import LLM
llm = LLM(model="gpt-4o-mini")
chunks = llm.stream_call(
stream = llm.stream_events(
messages=[
{
"role": "user",
@@ -131,26 +137,14 @@ chunks = llm.stream_call(
]
)
for chunk in chunks:
print(chunk.content, end="", flush=True)
result = chunks.result
```
Use `llm.stream_events(...)` when a runtime needs the full `StreamFrame` envelope rather than text chunks:
```python
stream = llm.stream_events("Explain CrewAI streaming in two short sentences.")
with stream:
for frame in stream.llm:
if frame.type == "llm_stream_chunk":
print(frame.data.get("chunk", ""), end="", flush=True)
for chunk in stream:
print(chunk.content, end="", flush=True)
result = stream.result
```
Both methods temporarily enable streaming for the wrapped call and restore the LLM's previous `stream` setting afterward. Provider integrations continue to emit the underlying LLM stream events; these helpers provide a common iterator API over those events for every LLM provider.
`llm.stream_events(...)` temporarily enables streaming for the wrapped call and restores the LLM's previous `stream` setting afterward. Provider integrations continue to emit the underlying LLM stream events; this helper provides a common iterator API over those events for every LLM provider.
## Conversational Turns
@@ -172,7 +166,7 @@ stream = flow.stream_turn("What can you help me with?", session_id="session-1")
with stream:
for frame in stream.events:
if frame.channel == "llm" and frame.type == "llm_stream_chunk":
print(frame.data.get("chunk", ""), end="", flush=True)
print(frame.content, end="", flush=True)
reply = stream.result
```