做 Agent 开发有一类时刻人人都经历过:你改了 system prompt 里的一句话,拿几个 case 手动试了试,感觉 “好像好了一点”,于是发上线。三天后,用户投诉率不降反升。你不知道是这次改动坏了事,还是别的什么原因 —— 因为你从来就没有能力回答 “变好了” 这三个字。
这不是个例,而是这个行业的常态:大量团队的 Agent 迭代,靠的是感觉、胆量和运气。本系列的上一篇说过一句话 ——”没有评估,一切上下文优化都是玄学”。这一篇就把这句话展开:为什么凭感觉调优注定失败,一套最小可行的评估集长什么样,以及怎么让它成为团队真正用得起来的迭代引擎。
一、为什么 “感觉变好了” 不算数
传统软件的验证逻辑非常干脆:输入确定、输出确定、断言可写。一个排序函数,喂进去 [3,1,2],出来必须是 [1,2,3],错了就是错了。
LLM 应用把这三件事同时拆掉了。输入是自然语言 —— 同一句话有一百种问法;输出是自然语言 —— 同一层意思有一百种说法;”正确” 不再是布尔值,而是程度、维度和权衡:答案对了但语气差,算好吗?没调用工具但直接答对了,算好还是算坏?
更要命的是概率性。同一个输入跑两遍,输出可能不同。这意味着任何单次对比实验都不构成证据:你改了 prompt,试了三个 case 都变好,可能只是随机波动;哪怕试十个 case,也未必能区分 “真的提升” 和 “运气好”。没有统计意义上的观测,你在做的不是工程,是掷骰子。
解法只有一个:把验证从 “一次性的手感” 变成 “可重复的测量”。这件事的载体,就是评估集。
二、评估集的最小可行结构
很多团队一听 “评估” 就想到几十万的评测平台和复杂的指标体系,于是迟迟不动手。其实一个能用的评估集,最小结构只有三列:输入、期望、标签。
输入是完整的一次请求内容:用户消息,以及触发问题所需的上下文背景。期望不是标准答案 —— 对开放性任务写标准答案是自欺欺人 —— 而是判据(rubric):这条回答必须做到什么。比如 “必须提及青霉素过敏并建议就医”” 必须调用 query_inventory 而非凭记忆作答 “”给出的退货期限必须与文档一致”。标签是这个 case 的属性:考的是哪类能力、属于哪种失败模式、难度如何 —— 标签的价值在归因时会显现,下文会讲。
数量上,二十到五十条就是够用的起点,远比 “等搭好完美体系再开始” 有价值。case 的来源有优先级:真实使用中翻车的对话是最好的种子,每一条都自带真实分布;其次用合成数据补齐核心路径的覆盖,比如正常流程、边界输入、恶意提问各来几条。从未上线过、没有日志?那就让团队里最懂业务的人,凭经验手写二十条 “这个 Agent 绝对不能搞砸的事”。
判据的设计决定评估集的可用性。原则是:能拆成检查点就别写成一段话。”回答质量要好” 没法判,”是否包含:①过错的承认 ②具体的解决步骤 ③后续补偿方案” 这三个布尔题,人和模型都能稳定判卷。
有了这三样,一个极简的评估跑批器几十行就能写出来:
def run_eval(cases, agent_fn, judge_fn):
"""极简评估跑批:逐条执行、判卷、汇总通过率"""
results = []
for case in cases:
output = agent_fn(case.input) # 跑 Agent
checks = judge_fn(output, case.rubric) # 逐条判据打分
results.append({
"id": case.id,
"tags": case.tags,
"pass": all(checks),
"failed_checks": [c for c, ok in checks if not ok],
})
rate = sum(r["pass"] for r in results) / len(results)
return rate, results # 总通过率 + 逐条明细,供归因
它的输出只有两个数字也不嫌少:总通过率,和每条失败 case 的失败原因。这两个数字,就是后续一切优化的地基。
三、让判卷自动化:规则与法官的混合
评估集最怕的是 “每次判卷标准不一”。人工判卷既慢又漂移 —— 判到第三十条时的你,和判第一条时的你,大概率已经不是同一个严格程度。所以判卷必须自动化,思路是分两层。
能用代码判的,绝不用模型判。工具名对不对、参数结构是否合法、回答里是否包含 “过敏” 二字、字数是否超限 —— 这些用断言和正则就能覆盖,成本为零、结果确定。设计 rubric 时就该有意往这层倾斜:把 “必须调用的工具”” 必须包含的要点 ” 都设计成可检索的形态。
必须主观判断的,用强模型当法官(LLM as judge)。语气是否得体、步骤是否有逻辑、有没有答非所问 —— 这类维度把 rubric 和回答一起交给一个强模型,让它逐项输出通过与否。但法官自身也有毛病:偏好长回答、偏好某种句式、对不同模型存在亲疏(判自己家族的输出更宽)。解法是老三样:固定一份详细的评分 rubric、给法官两三个 “满分答案” 和 “零分答案” 做锚定、定期抽一批结果人工复核校准。法官不必完美,只需稳定 —— 评估要比较的是 “这次比上次好不好”,稳定的尺子量出来的差值才有意义。
四、评估集驱动的迭代闭环
工具齐了,工作流就变了。以前是 “改 prompt → 感觉一下 → 上线”,现在是一个闭环:线上翻车的 case,清洗后进入评估集;先归因,再改动;每次改动跑一遍全集,通过率提升且没有旧 case 复发,才允许上线。
归因这一步,本系列第一篇给过框架 —— 把失败按上下文缺陷分成四类:信息缺失、信息过载、信息过期、位置不当。评估集的标签列在这里发挥作用:跑完发现失败集中在 “信息缺失” 类,那就别在 prompt 措辞上浪费力气,去补记忆和检索;集中在 “信息过载”,就去做压缩和淘汰。改动有了靶子,回归有了保障,迭代速度的量级就拉开了。
两个工程细节值得强调。其一,评估集是比 prompt 更持久的资产:prompt 会过时,模型会换代,但 “这个业务里什么是对的好回答” 的定义相对稳定 —— 换基座模型时,把同一张考卷跑一遍,新旧模型的差距一目了然。其二,警惕把模型练成背题机器:如果评估集既用来发现问题、又用来验证改动,反复迭代后你优化的可能不是能力,而是 “对这几十道题的记忆”。对策是切一块 holdout 集 —— 只用于终验、从不参与日常调试 —— 它之上的通过率,才是真实水平。
五、让改进可积累
回顾这个系列的完整链条:上下文工程解决 “模型看见什么”,KV Cache 解决 “看见的成本”,Function Calling 解决 “看见之后怎么做”,而评估解决的是最后也是最关键的一环 ——怎么做得更好这件事,由证据说了算。
工程化和手工作坊的分水岭,从来不是用了多复杂的框架,而是 “改进是否可积累”。没有评估,每次改动都是一次独立的赌局,三个月的开发可能在做循环往复的无功运动;有了评估,每一分努力都沉淀为通过率曲线上一个可确认的台阶。
所以,如果你现在只做一件事,就做这件:把最近十个翻车的真实 case 抄下来,写上判据,凑成你的第一版评估集。它很丑,二十行脚本能跑,但它会让你此后的每一次 “我觉得”,都变成 “我测过”。











暂无评论内容