Async Iterators for Streaming
Section titled “Async Iterators for Streaming”Async iterators provide asynchronous streaming of agent events, allowing you to process events as they occur in real-time. This approach is ideal for asynchronous frameworks where you need fine-grained control over async execution flow.
For a complete list of available events including text generation, tool usage, lifecycle, and reasoning events, see the streaming overview.
Basic Usage
Section titled “Basic Usage”=== “Python”
Python uses the [`stream_async`](../../../api-reference/python/agent/agent.md#strands.agent.agent.Agent.stream_async), which is a streaming counterpart to the [`invoke_async`](../../../api-reference/python/agent/agent.md#strands.agent.agent.Agent.invoke_async) method, for asynchronous streaming. This is ideal for frameworks like FastAPI, aiohttp, or Django Channels.
> **Note**: Python also supports synchronous event handling via [callback handlers](./callback-handlers.md).
```pythonimport asynciofrom strands import Agentfrom strands_tools import calculator
# Initialize our agent without a callback handleragent = Agent( tools=[calculator], callback_handler=None)
# Async function that iterators over streamed agent eventsasync def process_streaming_response(): agent_stream = agent.stream_async("Calculate 2+2") async for event in agent_stream: print(event)
# Run the agentasyncio.run(process_streaming_response())```=== “TypeScript”
TypeScript uses the [`stream`](../../../api-reference/python/agent/agent.md) method for streaming, which is async by default. This is ideal for frameworks like Express.js or NestJS.
```typescript// Initialize our agent without a printerconst agent = new Agent({ tools: [notebook], printer: false,})
// Async function that iterates over streamed agent eventsasync function processStreamingResponse(): Promise<void> { for await (const event of agent.stream('Record that my favorite color is blue!')) { console.log(event) }}
// Run the agentawait processStreamingResponse()```Server examples
Section titled “Server examples”Here’s how to integrate streaming with web frameworks to create a streaming endpoint:
=== “Python - FastAPI”
```pythonfrom fastapi import FastAPI, HTTPExceptionfrom fastapi.responses import StreamingResponsefrom pydantic import BaseModelfrom strands import Agentfrom strands_tools import calculator, http_request
app = FastAPI()
class PromptRequest(BaseModel): prompt: str
@app.post("/stream")async def stream_response(request: PromptRequest): async def generate(): agent = Agent( tools=[calculator, http_request], callback_handler=None )
try: async for event in agent.stream_async(request.prompt): if "data" in event: # Only stream text chunks to the client yield event["data"] except Exception as e: yield f"Error: {str(e)}"
return StreamingResponse( generate(), media_type="text/plain" )```=== “TypeScript - Express.js”
> **Note**: This is a conceptual example. Install Express.js with `npm install express @types/express` to use it in your project.
```typescript// Install Express: npm install express @types/express
interface PromptRequest { prompt: string}
async function handleStreamRequest(req: any, res: any) { console.log(`Got Request: ${JSON.stringify(req.body)}`) const { prompt } = req.body as PromptRequest
const agent = new Agent({ tools: [notebook], printer: false, })
for await (const event of agent.stream(prompt)) { res.write(`${JSON.stringify(event)}\n`) } res.end()}
const app = express()app.use(express.json())app.post('/stream', handleStreamRequest)app.listen(3000)```
You can then curl your local server with:```bashcurl localhost:3000/stream -d '{"prompt": "Hello"}' -H "Content-Type: application/json"```Agentic Loop
Section titled “Agentic Loop”This async stream processor illustrates the event loop lifecycle events and how they relate to each other. It’s useful for understanding the flow of execution in the Strands agent:
=== “Python”
```pythonfrom strands import Agentfrom strands_tools import calculator
# Create agent with event loop trackeragent = Agent( tools=[calculator], callback_handler=None)
# This will show the full event lifecycle in the consoleasync for event in agent.stream_async("What is the capital of France and what is 42+7?"): # Track event loop lifecycle if event.get("init_event_loop", False): print("🔄 Event loop initialized") elif event.get("start_event_loop", False): print("▶️ Event loop cycle starting") elif "message" in event: print(f"📬 New message created: {event['message']['role']}") elif "result" in event: print("✅ Agent completed with result") elif event.get("force_stop", False): print(f"🛑 Event loop force-stopped: {event.get('force_stop_reason', 'unknown reason')}")
# Track tool usage if "current_tool_use" in event and event["current_tool_use"].get("name"): tool_name = event["current_tool_use"]["name"] print(f"🔧 Using tool: {tool_name}")
# Show only a snippet of text to keep output clean if "data" in event: # Only show first 20 chars of each chunk for demo purposes data_snippet = event["data"][:20] + ("..." if len(event["data"]) > 20 else "") print(f"📟 Text: {data_snippet}")```
The output will show the sequence of events:
1. First the event loop initializes (`init_event_loop`)2. Then the cycle begins (`start_event_loop`)3. New cycles may start multiple times during execution (`start_event_loop`)4. Text generation and tool usage events occur during the cycle5. Finally, the agent completes with a `result` event or may be force-stopped (`force_stop`)=== “TypeScript”
```typescriptfunction processEvent(event: AgentStreamEvent): void { // Track agent loop lifecycle switch (event.type) { case 'beforeInvocationEvent': console.log('🔄 Agent loop initialized') break case 'beforeModelCallEvent': console.log('▶️ Agent loop cycle starting') break case 'afterModelCallEvent': console.log(`📬 New message created: ${event.stopData?.message.role}`) break case 'beforeToolsEvent': console.log("About to execute tool!") break case 'beforeToolsEvent': console.log("Finished execute tool!") break case 'afterInvocationEvent': console.log('✅ Agent loop completed') break }
// Track tool usage if (event.type === 'modelContentBlockStartEvent' && event.start?.type === 'toolUseStart') { console.log(`\n🔧 Using tool: ${event.start.name}`) }
// Show text snippets if (event.type === 'modelContentBlockDeltaEvent' && event.delta.type === 'textDelta') { process.stdout.write(event.delta.text) }}const responseGenerator = agent.stream( 'What is the capital of France and what is 42+7? Record in the notebook.')for await (const event of responseGenerator) { processEvent(event)}```
The output will show the sequence of events:
1. First the invocation starts (`beforeInvocationEvent`)2. Then the model is called (`beforeModelEvent`)3. The model generates content with delta events (`modelContentBlockDeltaEvent`)4. Tools may be executed (`beforeToolsEvent`, `afterToolsEvent`)5. The model may be called again in subsequent cycles6. Finally, the invocation completes (`afterInvocationEvent`)