AI Agent界顶顶大名的Pi是如何设计实现的?
作为龙虾(OpenClaw)早期的实现基座, 开源AI Agent项目“pi”,正被越来越多人所提及. 其优雅简洁的设计以及便捷的扩展能力让你可以轻松的基于它进行二次创作而快速地得到自己独有的Agent. 尤其在各路主流Coding Agent内置了各种臃肿上下文的情况下(比如在claude code输入一个hello则动辄携带上万token的提示词), 清爽简洁的pi则看起来别具一格.
而从agent设计入门和借鉴的角度, pi也是再好不过的一个参考项目.
废话不多说, 本篇从全局出发一窥pi的设计思想和整体架构.
从v0.80.3开始, pi逐渐调整架构, 拆分出更多细分职责的包. 然而旧版本在包划分上更简洁易于理解. 本篇先使用v0.80.3以前版本来分析.下篇将分析最新版本.
1. 概览
pi主要是用TypeScript开发, 是一个 monorepo,4个npm包按依赖方向自下而上堆叠:
层 | 包名 | 角色 | 能否独立运行 |
|---|---|---|---|
L1 | @earendil-works/pi-ai | 多 provider 统一 LLM 流式 API | 可(被其它包引用) |
L2 | @earendil-works/pi-agent-core | 通用 Agent 运行时(循环 / 状态 / 工具 / Session) | 可(提供 SDK) |
L3 | @earendil-works/pi-tui | 终端 UI 与差分渲染 | 可(提供组件库) |
L4 | @earendil-works/pi-coding-agent | 交互式 CLI 应用(main.ts + 模式分发) | 最终运行入口 |
用户执行的pi命令 = L4 启动,依次调用 L2 驱动 Agent,Agent 通过 L1 调用大模型,最终通过 L3 在屏幕渲染消息。
2. 依赖方向图
依赖关系:L4 同时依赖 L2、L3、L1;L2 与 L3 都依赖 L1。方向单向,下层不知道上层。
关键约束:依赖方向是单向的,下层永远不知道上层的存在。这让pi-agent-core可以脱离 CLI 被任何宿主(SDK、测试、第三方应用)使用。
3. 各模块职责
pi-ai:屏蔽各 LLM provider(Anthropic / OpenAI / Google / Bedrock / Mistral / Cloudflare / Vertex / GitHub Copilot / OpenAI Codex 等)的协议差异,对外只暴露
streamSimple(model, context, options)。内置fauxprovider 用于测试。pi-agent-core:与 UI / CLI / 应用场景无关的通用 Agent 运行时。提供低层 Agent Loop(流式 + 工具调用循环)、高层 AgentHarness(Session 集成 + Compaction + Skills)、状态机、工具协议。
pi-tui:通用 TUI 库。
TUI类提供组件树 + 键盘事件 + 差分渲染;Editor、Input、Markdown等是可复用组件。无任何 Agent 业务逻辑。pi-coding-agent:把上述三者组装成用户可用的 CLI。负责 CLI 解析、SessionManager、扩展系统、内置工具、多种运行模式(interactive / print / json / rpc)。
4. 启动链路:从pi命令到第一次回复
以下时间线描述一次pi启动在 4 个包之间发生了什么。
4.0 启动总览
粉=L4 / 蓝=L2 / 黄=L1。虚线是事件回流方向,与实线反向。
步骤 1:CLI 入口(L4)
packages/coding-agent/src/main.ts:477pi-coding-agent
export async function main(args: string[], options?: MainOptions)main()是CLI入口函数(由 dist 编译后的dist/cli.js调用)。入口函数顺序执行:解析参数 → 决定模式 → 加载配置 → 构建 runtime → 分发到模式。
步骤 2:参数解析与模式分发(L4)
main.ts:497-509.pi-coding-agent
const parsed = parseArgs(args); // cli/args.tslet appMode = resolveAppMode(parsed, process.stdin.isTTY);
appMode类型为"interactive" | "print" | "json" | "rpc",由命令行参数和 stdin 是否是 TTY 共同决定。
步骤 3:创建 SessionManager(L4)
main.ts:250-322pi-coding-agent
根据--fork/--session/--resume/--no-session等参数决定是新建、分叉、恢复还是纯内存 Session。核心 API:
SessionManager.inMemory(cwd) // 纯内存,不落盘SessionManager.open(path, dir) // 打开已有 JSONLSessionManager.forkFrom(path) // 从已有分叉
步骤 4:构建 Agent Session runtime(L4 ↔ L2)
packages/coding-agent/src/core/sdk.ts:204pi-coding-agent
export async function createAgentSession(options)这是 SDK 入口。它做 5 件事:
解析cwd、agentDir、authStorage、modelRegistry。
恢复历史Session(sessionManager.buildSessionContext())。
解析模型(options → 历史 → 配置 → provider 默认)。
- 实例化
Agent(来自 L2 pi-agent-core)。
构造
AgentSession封装 Agent 与 SessionManager。
关键代码:sdk.ts:331-394
agent = new Agent({
initialState: { systemPrompt: "", model, thinkingLevel, tools: [] },convertToLlm,streamFn: async (model, context, options) => { return streamSimple(model, context, { ... }); },transformContext, steeringMode, followUpMode, ...});
步骤 5:Agent 启动首轮对话(L2)
packages/agent/src/agent.ts:386-400pi-agent-core
private async runPromptMessages(messages: AgentMessage[]) {
await this.runWithLifecycle(async (signal) => {await runAgentLoop(messages,this.createContextSnapshot(),this.createLoopConfig(),(event) => this.processEvents(event), // ← emit 回调signal,this.streamFn, // ← streamSimple);});}
Agent 持有_state状态机,调用runAgentLoop驱动 LLM 与工具循环。详见后续对Agent Loop的详解。
步骤 6:流式调用 LLM(L1)
packages/ai/src/stream.tspi-ai
streamSimple(model, context, options)根据model.api在api-registry.ts查找对应provider实现,返回AssistantMessageEventStream。provider 屏蔽 HTTP/SSE/WebSocket 差异。
步骤 7:事件回流到 UI(L4 → L3)
agent.ts:509-556(Agent 层)+ agent-session.ts:460-510(Coding Agent 层)pi-agent-core / pi-coding-agent
LLM 流式事件经processEvents更新 Agent 状态,然后通过subscribe()传递给AgentSession._handleAgentEvent,再转发给InteractiveMode,最终由TUI差分渲染到屏幕。详见后续消息传递分发链路详解。
5. 端到端数据流
从用户按键到屏幕像素的完整调用链,每个箭头都是一次跨层调用。
6. 关键模块职责映射
职责 | 所在包 | 关键文件 | 说明 |
|---|---|---|---|
CLI 入口 | pi-coding-agent | main.ts:477 | 解析参数、决定模式、调用 createAgentSession |
Session 持久化 | pi-coding-agent | session-manager.ts | 条目树 + JSONL 读写 |
扩展系统 | pi-coding-agent | core/extensions/ | 扩展加载、事件总线、生命周期 |
内置工具 | pi-coding-agent | core/tools/ | read / write / edit / bash / grep / find / ls |
交互模式 | pi-coding-agent | interactive-mode.ts | 主循环、事件订阅、UI 协调 |
Agent 状态机 | pi-agent-core | agent.ts | _state 状态、steering/followUp 队列 |
Agent Loop | pi-agent-core | agent-loop.ts | runAgentLoop / runLoop / 流式处理 |
Agent Harness | pi-agent-core | harness/agent-harness.ts | 高层封装:Session + Compaction + Skills |
通用 Session | pi-agent-core | harness/session/ | JSONL/Memory repo、buildContext |
Compaction | pi-agent-core | harness/compaction/ | 上下文压缩 / 分支摘要 |
流式 API | pi-ai | stream.ts | streamSimple 统一入口 |
Provider 注册 | pi-ai | api-registry.ts | 按 api 类型查找 Provider |
模型元数据 | pi-ai | models.ts | models.generated.ts 静态索引 |
OAuth | pi-ai | utils/oauth/ | Claude / ChatGPT / Copilot OAuth |
TUI 主类 | pi-tui | tui.ts | 组件树 + 键盘事件循环 |
编辑器 | pi-tui | components/editor.ts | 输入框 + 历史 + 自动补全 |
差分渲染 | pi-tui | tui.ts:extractSegments... | 按行比较,仅重绘差异行 |
7. 设计原则
- 单向依赖
:L1 ← L2 ← L3 ← L4,禁止反向。下层不知道上层存在,
pi-agent-core可被任意宿主复用。 - 事件流而非命令流
:Agent Loop 通过
emit(event)推送事件,监听器按订阅顺序处理。UI 端订阅 → Coding Agent 层订阅 → Agent 处理 状态。 - 协议式工具调用
:LLM 返回的
tool_use块被解析为AgentTool调用,工具执行结果以toolResult消息回写上下文。 - Session 与 State 解耦
:Agent 的
_state是运行时内存,SessionManager是 JSONL 持久层,二者通过 AgentSession 桥接并双写。 - 扩展点优先于硬编码
:扩展系统提供
tool_call、before_agent_start、input、tool_result等钩子,业务能力大多可由扩展覆盖。 - 可测试的 Provider 边界
:
pi-ai内置fauxprovider,所有上游代码都可以在零成本下测试。
