Agents
Run the default agent loop with tools.
Use an agent when the model needs to call tools and continue with the tool results.
Create an agent
An agent wraps ai.stream in a loop. It streams model output, executes requested
tools, appends tool results to history, and repeats until the model returns a
final assistant message.
Subclass ai.Agent and override async def loop() when you need to change
control flow.
import asyncio
import ai
@ai.tool
async def contact_mothership(query: str) -> str:
"""Contact the mothership for important decisions."""
return "Soon."
async def main() -> None:
model = ai.get_model("anthropic/claude-sonnet-4")
agent = ai.Agent(tools=[contact_mothership])
messages = [
ai.system_message(
"Use the contact_mothership tool when asked about the future."
),
ai.user_message("When will the robots take over?"),
]
async with agent.run(model, messages) as stream:
async for event in stream:
if isinstance(event, ai.events.TextDelta):
print(event.chunk, end="", flush=True)
print(stream.output)
if __name__ == "__main__":
asyncio.run(main())The stream yields model events and agent events. After the run finishes,
stream.messages contains the updated history, and stream.output contains
the final assistant output.
Unlike ai.stream, every agent.run can produce multiple messages alternating
between "user"/"tool" and "assistant", representing turns in the LLM request
and response cycle.
Understand multi-turn behavior
Each loop turn makes one call to ai.stream and produces one assistant
message. If the message contains tool calls, the agent executes them, appends
one tool-result message, and starts the next model turn.
async with agent.run(model, messages) as stream:
async for event in stream:
if isinstance(event, ai.events.ToolCallResult):
for result in event.results:
print(result.tool_name, result.result)
elif isinstance(event, ai.events.TextDelta):
print(event.chunk, end="", flush=True)
history = stream.messagesUse stream.messages when you want to persist the complete conversation after
the run.
Pass params and structured output
Pass provider options with params. Pass a Pydantic model with output_type
when the final assistant text should validate as JSON:
import pydantic
class Forecast(pydantic.BaseModel):
answer: str
eta: str
async with agent.run(
model,
[ai.user_message("Return a JSON mothership forecast.")],
output_type=Forecast,
params=ai.InferenceRequestParams().with_temperature(0),
) as stream:
async for event in stream:
if isinstance(event, ai.events.TextDelta):
print(event.chunk, end="", flush=True)
forecast = stream.output
print(forecast.eta)