第 1 章:Pi-Agent 总览 —— 一个框架的三重身份
你可能因为三种不同的原因开始学习 Pi:
- 想找个好用的编码 Agent——受够了臃肿的工具,想要极简、透明、快的东西;
- 想知道 Agent 到底怎么做的——翻过的框架要么太复杂(几万行起跳),要么太简陋(一个 while 循环就敢叫 Agent);
- 要做自己的 Agent——有垂直场景需求,需要基于 SDK 二次开发,不想从零造轮子。
这三个问题恰好对应 Pi 的三个身份:编码工具、学习教材、开发 SDK。三个身份指向同一个项目,这本身就值得好奇。
Pi 是什么
一句话定义:Pi 是一款极简、可扩展的终端编码 Agent 外壳(coding agent harness),由 libGDX 作者 Mario Zechner 创建,全部用 TypeScript 编写,MIT 协议开源。
拆开看:
- "编码 Agent"——能读懂代码库,写代码、改代码、跑命令,像坐在你旁边的结对编程伙伴;
- "终端外壳"——住在终端里,没有 GUI 和 IDE 插件,输出写进终端回滚缓冲区。这个形态决定了一切后续设计选择;
- "极简"——核心四个内置工具(read / write / edit / bash)、约 90 词的静态系统提示词模板(运行时拼接后通常 200–400 词)、约 12000 行 TUI 代码。它刻意不构建 MCP、子 Agent、计划模式、权限弹窗、后台 bash;
- "可扩展"——缺失的功能通过 TypeScript 扩展、技能、Pi Package 补充。
关键数字(基于 v0.80.2 源码)
| 指标 | 数值 | 说明 |
|---|---|---|
| GitHub Stars | 64,000+(截至 2025 年中) | 十个月的增长,社区验证了需求 |
| 内置工具数 | 4 核心 + 3 辅助 | 核心:read / write / edit / bash;辅助:grep / find / ls |
| 系统提示词 | 静态模板约 90 词,运行时 200–400 词 | 对比 Claude Code 的数万 token |
| TUI 代码量 | 约 12000 行 | 核心 tui.ts 单文件约 1700 行 |
| 支持供应商 | 30+ 家 | 源码 KnownProvider 枚举实际 35 个(含区域变体),独立品牌约 27 个 |
| 核心包数量 | 4 个 | pi-ai / pi-agent-core / pi-tui / pi-coding-agent |
| 运行模式 | 4 种 | 交互 / print-JSON / RPC / SDK |
关于数字的说明:官网早期材料常说"4 个内置工具"、"15+ 家供应商"、"约 600 行 TUI"——前两者分别指核心工具(不含辅助工具)和早期版本列举的知名厂商;"600 行 TUI"是早期版本数字,v0.80.2 已增长到约 12000 行。本表按源码实际数字呈现,避免你对照源码时困惑。
四个核心包,各司其职
┌──────────────────────────────────────────┐
│ pi-coding-agent │ ← 完整 CLI 产品 + SDK
│ 系统提示词 · 内置工具 · 会话管理 · 扩展 │
├──────────────────────────────────────────┤
│ pi-tui │ pi-agent-core │ ← 终端 UI + Agent 引擎
│ 差分渲染 · 组件系统 │ AgentLoop · 工具 │
│ │ 系统 · 事件流 │
├──────────────────────┴───────────────────┤
│ pi-ai │ ← 多供应商 LLM 抽象
│ 统一 API · 上下文交接 · 流式 · Token 追踪│
└──────────────────────────────────────────┘
其中 pi-ai / pi-agent-core / pi-coding-agent 构成一条三层堆栈(每层可独立使用),pi-tui 是一个正交的 UI 库,与 Agent 体系完全解耦——你可以只用 pi-ai 调模型,也可以只用 pi-agent-core 在自己应用里跑 Agent Loop,完全不碰 CLI。这是 Pi 作为 SDK 的核心价值。
外围还有一个实验性的
pi-orchestrator(v0.80.x 新增),负责多 Agent 编排,不在核心学习主线内。
视角一:作为编码工具
上下文干净得令人羡慕
Pi 的系统提示词 + 工具定义加起来不到 1000 个 token。对比 Claude Code 的数万 token,差距不是一点半点。上下文窗口是 Agent 最稀缺的资源——固定指令占得越少,留给你的代码和项目上下文的空间就越多。而且 Pi 不会在你背后偷偷注入任何东西:所有 prompt 源码公开可见,你甚至可以用 SYSTEM.md 文件把整个系统提示词替换掉。
技能(Skills)采用渐进式披露:只在被调用时才加载,不预加载进每个会话。你可以拥有一座能力库,而不必为用不上的会话付上下文开销。
透明到骨头里
你能看到模型收到的每一条消息、每个工具调用的完整输入输出、跨会话的成本追踪、会话的 HTML/JSON 导出。在其他编码 Agent 里,Agent 做了奇怪决定、你却看不到它"看到"了什么——在 Pi 里没有这种黑箱。
模型自由与树状会话
Pi 支持 30+ 家供应商,更重要的是可以在会话中途切换模型(/model 或 Ctrl+L)——比如用 Claude 做复杂推理,再切到便宜模型做简单文本处理。pi-ai 在底层处理跨供应商的上下文交接(思考轨迹转换、供应商特定签名 blob 回放等),本质上有损,但远比"切换等于重新开始"好。
会话存成**树结构(DAG)**而不是线性日志:用 /tree 跳到任意历史消息,从那里分叉出新分支继续探索,所有分支活在同一个文件里。调试时可以在同一起点尝试三种修复方案,不必担心"回不去了"。
YOLO 模式的安全哲学
Pi 默认不经审批弹窗直接执行动作。Mario 的论点是:基于审批的安全措施会导致"弹窗疲劳",最终要么被整体禁用、要么沦为看都不看就机械点同意的"安全表演(security theater)"。他建议以容器化作为真正的安全边界。如果确实需要审批流程,大约 50 行扩展代码就能自己实现——框架提供了所有必要的钩子。
用 models.json 接入第三方模型
官方默认让你设 ANTHROPIC_API_KEY,但实际项目里你大概率想用国内的智谱、DeepSeek、Kimi、Qwen——这些不可能靠一个环境变量搞定,你需要告诉 Pi:base URL 在哪、用哪种 API 协议、模型 ID 叫什么、上下文窗口多大。
Pi 的解法是本地配置文件 ~/.pi/agent/models.json,由 ModelRegistry.create() 在启动时自动读取,无需命令行参数:
{
"providers": {
"zhipu": {
"baseUrl": "https://open.bigmodel.cn/api/paas/v4",
"api": "openai-completions",
"apiKey": "<your-zhipu-key>",
"models": [
{ "id": "glm-4.5-air", "name": "GLM-4.5-Air" },
{ "id": "glm-4-flash", "name": "GLM-4-Flash" }
]
},
"deepseek": {
"baseUrl": "https://api.deepseek.com",
"api": "openai-completions",
"apiKey": "<your-deepseek-key>",
"models": [
{ "id": "deepseek-chat", "name": "DeepSeek V3" },
{
"id": "deepseek-reasoner",
"name": "DeepSeek R1",
"contextWindow": 128000,
"maxTokens": 64000
}
]
}
}
}
上面的模型 ID 和窗口数值仅作示意(
deepseek-chat/deepseek-reasoner是 DeepSeek 官方真实模型 ID),实际以各厂商文档为准。
关键字段:
providers——键名(zhipu/deepseek)是你自己起的,会作为模型的provider字段显示;api——选协议,最常见的是openai-completions(国内厂商几乎都支持 OpenAI 兼容接口),还有anthropic-messages、openai-responses。这个字段决定 Pi 用哪种请求格式去调;apiKey——明文存放,务必把.pi/加进.gitignore;models——id是调 API 时传的真实模型名,name是 TUI 里的显示名;contextWindow/maxTokens——可选,影响上下文压缩策略。
配置后三种用法:会话中 /model 或 Ctrl+L 临时切换;在 ~/.pi/agent/settings.json 里设 "defaultProvider" 和 "defaultModel" 作为默认;用 pi models 命令查列表(出错时会在终端顶部打印解析错误,方便排查)。进阶用法还有 modelOverrides(给内置 provider 打补丁,比如把 baseUrl 指向自部署网关)和 compat 字段(处理非标准接口兼容性),schema 完整定义见 model-registry.ts:158-218。
视角二:作为学习教材
很多 Agent 框架动辄几万行代码,光是搞清楚启动流程就要读几十个文件。Pi 的核心循环只有几百行,但设计质量毫不"简陋"——它在 TerminalBench 基准测试中排名第二(使用 Claude Opus 4.5,截至 2025 年中),仅次于 Terminus,尽管缺少 MCP、子 Agent、计划模式等功能。
这意味着你可以在有限时间内真正"读完"一个高质量 Agent 的全部核心代码——这对 Claude Code 和 LangChain 都是不可能的。
更宝贵的是它的减法哲学。Pi 官网的 "What we didn't build" 是一份倒过来的宣言:竞争对手在罗列功能,Pi 在罗列舍弃。每一次舍弃背后都有清晰的工程理由:
| Pi 不做的 | 为什么不做 | 替代方案 |
|---|---|---|
| MCP 支持 | MCP 服务器(如 Playwright MCP)会在会话开始灌入 13,700+ token 的工具描述 | 带 README 的 CLI 工具,Agent 按需读取 |
| 子 Agent | 增加复杂度,降低可观察性 | tmux 多实例,或专用扩展 |
| 权限弹窗 | 导致"弹窗疲劳",沦为安全表演 | 容器化隔离,或用扩展搭审批流程 |
| 计划模式 | 计划写到 markdown 文件里更持久、可复用 | 写 plan.md |
| 后台 bash | tmux 已经解决了这个问题 | 用 tmux |
| 内置待办 | TODO.md 文件更灵活 | markdown 文件或自建扩展 |
看一个"什么都做了"的框架,你只能学到"他们做了什么";看一个刻意什么都不做的框架,你才能学到"做 Agent 到底需要什么"。
视角三:作为开发 SDK
三层堆栈 + 一个正交 UI 库
Layer 1: pi-ai——只管调模型。 不依赖任何 Agent 概念,可以用在任何需要调 LLM 的项目里:
// 入口在 compat 子模块(不在主入口)
import { getModel, stream } from '@earendil-works/pi-ai/compat';
import type { Context } from '@earendil-works/pi-ai';
const model = getModel('anthropic', 'claude-sonnet-4-5');
// Context 是 interface(不是 class),用对象字面量构造
const context: Context = {
systemPrompt: 'You are helpful.',
messages: [{ role: 'user', content: 'Hello!' }],
};
// stream() 返回事件流;complete() 则直接 await 拿到最终 AssistantMessage
const eventStream = stream(model, context);
for await (const event of eventStream) {
if (event.type === 'text_delta') process.stdout.write(event.delta);
}
Layer 2: pi-agent-core——只管跑循环。 依赖 pi-ai,但不依赖 pi-coding-agent 或 pi-tui。任何需要"模型思考 → 调工具 → 看结果 → 再思考"循环的场景都能用,不限于编码:
// 教学示意(简化);真实 API 见 agent.ts 的 Agent 类
// 构造函数只接受 AgentOptions(钩子、streamFn、convertToLlm 等)
// model/tools/systemPrompt 在调用 prompt() 时通过 AgentSessionConfig 传入
import { Agent } from '@earendil-works/pi-agent-core';
const agent = new Agent({ /* AgentOptions */ });
// 运行入口是 agent.prompt(),事件通过 subscribe(listener) 订阅
Layer 3: pi-coding-agent——完整 CLI + SDK。 把下面两层组装成产品,同时暴露"无头"(headless)SDK 接口:
import { createAgentSession } from '@earendil-works/pi-coding-agent';
import { getModel } from '@earendil-works/pi-ai/compat';
const session = await createAgentSession({
cwd: '/path/to/project',
model: getModel('anthropic', 'claude-sonnet-4-5'), // Model 对象,不是 {id, api}
});
session.subscribe((event) => {
if (event.type === 'turn_end') console.log('Agent 完成了一轮思考');
});
await session.prompt('Read the codebase and explain the architecture.');
侧库: pi-tui——与 Agent 无关的终端 UI 库。 它的 package.json 只依赖 get-east-asian-width 和 marked 两个包,源码里零处 import 来自 @earendil-works/pi-* 兄弟包。约 12000 行实现了差分渲染(每帧只重绘变化的单元格)、保留模式 UI(类似 React 的声明式组件,而非 ncurses 的命令式)和内置组件(带自动补全的输入框、markdown 渲染、语法高亮、模糊搜索)。任何需要终端交互界面的 Node.js 程序都能用——这是游戏引擎作者 Mario 按游戏引擎思路重写的"副产物",反而成了最容易脱离 Pi 单独复用的部分。
四种运行模式
| 模式 | 用途 | 示例 |
|---|---|---|
| 交互模式 | 日常编程的经典 TUI | pi |
| print/JSON 模式 | 脚本和 CI/CD 流水线 | pi -p "explain this code" |
| RPC 模式 | 通过 stdin/stdout 交换 JSON | 集成进非 Node.js 程序 |
| SDK 模式 | 嵌入自己的应用 | createAgentSession() |
多模式设计意味着 Pi 可以从"开发者手边的工具"无缝演进为"生产系统中的 Agent 能力提供者",不需要在项目成长后换框架。
总结
Pi 是一个"三位一体"的项目:作为工具,它极简、透明、可驾驭;作为教材,它高质量且读得完,每行代码都有"为什么"的答案;作为 SDK,它层次分明、可独立复用。
但最重要的是,Pi 证明了做减法是一种有竞争力的产品立场。在一个朝着"全包"狂奔的赛道里,"我不需要的,就不会被构建"这句话本身,就是一项真正的功能。
版本说明:本系列基于 Pi v0.80.2 编写,代码分析以源码为准。官方仓库已从
badlogic/pi-mono迁移至earendil-works/pi。本文改编自 CC-BY-SA-4.0 许可的开源教程。