Appearance
🐣 小a的"考试"难题
Agent 越来越强,但老z提出一个尖锐问题:"你怎么知道它行不行?"
小a:"跑几次看看呗?"
老z摇头:"Agent 是非确定性的——同样的输入,这次对、下次可能错。你怎么测?跑一次对不算对,跑十次对八次算及格吗?"
小a傻眼了。传统软件测试是"给输入,断言输出完全相等",但 Agent 的输出每次都可能不同,这怎么测?
老z:"这就是 pi-evals 解决的——专门给 Agent 做的评测体系。"
23.1 Agent 评测为什么难
先讲清楚难点。
🧙 传统测试 vs Agent 评测:
传统单元测试 Agent 评测 被测对象 确定性函数 非确定性 Agent 同样输入 输出一定相同 输出可能不同 断言方式 expect(result).toBe(42)精确匹配不能精确匹配 评判 对/错二值 多维度打分 传统断言失效:你不能写
expect(agentReply).toBe("正确答案")——Agent 这次可能这么说,下次换个说法,都对,但字符串不等。所以需要新的评测方法。
23.2 pi-evals 的思路:行为评测 + 代码检查
pi-evals 的方案,看它的 README 就清楚:
📄 源码证据
packages/evals/README.mdPi evals are behavioral, model-backed checks for Pi workflows. They adapt a real AgentSession to vitest-evals, run it in isolated temporary project and agent directories, and attach native Pi session artifacts. Use them to measure end-to-end behavior and compare prompts, tools, skills, models, or other harness configurations.
🧙 三个关键词:
- behavioral(行为评测):不测"输出字符串相等",测"行为对不对"(任务完成了没?代码改对没?)
- model-backed(真实模型驱动):评测跑的是真实的、模型驱动的 Agent——不是模拟器,是端到端真跑
- isolated(隔离环境):每次评测在临时目录跑,互不污染
⚠️ 注意措辞:README 里的 "model-backed" 指的是"被测对象是真实模型驱动的 Agent",不是"用另一个模型当裁判打分"。评判对错的是代码写的检查函数(下一节你会看到),这一点别被字面意思带偏。
基于真实 AgentSession
📄 源码证据
packages/evals/src/├── pi-harness.ts ← 把真实 AgentSession 适配成评测 harness ├── smoke.eval.ts ← 冒烟测试(基础可用性) ├── extensions.eval.ts ← 扩展功能评测 └── vitest-evals/ ← 评测框架适配
🧙 pi-evals 不是"模拟"测,是"真刀真枪"测:
它用真实的 AgentSession(第 20 章那个编码会话),跑真实的 Agent 循环、调真实工具。只是把环境隔离到临时目录,不污染真实项目。
这样测出来的,是 Agent 的端到端真实表现,不是简化的模拟。
23.3 怎么判对错:代码写的 Judge 函数
传统断言失效了,那 pi 怎么判断"Agent 干对了没"?答案:写一个检查函数(judge),检查"行为的结果"。
🧙 judge 是什么?
judge 是一个普通的代码函数:拿到 Agent 的输出结果,检查关键点,返回"过/不过"和理由。
看一个真实的 judge——
extensions.eval.ts里评测"Agent 能不能自己写一个扩展":
📄 源码证据
packages/evals/src/extensions.eval.ts:53typescriptconst ExtensionAuthoringJudge = createJudge<PiCodingAgentInput, ExtensionAuthoringOutput>( "ExtensionAuthoringJudge", ({ output, toolCalls }) => { const failures: string[] = []; if (output.extensionSource === null) { failures.push("generated extension source is unavailable"); } // ... if (output.response !== "Hello, Bob!") { failures.push('final response was not exactly "Hello, Bob!"'); } return { score: failures.length === 0 ? 1 : 0, // 没失败 → 1 分,有失败 → 0 分 metadata: { rationale: failures.length === 0 ? "Extension authoring workflow completed." : failures.join("; "), // 失败原因拼成评语 }, }; }, );
🧙 读懂这个 judge:
- 它检查的是"结果",不是"过程":有没有生成扩展源码、最终回复是不是预期的那句
- 它是确定性代码:没有让另一个模型来打分——
response !== "Hello, Bob!"就是纯字符串比较- 它返回分数 + 理由:
score(0/1)给框架统计,rationale(评语)给人看"为什么挂"所以"评测" = 设计好的检查点 + 确定性判定。 能写成代码的检查点,全用代码写——可靠、免费、可复现。
两种 judge 风格:规则检查 vs LLM-as-a-judge
🧙 诚实地说,"用模型当裁判"(LLM-as-a-judge)也是行业里常见的做法——让另一个模型看输出"这段改动解决问题了吗?"给个分。pi 的仓库里用的是代码检查(确定性),但你设计自己的评测时,两种都可以:
规则检查(代码 judge) LLM-as-a-judge 谁判 你写的代码 另一个模型 成本 免费 每次打分烧 token 可靠 确定、可复现 裁判模型也可能误判 能判什么 能写成规则的结果(文件内容、响应文本) 语义层面的好坏(代码风格、方案优劣) 适用 结果可枚举、可断言 结果开放、要理解语义 工程建议:能用代码判的用代码(便宜可靠),代码判不了的(语义、风格)才考虑用模型判。 这和你写单元测试的原则一样——能用断言,就别上裁判。
评测的维度
📄 README 提到:"compare prompts, tools, skills, models, or other harness configurations"
pi-evals 能对比的维度:
- prompt:换了系统提示词,Agent 表现变好还是变差?
- tools:加了/减了工具,影响如何?
- skills:新技能有用吗?
- models:换个模型(GPT vs Claude),谁更强?
- configurations:不同配置组合的效果
评测不只是"及格/不及格",更是"对比"——A 方案和 B 方案哪个好。 这对持续优化 Agent 至关重要。
23.4 隔离环境:每次评测都干净
🧙 为什么隔离?
Agent 会改文件、跑命令。如果评测在真实项目里跑,会污染项目(改坏代码、留下垃圾)。pi-evals 每次评测在临时目录跑,跑完就删,互不影响。
这保证了:
- 评测可重复(每次环境一样)
- 不破坏真实项目
- 多个评测能并行(各自独立目录)
23.5 怎么跑评测
📄 源码证据 README
bashnpm run eval -- --provider openai --model gpt-5.6-sol # 模型名仅为示意,换成你实际用的或用环境变量:
bashPI_PROVIDER=openai PI_MODEL=gpt-5.6-sol npm run eval # 同上,模型名示意
🧙 跑评测要指定模型——因为评测要跑真实的模型驱动 Agent(Agent 本身用模型干活)。注意:评测要花真钱(调真实 LLM)。这是评测的代价。
23.6 评测的价值:回归守护
🧙 为什么花精力做评测?最大的价值是"回归守护":
Agent 在持续迭代——改 prompt、加工具、换模型。每次改动都可能让 Agent"变蠢"(比如某个工具的描述改了,模型不会用了)。
没有评测:你改了代码,看着没问题就发布 → 用户发现 Agent 变蠢了 → 事故。
有评测:每次改动跑评测 → 评测发现某项分数掉了 → 阻止发布 → 保证 Agent 不会"悄悄变差"。
这就是 Agent 类项目的"测试网"。 传统软件靠单元测试防回归,Agent 靠 evals 防回归。
23.7 代价
🧙 代价一:贵
每次评测调真实 LLM,烧 token、烧钱。不能像单元测试那样随便跑几千次。
代价二:慢
Agent 跑一个任务要几十秒到几分钟,评测一堆任务很慢。不像单元测试毫秒级。
代价三:judge 检查不到"语义好坏"
代码写的 judge 只能检查"能写成规则的结果"(文件改没改、回复对不对)。"这段代码风格好不好""这个方案优不优"这类语义判断,代码 judge 无能为力——要么人肉 review,要么上 LLM-as-a-judge(但裁判也可能误判)。所以评测的覆盖面,受限于你写的检查规则。
本章小结
🐣 小a的第二十三课(Part II 收官)
┌──────── Agent 评测的智慧 ────────┐ │ │ │ • 难点:Agent 非确定性 │ │ 传统断言失效 │ │ │ │ • pi-evals 方案: │ │ 行为评测(测行为对不对) │ │ + 代码 judge(判结果) │ │ + 隔离环境(临时目录) │ │ │ │ • judge = 确定性检查函数 │ │ score + rationale │ │ (需要语义判断才上模型裁判) │ │ │ │ • 用真实 AgentSession: │ │ 端到端真实表现,非模拟 │ │ │ │ • 评测维度: │ │ prompt/tools/skills/models │ │ → 对比优化 │ │ │ │ • 最大价值:回归守护 │ │ 防 Agent 悄悄变蠢 │ │ │ │ • 代价:贵 + 慢 + judge 有边界│ └─────────────────────────────────┘
关键认知:Agent 评测不能用传统测试,要用"行为评测 + 代码 judge"——judge 是检查结果的确定性函数,语义层面的判断才需要上模型裁判(LLM-as-a-judge)。它最大的价值不是"及格判定",而是"回归守护"——保证 Agent 在迭代中不会悄悄变差。
🎉 Part II 源码篇完结
读完 16-23 章,你已经拆解了 pi 的所有核心包:
- pi-ai(统一 LLM 调用)
- pi-agent-core(Agent 运行时)
- pi-coding-agent(编码产品)
- pi-tui(终端界面)
- pi-protocol(通信协议)
- pi-server/client(远程服务)
- pi-evals(评测体系)
接下来 Part III:我们不再"读别人的代码",而是自己用 TypeScript 从零造一个 Agent。
课后实验
- 读一个 eval:打开
packages/evals/src/smoke.eval.ts(或extensions.eval.ts),找到 judge 函数,看它检查了哪些结果、怎么打分。 - 写一个 judge:给你的 Agent 设计一个评测任务(如"让它改一个文件的某处"),写一个代码 judge 检查"改对没有"。
- 理解两种判法:想一个"代码 judge 判不了"的任务(如"评价这段代码风格"),你会怎么解决——人肉 review 还是 LLM-as-a-judge?各有什么代价?
- 体会回归守护:假设你改了 pi 的某个工具描述,该跑什么确认没让 Agent 变蠢?(答案:跑相关 eval)
进入 Part III:理论学完了,源码看过了,现在自己造一个。 → 第 24 章 · 开张准备