Skip to content

Lesson 4: Adding Callbacks & Response Streaming

Play

Watch on YouTube

Code for this lesson can be found here.

Every agent we’ve built so far has been streaming text to the terminal as the model generates it. But we didn’t configure that. It just works out of the box that way. But how does this work under the hood?

Strands provides a callback handler, which is a function that gets called for every event the agent produces, like text chunks, tool calls, lifecycle signals, and the final result. The default callback handler prints text to standard out and shows the tool usage information inline. That’s what you’ve been seeing when we run Python files from the command line.

But callbacks are actually one of the main ways you can control how an agent’s output surfaces into a user interface, an API server, or any sort of orchestration system.

Let’s create a simple callback function so you can see how it’s done. A callback handler is just a function that accepts arguments.

This is a custom one that buffers text and only shows it when a complete message is generated. This pattern is useful when you want tighter control over how responses are presented to users. To use it, you can just pass it to the agent through the callback_handler parameter. You can also set the callback to be none, and then nothing prints when you run it. The agent runs, then you get the result back as a variable, and you can do whatever you want with that, like print it out.

This will be more important when we get to multi-agent systems and you’ll have sub-agents running behind the scenes.

Let’s run this so we can see how each agent behaves differently.

So we can see the default streaming coming back here, printing out which tools are being called and then the final result. And then for our custom callback handler, we really don’t want to see those tools being called, so you can see it just gives us the result that comes back. And then for silent mode, this is us just simply printing out the result that we’ve captured in this variable, but it doesn’t print out those tool calls or anything in between.

So these are callback handlers, and you should know that they’re synchronous. They work great for CLI tools and scripts, but if you’re building a server with something like FastAPI, AIOHTTP, or anything async, you’ll want to use the async iterator pattern instead. This gives you programmatic access to the same events but using a different execution model.

Here’s what that looks like in practice: We define a FastAPI endpoint that accepts a prompt, creates an agent, and then streams the response back to the client. The generate function yields only the data chunks to stream back, so the user sees text appearing in real time just like a chat interface.

You set the callback handler to be none on the agent itself because you don’t want the default handler also printing to your server’s standard out. The async iterator is your output channel.

Time to give this a run and see how it works in practice. To do that, I will start up the FastAPI server, and then in another terminal tab we can run this curl command, and we can see the result coming back from the agent.

Now you can control how output reaches the user, but you still can’t control what the agent does before it produces that output.

That’s hooks, and that’s what’s coming up in the next lesson.

Learn more: Callback Handlers · Async Iterators