Pi Agent框架设计逻辑深度解析(AI Agent框架、Agentic AI框架)
文章目录
- Pi Agent 设计逻辑深度解析:碾压 Claude Code?
- 一、反直觉的极简主义
- 1.1 反:冗长的系统提示词
- 1.2 反:海量的内置工具
- 1.3 反:复杂的规划模块(Plan Mode)
- 1.4 反:内置的子 Agent(Sub-Agent)
- 二、架构分层解析
- 三、设计精髓
- 3.1 设计精髓之一:类型系统(types.ts)——应用状态与模型上下文的解耦
- 最晚转换(Latest Possible Conversion)策略
- 3.2 设计精髓之二:核心循环(agent-loop.ts)——双层 While 循环的自主引擎
- 用一个具体的例子走一遍流程
- 双层循环图示(文字版)
- 3.3 设计精髓之三:Agent 类(agent.ts)——优雅的状态容器与消息队列
- 四、Pi 的设计哲学:“不做什么”的智慧
- 我们能从 Pi 身上学到什么?
Pi Agent 设计逻辑深度解析:碾压 Claude Code?
原作者:AI产品赵哥
最近 AI Agent 领域有点热闹。各家框架都在疯狂堆料——更多的工具、更长的提示词、更复杂的规划链路、更多的子 Agent,好像不这样就不够强大。
但今天想聊一个不太一样的项目——Pi Agent。
作者是Mario Zechner,就是那个著名游戏框架 libGDX 的作者。他做了个极为克制的 Agent:核心代码不到 1500 行,5 个文件,系统提示词加工具定义不到 1000 token,内置工具只有 4 个。
听起来很寒酸是吧?但它在 Terminal-Bench 2.0 排行榜上表现相当靠前,吊打那些架构复杂的豪华版 Agent 同台竞技。
这挺有意思的。我们是不是把 Agent 想得太复杂了?是不是堆得越多,越觉得心里踏实?
这篇文章会从设计哲学、架构分层到核心实现,把 Pi Agent 彻底拆解一遍。看看这个“扫地僧”是怎么做到的。
大家准备好了吗?咱可发车了!!
一、反直觉的极简主义
要理解 Pi,首先要理解它的作者 Mario Zechner 那句掷地有声的总结:
An autonomous agent is just an LLM + tools + a loop.
一个自主智能体,不过是一个大语言模型 + 一堆工具 + 一个循环而已。
这句话,就是 Pi 所有设计决策的第一性原理。
在长期开发 Agent 的实践中,Mario 发现,很多复杂的框架设计,非但没有提升效率,反而增加了系统的冗余、降低了灵活性,甚至让 Agent 变笨。
于是,他决定反其道而行之,用减法来重构 Agent。我们来看看,Pi 到底在反什么主流趋势。
1.1 反:冗长的系统提示词
- 主流思路:为了让 Agent 更听话、更聪明,我们得给它写一个几千甚至上万 token 的系统提示词,像一本《员工手册》一样,事无巨细地告诉它应该扮演什么角色、遵循什么原则、如何使用工具。
- Pi 的反思:前沿的大模型(如 Claude Sonnet 4.5、GPT-5.2)已经被海量的 RLHF(人类反馈强化学习)数据训练得足够通人性了。你根本不需要花一万个 token 去教它“什么是编码 Agent”。它早就知道了。过长的提示词不仅浪费宝贵的上下文窗口,还可能限制 LLM 自身强大的推理和泛化能力。
- Pi 的做法:系统提示词 + 工具定义,总共不到 1000 token。简洁明了地告诉它核心职责,然后就放手让它自己干。
1.2 反:海量的内置工具
- 主流思路:Agent 的能力 = 工具的数量。工具越多,能干的事就越多。于是,文件操作、网络请求、数据库查询、代码分析……恨不得把所有可能用到的功能都做成内置工具。
- Pi 的反思:很多工具其实是冗余的。一个设计良好的、通用的工具,远胜过十个功能单一的专用工具。
- Pi 的做法:只提供 4 个核心到不能再核心的内置工具:
read(读文件)、write(写文件)、edit(编辑文件)、bash(执行 shell 命令)。
这 4 个工具,尤其是bash,简直是神来之笔。几乎所有你需要的操作,比如创建目录、移动文件、安装依赖、运行测试,都可以通过bash命令来完成。这使得 Agent 具备了近乎无限的扩展能力,而无需增加任何新的内置工具。
1.3 反:复杂的规划模块(Plan Mode)
- 主流思路:对于复杂任务,Agent 需要一个专门的规划模块来制定行动计划。这个模块通常是一个黑盒,我们不知道它内部是如何思考的。
- Pi 的反思:黑盒子的规划过程,难以观测、难以调试、难以版本控制,也难以跨会话复用。
- Pi 的做法:压根没有 Plan Mode。取而代之的是一个简单的
PLAN.md文件。Agent 会把它的思考过程和行动计划,像写文档一样,实时地写入这个 Markdown 文件。
这样做的好处是:
- 完全可观测:你可以实时看到 Agent 在想什么,打算怎么做。
- 版本可控:
PLAN.md可以像代码一样被 Git 管理。 - 可复用:这个计划可以被其他 Agent,甚至被你自己,在未来的任何时候参考和执行。
换句话说,主流 Agent 的 Plan Mode 像一个黑盒:内部如何运作完全看不到,只能看到最终结果,无法干预过程。Pi Agent 则把PLAN.md放在“透明盒子”里,展示目标分析、信息收集、方案制定、执行与验证全过程:不仅给结果,更展示思考过程。
1.4 反:内置的子 Agent(Sub-Agent)
- 主流思路:借鉴“多智能体协作”的理念,让一个主 Agent 去调用多个专业的子 Agent 来完成任务。
- Pi 的反思:子 Agent 会形成黑盒中的黑盒,让系统的可观测性雪上加霜。
- Pi 的做法:通过
bash命令实现自我调用。当需要执行一个独立的子任务时,Agent 可以自己调用pi命令行工具,并传入新的指令。这相当于它自己在命令行里启动了一个新的自己来处理子任务。这个过程的所有输入输出都在同一个终端里可见,完全透明。
总结:Pi 的极简主义,不是简陋,而是精准。它放弃了所有非核心的、会增加系统复杂度和不可观测性的高级功能,把信任和控制权,最大限度地交还给了 LLM 本身和最基础的命令行工具。
二、架构分层解析
Pi 的极简哲学,同样体现在它的代码实现上。整个核心运行时(@earendil-works/pi-agent-core)只有 5 个核心的 TypeScript 文件,总代码量约 1500 行。但这 5 个文件,却构建起了一个完整、高效、灵活的 Agent 运行时。
这 5 个文件分别是:
types.ts:类型系统,定义了整个系统的数据结构蓝图。agent-loop.ts:核心循环,Agent 的心脏,负责任务的自主流转。agent.ts:Agent 类,是 Agent 的大脑,负责状态管理。proxy.ts:代理流,为 Web 应用场景设计的网络加速器。index.ts:入口文件,将所有模块组织在一起。
接下来,我们将逐层剖析这几个核心模块的设计精髓。
三、设计精髓
3.1 设计精髓之一:类型系统(types.ts)——应用状态与模型上下文的解耦
types.ts是整个架构的基础。它的设计核心是“少即是多”,但其中最精妙、也最值得学习的设计,莫过于AgentMessage 类型的抽象。
这个设计巧妙地实现了应用状态与模型上下文的彻底分离,也是 Pi 能够实现灵活的上下文管理和“最晚转换”策略的关键。
我们来思考一个场景:
在一个 Agent 应用中,用户提问、Agent 回复、工具调用结果,这些信息都需要发送给 LLM,让它理解当前的对话状态。我们称之为模型消息(LLM Messages)。
但同时,应用本身还有很多内部状态信息,比如:
- UI 上弹出的一个加载中(loading)的提示。
- 用户正在编辑但还未发送的草稿。
- 一个标记,用来区分这是对话的第几个分支。
- 一个错误信息,提示某个工具执行失败了。
这些信息,我们称之为应用消息(App Messages)。它们对于应用本身至关重要,但完全不需要、也不应该发送给 LLM。如果把它们也发给 LLM,不仅浪费上下文,还可能干扰 LLM 的判断。
主流框架的做法:要么强行把所有信息都塞进 LLM 能理解的格式里,要么在发送前做一堆复杂的过滤逻辑。
而 Pi 的做法:
- 它定义了一个
AgentMessage类型。 - 这个
AgentMessage类型是一个联合类型,它既包含了 LLM 能理解的标准消息(如UserMessage、AssistantMessage),也允许应用通过扩展一个CustomAgentMessages接口,来自由地定义任意多种自定义的应用消息。
最晚转换(Latest Possible Conversion)策略
在 Pi 的整个内部逻辑中,无论是上下文压缩、会话管理、UI 渲染,所有的操作都是基于这个包罗万象的AgentMessage数组来进行的。
只有在调用 LLM API 的最后一刻,它才会调用一个convertToLlm方法,像过滤器一样,从AgentMessage数组中只过滤出 LLM 能理解的那些标准消息,转换成最终要发送的格式。
这个设计的好处是很大的:
- 灵活性:应用层可以随心所欲地添加任何自定义消息类型来管理状态,而无需担心影响 LLM。
- 效率:最大限度地减少了发送给 LLM 的无效信息,节省了上下文窗口和 token 消耗。
- 解耦:应用逻辑和模型交互逻辑被清晰地分离开来。
此外,types.ts中定义的三层嵌套的生命周期事件(AgentEvent)也极其精妙,它让 Agent 的每一步行动(agent 启动/结束 → turn 启动/结束 → message 启动/更新/结束)都变得细粒度可观测,彻底解决了 Agent 的黑盒问题。
3.2 设计精髓之二:核心循环(agent-loop.ts)——双层 While 循环的自主引擎
agent-loop.ts是 Pi 的心脏,它实现了 Agent 自主执行任务的核心循环。这个模块最核心的设计,是一个双层while循环结构。
- 内层循环:由工具调用(Tool Call)和用户中途干预(Steering)驱动。
- 外层循环:由后续任务(Follow-Up)驱动。
这种设计,让 Agent 的任务流转变得既高效又极具弹性。
用一个具体的例子走一遍流程
任务:帮我写一个 Python 脚本,计算斐波那契数列的第 n 项,并把它保存到fib.py文件中,然后运行它测试一下。
流程开始:
- 用户输入初始 Prompt,核心循环启动。
- 进入内层循环:
- LLM 被调用,它分析了任务,决定第一步是写代码。它返回一个
write工具调用指令,内容是 Python 代码。 write工具被执行,fib.py文件被创建。- 内层循环继续,LLM 看到文件已创建,决定下一步是运行测试。它返回一个
bash工具调用指令,内容是python fib.py。 bash工具被执行,脚本运行结果返回。- 关键点 1:用户中途干预。就在
bash工具执行时,用户突然发现代码里有个小 bug,他立刻输入了一条修正消息:“不对,n=0 时应该返回 0!”这条消息被标记为Steering消息。 - 系统检测到
Steering消息,会立即中断当前正在执行的工具链。后续如果还有排队的工具调用,都会被跳过,并返回一个“因用户干预而跳过”的错误。 - 内层循环跳出,并将用户的
Steering消息、已成功执行的工具结果、被跳过工具的错误信息,一起注入到上下文中。 - 再次进入内层循环:LLM 看到了所有这些信息,它理解了“哦,用户在我执行测试的时候打断了我,指出了一个 bug”。于是,它生成一个新的
edit工具调用,去修复fib.py里的 bug。 - 修复完成后,它再次生成
bash工具调用来运行测试。这次测试通过。 - 内层循环检查,没有更多的工具调用了,于是内层循环结束。
- LLM 被调用,它分析了任务,决定第一步是写代码。它返回一个
- 进入外层循环:
- 关键点 2:后续任务。就在 Agent 即将结束任务时,用户又输入了一条消息:“干得不错!现在帮我把它改成一个递归版本的。”这条消息被标记为
FollowUp消息。 - 外层循环检测到了
FollowUp消息,它不会让 Agent 停下来,而是把这条新消息作为新的待处理任务,重新启动内层循环。 - Agent 开始新一轮的
edit和bash操作,直到递归版本也完成并通过测试。
- 关键点 2:后续任务。就在 Agent 即将结束任务时,用户又输入了一条消息:“干得不错!现在帮我把它改成一个递归版本的。”这条消息被标记为
- 任务结束:这次,当内层循环结束后,外层循环没有检测到任何新的
FollowUp消息,于是整个核心循环优雅地退出。
通过这个精巧的双层循环设计,Pi 在没有复杂规划模块的情况下,实现了任务的自主推进、用户的实时干预,以及任务的无限续杯,同时逻辑保持得异常简洁。
双层循环图示(文字版)
- 内圈:工具执行与干预循环
- AI 根据当前目标进行一系列工具调用(Tool Call)。
- 用户通过 Steering 提供反馈或新指令。
- AI 根据用户干预调整策略,继续工具执行。
- 一轮完成后,交付阶段性结果。
- 外圈:任务推进循环
- Follow-Up 检查当前任务是否还有新的子任务或下一步。
- 有新任务:推进下一轮。
- 无新任务:任务完成。
3.3 设计精髓之三:Agent 类(agent.ts)——优雅的状态容器与消息队列
如果说agent-loop.ts是 Agent 的心脏,那么agent.ts里实现的 Agent 类,就是Pi 的大脑。它负责管理 Agent 的运行状态,并为上层应用提供了一套极其简洁、职责清晰的 API。
核心设计:Steering 与 FollowUp 双消息队列。
- Steering 队列:用于存放用户在 Agent 工作时发送的中途干预消息。
- FollowUp 队列:用于存放用户随时添加的后续任务消息。
这两种队列的设计,对应了核心循环中的两种驱动力。
为什么要分两种队列?
因为它们处理的时机和方式完全不同。Steering消息需要立即中断当前工作,具有高优先级;而FollowUp消息则是在当前工作完成后,才按顺序执行。
此外,每个队列还支持两种模式:all(一次性处理队列中的所有消息)和one-at-a-time(一次只处理一条)。后者的设计非常贴心,比如用户快速连续发送了 3 条修正指令,如果用all模式,Agent 可能只会响应最后一条;而用one-at-a-time模式,Agent 会确保逐条处理,不会遗漏任何指令。
Agent 类暴露给外部的 API 也极其清晰。
核心 API:prompt、continue、steer、followUp、abort
prompt():当 Agent 空闲时,用于发起一个新任务。continue():当 Agent 因出错或暂停而空闲时,用于从当前状态继续。这在错误恢复时特别有用,LLM 能看到之前的错误上下文,并尝试不同的策略。steer():当 Agent 正在工作时,用于发送中途干预指令。followUp():随时用于添加后续任务。abort():强制中止当前任务。
这套 API 的设计,让上层应用(比如一个 UI 界面)可以像遥控一个真人一样,轻松地与 Agent 进行交互。
四、Pi 的设计哲学:“不做什么”的智慧
深入剖析完 Pi 的核心模块,我们再回过头来看它的设计哲学,会有一种豁然开朗的感觉。Pi 的强大,不在于它“做了什么”,而在于它经过深思熟虑后,决定“不做什么”。
- 不做 Plan Mode:用
PLAN.md文件换来完全的可观测性和可复用性。 - 不做 MCP 支持:用 CLI 工具的按需加载,避免了对上下文窗口的浪费。
- 不做 Sub-Agent:用
bash自我调用,在实现功能的同时,避免了“黑盒中的黑盒”。 - 不做 maxSteps 限制:让循环自然结束,把控制权交给任务本身。
- 不做复杂的权限检查:默认信任运行环境(当然官方也提供了容器化方案给需要安全隔离的用户)。
这些所谓的“不做”,每一个都是在解决当前 Agent 开发的痛点,每一个都体现了对极简主义、可观测性、可干预性这三大核心原则的坚守。
我们能从 Pi 身上学到什么?
Pi 的设计哲学虽然激进,但并非适用于所有场景。它最适合的是那些需要高可观测性、高灵活性和用户强交互的编码/CLI 类 Agent。
那么,我们能从 Pi 身上借鉴哪些思路呢?
- 如果你在构建编码 Agent:可以大胆借鉴 Pi 的“4 工具 +
bash”极简策略,你会发现,这远比你想象的要强大。 - 如果你的 Agent 需要支持跨模型迁移:一定要学习 Pi 的“最晚转换”模式,将应用状态和模型上下文解耦,能让你的应用层逻辑变得异常清爽。
- 如果你的 Agent 需要支持用户中途干预:Pi 的双队列机制和 Steering 中断逻辑,是目前我见过最优雅的实现之一。
- 如果你受够了 Agent 的黑盒调试:Pi 的三层事件生命周期系统,能让你对 Agent 的每一步都了如指掌。
相反,如果你的项目需要极其复杂的多 Agent 编排,或者需要严格的安全沙箱,那么 Pi 的理念可能与你的需求背道而驰,需要谨慎借鉴。