三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

AI Agent界顶顶大名的Pi是如何设计实现的?

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类提供组件树 + 键盘事件 + 差分渲染;EditorInputMarkdown等是可复用组件。无任何 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 件事:

  1. 解析cwd、agentDir、authStorage、modelRegistry。

  2. 恢复历史Session(sessionManager.buildSessionContext())。

  3. 解析模型(options → 历史 → 配置 → provider 默认)。

  4. 实例化Agent

    (来自 L2 pi-agent-core)。

  5. 构造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. 设计原则

  1. 单向依赖

    :L1 ← L2 ← L3 ← L4,禁止反向。下层不知道上层存在,pi-agent-core可被任意宿主复用。

  2. 事件流而非命令流

    :Agent Loop 通过emit(event)推送事件,监听器按订阅顺序处理。UI 端订阅 → Coding Agent 层订阅 → Agent 处理 状态。

  3. 协议式工具调用

    :LLM 返回的tool_use块被解析为AgentTool调用,工具执行结果以toolResult消息回写上下文。

  4. Session 与 State 解耦

    :Agent 的_state是运行时内存,SessionManager是 JSONL 持久层,二者通过 AgentSession 桥接并双写。

  5. 扩展点优先于硬编码

    :扩展系统提供tool_callbefore_agent_startinputtool_result等钩子,业务能力大多可由扩展覆盖。

  6. 可测试的 Provider 边界

    pi-ai内置fauxprovider,所有上游代码都可以在零成本下测试。

← 返回列表