Day 51 / 共 68 天 · 阶段 9 评测(最重要)
指标体系:给 Agent 出一张体检报告
昨天(Day 50)你学会了给"单个回答"判分。但一个真实 Agent 好不好,远不止"答得对"——还要看它完成任务的比例、快不快、贵不贵、常怎么错。今天把这些指标搭成一套完整的指标体系,让你能像读体检报告一样全面判断一个 Agent。明天(Day 52)把这套指标接进 CI,变成自动拦截的关卡。
📍 你在阶段 9(评测 D49-52)的位置
D49 为什么评测最重要→
D50 LLM-as-Judge→
D51 指标体系→
D52 测试集&回归&CI
💡 用一个类比兜住今天(今天全程沿用「体检报告」的世界观)
判断一个 Agent,像给它做全面体检。成功率=最重要的那项"及格没有"(能不能把事办成);延迟 P95=排队最久的那批病人等了多久(别只看平均,要看最惨的);成本=这次体检花了多少钱(token 账单);失败模式归类=医生给病因分类(不是只说"不健康",而是"高血压/血糖高/缺铁")。今天你从"随口说它挺好"升级到"出具一张有理有据的体检报告"。
L01
为什么一个数字远远不够
🤔 痛点你说"我的 Agent 准确率 90%",听起来很棒。但它每次要等 30 秒、每次调用花 5 毛钱、剩下 10% 全是把用户信息答错的严重错误——这还棒吗?
💡 本质只看一个数字,就像体检只量体重:可能瞒住了高血压。好不好是多维度的权衡——准、快、省、错得安全,四条腿缺一条都可能翻车。指标体系就是把这几条腿一起摆出来看。
图注:四类指标常常互相拉扯——换个更聪明的大模型准了,但更慢更贵。指标体系让你看清这笔权衡账。
👶 "指标互相拉扯"是啥意思?提升一个常牺牲另一个。比如让模型"想得更久、检索更多资料",成功率会上去,但延迟和成本也跟着涨。没有免费的午餐——工程的价值就在于找到符合你业务的那个平衡点(客服要快、法律要准、内部工具可以慢但要省)。把指标都摆出来,才谈得上平衡。
L02
成功率 / 任务完成率:最重要的那项
🤔 痛点"准确率"这个词太笼统。Agent 常常要做多步任务(查库→算→回信),中间一步错就前功尽弃——你到底该数"每步对不对"还是"整件事办成没"?
💡 本质对 Agent,最该看的是任务完成率:整件事从头到尾办成的比例,而不只是单步准确。像体检的核心项——不是"某个指标好看",而是"这人能不能正常生活"。
📝 举个例子:单步 vs 整任务
一个订机票 Agent 要走 3 步:查航班→选座→下单。
· 假设每步单独看都有 90% 正确率,听着不错。
· 但整件事全对的概率 ≈ 0.9×0.9×0.9 ≈ 73%——近 1/4 的用户订不成。
只看单步会严重高估体验;任务完成率才是用户真正感受到的那个数。
· 假设每步单独看都有 90% 正确率,听着不错。
· 但整件事全对的概率 ≈ 0.9×0.9×0.9 ≈ 73%——近 1/4 的用户订不成。
只看单步会严重高估体验;任务完成率才是用户真正感受到的那个数。
👶 小白:那我是不是只看任务完成率就够了?
👨🏫 老师:任务完成率是"总分",但当它掉下去时,你需要单步指标来定位是哪一步拖后腿——像体检既看整体状态,也要看具体是哪项指标异常。所以两个都记:完成率告诉你"病了没",单步指标告诉你"病在哪"。
L03
延迟 P50 / P95:别被平均值骗了
🤔 痛点你说"平均响应 2 秒",老板很满意。但用户投诉"经常要等十几秒"。谁在说谎?——都没说谎,是"平均"把慢的那批藏起来了。
💡 本质平均值会被少数极端值稀释,掩盖真实体验。业界看分位数(percentile):P50=中位数(一半请求比它快),P95=95% 的请求比它快、只有最慢的 5% 更慢。P95 才反映"倒霉用户"的等待,是体验的真实底线。
图注:红色"长尾"就是被平均值掩盖的慢请求。盯 P95/P99,才能发现并治好这些"倒霉体验"。
口诀:P50 看典型体验,P95/P99 看最差体验。面试常问"为什么不用平均延迟?"——答"平均会被极端值稀释、掩盖长尾,分位数才反映真实用户体验",就是满分回答。
L04
成本:算清每次调用的 token 账
🤔 痛点demo 阶段几乎不花钱,你毫无感觉。可一旦上线、每天几万次调用,账单能吓你一跳——而且你还说不清钱花在哪一步。
💡 本质大模型按 token(词元) 计费,输入和输出分开算(回忆 Day 13)。成本指标 = 每次任务平均消耗多少 token / 折算多少钱。像体检的费用清单:不只看总价,还要看哪个项目最烧钱,好针对性砍。
# 把一次任务的 token 折算成钱(价格随厂商/模型不同,这里用示意数值)
def cost_of(in_tokens, out_tokens,
in_price=0.5, out_price=1.5): # 单位:元 / 每百万 token(示意)
# 成本 = 输入token * 输入单价 + 输出token * 输出单价,再从"每百万"换算
c = (in_tokens * in_price + out_tokens * out_price) / 1_000_000
return c
# 假设一次客服任务:输入 1200 token(含 prompt+检索资料),输出 300 token
one = cost_of(1200, 300)
print(f"单次约 {one*1000:.2f} 厘") # 换成"厘"更直观
print(f"每天 5 万次约 {one*50000:.1f} 元/天") # 一眼看清规模化后的账单
📝 举个例子:成本藏在你想不到的地方
很多人以为钱花在"模型回答"上,其实大头常常是输入——每次都把长长的系统提示、几千字检索资料、整段对话历史塞进去。优化点往往是:精简 prompt、控制检索条数、压缩历史(Day 18)、给便宜活分流到小模型(Day 15)。先度量,才知道该砍哪里。
L05
失败模式归类:不止"错了",还要"为啥错"
🤔 痛点你知道"20% 的任务失败了",然后呢?盯着这个数字发呆也不知道从哪下手改。数字告诉你"病了",但没告诉你"病因"。
💡 本质把失败的样本逐条看一遍、按原因分堆,就像医生把"不健康"细分成具体病因。有了分类,你才知道该先治哪一类——因为失败往往高度集中在少数几种原因上。
| 失败模式 | 典型表现 | 大概该往哪修 |
|---|---|---|
| 幻觉/编造 | 答了资料里没有的信息 | 加 Critic 核对证据(Day 53) |
| 检索没召回 | 知识库有答案却没找到 | 改 chunking/rerank(Day 22/26) |
| 没答全 | 用户问 3 点只答 1 点 | 改 prompt 要求逐点作答 |
| 工具调用出错 | 参数填错、调错工具 | 改工具描述 schema(Day 30) |
| 超时/崩溃 | 卡死或报错没兜底 | 加重试/降级(Day 54) |
📝 举个例子:归类带来的省力
你抽 50 个失败样本一分类,发现 32 个都是"检索没召回"。这下方向明确了:与其瞎调 prompt,不如集中火力优化检索——一下就能把失败率砍掉一大半。这就是"分类"的威力:把无从下手的 20%,变成"先啃最大那一堆"。
L06
拼成一张记分卡
🤔 痛点四类指标各看各的太散。有没有一张表,一眼就能看清这次改动"整体是变好还是变坏"?
💡 本质把关键指标汇成一张记分卡(scorecard),每次评测输出这一张——就是 Agent 的"体检报告"。有了它,改动前后一对比,好坏一目了然,还能直接贴进 PR 和汇报。
# 跑完一批评测后,汇总成一张记分卡
def scorecard(results):
# results: 每条 = {"ok": bool, "latency": 秒, "cost": 元, "fail_type": str or None}
n = len(results)
success = sum(r["ok"] for r in results) / n # 任务完成率
lat = sorted(r["latency"] for r in results)
p95 = lat[int(n * 0.95) - 1] # 第 95 百分位延迟
avg_cost = sum(r["cost"] for r in results) / n # 平均单次成本
fails = {} # 失败模式归类计数
for r in results:
if not r["ok"]:
fails[r["fail_type"]] = fails.get(r["fail_type"], 0) + 1
return {"完成率": f"{success:.0%}", "P95延迟": f"{p95:.1f}s",
"平均成本": f"{avg_cost*1000:.2f}厘", "失败分布": fails}
print(scorecard(my_eval_results))
# → {'完成率':'88%','P95延迟':'11.5s','平均成本':'2.30厘','失败分布':{'检索没召回':32,'幻觉':6}}
这张卡就是你和昨天、和基线对比的"锚"。Day 52 会把它接进 CI:完成率若比基线掉超过阈值,就自动拦住合并——评测从"我记得看一眼"升级成"机器强制把关"。
L07
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 为什么"一个准确率数字"不够?四类指标分别是什么?
- 任务完成率和单步准确率差在哪?为什么多步任务会"放大失败"?
- 为什么看 P95 而不是平均延迟?P50/P95 各代表什么?
- 大模型成本大头常常在哪(输入还是输出)?怎么砍?
- 失败模式归类为什么关键?举 3 种失败模式和对应修法。
✋ 动手 10 分钟:给一批假数据出记分卡
把 L06 的 scorecard 复制到 metrics.py,喂一批手编的数据跑出报告:
my_eval_results = [
{"ok": True, "latency": 1.8, "cost": 0.0021, "fail_type": None},
{"ok": True, "latency": 2.4, "cost": 0.0025, "fail_type": None},
{"ok": False, "latency": 12.0,"cost": 0.0040, "fail_type": "检索没召回"},
{"ok": True, "latency": 1.5, "cost": 0.0018, "fail_type": None},
{"ok": False, "latency": 3.0, "cost": 0.0022, "fail_type": "幻觉"},
]
# 跑 print(scorecard(my_eval_results)) 看看这张体检报告
然后做个小实验:把那条 12 秒的延迟改成 2 秒,再看 P95 怎么变——体会"一条长尾如何主导 P95"。再多加几条 "检索没召回" 的失败,看失败分布如何指向"该先优化检索"。
明日预告 · Day 52:现在你会算指标了,但每次都靠"我记得手动跑一遍"太不靠谱。明天学怎么攒一套稳定的golden set 测试集、做回归测试(确保老功能没被改坏),并把这套评测接进 CI——指标不达标就自动拦住合并,谁都别想把变差的代码偷偷混上线。还会放一个《gov-agents 21 天精讲》的深潜入口。