智能体面试准备(五十六)智能体评测与回归门禁工程实战------从 Eval-as-CI 到上线守护
引言:本篇与系列的关系
B31 讲过「智能体评测体系深化」------轨迹指标、部分分、LLM-as-Judge。B54 讲过「线上实验与效果归因」------A/B、影子、因果归因、上线门禁。两篇解决「怎么衡量 Agent 好不好」。本篇把衡量变成「工程动作」:Eval-as-CI,让每次代码提交、每次发布都自动过评测门禁,防止悄悄退化。
为什么放在工程实战深化收尾?因为前面 A53-A56、B53-B55 讲的都是「怎么把 Agent 造出来、跑起来、交付出去」,但如果没有评测门禁兜底,任何一次重构、依赖升级、超参调整都可能让效果无声无息地掉。门禁是「工程化的质量护栏」。本篇也呼应 A55 的「黄金 query 回归集」和 A56 的「冒烟测试门禁」------大模型侧和 Agent 侧用的是同一套工程思想。
评测与回归门禁全景(Eval-as-CI)
┌──────────────────────────────────────────────────────────┐
│ 开发者提交 PR / 发布流水线触发 │
└─────────────────────────┬────────────────────────────────┘
▼
┌────────────────────────────────────────┐
│ 离线评测集(Golden Set,版本化) │
│ 覆盖:单跳/多跳/拒答/工具调用正确性 │
└──────────────┬─────────────────────────┘
▼
┌────────────────────────────────────────┐
│ 自动评分(LLM-as-Judge / 规则 / 部分分) │
│ → 算指标:准确率/轨迹得分/工具正确率 │
└──────────────┬─────────────────────────┘
▼
┌────────────────────────────────────────┐
│ 门禁判定(非劣性:新 ≥ 基线 - ε) │
│ 通过→合并/发布;不通过→拦截+报告 │
└──────────────┬─────────────────────────┘
▼
┌────────────────────────────────────────┐
│ 线上回归守护(影子流量回放 + 看板) │
│ 指标异常→自动拦截/回滚(接 B54) │
└────────────────────────────────────────┘
第一节 评测集管理:Golden Set 是门禁的地基
门禁要「可判定」,前提是有一份可信的离线评测集(Golden Set)。它和 A55 的「黄金 query 回归集」是同一回事,只是 Agent 侧要评的更多:
- 单跳问答:检索/生成正确即可。
- 多跳推理:要评「推理路径是否走对」,而不只是最终答案对(B31 的部分分思想)。
- 拒答场景:该拒答的(证据不足/越权)必须拒,误答比拒答更糟。
- 工具调用正确性:调用的工具对不对、参数对不对、顺序对不对(B31 的「工具正确性」)。
工程要点:评测集要版本化、可复现。每次门禁跑的是「固定 Git tag 的 golden set」,新增用例走 PR 评审后进集,避免「为了过门禁偷偷改答案」的作弊。评测集还要和代码同仓或独立版本库,和模型权重 manifest 一样受管控。
# Golden Set 的版本化加载(示意)
def load_golden(tag: str) -> list[Case]:
# 从版本库按 tag 拉固定评测集,保证可复现
cases = git_fetch("golden-set", tag)
return [Case(**c) for c in cases]
def run_eval(agent, cases) -> dict:
rows = []
for c in cases:
out = agent.invoke(c.query)
rows.append({
"id": c.id,
"answer_ok": judge_answer(out, c.ref),
"traj_ok": judge_trajectory(out.trace, c.ref_trace), # 多跳路径
"tool_ok": judge_tools(out.calls, c.ref_calls), # 工具正确性
"refusal_ok": judge_refusal(out, c.should_refuse),
})
return aggregate(rows)
第二节 自动评分工程:LLM-as-Judge 不是无脑调
B31 提过 LLM-as-Judge。工程落地有三个坑必须讲:
-
一致性:同一个答案让 Judge 评两次,结果可能不同。解法是对关键用例做「多次评审取多数」,或固定 Judge 的 temperature=0 + seed。
-
位置偏置:Judge 容易偏向「排前面的答案」。要成对打乱顺序评(A vs B 各评一次取平均)。
-
成本:每条用例都过一次大模型 Judge,千条集就是千次调用。要分层------规则能判的(如工具调用格式)不调 Judge,只有语义类才调,并按 B54 的成本账核算。
带一致性 + 位置偏置抵消的 Judge(示意)
def llm_judge(answer, ref, judge_model, n=3):
scores = []
for _ in range(n): # 多次取多数,抵消随机
# 顺序打乱:本轮 answer 在前,下轮 ref 在前
order = shuffle([answer, ref])
s = judge_model.score(f"哪个更好?\nA:{order[0]}\nB:{order[1]}")
scores.append(s)
return mode(scores) # 多数投票def stratified_eval(cases, agent, judge):
cheap, need_judge = [], []
for c in cases:
out = agent.invoke(c.query)
if c.kind in ("format", "tool_call"): # 规则可判,省 Judge
cheap.append(rule_judge(out, c))
else:
need_judge.append((c, out))
j = [llm_judge(o.text, c.ref, judge) for c, o in need_judge]
return cheap + j
第三节 回归门禁接入 CI:非劣性检验
门禁的核心是「非劣性」:新版本不要求比基线更好(那样永远过不了),只要求「不比基线差超过阈值 ε」。例如基线准确率 0.82,ε=0.02,则新版本 ≥ 0.80 才放行。这避免了「一次重构小幅掉点就被卡死」的误伤,也拦住了「无声退化超过容忍」的真问题。
接入 CI:PR 触发流水线 → 拉 golden set → 跑 agent → 算指标 → 比对基线 → 出报告(哪些用例回归了)。报告要「可定位」:列出具体退化的 case id,开发者一眼看到是「多跳路径第 2 步工具选错」。门禁失败不让合并,而不是事后发现。
# CI 门禁(GitHub Actions 风格,示意)
name: agent-eval-gate
on: [pull_request]
jobs:
eval:
runs-on: gpu-runner
steps:
- uses: actions/checkout@v4
- run: python run_eval.py --golden v1.3 --baseline-metric 0.80
# 门禁逻辑:新指标 < 0.80 则非零退出,PR 被挡
- run: python gate.py --min 0.80 --report eval_report.md
| 门禁项 | 基线 | 阈值 ε | 不通过后果 |
|---|---|---|---|
| 答案准确率 | 0.82 | 0.02 | 拦截合并 |
| 多跳轨迹得分 | 0.75 | 0.03 | 拦截合并 |
| 工具调用正确率 | 0.90 | 0.02 | 拦截合并 |
| 拒答准确率 | 0.88 | 0.03 | 拦截合并 |
第四节 线上回归守护:影子流量回放
CI 门禁管「代码合不合」,线上守护管「发布后稳不稳」。做法接 B54 的影子流量:
- 新版本先接「影子流量」:把线上真实请求的副本发给新版本,不影响用户,只记录新版本的回答和指标。
- 回放 golden set + 真实样本,比对「新版本 vs 线上版本」的指标差,超阈值自动拦截全量发布、甚至回滚。
- 看板实时显示:答案准确率、拒答率、工具失败率、异常输出率(A56 提的坏权重信号也归这里)。
这一步和 B54 的「因果归因 / 上线门禁」完全对接------本篇是 B54 的「自动化执行版」:把人工看报告变成系统自动拦截。
第五节 工程落地清单
把评测门禁做成可交付,面试报这 5 条:
- Golden Set 版本化、独立管控,新增用例走 PR 评审,防作弊。
- 评分分层:规则可判的不调 Judge,语义类才调,并做一致性 + 位置偏置抵消。
- 门禁用非劣性(新 ≥ 基线 - ε),报告定位到退化 case id,失败拦截合并。
- 线上影子流量回放 golden + 真实样本,超阈值自动拦截/回滚,接 B54 看板。
- 门禁和看板指标对齐 A55/A56 的检索质量与权重质量,全链路一套护栏。
第六节 真实门禁踩坑(面试可讲)
讲两个真实教训,比背流程更可信:
教训一:门禁被「平均水平」骗过。某次重构后,整体准确率 0.81 看似没掉(基线 0.82、ε=0.02 放行),但多跳子类从 0.75 掉到 0.60。原因是平均被单跳类(占 80%)拉平了。修复:门禁从「总指标」改成都按子类分别过非劣性,任何子类退化都拦截。这点和 A55「黄金集分模态回归」是同一思路------聚合指标会掩盖结构性退化。
教训二:Judge 漂移导致门禁抖动。Judge 模型升级后,同一答案评分整体偏高 0.03,结果「旧版本突然全过、新版本也全过」但两者不可比,门禁失去意义。修复:Judge 版本写进 golden set 的 manifest,门禁比对必须「同一 Judge 版本」,Judge 升级要走独立校准而非静默替换。
这两个坑的共同点是「聚合/静默掩盖了真实退化」,工程上靠「分层 + 版本化 + 可定位报告」解决------和 A55/A56 的护栏思想一脉相承。
面试速答(本篇可直接背的 3 句)
- 门禁地基是版本化 Golden Set(单跳/多跳/拒答/工具正确性四类),同仓版本管控、新增用例走评审,防「改答案过门禁」作弊。
- LLM-as-Judge 工程化三件事:多次评审取多数抵随机、顺序打乱抵位置偏置、规则可判的不调 Judge 省成本。
- 门禁用非劣性(新 ≥ 基线 - ε)而非「必须更好」,失败定位到退化 case 拦截合并;线上靠影子流量回放自动拦截/回滚,是 B54 的自动化执行版。
高频追问清单
- Golden Set 怎么防止「为了过门禁改答案」?(提示:版本化 + 用例评审)
- LLM-as-Judge 两次结果不一致怎么办?位置偏置怎么消?
- 为什么门禁用「非劣性」而不是「必须更好」?ε 怎么定?
- CI 门禁和线上守护分工是什么?影子流量怎么不影响用户?
- 评测集上千条,Judge 成本怎么控?(提示:分层评分 + 部分分)
- 门禁和 A55/A56 的质量护栏怎么统一?(提示:检索质量 + 权重质量 + Agent 质量一套指标)
系列收尾
B 系列工程实战深化(B53 多模态 Agent / B54 线上实验 / B55 工程化交付 / B56 评测门禁)与 A 系列(A53 服务化 / A54 成本 / A55 多模态 RAG / A56 训推一体)一起,把「大模型 + 智能体」从论文到生产的全链路讲透。你正在做的 4MRAG 论文工程,恰好能在这八篇里找到完整的工程映射------这是面试最硬的差异化弹药。