CrewAICrewAI 1.15 · Python 3.10 to 3.13
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
35 small wins to finish your pathNext lesson →

Streaming a crew's output

Streaming hands you a crew's output as it is produced: Crew(stream=True) yields text chunks, and a flow's stream_events yields frames for each step.

Last updated: 28 Sep, 2026 · CrewAI 1.15

So far a kickoff returned only after the whole run finished. Streaming lets a program show the answer as it forms, and watch each step of a flow as it happens, instead of waiting for the end.

The streaming API

A crew built with stream=True returns an object you iterate for chunks; its .result holds the final output once you have read them all. A flow's stream_events returns frames tagged by channel.

python
crew = Crew(agents=[...], tasks=[...], stream=True)
streaming = crew.kickoff(inputs={...})
for chunk in streaming:      # StreamChunk objects
    print(chunk.content, end="")
result = streaming.result    # the CrewOutput, after the loop

for frame in flow.stream_events(inputs={...}):   # StreamFrame objects
    print(frame.channel, frame.type)

A stand-in that streams

A hosted model streams its tokens as it writes them. The stand-in returns a whole string, so it emits the words as chunks itself, which is what makes this a real streamed run with no key.

python
class StreamingShopLLM(ShopLLM):
    def call(self, messages, tools=None, **kwargs):
        answer = super().call(messages, tools=tools, **kwargs)
        if isinstance(answer, str):
            for i, word in enumerate(answer.split(" ")):
                piece = word if i == 0 else " " + word
                self._emit_stream_chunk_event(piece, from_task=kwargs.get("from_task"),
                                              from_agent=kwargs.get("from_agent"))
        return answer

Turning streaming on

Build the crew with stream=True and give the clerk the streaming stand-in.

python
clerk = Agent(role="Order clerk", ..., llm=StreamingShopLLM(model="shop"),
              tools=[lookup_order])
crew = Crew(agents=[clerk], tasks=[look], stream=True, verbose=False)

Streaming a crew chunk by chunk

Iterate the streaming object for chunks, then read the final result.

Example
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"

from crewai import Agent, Crew, Task
from stream_llm import StreamingShopLLM
from tools import lookup_order

clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
              backstory="You can look up any order in the shop's system.",
              llm=StreamingShopLLM(model="shop"), tools=[lookup_order])
look = Task(description="Find the order in this message: {question}",
            expected_output="The order's status.", agent=clerk)
crew = Crew(agents=[clerk], tasks=[look], stream=True, verbose=False)

streaming = crew.kickoff(inputs={"question": "Where is my order A17?"})
for chunk in streaming:
    print(f"{chunk.chunk_type.value}: {chunk.content!r}")
print("full reply:", streaming.result.raw)

What the chunks carried

  • Each chunk is a StreamChunk whose content is a piece of the answer and whose chunk_type is text.
  • The tool call emitted no text chunks; only the final answer streamed, word by word.
  • streaming.result is the full CrewOutput, available once every chunk has been read.

Streaming a flow's frames by channel

A flow streams frames, not text. stream_events returns a session; its .flow view yields only the lifecycle frames.

Example
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"

import re
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel


class Ticket(BaseModel):
    message: str = ""
    order_id: str = ""
    reply: str = ""


class Desk(Flow[Ticket]):
    @start()
    def read_ticket(self):
        found = re.findall(r"\b[A-Z]\d+\b", self.state.message)
        self.state.order_id = found[0] if found else ""

    @listen(read_ticket)
    def answer(self):
        self.state.reply = f"Looking into {self.state.order_id}."
        return self.state.reply

desk = Desk(suppress_flow_events=True)
session = desk.stream_events(inputs={"message": "Where is my order A17?"})
for frame in session.flow:
    print(f"{frame.channel} | {frame.type}")
print("result:", session.result)

A StreamFrame carries a channel (llm, flow, tools, messages, lifecycle or custom), a type, a seq and a data dict. Filtering to the flow channel here shows the run start and finish.

Crew chunks vs flow frames

Crew stream=Trueflow.stream_events
What you getStreamChunk objectsStreamFrame objects
The payloadchunk.content, the textframe.channel and frame.type
Final resultstreaming.resultsession.result

When to stream

  • A person is waiting on the answer and should see it appear rather than a blank pause.
  • You want to trace a flow's steps live, for a progress display or a log.
Watch out. A crew emits chunks only when its model streams them, so a model that returns a whole string at once produces no chunks; the stand-in emits them by hand for this reason. Read every chunk before touching streaming.result, or it raises because the stream has not finished.
Try it yourself
  • Join the chunks with "".join(c.content for c in streaming) and compare with result.raw.
  • Print chunk.agent_role alongside each chunk.
  • Iterate session instead of session.flow and print every frame's channel.

Every expert started right here.