Agent 是什么:pi 的世界观
第 1 章你看过 jackdaw 成品的演示:请求从 curl 出发,经网关到 provider,再流着回来。当时说好不展开,现在拆。读完你能回答:
- 给你一条 chat 请求 JSON,你能指出哪段是系统提示词、什么在耗 token、整份历史为何必须塞进上下文窗口;
- 有人说"agent 就是一个循环",你能把它展开成五步,说出每转一圈 JSON 里多了什么;
- 看到 pi、magpie、jackdaw 三个名字,你能画出各自站在链路的哪一层。
一条请求的解剖
先立主线:agent 干活时,反复向一个地址 POST 的就是这样的 JSON(走 OpenAI 兼容的 chat 接口;本章所有 JSON 都只保留骨架字段,真实的请求有更多字段):
{
"model": "openrouter/qwen3-coder",
"messages": [
{ "role": "system", "content": "You are a careful Rust code reviewer. Be concise." },
{ "role": "user", "content": "帮我看 src/router.rs 有没有问题" },
{ "role": "assistant", "content": "先告诉我文件里有哪些函数。" },
{ "role": "user", "content": "resolve 和 pick_next 两个。" }
],
"stream": true
}读法:从 model 看起,它点名本次用哪个模型;供应商/模型的斜杠写法来自 magpie,本章末节回收。messages 是按时间排好的对话,每条有 role(system / user / assistant)与 content。"stream": true 是个开关,讲到流式再回来。
关键认识:**模型没有记忆。**每次调用都是无状态的;想让它"记得"上文,唯一办法是把整个历史原样重发。这个数组叫消息序列(messages)——它不是日志,是模型的全部输入。
agent 是一个循环
方向先行:代理(agent),就是在循环里调用模型的程序:发消息、执行工具、把结果喂回去,直到任务完成。
没有这个循环会怎样?裸调用只能一问一答,模型读不了你的文件、跑不了命令。所以需要一个程序替它跑腿、管好循环与状态——这个运行时外壳叫 harness。
五步:① 组装 messages(system + 历史 + 新指令),发给模型;② 模型返回纯文本,或工具调用(tool call)——结构化的一句"请替我调 read_file,参数 src/router.rs";③ harness 真的去执行;④ 结果作为一条 role: "tool" 的新消息回填;⑤ 把变长的 messages 整个重发,直到模型不再要工具。第②步与第④步的数据长这样:
{ "role": "assistant", "content": null,
"tool_calls": [
{ "id": "call_01", "type": "function",
"function": { "name": "read_file",
"arguments": "{\"path\": \"src/router.rs\"}" } } ] }{ "role": "tool", "tool_call_id": "call_01",
"content": "fn resolve(...) { /* ... */ }" }读法:assistant 这条 content 为 null——本轮它不说话,只要工具;回填的那条靠 tool_call_id 与之配对。注意:动你磁盘的是 harness,不是模型;模型只输出"想调用什么"。
pi 的世界观正是这句。它自称 "Pi Agent Harness"——一套可自扩展的 coding agent 工具族,分层是:pi-coding-agent(交互式 CLI)、pi-agent-core(agent 运行时:tool calling 与状态管理)、pi-ai(统一多 provider 的 LLM API)。harness 是壳,agent 是壳里转的那个循环。顺带一提:pi 不内建权限系统,以你的用户权限运行——透明可审计,是它的哲学。
模型的原料:token、上下文窗口、系统提示词
循环每转一圈,messages 长一截,这笔账要三个词。
令牌(token):模型处理文本的计量单位,约等于一个词或词的一部分;计费与上下文都以它计。
上下文窗口(context window):模型一次能看到的 token 上限。系统提示词、每轮对话、每个工具结果,总 token 数不得超过它。长对话后期"忘事"不是记性差——装不下的部分,根本没被发过去。
系统提示词(system prompt):role: "system" 那条,设定模型角色与行为的隐藏指令。用户通常看不见它,它却每轮都占窗口——主线第一行那句英文就是。
回程:流式与供应商
"stream": true 该兑现了。完整回答可能几百个 token,等全部生成再返回,首字延迟以秒计。流式(streaming / SSE,Server-Sent Events)让服务端生成一块、推一块,不必等完整回答。回程不是一个大 JSON,是一串事件:
data: {"choices":[{"delta":{"content":"fn"}}]}
data: {"choices":[{"delta":{"content":" resolve"}}]}
data: [DONE]读法:每个 data: 行是一个事件,delta 里是本步新增的字符,全部拼接才是完整回答,[DONE] 收尾(真实事件里还有 id 等字段,此处省略);字节级细节第 12 章展开。
最后一个词:供应商。一指提供模型 API 的一方(OpenAI、Anthropic、Google……);二指代码里的统一抽象——各家 API 形状不同,provider 把它们抹平成一个接口,agent 只对接口说话。pi 的 pi-ai 层干的就是这件事。
中间插一层:网关拓扑与 jackdaw
方向:在 agent 与 provider 之间,加一个常驻本机的转发服务——网关(gateway),链路变成 agent → 网关 → provider。
magpie 就是这样的本机网关加模型管理器:默认监听 127.0.0.1:3425,同时说 OpenAI、Anthropic、Gemini 三种协议并互相翻译(含流式与工具调用)。招牌是路由组(routing group):group/<id> 把多个模型、账号打包成一个名字,配策略与故障转移(failover:上游挂了自动换下一个),第 13 章展开。
这层怎么接上去?两条事实咬合:pi 支持自定义 provider,给 baseUrl 和 apiKey 即可;而 magpie 对外说 OpenAI 兼容协议。于是 agent 眼里的"一个 provider"其实是网关——主线 JSON 里 model 字段的斜杠命名,正是写给网关看的。magpie 把 Claude Code、Codex、pi 这类工具统称 "Agents",把选定的模型写进它们的配置文件,agent 浑然不觉。
jackdaw 扮演的就是这个小角色,并刻意只留最小核:OpenAI 兼容协议进出、手工配置 provider(无任何预设目录)、group/<id> 只有 order / rotate 两种策略加故障转移。magpie 的 smart / usage 策略、意图规则路由、最多八层的组嵌套,jackdaw 一概不做——本书只要你能读懂的那个核,砍掉的是教学点,不是缺陷。第 10 章起,我们逐层解剖它。
小结
回收开头:系统提示词是 role: "system" 那条;整份 messages 都在耗 token、都要塞进上下文窗口;agent 是五步循环,转一圈长一截。分层看:pi 是 harness,magpie 是中间的网关,jackdaw 是你要亲手读懂的那个网关。下一章立分工契约:AI 写,你读。
自检
- 迁移:把你的一次长对话(任意聊天产品)映射成 messages——system 大概是哪条?你看得见吗?"忘事"该怎么用本章概念解释?
- 预测:模型上一轮返回了 tool call,程序没执行工具、也没回填,就把原样的 messages 重发了一次。下一步会发生什么?为什么?
- 解释:pi 为什么能把 magpie 当成一个普通 provider 使用?靠哪两条事实?
参考答案
- system 通常是开头那条、由产品方注入,界面上一般看不到;你的输入是
role: "user",模型的回答是role: "assistant"。"忘事"是因为上下文窗口有 token 上限,装不下的早期内容不会被发过去——模型不是忘了,是没看到。 - 模型多半原样再次请求工具:它的输入里仍然缺工具结果,循环空转。这正是"结果回填"一步存在的意义。
- 其一,pi 支持自定义 provider,只需 baseUrl + apiKey;其二,magpie 对外说 OpenAI 兼容协议。对 pi 来说,网关与普通供应商无异,翻译、路由、故障转移都发生在网关内部。