📑 本页目录(点开跳转)
挑战项目 B · 评测驱动开发一个 Agent
⏱ 5–7 天 | 难度 ★★★★☆ | 前置:第 14 节吃透 + 第 17 节至少做过一个
🎯 为什么选这个项目(它的「特点」)
99% 的人做 Agent 的顺序是「先写,再凭感觉调」。这个项目强制你反过来:先建评测,再写 Agent,让每一次改动都有数字说话。
对照
你会亲身经历这条真理:
💀 "我觉得这个 prompt 更好" —— 跑评测,其实掉了 8 分
💊 "这个改动没感觉" —— 跑评测,稳定涨了 12 分
🔬 "改了之后总分没变" —— 分维度一看,简单题涨了、难题崩了
没有评测,Agent 优化就是玄学。
这个项目让你戒掉玄学,一辈子受用。
它教的是元技能:不是「怎么做一个 Agent」,而是「怎么知道你的 Agent 到底行不行」。这是初级和高级 Agent 工程师最大的分水岭(对应第 14 节全章)。
🎯 被测对象:一个「自然语言转结构化查询」Agent
选这个任务是因为它既有客观对错、又有主观质量,能练到三类评分器:
对照
输入:"上个月华东区销售额超过10万的订单,按金额降序"
输出:结构化查询 JSON
{"region":"华东", "metric":"销售额", "op":">", "value":100000,
"time":"上月", "sort":{"field":"金额","order":"desc"}}
- 代码评分器能判:JSON 合法吗?字段齐吗?数值对吗?
- 模型评分器能判:意图理解对吗?边界情况处理合理吗?
- 有明确的正反两面:该拒绝的模糊查询有没有拒绝?
🧠 ADHD 任务切分
第一阶段:先建评测,一行 Agent 代码都不许写(Day 1–2)
参考用时 · 90 分钟
从「真实失败」出发造 25 个任务(第 14 节第 0 步):15 个正常查询、5 个模糊该拒绝的、5 个边界(多条件/否定/相对时间)。每个配参考解参考用时 · 45 分钟
质量关(第 14 节第 2 步):每个任务问自己「两个人看了会得出同样的对错判断吗?」不能的重写参考用时 · 90 分钟
代码评分器:JSON 可解析 + 必填字段 + 数值/操作符精确匹配。支持部分给分(意图对但漏一个字段 ≠ 完全错)参考用时 · 90 分钟
模型评分器:给一个结构化 rubric(意图准确/字段完整/边界处理/该拒绝的拒绝了吗),分维度打分。⭐ 用 3-5 个参考解校准它——让它给参考解打分,分数应该都高参考用时 · 45 分钟
干净 harness(第 14 节第 4 步):每个任务独立跑,不共享状态;防作弊(别让 Agent 看到参考解)
第二阶段:现在才开始写 Agent(Day 3)
参考用时 · 60 分钟
V0 基线:最朴素的单 prompt。立刻跑评测存基线分参考用时 · 30 分钟
⭐ 读转录(第 14 节第 6 步):不看分数,读 5 个失败案例的完整过程,写下你的假设「它为什么错」参考用时 · 60 分钟
V1:针对假设改 prompt(加示例?加字段说明?)→ 跑评测 → 和基线比。涨了留下,跌了回滚
第三阶段:迭代循环 + 抓评测本身的问题(Day 4–5)
参考用时 · 90 分钟
迭代 V2/V3/V4:每版一个改动,每版跑评测,画分数演进折线。规则:一次只改一个变量,否则不知道是谁的功劳参考用时 · 60 分钟
⭐ 分维度看(第 14 节第 5 步):不要只看总分。是不是简单题涨了、难题崩了?正例好了、反例(该拒绝的)崩了?—— 单向优化是常见陷阱参考用时 · 60 分钟
抓评分器自己的 bug:人工核对 10 个「评分器判对/判错」的案例。你几乎一定会发现评分器误判——修它。「不读转录,你不知道评分器在不在工作」参考用时 · 45 分钟
加 pass^k(第 14 节):同一任务跑 5 次,看一致性。发现「平均分不错但方差大」的任务——这是面向用户 Agent 的隐患
第四阶段:报告(Day 6–7)
参考用时 · 90 分钟
报告:V0→V4 分数演进表、分维度雷达图、「我修过的一个评分器 bug」、「一个我以为有用其实有害的改动」参考用时 · 45 分钟
元反思:哪些改动离线涨了但你怀疑线上未必?为什么?(第 14 节的失真)
🔨 关键骨架
此处的函数供你自己的 Agent 与 judge 调用。评分器已检查嵌套 sort;测试时故意删掉排序、改成升序、返回数组,确认不会满分。历史版本漏查排序,错误答案也得满分,这正适合用来练习“评分器也要用反例测试”。澄清分支只检查动作,不检查理由质量;完整项目还需补业务执行结果、日期解释和过程约束。
import json
# ============ 评测任务 ============
TASKS = [
{"id": "n1", "input": "上个月华东区销售额超过10万的订单,按金额降序",
"reference": {"region":"华东","metric":"销售额","op":">","value":100000,
"time":"上月","sort":{"field":"金额","order":"desc"}},
"type": "normal"},
{"id": "r1", "input": "给我看看那些不太好的订单", # 模糊,该拒绝
"reference": {"action": "clarify", "reason": "'不太好'定义不明"},
"type": "should_reject"},
# ... 共 25 个
]
# ============ 代码评分器(支持部分分)============
def code_score(output, ref):
try:
out = json.loads(output)
except json.JSONDecodeError:
return 0.0, "JSON 不合法"
if not isinstance(out, dict):
return 0.0, "顶层必须是对象"
if ref.get("action") == "clarify": # 模糊任务应澄清
return (1.0, "正确拒绝") if out.get("action") == "clarify" \
else (0.0, "该拒绝却硬答了")
if set(out) != set(ref):
return 0.0, "字段缺失或包含未允许的字段"
fields = ["region", "metric", "op", "value", "time", "sort"]
hit = sum(1 for f in fields if out.get(f) == ref.get(f))
return hit / len(fields), f"{hit}/{len(fields)} 字段正确"
# ============ 模型评分器(结构化 rubric)============
RUBRIC = """给这个"自然语言转查询"的输出打分,四个维度各 0-25 分:
- 意图准确:查询意图理解对吗
- 字段完整:该有的字段都有吗
- 边界处理:相对时间/否定/多条件处理对吗
- 拒绝恰当:模糊查询是否恰当地要求澄清(不该拒绝的别拒绝)
输出 JSON:{"意图":分, "字段":分, "边界":分, "拒绝":分, "理由":"..."}"""
def model_score(judge_model, task, output):
prompt = f"{RUBRIC}\n\n输入:{task['input']}\n参考解:{task['reference']}\n实际输出:{output}"
return json.loads(judge_model.chat([{"role":"user","content":prompt}]))
# ============ 评测循环 ============
def evaluate(agent, judge, tasks, runs=1):
results = []
for task in tasks:
for _ in range(runs): # runs>1 用于测 pass^k 一致性
out = agent.run(task["input"])
cs, note = code_score(out, task["reference"])
ms = model_score(judge, task, out)
results.append({"id": task["id"], "type": task["type"],
"code": cs, "model": sum(ms[k] for k in
["意图","字段","边界","拒绝"])/100, "note": note})
return results
def summarize(results):
"""⭐ 一定要分维度,不能只看总分"""
from collections import defaultdict
by_type = defaultdict(list)
for r in results:
by_type[r["type"]].append(r["code"])
return {t: round(sum(v)/len(v), 3) for t, v in by_type.items()}
# 你会看到 normal 涨的时候 should_reject 可能在跌——这就是单向优化陷阱
🕳️ 专属坑
| 坑 | 说明 |
|---|---|
| 忍不住先写 Agent ⭐ | 违背整个项目的意义。评测没建完,禁止碰 Agent 代码 |
| 只看总分 | 总分持平可能掩盖「简单题涨、难题崩」。必须分维度、分正反例看 |
| 评分器当真理 | 评分器自己会错。不人工核对评分器,你优化的是幻觉 |
| 一次改多个变量 | 涨了跌了都不知道是谁干的。一版一改动 |
| 任务太少或太简单 | 25 个够起步,但要有难度梯度,否则很快饱和(第 14 节第 7 步) |
| 模型评分器没校准 | 让它先给参考解打分,如果参考解都拿不到高分,评分器坏了 |
| 0% 通过率就怪 Agent | 往往是任务写坏了(第 14 节第 2 步) |
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 全景导论 11 | 评测的全景版:有哪些榜、各测什么、为什么榜单会失真 |
| 上线之后 12 | ⭐「反复看结果」在统计上叫 peeking,会把假阳性推到 30% |
| Kaggle 22 | ⚠️ 盯着同一个评测集迭代 = 对它过拟合,这就是打榜里的 shake-up |
🏁 通关标准
- 25 个任务有正反两面 + 边界,每个有参考解,且通过「两人同判」质量关
- 代码评分器 + 模型评分器双轨,模型评分器用参考解校准过
- V0→V4 有清晰的分数演进曲线,每版只改一个变量
- 至少抓出并修复一个评分器自己的 bug
- 分维度报告揭示过至少一次「总分骗人」(某维度涨、某维度跌)
- 报告里诚实标出「离线涨但怀疑线上未必」的改动
🏆 加分
- 加 AI 抗性测试(第 14 节 5.5):造几个「训练数据里不太可能有」的刁钻查询,看分数掉多少
- 做基础设施噪声小实验:同一版本 Agent 连跑 3 次,看纯随机波动有多大——这是你判断「涨了 2 分算不算真涨」的底噪基准
✅ 检查点
- 「评测驱动」和普通开发的顺序有什么不同?为什么这个顺序重要?
- 25 个任务该怎么分配(正例/反例/边界)?为什么不能全是难题?
- 双轨评分器指哪两轨?为什么代码评分器要支持"部分分"?
- 「总分骗人」是什么意思?怎么防?
- 某一版的通过率是 0%,最该先怀疑什么?
- 为什么要做「同一版本连跑 3 次」的实验?它给你什么?
- 「离线涨但线上未必」——你怎么在报告里诚实地表达这一点?
👀 答案
- 先建评测,再写 Agent。重要是因为没有评测时,你所有的"改进"都是感觉——你会记住有效的那次、忘掉无效的那次,并逐渐相信自己在进步。
- 正例 + 反例(应该拒绝/应该说不知道的)+ 边界情况。不能全是难题是因为分数会全部趴在低位,没有区分度——你看不出哪个改动有用。
- 代码评分器(客观、可重复)+ 模型 rubric 评分器(处理主观项,必须先校准)。代码评分器支持部分分是因为二元的对错太粗——"格式对了但少一个字段"和"完全跑偏"应该得到不同的分数,否则你看不到渐进的改善。
- 总分持平,但某个维度涨了、另一个跌了——你以为没变化,其实发生了一次真实的权衡。防法:分维度报告,永远不只看总分。
- 先怀疑任务本身坏了(评分器写错、参考答案错、任务描述有歧义),而不是 Agent 太差。0% 这种极端值几乎总是基础设施问题。
- 得到基础设施噪声的底噪基准。有了它你才能判断"这次涨了 2 分"是真提升还是随机波动。🔗 和《机器学习基础》第 5 章的「别信单次切分:用 5 折交叉验证报均值 ± 标准差」是同一条纪律——那一章实测同一个模型只换切分种子,10 次的极差就有 3.5 个百分点。
- 在报告里显式标注:哪些改动是"针对评测集调的"(可能过拟合了评测),哪些是"原理上应该普遍有效的"。诚实标出不确定性,比给一个漂亮但可疑的数字有价值得多。
🛑 可以停在这里
⚡ 走神救援
先记住这几件事
- 先建立含正常、反例和边界情况的任务集与评分器,再开始改 Agent。
- 保存基线转录,每版只改一项;分维度看结果,不只看总分。
- 人工核对评分器,用重复运行估计波动,避免把偶然涨分当成进步。
- 报告诚实区分针对评测集的调优与能推广的改进。
下一个挑战 👉 20-挑战项目C-提示注入攻防.md