Stream a tool round trip#
Turn a Python function into a tool and show its progress in your application.
The model asks for add, receives 42, and produces a final answer.
Run it#
From the repository root:
uv run python docs/_examples/streaming_tools.py
The output includes:
Answer: 42
This program uses deterministic offline models and temporary storage where needed. It requires no API key and is executed by the documentation test suite.
The three pieces to reuse#
Define the capability. Type annotations describe the arguments, and the docstring tells the model when to call it:
@tool
def add(left: int, right: int) -> int:
"""Add two integers exactly.
Args:
left: First operand.
right: Second operand.
Snippet:
add(left=20, right=22)
Guidelines:
- Use this tool for exact integer addition.
"""
return left + right
Display progress. Event callbacks receive reasoning, the tool call, its result, and incremental answer text:
class ConsoleEvents(AgentEventDispatcher):
async def on_reasoning_delta_event(self, event: AgentEvent) -> None:
print(f"Reasoning: {event.delta}")
async def on_tool_started_event(self, event: AgentEvent) -> None:
call = event.tool_calls[0]
print(f"Tool: {call.name} {call.arguments}")
async def on_tool_completed_event(self, event: AgentEvent) -> None:
assert event.message is not None
print(f"Result: {event.message.content}")
async def on_text_delta_event(self, event: AgentEvent) -> None:
print(event.delta)
Connect them to a client. Replace MathModel() with your provider when
you are ready to make real requests:
async def main() -> None:
client = await create_agent(MathModel(), tools=[add], event_dispatcher=ConsoleEvents())
reply = await client.run("What is 20 plus 22?")
assert reply.content == "Answer: 42"
The scripted model always calls the tool. A real model chooses whether to call it, and may not return visible reasoning even when it reasons internally.
Complete source#
1"""Run a typed tool and display reasoning, tool results, and final text."""
2
3import asyncio
4from collections.abc import AsyncIterator
5
6from zett_agent.client import create_agent
7from zett_agent.dispatcher import AgentEventDispatcher
8from zett_agent.events import AgentEvent
9from zett_agent.messages import (
10 AssistantMessage,
11 ToolCall,
12 ToolMessage,
13)
14from zett_agent.model import (
15 ModelEvent,
16 ModelRequest,
17 ModelResponse,
18 RetryOptions,
19)
20from zett_agent.tools.base import tool
21
22
23@tool
24def add(left: int, right: int) -> int:
25 """Add two integers exactly.
26
27 Args:
28 left: First operand.
29 right: Second operand.
30
31 Snippet:
32 add(left=20, right=22)
33
34 Guidelines:
35 - Use this tool for exact integer addition.
36 """
37 return left + right
38
39
40class MathModel:
41 """Script a model/tool/model round trip without making a network request."""
42
43 retry = RetryOptions(max_retries=0)
44
45 async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
46 if isinstance(request.messages[-1], ToolMessage):
47 answer = f"Answer: {request.messages[-1].content}"
48 yield ModelEvent.text(answer)
49 yield ModelEvent.completed(ModelResponse(AssistantMessage(content=answer)))
50 else:
51 assert any(definition.name == "add" for definition in request.tools)
52 yield ModelEvent.reasoning("Use exact arithmetic.")
53 yield ModelEvent.completed(
54 ModelResponse(AssistantMessage(tool_calls=(ToolCall("sum-1", "add", {"left": 20, "right": 22}),)))
55 )
56
57
58class ConsoleEvents(AgentEventDispatcher):
59 async def on_reasoning_delta_event(self, event: AgentEvent) -> None:
60 print(f"Reasoning: {event.delta}")
61
62 async def on_tool_started_event(self, event: AgentEvent) -> None:
63 call = event.tool_calls[0]
64 print(f"Tool: {call.name} {call.arguments}")
65
66 async def on_tool_completed_event(self, event: AgentEvent) -> None:
67 assert event.message is not None
68 print(f"Result: {event.message.content}")
69
70 async def on_text_delta_event(self, event: AgentEvent) -> None:
71 print(event.delta)
72
73
74async def main() -> None:
75 client = await create_agent(MathModel(), tools=[add], event_dispatcher=ConsoleEvents())
76 reply = await client.run("What is 20 plus 22?")
77 assert reply.content == "Answer: 42"
78
79
80if __name__ == "__main__":
81 asyncio.run(main())
Next: Stream to a terminal or UI for event handling and cancellation, or Define and register tools for schemas, errors, and safe execution.