流式展示一次工具往返#

把一个 Python 函数变成工具,并在你的应用中显示它的进度。模型请求 add,收到 42,然后给出最终回答。

运行示例#

在仓库根目录下:

uv run python docs/_examples/streaming_tools.py

输出包含:

Answer: 42

这个程序使用确定性的离线模型,并在需要时使用临时存储。它不需要 API 密钥,由文档测试套件执行。

可复用的三个部分#

定义能力。类型注解描述参数,文档字符串告诉模型何时调用它:

@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

显示进度。事件回调会收到推理、工具调用、工具结果以及增量的回答文本:

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)

连接到客户端。当你准备好发出真实请求时,把 MathModel() 替换成你的模型提供商:

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"

预设脚本的模型总是调用工具。真实的模型会自行决定是否调用,而且即使内部进行了推理,也可能不会返回可见的推理内容。

完整源码#

下载 streaming_tools.py。

 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())

下一步:阅读把流式输出接入终端或界面了解事件处理和取消,或阅读定义并注册工具了解模式、错误和安全执行。