返回 AICAP-180
B3 · Day 26agent loop + 评测统计

runTaskEval 打通

B3 这一批的主题是「把 agent loop 跑起来 + 把它量出来」。

阶段: B3 · agent loop + 评测统计(Day 21-30) 标签: #agent-eval #llm-judge #completion-rate #tool-calls

今日导引(由浅入深)

B3 这一批的主题是「把 agent loop 跑起来 + 把它量出来」。

  • Day 21-22 厘清了 workflow vs agent 的边界,定下 tool 契约;
  • Day 23 用 Wald CI 提前感知「29 任务太少」;
  • Day 24 写了 framework-free 的 loop.ts
  • Day 25 把 loop 接到 AML 的 typology→SAR 三步任务上。

今天 Day 26 是这条线的「打通日」:把那个 framework-free 的 agent loop 挂进评测 harness runTaskEval,让每个任务同时被两把尺子量——completion(任务做对没有,code grader + LLM-judge 判)和 tool-call 数(效率/啰嗦度)。

在 B1→B18 的能力曲线上,这是从「能跑」走向「能量化地跑」的关键节点:没有这一步,后面 D27 的横向对比、D28 的 CI、D30 的选型结论都无从谈起。今天的最小可判定产出是:pnpm eval:agent 能在离线 fixture 下跑通、吐出报告骨架并 commit;真实 completion% 与 avg tool-calls 要等 key(待跑)。

1. 机理精读

为什么要双指标,而不是只看一个「分」。

单一 completion 率会把两种截然不同的失败混在一起:

  1. 「做错了」——结论错、格式坏、被 prompt 注入劫持、幻觉了一个数字;
  2. 「做对了但绕远路」——多调了 5 次工具、来回试探才收敛、触发 step cap。

第一类是质量问题,第二类是成本/效率问题,二者处理手段完全不同(前者改提示/约束,后者改工具设计/早停)。把 completiontool-call 数 分离,就能在同一张报告里区分「对不对」和「贵不贵」——这正是 Anthropic《Demystifying evals》(2026-01) 强调的判分口径:grade the OUTCOME(判结果不判过程),但要单独度量过程成本。

completion 怎么判:code grader + LLM-judge 双层。

runTaskEval 对每个任务先跑一个确定性的 codeCheck(廉价、启发式,如正则 has(/structur|smurf/i)),再跑一次 LLM-judge:

  • code grader 是第一道便宜的粗筛,确定性、可复现、零成本;
  • LLM-judge 才是真正的 outcome 判分,遵循三条纪律:
    • (1) 判 OUTCOME 不判步骤;
    • (2) 给 partial credit(score∈[0,1],pass≈1 / fail≈0 / 中间为部分分);
    • (3) 信息不足时明确返回 unknown 而非硬猜。

unknownRate 被单列成一个指标,因为「裁判不敢判」本身是信号——往往意味着任务描述不清或证据缺失,而不是模型对错。

为什么 code grader 与 LLM-judge 不能互相取代。

code grader 用正则,只能抓「关键词在不在」,抓不住语义对错(比如模型说了 "structuring" 但结论是「不可疑」,正则会误判 pass)。LLM-judge 懂语义但有主观偏差且要花钱。两者并存:code grader 当廉价烟雾测试和交叉验证,judge 当主判。报告里 codePassRatecompletionRate 若大幅背离,本身就是一个该深查的信号。

为什么 LLM-judge 必须配人标 κ 校准(但今天还没有)。

judge 是模型,模型会系统性偏差(偏松/偏严/对自己输出风格宽容)。所以 runTaskEval 在传入 humanLabels 且配对数 ≥2 时,会顺手算 judge↔human 的 Cohen's κ + bootstrap CI(复用 B2 已建的 cohensKappa.ts),把「裁判本身可不可信」也量出来。今天先把 harness 接通,κ 校准(≥50 条人标、目标 κ≥0.6)是后续的事——在拿到 κ 之前,不能单凭 judge 数字下选型结论,这是 SOTA 段的硬约束。

与相邻概念的边界。

  • 今天打通的是「评测 harness ←→ agent loop」这段管道,不是评测方法学本身(那是 Day 23 的 Wald CI、Day 28 的 paired bootstrap)。
  • 也不是真实多步轨迹的产生——tasks.ts 是单轮 prompt-in/output-out 的任务集,runAgentLoop 的多步闭环要在带工具的真实运行里才触发。
  • Day 26 的角色就是把两者接上线、让 pnpm eval:agent 端到端跑起来。

2. 推导 / 手算 / 代码走读

runTaskEvalsrc/agent/eval/agentEval.ts,关键走读:

  • 核心签名runTaskEval(tasks: EvalTask[], opts: RunTaskEvalOpts): Promise<TaskEvalReport>optsgenerate: GenerateFnjudge: JudgeFn 都是注入的——这就是「逻辑用 fake 单测、真实跑由 scripts/run-agent-eval.ts 注入 DeepSeek/OpenRouter 模型」的解耦点。
  • 逐任务三步(第 78–92 行):对每个 task,
    • g = await opts.generate(task) 拿模型输出;
    • task.codeCheck ? task.codeCheck(g.output) : undefined 跑确定性码检;
    • 最后 judge = await opts.judge({ task, output: g.output }) 拿 judge 裁决;
    • 三者打包成 TaskRunResult(含 costUsdlatencyMs)。
  • 聚合(第 94–100 行):
    • completionRate = passes/n(judge.label==='pass' 的占比);
    • partialCreditMean(judge.score 的均值,先逐个 clamp01);
    • unknownRate(judge.label==='unknown' 占比);
    • codePassRate只在声明了 codeCheck 的任务上求均值,否则 null 而非 0)。
  • κ 校准(第 102–115 行):仅当 opts.humanLabels 存在且配对 paired.length >= 2 时,调 cohensKappaWithCI(...)judgeHumanKappa;否则为 null——诚实表示「没人标就没校准」。
  • 真实模型接线makeModelGenerate(model, { modelName })generateText 跑 prompt,从 usage 取 in/out token,只有当 MODEL_PRICES[modelName] 存在时才 estimateCostcostUsd,否则留 undefined 而非静默 0(注释明说 "never a silent 0")。
  • 判分提示JUDGE_SYSTEM 把「grade the OUTCOME / give partial credit / unknown rather than guess / return ONLY JSON」写死进系统提示。
  • 容错解析parseVerdict(text)text.match(/\{[\s\S]*\}/) 抓第一个 JSON 块、clamp01 限分、解析失败默认 {label:'unknown', score:0}——judge 偶尔不守 JSON 也不会让整轮崩。

