第一个智能体#
你将学到:如何创建客户端、运行多个轮次,以及是什么让对话在轮次之间保持下来。前置条件:安装 Zett Agent。不需要 API 凭据。
本教程使用预设脚本的模型,因此你可以在任何机器上检查结果。它不是语言模型,也不会学习;它让你在选择模型提供商之前先体验运行时。如果你已经有凭据,可以直接看完整的工具调用请求。
1. 为循环提供一个模型#
智能体并不知道如何访问某个特定厂商的 HTTP API。它期望一个模型适配器:一个接受ModelRequest并流式返回ModelEvent对象的对象。
在第一个示例中,适配器会统计用户轮次并回显最近的一次。它不是 LLM,但它接入的循环与真实模型提供商完全相同。
class EchoModel:
"""Deterministic teaching adapter: count user turns and echo the latest one."""
retry = RetryOptions(max_retries=0)
async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
users = [message for message in request.messages if isinstance(message, UserMessage)]
answer = f"Turn {len(users)}: {users[-1].text}"
yield ModelEvent.text(answer)
yield ModelEvent.completed(ModelResponse(AssistantMessage(content=answer)))
每个模型步骤都以一个完整的ModelResponse结束。它之前的文本增量只用于显示;运行时真正使用的是最终响应。
2. 创建一个客户端,运行多个轮次#
async def main() -> None:
client = await create_agent(EchoModel())
first = await client.run("Hello")
second = await client.run("Remember this conversation")
assert first.content == "Turn 1: Hello"
assert second.content == "Turn 2: Remember this conversation"
print(first.content)
print(second.content)
create_agent()会构建运行时并返回一个AgentClient。由于没有提供会话,它会为你生成一个。
第二个请求能记住第一个请求,因为默认扩展会为会话保留内存中的历史记录。run()会返回完整的AssistantMessage。
3. 运行整个程序#
1"""A complete offline conversation through the real Agent runtime."""
2
3import asyncio
4from collections.abc import AsyncIterator
5
6from zett_agent.client import create_agent
7from zett_agent.messages import (
8 AssistantMessage,
9 UserMessage,
10)
11from zett_agent.model import (
12 ModelEvent,
13 ModelRequest,
14 ModelResponse,
15 RetryOptions,
16)
17
18
19class EchoModel:
20 """Deterministic teaching adapter: count user turns and echo the latest one."""
21
22 retry = RetryOptions(max_retries=0)
23
24 async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
25 users = [message for message in request.messages if isinstance(message, UserMessage)]
26 answer = f"Turn {len(users)}: {users[-1].text}"
27 yield ModelEvent.text(answer)
28 yield ModelEvent.completed(ModelResponse(AssistantMessage(content=answer)))
29
30
31async def main() -> None:
32 client = await create_agent(EchoModel())
33 first = await client.run("Hello")
34 second = await client.run("Remember this conversation")
35 assert first.content == "Turn 1: Hello"
36 assert second.content == "Turn 2: Remember this conversation"
37 print(first.content)
38 print(second.content)
39
40
41if __name__ == "__main__":
42 asyncio.run(main())
把下载的文件在已安装 Zett Agent 的环境中保存为first_agent.py后,运行如下:
python first_agent.py
或者在源码检出目录中运行:
uv run python docs/_examples/first_agent.py
预期输出:
Turn 1: Hello
Turn 2: Remember this conversation
发生了什么?#
create_agent用默认历史记录初始化了一个对话。每次run都会追加一条用户消息、调用适配器,并返回最终助手消息。第二次调用时,适配器统计到两条用户消息,因为上一轮已被恢复。你不需要手动传入历史记录,也不需要自己编写对话循环。
真实的模型提供商可能会在回答之前请求工具。运行时会执行这些工具,并把结果再次传给模型;因此一次run就可能涉及多个模型步骤。在发出付费请求之前,可以先试试确定性的工具往返,了解这种行为。
下一步:把预设脚本的模型换成真实模型提供商#
只有适配器的构造方式会变。同一个客户端、消息、扩展、工具和事件回调,在所有内置提供商上都能工作。接下来请阅读连接模型提供商,然后是把流式输出接入终端或界面。
警告
传入extensions=[]会移除这些可选的默认扩展,包括内存中的历史记录。每个请求都会从全新的上下文开始;如果希望对话延续下去,请添加InMemoryMessageAccumulator或一个持久化扩展。