Skip to content

🐣 小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.md

Pi 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.

🧙 三个关键词:

  1. behavioral(行为评测):不测"输出字符串相等",测"行为对不对"(任务完成了没?代码改对没?)
  2. model-backed(真实模型驱动):评测跑的是真实的、模型驱动的 Agent——不是模拟器,是端到端真跑
  3. 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:53

typescript
const 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

bash
npm run eval -- --provider openai --model gpt-5.6-sol   # 模型名仅为示意,换成你实际用的

或用环境变量:

bash
PI_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


课后实验

  1. 读一个 eval:打开 packages/evals/src/smoke.eval.ts(或 extensions.eval.ts),找到 judge 函数,看它检查了哪些结果、怎么打分。
  2. 写一个 judge:给你的 Agent 设计一个评测任务(如"让它改一个文件的某处"),写一个代码 judge 检查"改对没有"。
  3. 理解两种判法:想一个"代码 judge 判不了"的任务(如"评价这段代码风格"),你会怎么解决——人肉 review 还是 LLM-as-a-judge?各有什么代价?
  4. 体会回归守护:假设你改了 pi 的某个工具描述,该跑什么确认没让 Agent 变蠢?(答案:跑相关 eval)

进入 Part III:理论学完了,源码看过了,现在自己造一个。 → 第 24 章 · 开张准备