最小手算(验聚合直觉):假设跑了 4 个任务,judge 给出 [pass(1.0), pass(0.9), fail(0.1), unknown(0)]

  • completionRate = 2/4 = 0.50(两个 pass);
  • partialCreditMean = (1.0+0.9+0.1+0)/4 = 0.50
  • unknownRate = 1/4 = 0.25

注意 completionRate 与 partialCreditMean 这里恰好都 0.50,但语义不同——前者是「几个全对」,后者是「平均完成度」。当模型大量「半对」时两者会拉开,这正是 partial credit 存在的意义。

3. 今日实战

  1. harness 已建(事实):src/agent/eval/agentEval.ts(含 code grader + LLM-judge,支持 unknown + partial-credit)、src/agent/eval/tasks.ts(任务集)、scripts/run-agent-eval.tspnpm eval:agent 脚本,OpenRouter/OpenAI/DeepSeek provider 均已接线。
  2. src/agent/loop.ts 的 framework-free 循环作为「带工具的被测体」接进 runTaskEvalgenerate(注入式),跑全 29 任务 pnpm eval:agent
  3. 输出两个数:completion 率 + 平均 tool-call 数,报告写入 agent-evals/(runner 默认落 agent-evals/reports/run-<stamp>.json)。
  4. 无 key 时:scripts/run-agent-eval.ts 检测到 DEEPSEEK_API_KEY/OPENROUTER_API_KEY 缺失会打印 dry-run("Would run N tasks…")并 exit 0——可先用离线 fixture 跑通 harness 出报告骨架并 commit。

4. 今日实测 / 产出

  • harness 已建:agentEval(含 code grader + LLM-judge:unknown + partial-credit)/ tasks / scripts/run-agent-eval.ts + pnpm eval:agent 已建,OpenRouter provider 已接线。已完成
  • agent-evals/ 新报告:completion% + avg tool-calls —— 待跑(需 OPENROUTER_API_KEY);可先用离线 fixture 跑通 pnpm eval:agent 出报告骨架 + commit。
  • 真实 completion 率与平均 tool-call 数依赖真模型运行,未产出真实数字——不臆造。

5. 常见误区 / 陷阱

  • 只看 completion 不看 tool-calls:会把「绕远路」的高成本行为当成功,掩盖效率退化。
  • 把 codePassRate 当最终分:codeCheck 是廉价启发式正则,只做粗筛;真正的 outcome 判分是 LLM-judge。
  • 拿 judge 数字直接下结论:judge 未经 κ 校准前不可单独采信(κ 是后续才有的)。
  • 把 dry-run 的「Would run …」误当成真跑出了数字:无 key 时 runner 只打印计划并 exit 0,没有任何 completion% 产生。
  • 把 costUsd 的 undefined 当 0:模型未在 MODEL_PRICES 里定价时成本是 undefined(未知),不是免费。

6. 学习资源(每条带 YYYY-MM)

  • Anthropic, Demystifying evals(2026-01)—— 判分口径:grade the outcome、partial credit、unknown,本日双指标设计主引用。
  • 本仓 src/agent/eval/agentEval.ts(runTaskEval / makeLlmJudge / makeModelGenerate / parseVerdict)—— 代码走读底座。
  • 本仓 src/agent/eval/cohensKappa.ts(2026,AICAP B2 产出)—— judge↔human κ + bootstrap CI 实现。
  • Vercel AI SDK generateTextai 包,2025-2026 持续维护)—— 真实模型调用与 usage 取数接口。

SOTA检查 (2026-06 更新)

  • 当前主流:Anthropic《Demystifying evals》(2026-01) 仍是 current 的 LLM-eval 方法论基线;双指标(outcome 判分 + 过程成本)+ partial credit + unknown 三件套仍是 SOTA。
  • 是否仍 SOTA:是。outcome-grading + 独立 grader + 可校准 judge 仍是 2026 评审硬要求。
  • 硬约束:LLM-judge 必须配人标 κ 校准(后续落地),目标 κ≥0.6——勿单凭 judge 数字下结论
  • 过时黑名单:不要用「只报单点 completion 无 CI」的旧做法;不要把 judge 当无偏裁判直接采信;不要把未定价模型的成本静默记 0。
  • 下次复查点:Day 28(paired bootstrap + CI)、Day 30(接真实报告判 CI 是否含 0);OpenRouter 上 DeepSeek/Qwen3 可用性按当周重验(agent 半衰期 ~6 月)。

衔接

  • 昨天:Day 25 — 多步 AML 任务接入(loop 接 typology+sarDraft,跑三步轨迹)。
  • 今天:把 agent loop 挂进 runTaskEval,用 completion + tool-call 双指标度量;harness 全接通,真实数字待 key。
  • 明天:Day 27 — Qwen3 对比与稳健性(同任务集、同 temperature,换模型横向比 Δ)。