Pi Agent 完整使用与开发指南
Pi Coding Agent v0.83.0 · 2026-08-01 · 生产级实战手册
调研自源码 + 官方 30+ 文档 + 飞书集成实战 (pi-lark-bridge)
写在前面
这本手册不是 Pi 官方文档的搬运,而是一份生产级实战手册。它的目标读者分两类:
- 飞书集成开发者:要把 Pi 嵌进 IM 机器人、消息网关、自动化系统的工程师。重点看 Chapter 8 (RPC 协议) + Chapter 9 (SDK 集成) + Chapter 10 (架构)。
- 通用 Pi 用户:想在终端里用 Pi 干活的开发者、系统管理员。重点看 Chapter 2 (5 分钟上手) + Chapter 3 (models.json) + Chapter 4 (模式详解)。
每章都按”原理 → 操作 → 实战 → 坑”四段式组织。代码块都经过实际验证,可以直接复制运行。
章节导航
| # | 章节 | 核心内容 | 受众重点 |
|---|---|---|---|
| 1 | Chapter 1 · 安装与环境准备 | npm/curl/Termux/WSL 四种安装路径、API key vs OAuth 两种认证 | 通用 |
| 2 | Chapter 2 · 5 分钟快速上手 | 第一次 interactive / print / RPC 调用 | 通用 |
| 3 | Chapter 3 · models.json 完整配置 | Provider/Model schema、MiniMax m3 实战 | 通用 + 集成 |
| 4 | Chapter 4 · 运行模式详解 | interactive/print/RPC/SDK 四种模式 | 通用 + 集成 |
| 5 | Chapter 5 · 工具系统 | 内置工具 + 扩展 + custom tools | 通用 + 集成 |
| 6 | Chapter 6 · System Prompts 与 Context Files | AGENTS.md / SYSTEM.md / Skills | 通用 |
| 7 | Chapter 7 · 会话管理 | session 文件格式 + branching + compaction | 通用 |
| 8 | Chapter 8 · RPC 协议参考 ⭐ | JSONL 帧 + event 目录 + pi-lark-bridge 源码 | 集成必读 |
| 9 | Chapter 9 · SDK 与程序化集成 ⭐ | createAgentSession + 嵌入模板 | 集成必读 |
| 10 | Chapter 10 · 架构深读 ⭐ | 模块依赖 + 状态机 + 7 张 Mermaid 图 | 集成必读 |
| 11 | Chapter 11 · 故障排查与最佳实践 | 12 类常见错误 + 7 个坑 + 性能调优 | 全部 |
为什么需要这本手册
Pi 的官方文档(30+ 篇)覆盖完整但散落在 30+ 个 markdown 文件,缺乏结构化索引和实战串联。本书做了三件事:
- 按集成场景重组:把”如何把 Pi 塞进我的 IM bot”作为最高优先级的叙事线
- 补充实战章节:基于
pi-lark-bridge真实集成代码(已上线的飞书机器人),给出可复用模板 - 代码即文档:所有 CLI 命令、JSON 配置、TypeScript 代码均来自真实项目,可直接复制运行
适用版本
- Pi Coding Agent:
0.83.0(2026-07-29) - Node.js: 18+
- 平台: Linux / macOS / Termux (Android) / Windows WSL
- OS 验证环境: Termux on Android 10, Node 20
Chapter 1 · 安装与环境准备
本章覆盖 Pi 的四种主流安装路径(npm / curl / Termux / Windows WSL)、认证方式选择、目录结构、与常见代理的兼容性验证。
实战环境:Termux on Android 10 / Node 20 / Pi 0.83.0,所有命令均已验证可执行。
1.1 安装方式选择
Pi 通过 npm 分发,支持四种主流安装路径:
| 场景 | 推荐方式 | 一行命令 |
|---|---|---|
| 桌面 Linux/macOS | npm 全局安装 | npm i -g --ignore-scripts @earendil-works/pi-coding-agent |
| CI / Docker / 临时容器 | curl 一键安装 | curl -fsSL https://pi.dev/install.sh | sh |
| Android 手机/平板 | Termux + npm | 见 1.4 节 |
| Windows | WSL2 + npm | 见 1.5 节 |
1.1.1 为什么 --ignore-scripts
Pi 的 npm 包不需要 install scripts(不依赖 postinstall 钩子下载二进制)。--ignore-scripts 是显式声明这一点,避免某些 sandbox 环境卡在 hook 上。
1 | npm install -g --ignore-scripts @earendil-works/pi-coding-agent |
1.1.2 安装位置
- 全局命令:
pi→ 通常在/usr/local/bin/pi或~/.npm-global/bin/pi(取决于 npm 配置) - 代码包:
/usr/local/lib/node_modules/@earendil-works/pi-coding-agent/ - 运行配置目录:
~/.pi/agent/(用户级,每个用户独立) - 会话/凭据: 全部存在
~/.pi/agent/下
验证安装:
1 | pi --version |
1.1.3 卸载
1 | # npm 安装 |
卸载不会清理 ~/.pi/agent/(凭据、session、扩展、skills 都保留),需要手动 rm -rf ~/.pi/agent/ 才能彻底。
1.2 认证方式
Pi 支持两类认证,互斥但并存:
1.2.1 方式 A:API Key(推荐用于 CI / 自定义模型)
1 | export ANTHROPIC_API_KEY=sk-ant-... # Claude |
启动时 Pi 自动识别。如果多个变量都设置了,Pi 会在 /model 面板里都列出来供你选。
实用技巧:Pi 也认自定义环境变量(在 models.json 里通过 $VAR_NAME 引用),这样不用把 key 写死:
1 | { |
启动前 export MY_API_KEY=sk-... 即可。
1.2.2 方式 B:OAuth 订阅(Claude Pro/Max、ChatGPT Plus/Pro、GitHub Copilot)
启动 Pi 后在交互模式里:
1 | /login |
然后从列表选一个订阅 provider。会走浏览器 OAuth flow,token 存在 ~/.pi/agent/auth.json(600 权限)。
Headless 环境(SSH、容器):/login 走不通时,Pi 0.83+ 支持直接粘贴回调 URL:
1 | /login |
1.2.3 我们项目实际配置
飞书集成项目用的是 API Key + 自定义 provider(详见 Chapter 3):
1 | # ~/.hermes/.env(自动脱敏,仅本机) |
Pi 启动时通过 ~/.pi/agent/models.json 引用 MINIMAX_CN_API_KEY,避免把 key 写死在配置文件里。
1.3 目录结构
安装后 ~/.pi/agent/ 长这样:
1 | ~/.pi/agent/ |
关键洞察:
auth.json必须保持chmod 600——Pi 启动会校验,权限不对会直接报错退出models.json改了之后不用重启,在 interactive 模式按/model就能看到新加的 providersessions/按日期分目录2026-08-01/、2026-07-31/等
1.3.1 自定义配置目录
如果想隔离(比如一台机器跑多个项目各用不同 key):
1 | export PI_CODING_AGENT_DIR=/path/to/alt/.pi/agent |
PI_CODING_AGENT_SESSION_DIR 可以单独覆盖 session 路径,CLI 的 --session-dir 优先级更高。
1.4 Termux 安装
在 Android 上跑 Pi 的官方推荐路径:
1 | # 1. 装 Termux (从 F-Droid 或 GitHub, **不要** Google Play) |
1.4.1 Termux 专属坑
/tmp只读:Pi 默认会把临时日志写/tmp,在 Termux 上要手动指定TMPDIR:1
2export TMPDIR=$HOME/.tmp
mkdir -p $TMPDIRHOME 路径特殊:
/data/data/com.termux/files/home,不是/root或/home/user。脚本里要写绝对路径。剪贴板:依赖
termux-clipboard-set/termux-clipboard-get。需要 Termux:API app 配合(光 CLI 不够),且要授权 Android 存储权限。Ctrl+V 图片粘贴不支持:官方文档明确说 Termux 没法用这个 feature。建议通过
/path/to/image直接传文件。npm 全局路径:
/data/data/com.termux/files/usr/lib/node_modules/,不是/usr/lib/node_modules/。手动 link 时要小心。
1.4.2 推荐 AGENTS.md
写到 ~/.pi/agent/AGENTS.md:
1 | # Agent Environment: Termux on Android |
Opening Files
1 | termux-open file.pdf |
Clipboard
1 | termux-clipboard-set "text to copy" |
1 |
|
进 WSL 后按 Linux 流程走:
1 | sudo apt update && sudo apt upgrade -y |
1.5.1 WSL 与 Windows 文件互通
- Windows 文件:
/mnt/c/Users/<you>/... - WSL 文件:
\\wsl$\Ubuntu\home\<you>\... - 建议 Pi 项目放在 WSL 文件系统(
~/projects/),不要放/mnt/c/——后者慢 10-100 倍
1.6 验证安装完整性
装完跑这几条确认一切 OK:
1 | # 1. 命令能找到 |
任意一步失败,回到对应章节查。
1.7 升级
1 | npm update -g @earendil-works/pi-coding-agent |
升级不会覆盖你的 ~/.pi/agent/(npm 全局包升级不影响用户配置)。新版本的 settings 迁移由 Pi 启动时自动跑。
小结
- 安装走
npm i -g --ignore-scripts,加上--ignore-scripts是惯例 - 认证分 API Key 和 OAuth 两条路,飞书集成项目统一走 API Key + 自定义 provider
- Termux 上要处理
/tmp只读、HOME 路径、剪贴板依赖 - 装完用
pi -p "回复 OK"验证最直观
Chapter 2 · 5 分钟快速上手
本章用三个最短路径带你跑通 Pi:一次 interactive 会话 → 一次 print 调用 → 一次 RPC 调用。
所有命令均已在 Pi 0.83.0 上验证。
2.1 准备工作
确认你已经按 Chapter 1 装好 Pi,且至少配了一种认证:
1 | pi --version |
如果你还没装,回到 Chapter 1 安装。
2.2 第一次:Interactive(交互模式)
最常见的用法,人在终端里和 Pi 对话。
1 | mkdir -p ~/projects/hello-pi && cd ~/projects/hello-pi |
启动后看到 TUI,输入:
1 | > Create a hello world Node.js script and run it |
Pi 会:
- 用
write工具创建hello.js - 用
bash工具跑node hello.js - 把输出读回来给你看
整个过程在终端里实时显示工具调用。退出:Ctrl+C 或输入 /exit。
2.2.1 关键交互
@引用文件:在 prompt 里输入@,触发 fuzzy search 文件1
> @src/app.ts 帮我找出所有的 bug
!直接执行 shell:不经过 LLM,自己跑命令1
> !ls -la
!!把输出喂给 LLM:自己跑命令但把结果当作 context1
> !!git diff HEAD~1
- Ctrl+V 粘贴图片(不支持 Termux):多模态模型可以看图
2.2.2 实用命令
| 命令 | 作用 |
|---|---|
/model |
切换 provider/model |
/login |
OAuth 登录 |
/compact |
压缩上下文 |
/branch |
会话分叉 |
/reload |
重载 AGENTS.md / 扩展 |
/export |
导出会话为 HTML |
/sessions |
列历史会话 |
/help |
看所有命令 |
2.3 第二次:Print(命令行单次)
适合 CI / 脚本调用,一次问一句话,回一次答。
1 | pi -p "What files are in this directory?" |
2.3.1 常用 flags
1 | # 指定模型 |
2.3.2 与 Claude Code / Codex 的区别
pi -p 在功能上等价于 claude -p / codex exec,但:
- Pi 支持自定义 provider(任意 OpenAI 兼容 endpoint)
- Pi 的 session 是 JSONL(人类可读、可 grep)
- Pi 的 print 输出包含 thinking block(可关闭)
2.4 第三次:RPC(协议集成)
这是 飞书集成 / IDE 嵌入 / Web 前端 的标准入口。Pi 作为子进程,通过 stdin/stdout JSON line 通信。
2.4.1 最简 RPC 调用
启动一个 Pi 子进程,发一条 prompt,收到回复后退出:
1 | # 用 print 模式直接看效果(不启动 RPC) |
输出:
1 | {"type":"response","command":"prompt","success":true,"id":"1"} |
2.4.2 Node.js 客户端(飞书集成就用这个)
不要自己 spawn + 解析 JSON line!用 Pi 自带的 RpcClient:
1 | import { RpcClient } from "@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js"; |
2.4.3 多轮对话
RpcClient 内置请求队列,单进程常驻:
1 | // 第一轮 |
重点:promptAndWait 会等当前 turn 完全结束才返回。如果中途调用,会自动排队。
2.5 选择哪个模式?
| 场景 | 推荐 |
|---|---|
| 自己用、探索代码 | pi (interactive) |
| 一次性问答、写脚本 | pi -p "..." (print) |
| CI / cron / 自动化 | pi -p "..." --no-session |
| IM 机器人、Web IDE、自动化平台 | RPC + RpcClient |
| 自定义 UI / deep 嵌入 | SDK createAgentSession |
2.6 下一步
- 想搞懂
models.json配置:跳 Chapter 3 - 想深入 4 种模式 的差异:跳 Chapter 4
- 要把 Pi 嵌入 飞书 / IM bot:直接看 Chapter 8 RPC 协议 + Chapter 9 SDK 集成
Chapter 3 · models.json 完整配置
本章是 Pi 配置的核心:所有自定义 provider / 模型都在
~/.pi/agent/models.json里定义。
重点:基于飞书集成项目实战,演示 MiniMax m3 / Step Plan / Anthropic / OpenAI / 自定义代理 5 种典型配置。
3.1 为什么需要 models.json
Pi 内置了一堆主流 provider(Anthropic、OpenAI、Google、Mistral、xAI、Bedrock 等),但:
- 新模型发布 → 内置版本不一定及时跟进
- 私有部署(vLLM、Ollama、LM Studio、自建代理)→ 必须自己配
- 中国区域 provider(MiniMax、Step Plan、DeepSeek、Kimi、智谱)→ 需要 baseUrl
- 多 provider 共存 → 不用每次
--provider xxx,/model面板直接列出来
models.json 解决了以上所有问题,改了不用重启 Pi,在 interactive 模式按 /model 立刻生效。
3.2 文件位置 & 权限
1 | ~/.pi/agent/models.json |
完整 schema 在源码 dist/core/model-config.js 里(TypeBox 定义)。下面挑重点讲。
3.3 顶层结构
1 | { |
注意:key 是 provider id,不是 provider 名字(minimax-cn vs “MiniMax China”)。
3.4 Provider 字段(ProviderConfig)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | - | 显示名(不写就用 provider id) |
baseUrl |
string | - | API endpoint(OpenAI 协议需要 /v1 后缀) |
api |
enum | ✅ | 协议类型(见下表) |
apiKey |
string | - | API key。$ENV_VAR 会自动展开 |
oauth |
"radius" |
- | OAuth provider 标识 |
headers |
object | - | 自定义 HTTP headers |
compat |
object | - | 协议兼容性配置 |
authHeader |
bool | - | true = 把 apiKey 放 Authorization header;false = 放 body 或 query |
models |
array | - | Provider 自带的模型列表 |
modelOverrides |
object | - | 覆盖内置 provider 的某些字段 |
3.4.1 api 协议类型
| api 值 | 适用 |
|---|---|
anthropic-messages |
Claude、MiniMax、Step Plan、任何 Anthropic 兼容代理 |
openai-completions |
OpenAI、DeepSeek、Ollama、vLLM、LM Studio、绝大多数代理 |
openai-responses |
OpenAI Responses API (新) |
google-generative-ai |
Google Gemini / AI Studio |
bedrock |
AWS Bedrock |
mistral |
Mistral |
vercel-ai-gateway |
Vercel AI Gateway |
重点:选错 api 协议会导致 stream 解析失败、tool call 不工作。
3.5 Model 字段(ModelDefinition)
| 字段 | 必填 | 说明 |
|---|---|---|
id |
✅ | 模型 ID,传给 provider 的实际值 |
name |
- | 显示名 |
api |
- | 覆盖 provider 的 api(极少数情况) |
baseUrl |
- | 覆盖 provider 的 baseUrl |
reasoning |
- | true = 启用思考模式 |
thinkingLevelMap |
- | 思考级别映射(如 {"off": null, "high": "max"}) |
input |
- | ["text"] / ["text", "image"] 声明支持多模态 |
contextWindow |
- | 上下文 token 数 |
maxTokens |
- | 单次输出最大 token |
cost |
- | { input, output, cacheRead, cacheWrite } 美元/百万 token |
headers |
- | per-model 自定义 headers |
compat |
- | per-model 兼容性覆盖 |
最少配置:只要 id 就够。
3.6 实战配置:5 种典型场景
3.6.1 MiniMax m3(飞书集成项目真实使用)
1 | { |
关键点:
baseUrl用/anthropic子路径(不是/v1),因为 MiniMax 的 Anthropic 兼容端点在那里api: "anthropic-messages"(不是 openai-completions)apiKey: "$MINIMAX_CN_API_KEY"自动展开环境变量,配置文件里不出现明文 key
启动前:export MINIMAX_CN_API_KEY=sk-cp-...
3.6.2 Step Plan(StepFun)
1 | { |
注意 step-3.5-flash 是支持 thinking 的——但实测在 RPC 模式下会幻觉出 tool_call XML(详见 Chapter 11 故障排查)。
3.6.3 Anthropic(直接用)
1 | { |
如果你直接 export ANTHROPIC_API_KEY=... 启动 Pi,Pi 会自动用内置 anthropic provider,这条配置是可选的。
3.6.4 DeepSeek(OpenAI 协议)
1 | { |
注意 baseUrl 不带 /v1——OpenAI 协议下 Pi 会自己补 /chat/completions 等路径。
3.6.5 本地 Ollama / vLLM / LM Studio
1 | { |
compat.supportsDeveloperRole: false 是因为 Ollama 等本地服务不理解 developer role(这是 OpenAI reasoning 模型专用),Pi 会改用 system role 兼容。
3.7 兼容性配置(compat)
3.7.1 Anthropic 兼容(AnthropicMessagesCompat)
1 | { |
针对不同代理的具体兼容性配置不同。踩坑:
- 如果代理不认
temperature字段 →supportsTemperature: false - 如果 thinking signature 校验失败 →
allowEmptySignature: true - 如果 tool 描述的 cache_control 报错 →
supportsCacheControlOnTools: false
3.7.2 OpenAI 兼容(OpenAICompletionsCompat)
1 | { |
最常见的踩坑:
supportsDeveloperRole: false→ 本地 Ollama/vLLM 必备supportsReasoningEffort: false→ 同上maxTokensField: "max_completion_tokens"→ OpenAI 新模型;"max_tokens"→ 旧模型
3.8 覆盖内置 Provider(modelOverrides)
Pi 内置的 anthropic/openai 都有模型列表。如果想给内置 provider 加模型:
1 | { |
这样 /model 面板会列出 Opus 5,不需要重写整个 provider 配置。
3.9 认证:apiKey vs OAuth vs 环境变量
3.9.1 三种方式并存
1 | { |
启动 Pi 前 export MY_KEY=sk-...,配置文件永远不出现明文。
3.9.2 Pi 0.83+ 新增能力
1 | # 导出 API key(自动 OAuth 刷新 + 最短有效期保证) |
这两个命令可以配合外部客户端(比如我们的 pi-lark-bridge),不用手动维护 token。
3.10 验证 models.json
1 | # Pi 启动时会自动校验 schema,出错直接退出 |
排错:
1 | # schema 错误时 Pi 会打印具体哪个字段错 |
3.11 小结
models.json是 Pi 配置的中枢- Provider 字段:必填
api,常用baseUrl+apiKey - Model 字段:最少
id,其他都有合理默认 - 环境变量引用:
"$VAR_NAME"避免明文 key - 兼容性配置
compat:解决 90% 的”连上但行为不对”问题 - 改了不重启:
/model立刻刷新
Chapter 4 · 运行模式详解
Pi 有 4 种运行模式:interactive(TUI)、print(CLI 单次)、RPC(stdin/stdout 协议)、SDK(嵌入式 API)。
本章用决策树 + 实战案例帮你选对模式,避免在错误场景里挣扎。
4.1 模式全景对比
| 维度 | Interactive | RPC | SDK | |
|---|---|---|---|---|
| 入口 | pi |
pi -p "..." |
pi --mode rpc |
createAgentSession() |
| 输入 | 终端 + 文件 | 命令行参数 | stdin JSON | 函数调用 |
| 输出 | TUI 渲染 | stdout 文本 | stdout JSON | Event 对象 |
| 多轮 | ✅ session 持久化 | ❌ 一次一答 | ✅ 自动排队 | ✅ 手动管理 |
| 流式 | ✅ 实时 | 默认关闭 (可开) | ✅ 实时 | ✅ 订阅事件 |
| 工具 | ✅ 全部 | ✅ | ✅ | ✅ |
| OAuth | /login |
环境变量 | 环境变量 | 环境变量 / apiKey 直接传 |
| 适用 | 探索代码 | CI / 一次性问答 | IM 机器人 / Web IDE | 深度嵌入 |
4.2 Interactive 模式
4.2.1 启动 & 退出
1 | pi # 当前目录 |
退出:Ctrl+C、/exit、/quit
4.2.2 关键命令速查
| 命令 | 功能 |
|---|---|
/model |
切换 provider/model |
/login |
OAuth 登录 |
/resume |
列历史会话 |
/new |
开始新会话 |
/compact [prompt] |
压缩上下文(可选指令) |
/branch |
从某条消息分叉 |
/tree |
树状导航 |
/fork |
创建新 session 文件 |
/name <name> |
给当前会话起名 |
/reload |
重载 AGENTS.md / 扩展 |
/export [file] |
导出会话为 HTML |
/share |
上传为 GitHub gist |
/skills |
列出 skills |
/extensions |
列出扩展 |
/sessions |
列当前项目历史 |
/exit |
退出 |
完整 30+ 个命令见 dist/cli.js 的 CommandDef 表。
4.2.3 关键快捷键
| 按键 | 功能 |
|---|---|
Ctrl+C |
取消当前操作 / 退出 |
Ctrl+D |
EOF / 退出 |
Ctrl+L |
清屏 |
Ctrl+O |
折叠/展开工具输出 |
Ctrl+T |
折叠/展开 thinking block |
Ctrl+V / Alt+V |
粘贴图片(Termux 不支持) |
↑/↓ |
历史 prompt |
Tab |
自动补全 |
Esc |
取消编辑 |
完整快捷键见 docs/keybindings.md。
4.2.4 消息队列(busy mode)
Pi 在工作时还能接受输入,由 display.busy_input_mode 控制:
| 模式 | 行为 |
|---|---|
queue (默认) |
输入排队,等当前 turn 完成后插入 |
steer |
输入立即接管当前 turn(中断 LLM) |
interrupt |
Ctrl+C 立即中断当前 turn |
1 | # ~/.pi/agent/settings.json |
4.3 Print 模式
4.3.1 基本用法
1 | # 单次问答 |
4.3.2 输出格式
默认输出纯文本。如果想结构化输出:
1 | pi -p "列出当前目录文件" --json |
JSON 模式输出:
1 | { |
4.3.3 流式 vs 非流式
1 | # 默认非流式(一次输出全部) |
飞书集成项目里 print 模式用于测试和调试,生产用 RPC。
4.4 RPC 模式
4.4.1 协议基础
1 | stdin → JSON command (one per line, LF delimited) |
LF 严格:用 \n(LF)做记录分隔符,不要用 \r\n。Node 的 readline 默认拆 U+2028 和 U+2029,不兼容——Pi 0.83+ 文档明确警告。
4.4.2 启动
1 | pi --mode rpc [options] |
4.4.3 命令(stdin → Pi)
| 命令 | 用途 |
|---|---|
prompt |
发用户消息 |
steer |
流式期间排队接管 |
follow_up |
流式结束后追加 |
abort |
中断当前 turn |
new_session |
开启新会话 |
get_state |
查询当前状态 |
get_messages |
拉所有消息 |
get_session_info |
当前 session 元数据 |
set_model |
切换模型 |
set_thinking_level |
切换思考级别 |
set_steering_mode |
改 steer 行为 |
set_follow_up_mode |
改 follow_up 行为 |
compact |
压缩上下文 |
set_session_name |
改 session 名 |
get_commands |
列扩展命令 |
get_available_models |
列可用模型 |
每条命令可带 id,响应里同 id 回传(请求/响应关联)。
4.4.4 事件(stdout ← Pi)
事件分四类:
- lifecycle:
agent_start、agent_end、turn_start、turn_end - message:
message_start、message_update、message_end - tool:
tool_call、tool_result、tool_execution_start、tool_execution_update、tool_execution_end - state:
model_select、thinking_level_change、session_stats
详细目录见 Chapter 8 RPC 协议参考。
4.4.5 错误处理
1 | // 命令失败 |
重点:success: true 表示命令被接受,不代表 turn 完成。turn 失败通过正常 event 流报告。
4.4.6 实战模板
4.5 SDK 模式
4.5.1 两种集成方式
| 方式 | 适用 | 代码量 |
|---|---|---|
| RPC 模式(子进程) | Node/Node-bridge、跨语言集成 | 中 |
| SDK 模式(库嵌入) | Node-only 嵌入、性能敏感 | 多但可控 |
官方建议(rpc.md 顶部原文):
If you’re building a Node.js/TypeScript application, consider using
AgentSessiondirectly from@earendil-works/pi-coding-agentinstead of spawning a subprocess.
4.5.2 SDK 入口
1 | import { |
或子进程 RPC:
1 | import { RpcClient } from "@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js"; |
4.5.3 决策树
1 | 你的语言? |
4.5.4 飞书集成项目用了哪个?
RPC + RpcClient(Node.js)。原因:
- 飞书 SDK 是 Node,Pi 是 Node,同进程没必要走子进程
- RpcClient 是单进程常驻,比每条消息 spawn 新进程快 100x
- RPC 协议简单,调试方便(直接在 stdio 看 JSON)
详见 Chapter 9 实战案例。
4.6 模式切换的常见误区
误区 1:用 print 跑多轮
1 | # ❌ 错误:每次都丢失上下文 |
或直接 RPC / Interactive。
误区 2:用 RPC 跑 CLI 单次任务
1 | # ❌ 杀鸡用牛刀 |
误区 3:用 SDK 处理跨语言
1 | # Python 项目不应该硬塞 Node SDK |
4.7 小结
- 选模式三步:自己用 → interactive;自动化/CI → print;集成/嵌入 → RPC(跨语言)/ SDK(Node)
- RPC + RpcClient 是飞书集成项目的标准答案
- 改模式不改语义:所有模式共享同一套模型配置、tools、session
- 出错了看
success: false还是 event 里的stopReason: "error",两者意义不同
下一步:Chapter 5 工具系统 →
Chapter 5 · 工具系统
Pi 默认给 LLM 4 个写权限工具(read/write/edit/bash)+ 3 个只读工具(grep/find/ls),加扩展可无限扩展。
本章讲清楚:内置工具行为、参数、限制;如何禁用/启用;如何写自定义工具。
5.1 内置工具清单
| 工具 | 读写 | 默认启用 | 用途 |
|---|---|---|---|
read |
R | ✅ | 读文件(按行/字节范围) |
write |
W | ✅ | 创建/覆盖文件 |
edit |
W | ✅ | 精确替换(多份备份可回滚) |
bash |
E | ✅ | 执行 shell 命令 |
grep |
R | ✅(按需) | ripgrep 内容搜索 |
find |
R | ✅(按需) | 文件查找 |
ls |
R | ✅(按需) | 目录列表 |
E = Execute(bash 不直接读写文件系统,但能跑任何 shell 命令,包括读和写)
5.2 read:读取文件
1 | { |
特性:
- 默认读取整个文件,超大文件自动截断(按行)
- 二进制文件检测到自动拒绝(避免 LLM 读图片/可执行)
- 支持图片(多模态模型):
input: ["text", "image"]的模型会传 image content block - 输出包含行号,方便和
edit配合
限制:
- 单次读取有最大行数(约 2000 行)
- LLM 可能跳读(不让它跳):用
offset+limit强制分块
实战:
1 | > @src/app.ts # 直接把文件喂给 prompt |
5.3 write:创建/覆盖文件
1 | { |
特性:
- 完整覆盖,不存在则创建
- 自动创建父目录
- 写完自动
chmod(保留执行位 if 文件是脚本) - 写入前 Pi 会做基本校验:路径在 trust 范围内吗?
安全:
.env、secrets.json等文件名 Pi 会警告(trust 决策里)- 可以用扩展拦截(
tool_callevent)
5.4 edit:精确替换(推荐用这个)
1 | { |
特性:
- 基于 oldText 匹配,不基于行号(line shifting 自动适应)
- 多份
oldText时按出现顺序匹配 - 不匹配会整文件报错,不会部分成功
- LLM 看不到的差异会被自动捕获(缩进、空白)
实战技巧:
1 | // 一次 edit 做多份替换(原子操作) |
坑:oldText 必须精确匹配(whitespace、换行都算)。LLM 经常搞错,建议给它看上下文行号后再 edit。
5.5 bash:执行 shell
1 | { |
安全机制:
user_bash扩展事件:扩展可以拦所有 bash 调用- 环境变量注入:Pi 自动注入
PI_SESSION_ID/PI_SESSION_FILE/PI_PROVIDER/PI_MODEL/PI_REASONING_LEVEL(详见docs/environment-variables.md) - 超时:默认 2 分钟,可以
timeout参数拉长 - 取消:用户按 Ctrl+C 会取消所有并发 bash(0.83+ 修复了只取消一个的 bug)
沙箱:Pi 不内置沙箱。本地跑等于你 user 的权限。生产用 container / VM(详见 docs/security.md)。
环境变量关闭:
1 | const bashTool = createBashTool(cwd, { |
这样嵌套 Pi 进程不会暴露父 session 的 metadata。
5.6 grep:内容搜索
1 | { |
底层用 ripgrep(不是纯 JS 实现),性能极好。
5.7 find & ls:文件查找
find:
1 | { |
ls:
1 | { |
5.8 启用 / 禁用工具
5.8.1 全局配置(settings.json)
1 | { |
5.8.2 CLI 启动时指定
1 | pi --tools read,bash,grep |
5.8.3 SDK 中指定
1 | const { session } = await createAgentSession({ |
5.8.4 RPC 模式动态切换
RPC 不直接支持运行时注册工具。要在启动前通过配置文件(models.json 不行,那是模型配置):
- 用 SDK 模式启动,自己注册工具,再走 RPC 调用 Pi 内部(不现实)
- 或 fork Pi 子进程 + 自定义 cli.js(重)
实战:
- 大多数场景用 SDK 模式 + customTools
- 飞书集成项目不用自定义工具——LLM 只需要 read/bash/edit/write 就能干所有活
5.8.4 扩展工具(TypeScript 扩展)
写到 ~/.pi/agent/extensions/:
1 | // ~/.pi/agent/extensions/weather.ts |
启动时自动加载(global extensions),或项目级 .pi/extensions/,或 CLI -e ./path.ts。
5.9 工具执行的事件订阅
扩展可以监听 6 类工具事件:
1 | pi.on("tool_call", async (event, ctx) => { |
实战:飞书集成项目没装工具扩展——bash 工具的 ! 命令场景不需要拦。
5.10 工具结果截断与输出累积
超大输出(git diff、build log)会触发两阶段保护:
- 软截断:显示前 N 行 + “…(N more lines, .. to expand)” 提示
- 用户按 Ctrl+O 展开:调
tool_execution_update拉全文
源码在 dist/core/tools/output-accumulator.js、dist/core/tools/truncate.js。
实战建议:
- LLM 跑
npm test卡住 → 加| head -50限制输出 - LLM 跑
git log拉太多 → 给个--max-count=20
5.11 工具 vs MCP
Pi 支持 MCP(在 ~/.pi/agent/settings.json 配置),但用法上:
| 场景 | 推荐 |
|---|---|
| 简单 API 调用 | 自定义工具(pi.registerTool) |
| 复杂协议(MCP 已有服务) | MCP server |
| 数据库 / 外部 SaaS | MCP server |
| 单文件 / 单操作 | 自定义工具 |
实战:飞书集成项目不用 MCP——只是 Pi 接收飞书消息并回复,不需要 LLM 调外部服务。
5.12 小结
- 内置 7 个工具(read/write/edit/bash + grep/find/ls)
- 写权限工具(write/edit/bash)默认启用,生产必加扩展拦截
- 自定义工具:TypeBox 定义 schema +
execute函数 - bash 沙箱靠 OS(容器/VM),Pi 不内置
- 环境变量自动注入
PI_*给所有 bash 调用
下一步:Chapter 6 System Prompts 与 Context Files →
Chapter 6 · System Prompts 与 Context Files
这一章讲 Pi 怎么”加载外部知识”:AGENTS.md、CLAUDE.md、SYSTEM.md、Skills、Prompt Templates、Settings.json。
重点:加载顺序、优先级、如何避坑。
6.1 Context Files 加载机制
Pi 启动时按以下顺序扫描 context files(多文件叠加,不是覆盖):
- Global:
~/.pi/agent/AGENTS.md - Ancestor walk:从 cwd 往 root 走,每个目录找
AGENTS.md或CLAUDE.md(离 cwd 最近的优先) - Current cwd:
AGENTS.md或CLAUDE.md - Pi-managed:
~/.pi/agent/SYSTEM.md/~/.pi/agent/APPEND_SYSTEM.md
6.1.1 AGENTS.md vs CLAUDE.md
| 文件 | 谁读 |
|---|---|
AGENTS.md |
Pi、Cursor、Aider 等都认 |
CLAUDE.md |
Claude Code、Pi |
建议:项目里两个都放,内容相同。Pi 优先 AGENTS.md。
6.1.2 实战模板
~/.pi/agent/AGENTS.md(全局):
1 | # Hermes Agent Environment: Termux on Android |
项目 AGENTS.md:
1 | # Pi Agent Manual Project |
6.1.3 加载时机
- Interactive / RPC:每次 session 启动加载一次
- 改了之后:按
/reload重载 - Trust:项目级 AGENTS.md 不需要 trust(
context_files_loading默认开),但项目级SYSTEM.md需要 trust
6.2 SYSTEM.md & APPEND_SYSTEM.md
Pi 自己的 system prompt 通过 ~/.pi/agent/SYSTEM.md 和 APPEND_SYSTEM.md 注入:
| 文件 | 作用 |
|---|---|
SYSTEM.md |
完全替换 Pi 默认 system prompt(不推荐) |
APPEND_SYSTEM.md |
在默认 system prompt 之后追加(推荐) |
6.2.1 实战:APPEND_SYSTEM.md
1 | # Hermes Agent Guidelines |
这个文件让 Pi 在飞书集成里表现得像 Hermes Agent而不是通用 coding agent。
6.3 Skills(技能包)
Skills 是按需加载的能力包。Agent Skills 标准,Pi 实现了完整规范。
6.3.1 加载位置(多级)
1 | Global: ~/.pi/agent/skills/ (用户级) |
6.3.2 Skill 结构
1 | my-skill/ |
SKILL.md 模板:
1 | --- |
6.3.3 触发方式
1 | # 自动触发 |
6.3.4 实战:飞书集成项目用 skill 吗?
没用。飞书消息进来是单一通道,LLM 只需要”读 bash 跑命令 + 回答”两件事,skill 没必要。
但通用 Pi 用户会重度用 skill,比如:
/skill:pdf-tools处理 PDF/skill:brave-search搜索网络/skill:code-review审查代码
6.4 Prompt Templates
/name 命令触发,比 skill 更轻量(纯 Markdown 片段)。
6.4.1 位置
1 | ~/.pi/agent/prompts/*.md # global |
6.4.2 模板格式
1 | --- |
文件名 review.md → 命令 /review。
6.4.3 触发
1 | > /review https://github.com/foo/bar/pull/123 |
{{arg}} 自动替换为后续参数。
6.5 Settings.json(settings 而非 prompt)
settings 控制 Pi 行为,不影响 prompt 内容。完整清单在 docs/settings.md。
6.5.1 常用设置
1 | { |
6.5.2 项目级 settings
.pi/settings.json 覆盖全局。CI / 团队共享:
1 | { |
6.6 实战:飞书集成的 system prompt 组装
飞书集成项目里 Pi 看到的 system prompt 是这么拼出来的:
1 | [Pi 默认 system prompt] |
APPEND_SYSTEM.md 让 Pi 知道这是 Hermes Agent 而非通用 coding agent。
6.7 常见坑
坑 1:AGENTS.md 写太多
LLM context window 有限。AGENTS.md 每个 token 都要付费。只写必要的约束,不要写文档。
坑 2:APPEND_SYSTEM.md 覆盖默认行为
APPEND_SYSTEM.md 是追加不是覆盖。如果要禁用某个默认行为,需要用 settings(hideThinkingBlock 等),不是 prompt。
坑 3:项目 trust 被拒
1 | [warning] Project not trusted, .pi/SYSTEM.md not loaded |
解决:/trust 命令,或 defaultProjectTrust: "always"(不推荐)。
坑 4:Skill 不触发
LLM 经常不主动调用 skill。强制方式:
- 在 prompt 里明确说”用 skill X”
- 直接
/skill:x - 在 SKILL.md 的 description 里写更明显的触发词
6.8 小结
- Context 文件分四层:global → ancestor → cwd → pi-managed
- AGENTS.md / CLAUDE.md 是项目指令主载体
- APPEND_SYSTEM.md 是 Hermes 行为定制的入口(飞书集成项目用)
- Skills 按需加载(自动 + 手动)
- Prompt Templates 是
/name触发的 Markdown 片段 - Settings.json 是行为配置,不影响 prompt 内容
下一步:Chapter 7 会话管理 →
Chapter 7 · 会话管理
Pi 的 session 是 JSONL 文件 + 树状结构,支持 branching、compaction、resume。
本章讲清:session 文件怎么读、怎么写、怎么通过 RPC 操作。
7.1 Session 文件存储
1 | ~/.pi/agent/sessions/ |
目录名规则:cwd 的 / 替换为 -,前后加 --。
例:/home/user/projects/myproject → --home-user-projects-myproject--
7.2 JSONL 文件结构
每行一个 JSON 对象,类型有:
7.2.1 头(第一行)
1 | { |
7.2.2 消息行
1 | { |
7.2.3 树状结构
通过 id / parentId 形成 DAG:
1 | msg-1 (user) ← parentId: null |
当前活跃叶(active leaf)保存一个隐式指针。/tree 让你跳到任何点。
7.3 Session 版本
| Version | 变化 |
|---|---|
| 1 | 线性(已废弃,自动迁移) |
| 2 | 引入树状 id/parentId |
| 3 | hookMessage → custom(扩展统一) |
Pi 启动时自动把 v1/v2 迁到 v3。
7.4 消息类型(AgentMessage)
| 类型 | Role | 用途 |
|---|---|---|
UserMessage |
user |
用户输入 |
AssistantMessage |
assistant |
LLM 回复 |
ToolResultMessage |
toolResult |
工具执行结果 |
BashExecutionMessage |
- | Bash 输出(人类可读 + 完整记录) |
CustomMessage |
custom |
扩展消息(0.83+ 统一) |
CompactionEntry |
- | 上下文压缩产物 |
BranchSummaryEntry |
- | Branch 切换总结 |
7.4.1 AssistantMessage 内容块
1 | interface AssistantMessage { |
实战:飞书桥接的关键
1 | // pi-adapter.js 我们项目的核心代码 |
关键 insight:assistant 的 content 是数组,有 text / thinking / toolCall 三种块。LLM 可能在一个 turn 里多次发 thinking 然后发 text(也可能混着 toolCall)。只看最后一个 text block 是最稳的做法。
7.5 Compaction(上下文压缩)
7.5.1 自动触发
1 | contextTokens > contextWindow - reserveTokens |
reserveTokens 默认 16384,settings.json 可改。
7.5.2 手动触发
1 | > /compact |
7.5.3 工作流程
1 | 1. 倒序遍历 message 累计 token,到 keepRecentTokens (默认 20k) 停 |
7.5.4 RPC 触发
1 | {"type": "compact", "options": {"customInstructions": "保留关键决策"}} |
7.6 Branching(分支)
7.6.1 Interactive 命令
| 命令 | 作用 |
|---|---|
/tree |
树状导航 + 编辑回退 |
/fork |
创建新 session 文件(从某条消息分叉) |
/clone |
复制当前活跃分支到新 session |
7.6.2 RPC 操作
RPC 不直接支持 /tree UI。实现方式:
1 | // 通过 SDK 直接操作 |
7.6.3 Branch Summarization
/tree 跳到旧 entry 时,Pi 自动生成 branch summary:
- 当前活跃分支的 LLM 生成
- 总结”从旧点到现在发生了什么”
- 保留上下文连续性
7.7 Session 操作 CLI
1 | # 列历史 |
7.8 RPC 模式下的 Session 操作
| 操作 | RPC 命令 |
|---|---|
| 查状态 | get_state |
| 拉消息 | get_messages |
| 拉 session info | get_session_info |
| 改 session 名 | set_session_name |
| 开新 session | new_session |
| 压缩 | compact |
7.8.1 飞书桥接的 session 策略
关键决策:每个飞书用户 / 每个 chat 一个 session?
我们项目用 per-chat session(每个 chat_id 一个独立 session 文件):
1 | // 伪代码 |
优点:
- 用户重开 chat 仍有完整历史
- 多用户隔离
- session 文件可审计
缺点:
- 文件数量爆炸(每个 chat 一个)
- 需要清理逻辑(90 天前的删)
7.9 Session Manager(SDK)
1 | import { SessionManager } from "@earendil-works/pi-coding-agent"; |
7.9.1 飞书桥接为什么用 RPC + 文件 session 而不是 SDK?
| 维度 | RPC + 文件 | SDK |
|---|---|---|
| 隔离 | Pi 子进程独立(崩溃不影响主程序) | 同进程,崩溃影响主程序 |
| 配置 | 改 models.json 重启即可 | 改 SDK 配置要重启 Node |
| 调试 | 单独 log 目录 | 混在主程序 log |
| 性能 | 稍慢(IPC 序列化) | 略快 |
我们选 RPC 是稳定性优先。
7.10 Session 清理
1 | # 手动 |
Pi 0.83+ 优先用 trash CLI,避免物理删除:
1 | which trash # 不存在就装: brew install trash / apt install trash-cli |
7.11 Session 监控与成本
session 头里有 usage 累计:
1 | { |
飞书集成项目不计算成本——单次消息 cost 很低,没必要。
7.12 实战:session 文件监控
我们项目里有个小工具,定期把 session 文件归档:
1 | # ~/pi-lark-bridge/scripts/archive-old-sessions.sh |
配合 cron:
1 | # ~/.hermes/cron/... |
7.13 小结
- Session = JSONL 文件 + 树状 DAG
- 三种操作:append(新消息)、navigate(跳叶)、compact(压缩)
- Assistant message content 是数组:text + thinking + toolCall 混合
- 飞书集成用 per-chat session 文件
- RPC + 文件 session 比 SDK + in-memory 更稳定
下一步:Chapter 8 RPC 协议参考(飞书集成重头戏)→
Chapter 8 · RPC 协议参考(飞书集成重头戏)
本章是 飞书 / IM bot 集成的核心:完整 RPC 协议参考 + pi-lark-bridge 实战源码剖析。
不读这一章,集成会踩 90% 的坑。
8.1 协议基础
1 | stdin → JSON command (one per line, LF delimited) |
8.1.1 Framing 严格规则
- LF (
\n) 是唯一的记录分隔符 - 不要用
\r\n——但客户端要能处理服务端发的\r\n(剥离 trailing\r) - 不能用 Node
readline——它会把U+2028和U+2029当分隔符,但这两个字符在 JSON 字符串里合法
正确写法:
1 | // 用 split('\n') 而不是 readline |
8.2 启动 RPC 模式
1 | pi --mode rpc \ |
常用 flags:
| Flag | 说明 |
|---|---|
--provider <name> |
LLM provider |
--model <id> |
model ID 或 pattern(provider/id:thinking) |
--name / -n |
session 显示名 |
--no-session |
不持久化 |
--session-dir |
自定义 session 目录 |
--thinking <level> |
初始思考级别 |
8.3 命令目录(stdin → Pi)
按使用频度排序:
8.3.1 消息类
| 命令 | 字段 | 用途 |
|---|---|---|
prompt |
message, images?, streamingBehavior? |
发用户消息 |
steer |
message, images? |
流式期间插入新指令 |
follow_up |
message, images? |
流式结束后追加 |
abort |
- | 中断当前 turn |
8.3.2 状态类
| 命令 | 用途 |
|---|---|
get_state |
查 session 状态(model、thinking level、isStreaming 等) |
get_messages |
拉所有消息 |
get_session_stats |
用量统计 |
get_last_assistant_text |
只取最后一次 assistant 的文本(飞书桥接关键) |
8.3.3 模型 / 思考级别
| 命令 | 用途 |
|---|---|
set_model |
切换 provider/model |
cycle_model |
循环切换 |
get_available_models |
列所有可用 |
set_thinking_level |
off/minimal/low/medium/high/xhigh/max |
cycle_thinking_level |
循环切换 |
get_available_thinking_levels |
列可用 |
8.3.4 队列行为
| 命令 | 用途 |
|---|---|
set_steering_mode |
all/one-at-a-time/none |
set_follow_up_mode |
同上 |
8.3.5 压缩
| 命令 | 用途 |
|---|---|
compact |
customInstructions? |
set_auto_compaction |
enabled: bool |
8.3.6 Bash(直接执行)
| 命令 | 用途 |
|---|---|
bash |
command, cwd?, timeout?, env? 直接跑命令 |
abort_bash |
中断 |
8.3.7 Session 切换
| 命令 | 用途 |
|---|---|
new_session |
parentSession? 开新 |
switch_session |
sessionPath 切到已存 |
fork |
entryId 从某 entry 分叉 |
clone |
复制当前活跃分支 |
export_html |
outputPath? 导出 HTML |
set_session_name |
改 session 显示名 |
8.3.8 重试 / 扩展 UI
| 命令 | 用途 |
|---|---|
set_auto_retry |
enabled, maxRetries? |
abort_retry |
中断 |
get_commands |
列扩展命令 |
完整 30+ 命令见 docs/rpc.md。
8.4 事件目录(stdout ← Pi)
事件分四类。
8.4.1 Lifecycle
1 | {"type": "agent_start"} |
8.4.2 Message
1 | {"type": "message_start", "message": {"role": "user", "content": "..."}} |
message_update 的 assistantMessageEvent 子类型:
| type | 字段 | 说明 |
|---|---|---|
text_delta |
delta |
文本增量 |
thinking_delta |
delta |
thinking 增量 |
tool_call_start |
toolCall |
tool call 开始 |
tool_call_delta |
contentDelta |
tool call 参数增量 |
tool_call_end |
toolCall |
tool call 结束 |
8.4.3 Tool Execution
1 | {"type": "tool_execution_start", "toolCallId": "...", "toolName": "bash", "args": {...}} |
8.4.4 Bash Execution(特殊)
1 | {"type": "bash_execution_update", "id": "req-1", "command": "...", "output": "...", "exitCode": null} |
重要:id 对应发出 bash 命令的 id,用于把异步输出对应回原始调用。
8.4.5 Queue / Compaction / Retry
1 | {"type": "queue_update", "queuedPromptCount": 2} |
8.4.6 Extension UI Request(stdout → 客户端)
扩展可能弹出 UI 询问用户:
1 | {"type": "extension_ui_request", "id": "ui-1", "method": "select", "title": "Choose", "options": ["a", "b"]} |
客户端从 stdin 回:
1 | {"id": "ui-1", "type": "extension_ui_response", "value": "a"} |
8.5 完整 RPC 会话示例
1 | # 启动 Pi |
stdin 输入(每行一条):
1 | {"id":"1","type":"prompt","message":"Say hello"} |
stdout 输出(按顺序):
1 | {"type":"agent_start"} |
关键:response 在最后才到(turn 完成后),中间都是事件流。
8.6 RpcClient 源码剖析
不要自己写 spawn + JSON line 解析——直接用 Pi 自带的 RpcClient:
1 | // dist/modes/rpc/rpc-client.js |
8.6.1 构造
1 | const client = new RpcClient({ |
8.6.2 生命周期
1 | await client.start(); // spawn 子进程 + 等待初始化 |
8.6.3 核心方法
1 | // 等到指定 prompt 完成,返回所有事件 |
8.6.4 内部实现要点
RpcClient 维护:
_events: AgentSessionEvent[]—— 所有事件缓存_requestQueue: Map<string, Promise>—— requestId 队列_streamBuffer: string—— JSONL 解析缓冲_proc: ChildProcess—— pi 子进程
promptAndWait 工作流:
1 | 1. 递增 requestId |
坑:如果上一个 prompt 还没 agent_end 就发新 prompt,会自动排队,等当前 turn 完成才执行。这正是我们想要的”单进程常驻多轮”行为。
8.7 pi-lark-bridge 实战源码
我们项目的核心文件 src/pi-adapter.js(53 行):
1 | const path = require('path'); |
8.7.1 关键设计
- 单 client 实例:全局只
start()一次,所有飞书消息走同一个 Pi 子进程 - 倒序遍历:找最后一个 assistant message(LLM 可能在一次 turn 里发多个 message,比如先解释后回答)
- 找 text block:assistant content 数组里只看
type: "text",忽略thinking和toolCall - fallback 友好:找不到时返回中文提示,不抛异常
8.7.2 飞书侧(src/index.js,简化)
1 | const lark = require('@larksuiteoapi/node-sdk'); |
8.8 RPC 错误处理
8.8.1 三层错误
| 层 | 表现 |
|---|---|
| 命令接受失败 | response.success: false + error 字段 |
| 命令接受后 turn 失败 | message_end.stopReason: "error" + errorMessage |
| 子进程崩溃 | stdout EOF / pipe broken |
8.8.2 重连机制
RpcClient 不自动重连。我们的做法:
1 | let client = null; |
下次 getClient() 时会重新 start()。
8.8.3 超时处理
promptAndWait(prompt, undefined, 60000) 60 秒超时。
如果 Pi 在调一个长 bash 任务:
1 | // 用 abort 而不是干等 |
8.9 RPC 调试技巧
8.9.1 直接看 stdout
1 | # 把 pi 的 stdout 重定向到文件 |
然后从另一窗口观察:
1 | tail -f /tmp/pi-rpc.log | jq . |
8.9.2 node –inspect
1 | node --inspect-brk your-bridge.js |
8.9.3 RpcClient 的内部状态
1 | console.log('queue size:', client._requestQueue.size); |
(虽然 _ 前缀是 private,但调试时能看)
8.10 RPC 安全
RPC 模式没有内置认证。本地用没问题;远程用要自己加 TLS / auth:
1 | # 用 socat 包一层 TLS |
生产环境建议:
- 用 SSH 端口转发(
ssh -L 9999:localhost:9999) - 或用 unix socket(
/tmp/pi.sock),加文件权限 600 - 或用 TLS wrapper(stunnel / socat)
8.11 小结
- RPC 协议:stdin 命令 + stdout 事件 + LF 分隔
- 不用 Node
readline(U+2028/U+2029 陷阱) - 用官方 RpcClient,单进程常驻多轮
- 取文本只找
message_end.message.role=assistant.content[type=text],倒序 - 飞书集成:lark SDK WS 客户端 + RpcClient,两个进程稳定通信
Chapter 9 · SDK 与程序化集成(飞书集成重头戏)
本章讲 Pi 作为库嵌入 Node 应用:
createAgentSession、自定义工具、扩展、生命周期。
与 Chapter 8 RPC 互补:RPC 用于跨进程 / 跨语言,SDK 用于 Node 同进程深度嵌入。
9.1 SDK vs RPC:怎么选
| 维度 | RPC | SDK |
|---|---|---|
| 进程 | 子进程(spawn) | 同进程 |
| 语言 | 任意(Python/Go/Rust) | Node/TypeScript only |
| 性能 | 略慢(IPC 序列化) | 略快 |
| 稳定性 | Pi 崩了不影响主程序 | Pi 崩了 = 主程序崩 |
| 调试 | 单独 log 目录 | 混在主程序 log |
| 高级能力 | 受限 | 完整(事件订阅、自定义工具、compaction hook 等) |
| 飞书集成项目 | ✅ 选了 RPC | ❌ |
官方建议:Node 项目优先 SDK。我们选 RPC 是稳定性优先 + 调试隔离。
9.2 安装
1 | npm install @earendil-works/pi-coding-agent |
如果用 SDK + 嵌入到自己的 npm 项目里:
1 | { |
9.3 核心 API
9.3.1 最小集成
1 | import { |
9.3.2 AgentSession 接口
1 | interface AgentSession { |
9.4 进阶:createAgentSessionRuntime
当需要换 session / fork / clone 时,AgentSession 不够,要用 runtime:
1 | import { |
Runtime 操作:
1 | await runtime.newSession(); // 开新 |
坑:runtime 操作后 runtime.session 变了,event 订阅要重新绑:
1 | let session = runtime.session; |
9.5 事件订阅
9.5.1 Event 类型
1 | type AgentSessionEvent = |
9.5.2 实战:把 assistant 文本流式输出到 WebSocket
1 | import { WebSocketServer } from "ws"; |
9.6 自定义工具(custom tools)
9.6.1 定义工具
1 | import { Type } from "typebox"; |
9.6.2 注册到 session
1 | const { session } = await createAgentSession({ |
LLM 现在可以用 weather 工具了。
9.6.3 飞书集成项目为什么不用 custom tools?
我们只需要 read/bash/edit/write 就能干所有事(Pi 默认工具)。LLM 通过 bash 调外部 API 也很自然:
1 | > 用 curl 查北京今天天气 |
少一个工具 = 少一个 schema 注入到 system prompt = 省 token。
9.7 自定义 Bash 工具
createBashTool 是 Pi 暴露的工厂方法,可以包装:
1 | import { createBashTool } from "@earendil-works/pi-coding-agent/dist/core/tools/bash"; |
实战:可以加一个 allowedCommands 白名单:
1 | const customBash = createBashTool(cwd, { |
9.8 Extension(TypeScript 扩展)
比 custom tool 更重,可以订阅全部事件:
1 | // ~/.pi/agent/extensions/my-extension.ts |
加载位置:
~/.pi/agent/extensions/(global,auto-load).pi/extensions/(project,需 trust)pi -e ./my-extension.ts(CLI 临时加载)
9.9 完整实战:飞书集成改用 SDK
如果我们改用 SDK(理论上可行,实际我们没用):
1 | import { |
优点:
- 不用 spawn 子进程
- 事件订阅更精确
- 同一进程,性能略好
缺点:
- session 没隔离(Pi 崩 = 整个 bot 崩)
- 调试 log 混在一起
- 没法独立升级 Pi 版本
9.10 SDK 调试
1 | # Node inspect |
打印 session 状态:
1 | console.log({ |
9.11 SDK 性能 vs RPC
实测(Pi 0.83.0,MiniMax m3,单条 100 字消息):
| 指标 | RPC | SDK |
|---|---|---|
| 冷启动 | 800ms | 50ms |
| 单 turn | 5-8s | 5-8s(同 LLM 速度) |
| 内存 | +30MB(Pi 子进程) | +20MB(同进程) |
| 稳定性 | 子进程崩了主程序无影响 | Pi 崩了 = 主程序崩 |
9.12 小结
- Node 项目优先 SDK(同进程),跨语言或稳定性优先用 RPC(子进程)
createAgentSession一次拿到 session,订阅事件流createAgentSessionRuntime用于换 session / fork- Custom tools / extensions 都很容易加,但默认工具能覆盖 90% 场景
- 飞书集成项目用 RPC + RpcClient,稳定性 + 隔离性优先
Chapter 10 · 架构深读
本章从源码层面拆解 Pi 的架构:模块依赖、启动流程、ProviderComposer、AgentSession 状态机、消息流。
重点:让飞书集成开发者能精确理解 Pi 的行为,预判 / 调试问题。
基于 Pi v0.83.0 源码(/data/data/com.termux/files/usr/lib/node_modules/@earendil-works/pi-coding-agent/dist/)。
10.1 全局模块依赖图
flowchart TB
subgraph CLI["入口层 (Entry)"]
CLIJS["dist/cli.js
(17 行, shebang + import)"]
MAIN["dist/main.js
(746 行, 模式分发)"]
RPCENTRY["dist/rpc-entry.js
(RPC 子进程入口)"]
PKGCLI["dist/package-manager-cli.js"]
end
subgraph MODES["模式层 (modes/)"]
PRINT["modes/print-mode.js"]
RPC["modes/rpc/rpc-mode.js
+ rpc-client.js + rpc-types.js"]
INTER["modes/interactive/
(TUI)"]
end
subgraph CORE["核心层 (core/)"]
AGENTSES["core/agent-session.js
(会话状态机)"]
AGENTSRV["core/agent-session-services.js
(cwd-bound services)"]
AGENTRT["core/agent-session-runtime.js
(session 替换)"]
SDK["core/sdk.js
(createAgentSession)"]
MODEL["core/model-config.js
(models.json schema)"]
RUNTIME["core/model-runtime.js
(ModelRuntime)"]
COMPOSER["core/provider-composer.js
(3 层 provider 合并)"]
SESSMGR["core/session-manager.js
(JSONL 持久化)"]
RESLOAD["core/resource-loader.js
(extensions / skills / prompts 发现)"]
end
subgraph TOOLS["工具层 (core/tools/)"]
READ["read.js"]
WRITE["write.js"]
EDIT["edit.js"]
BASH["bash.js"]
GREP["grep.js"]
FIND["find.js"]
LS["ls.js"]
end
subgraph EXT["扩展层"]
EXTEN["dist/extensions/
(pi.sendMessage / 自定义)"]
end
subgraph PKGS["外部依赖"]
AI["@earendil-works/pi-ai
(LLM 客户端)"]
CORE2["@earendil-works/pi-agent-core
(Agent class)"]
end
CLIJS --> MAIN
MAIN --> MODES
MAIN --> CORE
RPCENTRY --> RPC
PRINT --> SDK
RPC --> SDK
INTER --> SDK
SDK --> AGENTSES
SDK --> RESLOAD
SDK --> RUNTIME
SDK --> MODEL
SDK --> SESSMGR
SDK --> TOOLS
AGENTRT --> AGENTSRV
AGENTRT --> AGENTSES
RUNTIME --> COMPOSER
RUNTIME --> MODEL
COMPOSER --> AI
AGENTSES --> CORE2
AGENTSES --> AI
EXTEN -.订阅.-> AGENTSES
RESLOAD -.加载.-> EXTEN
10.1.1 关键文件清单
| 文件 | 行数 | 职责 |
|---|---|---|
dist/cli.js |
17 | shebang + import('./main.js') |
dist/main.js |
746 | CLI 模式分发(interactive/print/rpc/json/sdk) |
dist/index.js |
46 | 包主入口(导出 createAgentSession 等) |
dist/rpc-entry.js |
- | RPC 子进程入口 |
dist/core/agent-session.js |
~2700 | 会话状态机 + 事件流 |
dist/core/model-config.js |
~250 | models.json 加载 + schema 校验 |
dist/core/provider-composer.js |
~400 | built-in + models.json + extensions 三层合并 |
dist/core/session-manager.js |
- | JSONL 文件读写 |
dist/modes/rpc/rpc-mode.js |
~700 | RPC 模式命令处理 |
dist/modes/rpc/rpc-client.js |
- | 官方 TypeScript RpcClient |
10.2 启动流程
sequenceDiagram
participant U as 用户
participant SH as Shell
participant CLI as cli.js
participant M as main.js
participant SDK as sdk.js
participant SRV as agent-session-services.js
participant SES as agent-session.js
participant PI as pi 子进程 / 同进程
U->>SH: pi --mode rpc --provider minimax-cn
SH->>CLI: spawn
CLI->>M: dynamic import
M->>M: parse argv
M->>M: dispatch by mode
alt RPC mode
M->>PI: rpc-entry.js (子进程)
else SDK mode
M->>SDK: createAgentSession({...})
SDK->>SRV: createAgentSessionServices({cwd})
SRV->>SRV: ModelRuntime.create()
SRV->>SRV: DefaultResourceLoader.reload()
SRV->>SRV: SettingsManager.create()
SRV-->>SDK: {services, diagnostics}
SDK->>SES: createAgentSessionFromServices({services})
SES->>SES: new Agent(model, systemPrompt, tools)
SES->>SES: subscribe to agent events
SES-->>SDK: {session}
SDK-->>M: {session, model, ...}
end
10.2.1 CLI 解析
main.js 用自定义 argv 解析(不引第三方包):
--mode <mode>:interactive/print/json/rpc/sdk--provider <name>--model <id>--name / -n--no-session--session-dir
10.2.2 模式分发
1 | // dist/main.js 简化 |
10.2.3 RPC 模式启动
1 | // dist/rpc-entry.js |
runRpcSession 起 stdin/stdout 双工流,循环读 JSON line → 派发到 handler。
10.3 ProviderComposer(三层 provider 叠加)
ProviderComposer 是 Pi 配置系统的核心。它把三层配置合成最终可用的 provider map:
1 | ┌────────────────────────────────────────────────────┐ |
10.3.1 modelFromJson 单 model 构建
provider-composer.js:46-78:
1 | function modelFromJson(providerId, definition, providerConfig, defaults) { |
重点:层层 fallback(model → provider → defaults),所以 id 必填,其他都有默认。
10.3.2 modelOverrides 合并
provider-composer.js:21-44:applyModelOverride(model, override)
可覆盖字段:
name,reasoning,thinkingLevelMap,inputcost(deep merge)contextWindow,maxTokenscompat(deep mergeopenRouterRouting,vercelGatewayRouting,chatTemplateKwargs)
compat.mergeCompat 处理嵌套对象:
1 | function mergeCompat(base, override) { |
10.3.3 三层叠加顺序
1 | 1. Built-in (e.g. anthropic, openai) |
实战:你可以在 ~/.pi/agent/models.json 里给内置 anthropic provider 加 claude-opus-5:
1 | { |
10.4 ModelConfig 生命周期
dist/core/model-config.js:
stateDiagram-v2
[*] --> LoadAttempt: ModelRuntime.create()
LoadAttempt --> ParseOK: JSON.parse OK
LoadAttempt --> ParseFail: ENOENT / SyntaxError
ParseOK --> SchemaOK: TypeBox Check pass
ParseOK --> SchemaFail: 校验失败
SchemaOK --> Frozen: deepFreeze + structuredClone
Frozen --> [*]: ModelConfig instance (immutable)
ParseFail --> [*]: ModelConfig with error
SchemaFail --> [*]: ModelConfig with error
10.4.1 三种状态
| 状态 | 行为 |
|---|---|
| 加载成功 | 冻结(deepFreeze + structuredClone)→ 不可变 Map |
| JSON 错 | 返回 ModelConfig with error string |
| Schema 错 | 返回 ModelConfig with error string(具体字段路径) |
10.4.2 不可变性
ProviderConfigSchema 校验后深度冻结:
1 | const config = parsed as ModelsJson; |
好处:多线程读无锁,运行时改不了配置。
实战:Pi 在 RPC 模式下收到 set_model 命令时,不会重载 models.json,而是直接用新 model 对象。
10.5 AgentSession 状态机
stateDiagram-v2
[*] --> Idle: createAgentSession 完成
Idle --> Streaming: prompt() / steer()
Streaming --> ToolExecuting: tool_call
ToolExecuting --> Streaming: tool_result (继续 LLM)
Streaming --> Compacting: auto-compaction 触发 / compact() 命令
Compacting --> Idle: compaction_end
Streaming --> Idle: turn_end (stopReason: stop)
Streaming --> Idle: turn_end (stopReason: length)
Streaming --> Idle: turn_end (stopReason: error)
Streaming --> Idle: turn_end (stopReason: aborted)
Streaming --> Streaming: queue_update (有 follow_up)
Idle --> Aborted: abort()
Streaming --> Aborted: abort()
Compacting --> Aborted: abortCompaction()
Aborted --> Idle: 恢复
10.5.1 关键方法
| 方法 | 状态转换 |
|---|---|
prompt() |
Idle → Streaming |
steer() / followUp() |
排队(不立即转状态) |
abort() |
* → Aborted |
compact() |
Idle/Streaming → Compacting |
abortCompaction() |
Compacting → Idle |
setModel() |
不转状态,运行时换 model |
10.5.2 Event 流
AgentSession 通过 EventBus 发 30+ 事件类型:
1 | // 简化版实际顺序 |
10.6 RPC 模式内部
flowchart LR
subgraph CLI["客户端 (bridge)"]
A[飞书消息] --> B[index.js
lark SDK WS]
B --> C[runPi]
C --> D[RpcClient]
end
subgraph PI["pi 子进程"]
E[stdin] --> F[RPCCommand parser]
F --> G[RpcSession]
G --> H[AgentSession]
H --> I[EventBus]
I --> J[stdout writer]
J --> K[stdout]
end
D -->|promptAndWait| E
K -->|JSONL events| D
D -->|extractAssistantText| C
C -->|text| B
B -->|im.message.create| L[飞书 API]
10.6.1 RpcSession 内部组件
dist/modes/rpc/rpc-mode.js:
1 | ┌────────────────────────────────────────────────┐ |
10.6.2 关键命令实现
prompt(最常用)
1 | // rpc-mode.js 简化 |
10.6.3 RpcClient 内部
dist/modes/rpc/rpc-client.js:
1 | class RpcClient { |
坑:
_events数组不清空旧数据,直到新 prompt push 第一个 event 才开始覆盖- 如果上一次 prompt 没
agent_end就发新 prompt → 排队(等当前 turn 完成才执行) pid file用于优雅关闭:stop()发abort命令后等 5s,超时 kill
10.7 SDK 模式内部
flowchart TB
A[createAgentSession] --> B[createAgentSessionServices]
B --> C[ModelRuntime.create]
B --> D[DefaultResourceLoader]
B --> E[SettingsManager.create]
B --> F[PendingExtensionRegister]
C --> C1[load ~/.pi/agent/auth.json]
C --> C2[load ~/.pi/agent/models.json]
C --> C3[merge 3-tier providers]
D --> D1[extensions/.ts 发现]
D --> D2[skills/ 发现]
D --> D3[prompts/ 发现]
D --> D4[themes/ 发现]
A --> G[createAgentSessionFromServices]
G --> H[new Agent]
H --> H1[model]
H --> H2[systemPrompt]
H --> H3[tools]
G --> I[new AgentSession]
I --> J[bind Extensions]
I --> K[subscribe EventBus]
10.7.1 createAgentSessionServices
1 | // core/agent-session-services.js 简化 |
10.7.2 createAgentSessionFromServices
1 | // core/sdk.js 简化 |
10.7.3 AgentSession 构造
1 | // core/agent-session.js 简化 |
10.8 消息流
sequenceDiagram
participant U as User
participant API as createAgentSession
participant SM as SessionManager
participant AG as Agent
participant LLM as pi-ai (LLM client)
participant TR as Tool runtime
participant FS as Filesystem
U->>API: prompt("Read README and summarize")
API->>AG: agent.prompt(message)
AG->>SM: append UserMessage
SM->>FS: write JSONL line
AG->>LLM: stream(messages, tools)
LLM-->>AG: AssistantMessage { content: [ToolCall(read, {path:"README.md"})] }
AG->>SM: append AssistantMessage
AG->>TR: execute tool_call
TR->>FS: cat README.md
FS-->>TR: file content
TR-->>AG: ToolResultMessage
AG->>SM: append ToolResultMessage
AG->>LLM: stream(messages + tool_result)
LLM-->>AG: AssistantMessage { content: [TextContent("This is a...")] }
AG->>SM: append AssistantMessage
AG-->>API: agent_end
API-->>U: Promise resolve
10.8.1 Agent Loop
@earendil-works/pi-agent-core/Agent 类的核心循环:
1 | while (true) { |
10.8.2 飞书集成里的关键代码
1 | // pi-adapter.js 我们项目 |
为什么倒序遍历?
- 一个 turn 可能产生多个 assistant message_end(tool call 后再来一个)
- 我们要的是最后一个 text block
为什么只找 type === "text"?
- 还要过滤掉
thinking和toolCall - LLM 经常先发 thinking block(带原因),然后 text block(最终回答)
10.9 启动耗时分解(实测)
我们在 Termux on Android 10 实测 MiniMax m3 + RPC 模式:
| 阶段 | 耗时 |
|---|---|
| spawn Node + cli.js | 200ms |
| parse argv + dispatch | 10ms |
runRpcSession 初始化 |
50ms |
createAgentSessionServices |
400ms |
createAgentSession (modelRuntime + Agent) |
100ms |
| stdin/stdout pipe open | 40ms |
| 总计 | ~800ms |
SDK 模式同进程略快(~50ms),但实际生产中单次 prompt耗时主要由 LLM 决定(5-8s),启动开销可忽略。
10.10 实战建议
10.10.1 不要 hack 核心模块
Pi 升级频繁(v0.83.0 比 v0.82.0 改了很多 API)。飞书集成项目只用了公开 API(RpcClient),即使 Pi 升级到 v0.90 也能直接用。
10.10.2 用事件订阅代替 polling
1 | // ❌ 错误:poll get_state |
10.10.3 RpcClient 永远单实例
1 | // ✅ 正确 |
10.10.4 处理 RPC 子进程崩溃
1 | proc.on('exit', (code, signal) => { |
10.11 小结
- 模块分四层:CLI 入口 → 模式层(modes) → 核心层(core) → 外部依赖(pi-ai / pi-agent-core)
- ProviderComposer 三层叠加:built-in + models.json + extensions
- ModelConfig 加载即冻结,运行时改不了
- AgentSession 状态机:idle / streaming / tool-executing / compacting / aborted
- RPC 模式内部用 SDK session + stdin/stdout JSONL
- 飞书集成用官方
RpcClient,只碰公开 API
Chapter 11 · 故障排查与最佳实践
本章汇总 Pi + 飞书集成项目实际踩过的坑。每个问题都包含:症状、根因、修复、预防。
按”出错频度”排序。
11.1 models.json schema 错误
症状
启动 Pi 报:
1 | Invalid models.json schema: |
根因
JSON 合法但不符合 Pi 的 TypeBox schema。常见字段:
api必须是允许的值(anthropic-messages/openai-completions/openai-responses/google-generative-ai/bedrock/mistral/vercel-ai-gateway)contextWindow必须正整数maxTokens必须正整数models[].id必填
修复
用 jq 验证:
1 | cat ~/.pi/agent/models.json | jq . |
然后对照 Chapter 3 字段表。
预防
改完配置后立刻 pi --version 验证(schema 错误会在启动时报)。
11.2 apiKey 环境变量没展开
症状
Pi 启动后 /model 看不到自定义 provider,或报 401:
1 | Authentication failed for provider minimax-cn |
根因
models.json 里写了 "apiKey": "$MINIMAX_CN_API_KEY",但启动前没 export 这个环境变量。
修复
1 | export MINIMAX_CN_API_KEY=sk-cp-... |
或写到 ~/.bashrc / ~/.zshrc:
1 | echo 'export MINIMAX_CN_API_KEY=sk-cp-...' >> ~/.bashrc |
预防
飞书集成项目的强制流程:
1 | # 启动前检查 |
11.3 RPC 模式 assistant 文本”丢”了
症状
bridge 收到飞书消息,Pi 处理了,但 runPi() 返回空字符串或 “pi 未返回有效回复”。
根因(我们踩过的)
11.3.1 模型不输出 text block
某些 reasoning 模型(如 Step Plan step-3.5-flash)在 RPC 模式下只输出 thinking block,没有 text block:
1 | { |
修复:换模型(MiniMax m3 在 RPC 下稳定),或在 system prompt 里强制要求 text 回复:
1 | 总是先用 text block 给出最终答案,不要只用 thinking。 |
11.3.2 文本里有 tool_call XML 幻觉
某些模型会把”想调工具”的意图写成 XML 文本放进 text block:
1 | {"type": "text", "text": "<tool_call>\n<function=bash>\n<parameter=command>env | grep '^PI_'</parameter>\n</function>\n</tool_call>"} |
bridge 没过滤这个,看到 text 就直接发给飞书,用户看到乱码。
修复(Plan A 已落地):bridge 端过滤:
1 | function extractAssistantText(events) { |
11.3.3 找错 message_end(顺序问题)
bridge 拿到的 events 数组里多个 message_end,找错了:
1 | // ❌ 错误:拿第一个 |
修复:始终倒序遍历(详见 Chapter 10)。
11.4 Pi 子进程频繁崩溃
症状
bridge log 看到:
1 | [bridge] calling runPi... |
根因
- OOM(Android 内存不足)
- 超时(60s prompt 超时被 kill)
- bash 工具跑长任务(如
npm install)
修复
- 加大 timeout:
1 | const events = await client.promptAndWait("...", undefined, 180000); // 3 分钟 |
- 监 exit 事件自动重启:
1 | client._proc.on('exit', (code, signal) => { |
- 避免长 bash:
1 | > 帮我装 npm 依赖 |
拆成多步:
1 | > 1. 看 package.json 列出依赖 |
11.5 “Model names cannot contain spaces” 错误
症状(Hermes 端,不在 Pi)
1 | Error: Model names cannot contain spaces. |
根因
Hermes 的 /model 命令校验模型名 不能有空格。这跟 Pi 没关系。
修复
模型 ID 用连字符或下划线:
1 | ❌ MiniMax M3 |
我们项目用 minimax-m3(lowercase + 连字符)。
11.6 RPC 子进程 spawn 太慢
症状
每条飞书消息首次延迟 1.5s(spawn + connect + 初始化)。
根因
RpcClient 默认每条 prompt 重新检查 start():
1 | // ❌ 错误:每次新建 client |
修复
单进程常驻(Plan A 已落地):
1 | let client = null; |
实测:常驻后单条消息延迟从 1.5s → 0.1s(RPC 调用开销)。
11.7 “Connection refused” on Feishu WS
症状
bridge log:
1 | [error] feishu WS 断开,code=1006 |
根因
- appSecret 错了(最常见)
- 网络问题(Termux 后台被杀)
- 没设
larkWSClient(用的是老的lark.Client)
修复
- 验证 appSecret(用
secrets-getter或 .env) - 加 ping watchdog:
1 | setInterval(() => { |
- Termux 加 wake lock:
termux-wake-lock(需要 root 或 Termux:API app 授权)
11.8 LLM 返回 “Tool use not supported”
症状
message_end 里:
1 | {"stopReason": "error", "errorMessage": "Tool use not supported by this model"} |
根因
模型不支持 tool call(老模型或纯文本模型)。
修复
- 换支持 tool call 的模型(绝大多数现代模型都行)
- 或在
models.json里加compat.requiresToolResultName: false等兼容配置 - 或干脆禁用 tools:
1 | pi --no-tools -p "纯对话" |
11.9 Bash 工具被 sandbox 拦
症状
LLM 调 bash 报:
1 | [error] Permission denied for command: sudo apt install |
根因
不是 Pi 拦的,是OS 级权限(Termux 没 root)。
修复
- 换不需要 sudo 的命令
- 或在
~/.bashrc里 alias sudo 到 echo - 或用
tsu(Termux su 替代)
11.10 Session 文件爆炸
症状
1 | du -sh ~/.pi/agent/sessions/ |
根因
每个 session 是 JSONL,永不删除(除非手动)。飞书集成 per-chat session 后,每天可能产生几百个新文件。
修复
写个 cron 脚本定期清理:
1 |
|
加 cron:
1 | # ~/.hermes/cron/... |
预防
控制每 chat session 大小:定期 /compact 或用 pi --no-session。
11.11 Provider baseUrl 拼错
症状
1 | [error] ECONNREFUSED api.minimaxi.com:443 |
或:
1 | 404 Not Found |
根因
- 缺
/v1后缀(OpenAI 协议) - 缺
/anthropic子路径(Anthropic 兼容端点) - 多打 / 少打 / 拼错
修复
实测常用 baseUrl:
| Provider | baseUrl |
|---|---|
| MiniMax (anthropic) | https://api.minimaxi.com/anthropic |
| Step Plan (anthropic) | https://api.stepfun.com/step_plan |
| DeepSeek (openai) | https://api.deepseek.com |
| Ollama (openai) | http://localhost:11434/v1 |
| OpenAI 官方 | https://api.openai.com/v1 |
| Anthropic 官方 | (不填,用内置) |
预防
curl 手动测一下:
1 | curl -X POST $baseUrl/chat/completions \ |
11.12 上下文超限(context overflow)
症状
1 | [error] context length exceeded: 200000 > 195840 (limit - reserve) |
根因
对话太长,超过 contextWindow - reserveTokens。
修复
- 主动
/compact压缩上下文 - 调大
reserveTokens:
1 | // ~/.pi/agent/settings.json |
- 切到 context window 更大的模型
11.13 总结:飞书集成项目踩过的 7 大坑
| # | 问题 | 根因 | 修复 |
|---|---|---|---|
| 1 | RPC 模式 assistant 文本”丢” | text block 含 tool_call XML 幻觉 | 提取时过滤 + 倒序 |
| 2 | 子进程频繁 spawn 慢 | 每条消息新建 client | 单进程常驻 |
| 3 | Hermes 报 “Model names cannot contain spaces” | /model 命令校验 |
模型 ID 用 minimax-m3 形式 |
| 4 | Pi 路径未脱敏导致 baseUrl 错 | .env 被系统脱敏 |
用 secrets-getter 或环境变量 |
| 5 | Step Plan 在 RPC 下不稳定 | thinking → tool_call XML 幻觉 | 切到 MiniMax m3 |
| 6 | 飞书 WS 断开 | appSecret 错 / Termux 后台被杀 | secrets-getter + wake lock |
| 7 | session 文件无限增长 | 永不删除 | cron 自动归档 |
11.14 性能调优清单
- 单 RpcClient 实例常驻
- 加合理的 timeout(30-180s)
- Pi 子进程挂了自动重启
- session 文件定期归档
- 上下文超过 80% 触发
/compact - 用
minimax-m3而非 step-3.5-flash(RPC 下更稳) - models.json 用
$ENV_VAR而非明文 apiKey -
appSecret/apiKey走 secrets-getter - 飞书 WS 加重连 watchdog
- Termux 加 wake-lock
11.15 监控指标(生产级)
1 | // 飞书桥接应该记录的指标 |
11.16 小结
- 配置错误最常见(apiKey / baseUrl / schema)
- RPC 文本”丢” 是模型 + 协议组合问题,必须双端防御(filter + retry)
- 单进程常驻比每条 spawn 快 10x
- .env 文件会被 Hermes 脱敏,用 secrets-getter 或环境变量
- session 文件要定期清理,否则 OOM
- Pi 升级会改 API,只依赖公开 API(RpcClient)
完结 🎉
至此,Pi Agent 完整使用与开发指南的 11 章全部写完。完整索引见开头的章节导航。
如有问题或要贡献新章节,请联系 Hermes Agent(bs 的 AI 助手)。