最近我一直在研究 Pi、OpenCode、Codex 这类 Coding Agent。它们已经具备模型调用、工具执行、会话管理、流式输出和扩展机制,甚至可以在执行过程中向用户提出结构化问题,等待用户选择后继续运行。
这很容易引出两个问题:
- Pi Agent 已经可以嵌入应用、注册工具、保存会话,它能不能直接做企业 RAG?
- 如果 Pi 可以做这些事情,LangGraph 还有什么是它做不了的?
我最终得到的结论是:
Pi Agent 与 LangGraph 不是简单的竞品关系。Pi 更接近一个可嵌入、可扩展的模型驱动 Agent Harness;LangGraph 更接近一个以显式状态和持久化执行为核心的工作流运行时。
两者都能完成工具调用,也都有状态,但它们把不同问题放在了架构中心。
一、先建立共同基础:Agent Loop 到底做了什么
不管是 Pi、OpenCode 还是 Codex,最小 Agent 都离不开下面这个反馈循环:
text
用户输入
→ Agent 把消息和工具描述发送给模型
→ 模型输出文本或 tool_call
→ Agent 根据工具名找到执行函数
→ 验证参数并执行工具
→ 把 tool_result 写入 Context
→ 再次调用模型
→ 直到没有新的工具调用
模型本身不能直接读取本地文件、数据库或者企业知识库。它只是生成一个结构化的行动请求,例如:
json
{
"type": "function_call",
"call_id": "call_42",
"name": "kb_search",
"arguments": {
"query": "差旅报销需要哪些材料?"
}
}
Agent Runtime 收到以后,调用真正的检索服务,再把结果写成对应的工具返回:
json
{
"type": "function_call_output",
"call_id": "call_42",
"output": {
"results": [
{
"title": "员工差旅报销制度",
"content": "报销时需要提交......",
"source": "https://knowledge.example/policy/42"
}
]
}
}
模型下一轮能够回答,是因为工具结果重新进入了 Context。
因此,一个 Coding Agent 的核心并不是"模型会写代码",而是它建立了模型与外部环境之间持续反馈的闭环。
二、Agent 为什么能暂停并询问用户
OpenCode 或 Pi 在执行过程中弹出问题,让用户从几个选项中选择,本质上仍然是一次工具调用。
模型可能输出:
json
{
"name": "ask_user",
"call_id": "question_123",
"arguments": {
"question": "你希望采用哪种实现?",
"options": [
{
"label": "方案 A",
"description": "改动较少,兼容现有实现"
},
{
"label": "方案 B",
"description": "重构成本较高,但边界更清晰"
}
]
}
}
接下来不是模型服务一直保持连接等待用户,而是 Agent Runtime 保存等待状态:
text
模型调用 ask_user
│
▼
Runtime 创建 pending[question_123]
│
▼
向 TUI / Desktop / Web UI 发送问题事件
│
▼
用户选择方案 B
│
▼
前端回传 answer(question_123, "方案 B")
│
▼
Runtime 完成等待,将答案作为 tool_result 返回模型
OpenCode 的方式
OpenCode 核心中存在 question 工具。其 Question Service 会创建 Question ID 和一个 Deferred,把问题放入 Pending Map,发布 Asked 事件,然后等待 Reply。客户端回传答案后,对应的 Deferred 被完成,Agent 才继续运行。
Pi 的方式
Pi 扩展可以调用 ctx.ui.select()、confirm()、input() 和 editor(),也可以通过 pi.registerTool() 注册一个模型可调用的 question 工具。
在交互式终端中,Pi 自己渲染界面;在 RPC 模式中,Pi 只发送 JSON 请求,由外部客户端负责展示。
三、RPC 模式解决的是什么问题
RPC 是 Remote Procedure Call。这里的"远程"可以只是本机中的两个进程,并不一定经过公网。
Pi 使用普通 TUI 时:
text
Pi Runtime + 终端渲染 + 键盘输入
Pi 使用 RPC 模式时:
text
Pi 后台进程
│ JSON 请求和事件
▼
桌面应用 / VS Code 插件 / 企业 Web 后端
例如 Pi 发出:
json
{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "选择处理方式",
"options": ["直接回答", "转人工复核"]
}
客户端选择后返回:
json
{
"type": "extension_ui_response",
"id": "uuid-1",
"value": "转人工复核"
}
RPC 让 Agent Runtime 与界面解耦。同一个 Pi 后端可以连接终端、桌面软件、编辑器插件或者企业自己的网页。
但需要注意:当前进程中的 UI 等待,和能够跨进程重启、跨天恢复的持久化审批,不是同一种能力。这一点正是 Pi 与 LangGraph 的重要边界之一。
四、Pi 如何实现企业知识库 RAG
Pi 不是向量数据库,也不应该承担所有文档解析和权限管理。更合理的架构是:
text
Pi:对话、推理和工具调度
RAG 服务:文档解析、索引、权限、检索和重排
身份系统:用户、租户、组织和角色
业务系统:工单、ERP、CRM、审批等真实数据与副作用
4.1 离线知识摄取
text
企业网盘 / Wiki / PDF / Word / 数据库
→ 连接器同步
→ 文本、标题、表格和 OCR 解析
→ 按语义和标题结构分块
→ 补充文档元数据和 ACL
→ 生成 Embedding
→ 建立关键词与向量索引
一个企业 Chunk 不应该只有文本和向量,还应保存:
json
{
"chunkId": "expense-policy#3.2",
"documentId": "expense-policy",
"title": "员工差旅报销制度",
"content": "......",
"source": "https://knowledge.example/expense-policy",
"tenantId": "tenant-a",
"allowedGroups": ["all-employees"],
"department": "finance",
"version": "2026-08",
"updatedAt": "2026-08-20T10:00:00+08:00"
}
4.2 在线问答
text
用户问题
→ Pi 调用 kb_search
→ RAG 服务读取可信登录态
→ ACL 权限过滤
→ 关键词+向量混合检索
→ Reranker 重排
→ 返回少量证据片段和来源
→ Pi 根据证据生成回答与引用
在 Pi 中可以注册一个自定义工具。下面是简化示例:
ts
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
pi.registerTool({
name: "kb_search",
label: "企业知识库检索",
description:
"搜索企业内部知识库。回答内部政策、产品和流程问题前调用。",
parameters: Type.Object({
query: Type.String({
description: "完整且适合检索的问题",
}),
topK: Type.Optional(
Type.Number({ minimum: 1, maximum: 10 }),
),
}),
async execute(_toolCallId, params, signal) {
const response = await fetch(
"https://knowledge.internal/api/search",
{
method: "POST",
signal,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.KB_SERVICE_TOKEN}`,
},
body: JSON.stringify({
query: params.query,
topK: params.topK ?? 6,
}),
},
);
if (!response.ok) {
throw new Error(`检索失败:${response.status}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify({ results: data.results }),
},
],
details: data,
};
},
});
}
4.3 为什么要把 RAG 做成独立服务
这样做至少有五个好处:
- 检索能力可以被 Pi、网页搜索框、客服机器人等多个入口复用;
- 权限逻辑集中在可信服务端,而不是依赖 Prompt;
- 文档更新和重新索引不影响 Agent Runtime;
- 可以独立评测召回率、重排质量和检索延迟;
- 更换 Pi、LangGraph 或模型时,企业知识层不需要重做。
4.4 企业 RAG 中最容易被忽略的安全问题
不要让模型自己传入用户身份:
json
{
"query": "查询董事会薪酬制度",
"userId": "admin"
}
userId、tenantId 和权限组必须来自登录态、网关或服务端 Token。否则用户可能通过 Prompt 诱导模型修改参数,实现越权检索。
权限过滤也必须发生在检索层,而不是先把所有结果返回模型,再要求模型"忽略无权内容"。只要敏感内容进入模型上下文,就已经发生了数据暴露。
五、LangGraph 与 Pi 的核心差异
如果只问"能不能写出来",Pi 扩展可以执行任意 TypeScript,理论上可以继续实现状态机、数据库、队列和恢复机制。因此,很难说某个功能在绝对意义上 Pi 永远做不了。
但工程选型应该比较的是:哪些能力由框架提供,哪些需要项目自己维护。
| 维度 | Pi Agent | LangGraph |
|---|---|---|
| 核心抽象 | Agent、Messages、Model、Tools、Events | State、Nodes、Edges、Commands、Subgraphs |
| 控制流 | 模型通常决定下一工具 | 固定边、条件边、规则或模型共同决定 |
| 工具调用 | 原生,Coding Agent 体验成熟 | 原生,但需要自行组装产品界面 |
| 自定义交互 | 扩展 UI、Question Tool、RPC | Interrupt 返回任意可序列化信息 |
| 并行能力 | 可并行执行工具 | 原生 fan-out/fan-in、superstep、reducer |
| 会话保存 | Messages 与会话树 | 完整图状态 Checkpoint |
| 失败恢复 | 业务级恢复需要自行实现 | 可从检查点恢复节点执行 |
| 长期人工等待 | 需要外部数据库和恢复协议 | Checkpointer + interrupt 原生组合 |
| 时间旅行 | 会话历史分支 | 工作流状态检查、重放和分叉 |
| 流程强约束 | Prompt 或外部代码 | 图拓扑直接约束 |
| 最适合 | 开放式工具任务、Coding、MVP | 长流程、审批、故障恢复、审计 |
六、LangGraph 真正原生解决、Pi 需要自己补齐的能力
6.1 显式且强制的业务状态机
假设退款流程规定:
text
身份校验
→ 订单查询
→ 规则检查
→ 金额超过阈值时人工审批
→ 执行退款
→ 写入审计
如果只给 Pi 一段 Prompt:"务必先校验,再审批,最后退款",模型仍然可能由于上下文、工具描述或异常情况跳过步骤。
LangGraph 可以把顺序直接写成图。模型只负责某个节点内部的开放式判断,不能越过不存在的边。
6.2 并行分支与确定性的状态合并
企业问题可能需要同时查询:
text
订单系统 ─┐
设备系统 ─┼─► 汇总诊断
知识库 ─┘
Pi 可以并行执行多个工具,但 LangGraph 进一步定义了并行节点属于同一个 superstep,以及不同节点对共享状态的更新怎样通过 Reducer 合并。
这使得"并行执行"和"并行工作流状态管理"成为两个不同层次的问题。
6.3 持久化检查点与故障恢复
LangGraph 的 Checkpointer 可以在执行步骤边界保存图状态:
json
{
"currentStep": "human_review",
"ticketId": "T-1001",
"retrievedEvidence": ["chunk-1", "chunk-9"],
"confidence": 0.63,
"approvalStatus": "waiting"
}
如果服务重启,可以从当前 Thread 的检查点恢复,而不是重新执行已经完成的查询。
Pi 能保存会话和工具消息,但这不自动等于完整业务状态检查点。项目仍需回答:
- 当前业务节点是什么?
- 哪些并行任务已经成功?
- 哪个 API 调用不能重复?
- 恢复后应该重试、跳过还是补偿?
- 审批结果属于哪个业务版本?
6.4 跨天 Human-in-the-loop
Pi 的 select() 很适合当前交互会话中的选择。
但是如果任务需要等待两天,期间服务可能发布、扩容或重启,仅仅在内存中等待一个 Promise 并不可靠。
LangGraph 的 interrupt() 会把图状态写入 Checkpointer,通过 thread_id 恢复。人工可以查看、批准、拒绝甚至修改状态后,再从相同流程位置继续。
6.5 时间旅行与问题排查
当一个生产 Agent 给出错误决定时,排查问题不能只看最终对话。往往需要知道:
text
当时的状态是什么?
哪个节点修改了哪个字段?
采用了哪条条件分支?
如果修改某个状态,后续结果会怎样变化?
LangGraph 的状态历史和检查点可以支持回看、重放和分叉。Pi 的会话树适合对话历史导航,但业务工作流状态仍需应用自行建模。
七、三个选型场景
场景一:简单企业知识问答
需求:员工询问制度,系统检索知识库并返回引用。
text
问题 → kb_search → Pi 生成带引用答案
选择:Pi + 独立 RAG 服务。
原因是流程很短,主要不确定性来自自然语言理解和检索,没有必要为了两个节点引入完整图编排。
场景二:跨系统售后工单
需求:识别问题、补齐信息、查询设备与订单、检索 SOP、判断保修、人工审批、创建派工单,流程可能持续数天。
选择:LangGraph 更合适。
这里的主要难点已经不是模型会不会调用工具,而是状态、顺序、恢复、幂等和审计。
场景三:既要可靠流程,又要开放式推理
选择:LangGraph 编排 + Pi Agent 节点。
text
LangGraph
├── 权限校验节点
├── 订单查询节点
├── Pi 开放式诊断节点
├── 人工审批节点
├── 业务执行节点
└── 审计节点
在这种结构里:
- LangGraph 管理必须可靠的业务流程;
- Pi 负责需要模型自主探索和多工具推理的局部任务;
- RAG 服务继续保持独立;
- 所有真实写操作使用幂等键并接受权限控制。
八、为什么不能只用 Prompt 控制流程
Prompt 是柔性约束,程序拓扑和权限策略才是硬约束。
适合写进 Prompt 的内容包括:
- 回答风格;
- 如何引用资料;
- 什么时候建议调用某个工具;
- 证据不足时应该怎样表达。
不应该只依赖 Prompt 的内容包括:
- 是否允许查看某份敏感文档;
- 是否必须经过审批;
- 是否允许退款或创建工单;
- 某个步骤失败后是否能够继续;
- 恢复执行时是否会重复扣款或重复创建记录。
这些要求必须落实到权限系统、状态机、幂等机制和持久化层。
九、如何通过一个小实验做选型
不要只阅读框架介绍,可以选一条脱敏流程,同时用两种方式实现:
text
客户报修
→ 补齐序列号
→ 查询保修
→ 检索 SOP
→ 给出处置建议
→ 人工批准
→ 创建模拟派工单
两组使用相同模型、Prompt、知识库和工具。
Pi 组
- 用工具描述和 Prompt 引导流程;
- 用自定义工具实现检索和模拟派工;
- 自己补充审批状态、恢复和幂等逻辑;
- 记录为此新增的框架外代码。
LangGraph 组
- 把业务事实放入 Typed State;
- 把查询、检索、审批和执行拆成节点;
- 使用 Checkpointer 和 interrupt;
- 在多个位置主动终止进程,验证恢复行为。
建议比较的指标
| 类别 | 指标 |
|---|---|
| 正确性 | 任务完成率、工具参数正确率、引用准确率 |
| 恢复 | 崩溃恢复成功率、重复副作用次数、审批续跑正确率 |
| 工程 | 胶水代码量、新增分支改动面、测试和调试时间 |
| 运行 | P50/P95、Token、模型调用次数、基础设施成本 |
| 治理 | 权限拦截、审批记录、Trace 完整度、人工接管体验 |
正确的问题不是谁的 Demo 代码更短,而是谁在目标 SLA 和风险等级下拥有更低的完整系统成本。
十、最终结论
Pi Agent 的优势是轻量、直接、交互性强、工具扩展方便,能够快速构建 Coding Agent、内部助手和企业知识问答。
LangGraph 的优势是显式状态、确定性流程、并行汇合、持久化检查点、故障恢复、长期人工介入以及工作流级别的观察和重放。
所以:
text
单 Agent + 开放式工具任务
→ 优先 Pi
简单 RAG 问答
→ Pi + 独立检索服务
固定步骤 + 分支 + 审批 + 恢复 + 审计
→ 优先 LangGraph
复杂业务流程中包含开放式 Agent 推理
→ LangGraph 编排 + Pi 节点
与其争论"谁会替代谁",更值得掌握的是 Agent Loop、Tool Calling、状态建模、权限、幂等、Checkpoint、Human-in-the-loop 和 RAG 检索。这些知识不会因为下一个 Agent 框架出现而失效。