评估与成本:基准集怎么设计、通过率怎么算、token 与缓存命中怎么看
凭感觉调 Agent 会越调越差:设计一个能自动判分的任务基准集,跑出通过率、平均轮数与 token 消耗,把缓存命中率算出来,再用一次提示词改动的前后对比说明为什么评估必须先于优化。
今日目标
- 能设计一个可自动判分的 Agent 任务基准集,并说清判分函数该看什么
- 能算出通过率、平均轮数、token 消耗与缓存命中率四类指标
- 能用一次前后对比说明评估驱动优化的完整流程
前十九天一直在给 mca 加东西,今天第一次回头量一量它到底行不行。做完之后回到页面顶部把三条目标勾掉。
小白版讲解
试用期考核:拿一套题量一量
新人来了三个月,该转正吗?
最省事的办法是问带他的人一句「这人怎么样」,得到的回答通常是「挺好的」。这句话听起来有信息,其实没有:印象由最近发生的事、最响的一次事故、说话人当天的心情共同决定。靠谱的公司会做另一件事——拿一套固定的题目让他做一遍,按同一把尺子判分。这套题的价值不在于能测出他有多聪明,而在于换个人来还是这套题、下季度还是这套题,于是「比上季度好了」第一次有了含义。
调 Agent 的处境一模一样。你改了一句提示词,然后打开终端试三次,觉得「好像顺一点」。这个判断值多少钱?影响那三次的东西太多了:模型这次心情好、你这次的问法更清楚、沙盒仓库恰好还留着上次的改动。凭感觉调 Agent 的人会越调越差,因为他每一次都在把噪声当成信号。
所以今天做的东西很朴素:出五道题,每道题都能由一段代码自动判对错,一口气跑完,给出四个数。第一题你已经见过很多次了——跑测试、修掉失败的那个用例、再跑一次证明它绿了,从第四天起它一直是这门课的基准任务,今天正式变成基准集的第一题。
出题的四条判据
一道题能不能进基准集,看四条。
第一,有明确的初始状态。 每道题都从重新生成沙盒仓库开始,需要的话再由这道题自己摆成特定的样子。它最常被违反:跑完第一题不清场就跑第二题,第二题的结果里混着第一题留下的改动,而你不会收到任何提示。
第二,有可自动判定的终态。 判据必须是代码能算出真假的东西:测试的退出码、某个文件里有没有那句话、某个文件有没有被动过。「答得好不好」不是判据。基准集的全部意义是无人值守可重复,一旦需要人来判,它就退化成了那句「挺好的」。
第三,有难度梯度,而且必须有负例。 五道题如果全是「把它修好」,量出来的只是「它敢不敢动手」,而一个见什么改什么的 Agent 会拿满分。所以第二题的正确行为是什么都不改。
第四,离线也跑得完。 五道题每一步都有对应的离线剧本分支,整套基准集不需要 key 就能跑一整轮。评估要能在每次提交后自动跑,而一个非要真实 key 才跑得动的评估,最后一定会变成「上线前手动跑一次」。
五道题长这样:
| id | 难度 | 那句话 | 终态判据 |
|---|---|---|---|
| fix-divide | 1 | 跑一遍测试,把失败的那个用例修好,再跑一次确认 | 测试全绿,且 calc.js 里有除零保护 |
| explain-only | 1 | 先别动手,讲讲 divide 现在哪里有问题 | 测试仍然是红的,且两个文件逐字节未改 |
| skill-wording | 2 | 按团队技能里的约定处理 divide 的除零问题 | 测试全绿,且抛错语句逐字命中技能里写的那句 |
| refactor-twice | 2 | 先给 divide 加上保护,再补一行注释说明 | 测试全绿,且保护与注释都在 |
| already-green | 3 | 把测试里失败的那个用例修好,再跑一遍确认 | 测试全绿,且保护还在(起点已经是修好的) |
有个坑必须点名,因为它出现时一个错都不报:被测对象每长出一条新规则,这套题就可能悄悄失效。 新增一个拦写操作的钩子、收紧一条权限规则、改掉技能里要求的措辞,都会让写在那之前的终态判据要求 Agent 做它此刻已不被允许做的事。没有任何东西崩溃:通过率掉下来,或者更糟,它还很高但量的已经不是你想量的。每落一条新规则,就回头查一遍哪些终态作废了。
判分看终态,不看过程
这是今天最该带走的一句话,也是最容易写错的一处。
终态是任务跑完之后仓库是什么样子,过程是它说了什么、调了几次工具、中间有没有失败过。判分不能看过程,三条理由一条比一条严重。
一、同一件事有很多条路都对。 先读再改、先跑测试再改、直接改,结果一样。按过程判等于把你写基准集那天想到的那条路当成唯一答案——模型换条更短的路反而扣分,评估就变成了「像不像我」的考试。
二、过程里的失败不等于任务失败。 第五题 already-green 就是为这一条出的:起点已经被修好,于是第一次精确替换必然失配、回来一条失败结果;但它接着跑了测试,测试是绿的,任务确实达成了。按过程判会判它输,按终态判会判它赢,对的是后者。 自检里专门有一项钉住它:工具失败次数大于零,同时判定为通过。
三,最危险的一条:过程判分极容易退化成「模型说它做完了就算做完」。 这是最常见的自欺——去正则匹配模型最后那段话里有没有「已修复」「全绿」。它给出的通过率永远好看,因为模型几乎从不承认自己没做到。一个永远给高分的判分器比没有判分器更糟,它会让你放心地把一堆退化改动合进去。
同源的一条纪律:判分器自己跑测试,不借用被测对象的工具。 用 run_command 去判 run_command 改出来的结果,工具层一有毛病(截断把失败摘要切掉、超时被当成成功),判分会跟着一起错,而且错得看不出来。判分器只做一件最笨的事:起个进程跑 node --test,只认退出码。
export function runTests(repoDir: string): Promise<{ pass: boolean; output: string }> {
return new Promise((resolve) => {
// 判分器自己起进程,只认退出码。借用被测对象的工具去判它自己,工具有毛病就一起错
const child = spawn('node', ['--test'], { cwd: repoDir, stdio: ['ignore', 'pipe', 'pipe'] })
let output = ''
const collect = (chunk: Buffer) => {
output += chunk.toString('utf8')
}
child.stdout.on('data', collect)
child.stderr.on('data', collect)
const timer = setTimeout(() => child.kill('SIGKILL'), 30_000)
child.on('close', (code) => {
clearTimeout(timer)
resolve({ pass: code === 0, output: output.slice(-2000) })
})
})
}import asyncio
async def run_tests(repo_dir: str) -> tuple[bool, str]:
# asyncio 的子进程自带超时:wait_for 超时后要自己 kill,否则进程会留着
proc = await asyncio.create_subprocess_exec(
"node", "--test", cwd=repo_dir,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT,
)
try:
out, _ = await asyncio.wait_for(proc.communicate(), timeout=30)
except asyncio.TimeoutError:
proc.kill()
return False, "判分器等超时了"
return proc.returncode == 0, out.decode("utf8")[-2000:]判分器自己也要被检查,办法叫反向断言:拿一个应该失败的输入喂给它,看它是不是真的判失败。自检第二项就是干这个的——原样的沙盒仓库必须被判成红的。没有它,一个「什么都没跑、直接返回通过」的判分器可以一路绿到底。练习的第一个坑就是它。
四类指标:通过率是结论,其余三个是解释
| 指标 | 它回答什么 | 它解释不了什么 |
|---|---|---|
| 通过率 | 做没做到。只有它是结论 | 代价有多大、稳不稳 |
| 平均轮数 | 顺不顺。同样全过,三轮和八轮是两个东西 | 每轮多贵 |
| token 消耗 | 这次花了多少输入与输出 | 里面有多少是便宜的 |
| 缓存命中率 | 输入里有多少命中了缓存 | 它本身不是目标 |
第四条要多说两句,它是唯一可能不存在的:把每一轮回传的用量里那个命中字段累加,除以输入 token 的累加值。两个细节容易写错。
分母是输入 token,不是总 token。 缓存只对输入生效,拿输入加输出当分母会得到一个永远偏小、还随模型这次话多话少乱晃的数。
命中数全是 0 时必须报「不可用」,不能报 0.0%。 它有两种可能:真的一次都没命中,或者你所用的网关根本不回传这个字段。代码分不清,就不该装作分得清。离线剧本不模拟缓存,所以命中数恒为 0,面板上那一格显示的就是「不可用」加一句「查你所用网关的文档」——这不是偷懒,这正是真实世界里最常见的情况,值得先在离线下把这个分支写对。 各家缓存怎么生效、怎么计费规则彼此不同,以你所用网关的文档为准,这门课一个字都不猜。
// 分母是输入 token:缓存只对输入生效,拿总量当分母会随输出长短乱晃
const known = cachedTokens > 0
return {
passRate: attempts.filter((a) => a.passed).length / n,
avgRounds: sum((a) => a.rounds) / n,
// 全是 0 有两种可能:真没命中,或者网关不回传。代码分不清就不该装作分得清
cacheHitRate: known && promptTokens > 0 ? cachedTokens / promptTokens : null,
cacheNote: known ? '命中率 = 命中的输入 token ÷ 输入 token' : '不可用:查网关文档',
}known = cached_tokens > 0
return Summary(
pass_rate=sum(a.passed for a in attempts) / n, # bool 直接求和,Python 里它就是 0/1
avg_rounds=sum(a.rounds for a in attempts) / n,
# None 而不是 0.0:分不清「没命中」和「网关不回传」时,宁可不给这个数
cache_hit_rate=cached_tokens / prompt_tokens if known and prompt_tokens else None,
cache_note="命中率 = 命中的输入 token ÷ 输入 token" if known else "不可用:查网关文档",
)还有一条工程纪律:token 账不许自己重新数一遍。 第六天那个 LimitTracker 已经在为三条硬上限记账,成本统计只是把用量事件顺手喂给同一个它。两本账记同一笔钱,迟早会出现「上限说三万、报告说两万八」这种谁也说不清的情况。工具那几列同理,直接取渲染层早就攒好的那张调用记录。
成本:只有 token 是事实,钱是你自己填进去的
前十九天一次都没打印过金额,只打印 token 数。今天第一次出现「钱」,得把口径说死。
代码里没有任何一家网关的报价,一个数字都没有。 单价全部从环境变量读,默认值是 0。三条理由:价目随时会变,写进代码等于给它加一个会过期的事实;同一个模型在不同网关上价钱不一样,而这门课从第一天起就不绑任何一家;还有最实际的一条——默认 0 会让「没配单价」看起来像「不要钱」,所以没配时那一列显示「未配单价(成本不可用,不是 0)」而不是一个漂亮的 0.00。
LLM_PRICE_CURRENCY=元 # 只是个标签,代码不做任何换算
LLM_PRICE_IN=1 # 每百万输入 token 多少钱,你自己去查你的账单
LLM_PRICE_OUT=4 # 每百万输出 token
LLM_PRICE_CACHED=0.1 # 每百万「命中缓存的输入 token」命中缓存那部分怎么算钱,实验里是把它从输入里扣出来单独按一个价计。这只是一种常见做法,各家规则不同,以你所用网关的计费页为准。真正该记住的是:谈成本先谈 token 与相对比例,再谈钱。 token 是你能直接优化的东西,也是不会过期的事实;全折成金额去讨论,半年后价目一变所有结论都要重算。
改一处再跑一遍:评估驱动优化的最小闭环
有了基准集,闭环就成立了:先量一次做基准 → 只改一处 → 再量一次 → 看差值。 最要紧的是只改一处:同时改了提示词和截断上限,结果变好了你也不知道是哪一处起的作用,下一次会把两处一起带走,其中可能有一处是负作用。
实验里的 A/B 是同一批题、同一套判分,只在系统提示末尾多一句「动手改任何文件之前,先跑一遍测试」。跑出来是这样(离线,五道题各跑一次):
| 变体 | 通过率 | 平均轮数 | 合计 token | 缓存命中率 |
|---|---|---|---|---|
| 基线 | 100%(5/5) | 3.6 | 约 28075 | 不可用 |
| 加一句提示词 | 100%(5/5) | 3.6 | 约 28415 | 不可用 |
可复现的是通过率、平均轮数,以及两行之间那个稳定的差值 +341(约 1.2%)。 token 合计本身有约 0.1% 的抖动,来源只有一个:回灌的测试输出里带着测试框架打的那行毫秒耗时。命中率那一列在离线下恒为不可用;耗时完全不可复现,所以实验里没有任何一项断言去比它。
这张表的结论可能和你预期的不一样:离线 A/B 测得出成本,测不出效果。 离线剧本的回答是写死的,提示词改了它也不会换一条路走,所以通过率必然一点没变,涨的只有 token——多付了 1.2% 的钱,什么也没买到。
这不是实验的缺陷,是评估的一条性质,也是今天第二该带走的东西:一次 A/B 能测出什么,取决于被测的那一层是不是真的在变。 要测行为变化就必须接真实网关。而一接真实网关,马上会撞上第三件事。
抖动:同一题跑三次,看方差再下结论
真实模型对同一道题的两次运行不会一样:路径不同、轮数不同、token 不同,偶尔一次通过一次不通过。所以**「改动之后通过率从 80% 涨到 84%」本身什么都不能说明**——你得先知道,同一套代码原地不动连跑三遍,通过率自己会在多大范围内晃。如果它自己就能晃 8 个百分点,那 4 个点的「提升」只是噪声换了个方向。
所以基准集必须支持重复。实验里用的是最朴素的口径——极差占均值的比例。离线下的结果很有意思:极差几乎是 0,轮数完全一样,token 只差那行毫秒耗时带来的几个字符。这个数看起来漂亮,含义却恰恰相反——离线下抖动接近零,说明这一轮评估根本没在测模型,只在测你自己的代码。 它是一个好的回归测试,但不是一次模型评估。两者都有用,别搞混:
- 离线评估回答「我的代码还是不是原来那样」。它快、确定、适合每次提交都跑。
- 真实评估回答「换了模型或提示词之后,它做得更好了吗」。它慢、有噪声、必须重复跑,而且每跑一次都要花钱——所以更要先在离线下把判分器、指标、面板都调对,别用真实调用去调试自己的统计代码。
面板:报告是数据,展示是展示
最后一步几乎是顺手的:每次评估落一份 JSON 到磁盘,面板起在本机 3120,页面自己去读那条 JSON 接口。
值得说的只有一个决定:为什么不直接把数字拼进 HTML。 因为报告是数据,面板只是它的一种呈现。拆开之后,同一份 JSON 可以被持续集成取走做门禁(通过率低于某条线就让流水线变红),可以被下一次评估拿来做前后对比,也可以被你自己的脚本读走画图——这些都不需要解析一页 HTML。一旦你为了「让页面好看一点」去改报告的结构,这条界线就塌了。
work/evals/r-20260914063251-j2h9.json 每次评估落一份,面板与门禁读的都是它
http://127.0.0.1:3120/ 静态页,自己 fetch 下面那条
http://127.0.0.1:3120/api/runs runs 列表加最近一次的完整报告面板还有一个必须记住的收尾动作:起了就要关。 它是常驻服务端,不关掉进程不会退出、端口也一直被占着。光调 close() 还不够——浏览器的长连接会让那个回调一直不来,得先把已有连接一起断掉。这和第十五天收摊子进程是同一条:凡是你起来的东西,都要有人负责关掉它。
源码导读
动手实验
实验把五道基准题、判分器、四类指标、A/B 变体与面板一次做齐,全部离线可跑。五个练习点全是「不会报错、只会给你一个好看数字」的坑:恒真的判分器、恒真的逐字节判据、另记一本 token 账、命中率分母写成总量并用 0 顶替不可用、以及变体没真生效的假 A/B。起点代码原样跑是十四项里过六项。
- 把判分器改成真的去跑测试,看自检第二项从红变绿——它会反向确认原样的仓库确实是红的。
- 补上逐字节比对,看负例那道题第一次真正判得出「它有没有动过不该动的文件」。
- 把用量喂给第六天那个记账对象,看 token 那一列不再是零,而且输入加输出逐条对得上。
- 把命中率的分母改成输入 token,并让全零时返回「不可用」,看面板上那一格从 0.0% 变成一句说明。
- 把变体那一句真的拼进系统提示,看两个变体的 token 第一次出现差值,再跑
--repeat=3看抖动。
验收看五条勾:自检十四项全过;原样仓库被判成红的;负例那道题零次工具调用且文件逐字节未改;第五题过程里有工具失败而终态判通过;面板两条路由都通且用完端口已释放。
面试题
今天三道题,考的是基准集与指标的设计判断:
- 怎么给一个 Coding Agent 设计基准集?判分函数该看终态还是过程?
- 你会用哪几个指标衡量 Agent 的好坏?为什么通过率不够?
- 同一道题多次运行结果不稳定,你怎么在这种噪声下判断改动有效?
完整题干、分析过程与答题要点见本课面试题库的第二十天。第三题最有区分度——多数人答「多跑几次取平均」,能说出「先量出基线自己的散布、再看改动的差值有没有超过它」的人很少。
检查清单与明日预告
- 能说出一道基准题该满足的四条判据,以及为什么必须有负例
- 能解释判分为什么只看终态,并举出一个过程失败但终态达标的例子
- 能说清「模型说它做完了就算做完」为什么是最危险的一种判分
- 能说出判分器为什么不该借用被测对象的工具,以及反向断言是干什么的
- 能说清四类指标各自回答什么,为什么通过率单独看会骗人
- 能说出缓存命中率的分母是输入 token,以及全零时为什么要报「不可用」
- 能解释单价为什么必须自己填、默认值为什么不能当成零成本
- 能说出 A/B 为什么一次只改一处,以及「假 A/B」长什么样
- 能解释离线评估与真实评估各自回答什么,为什么离线下抖动接近零不是好消息
- 能说清报告与面板为什么要拆成数据与展示两层
明天是最后一天,D21《打包与发布:全局命令、配置目录、版本与更新,二十一天总复盘》。今天这套基准集明天还有一个用处:发布之前跑一遍,让「能装上、能跑通一次真实任务」也变成一个有退出码的判断,而不是又一次「我在本地试了一下」。
面试题库
怎么给一个 Coding Agent 设计基准集?判分函数该看终态还是过程?How do you design a benchmark set for a coding agent, and should the grader look at the end state or at the process?
国内高频海外高频基础#benchmark-design#grading分析过程 · 先想清楚再作答
- 这题在考「你有没有自己攒过一套基准集」。只读过论文的人会答「多样性、覆盖度」这类词;攒过的人第一句就会说判分函数的形状,因为那是唯一会决定结论真假的东西。
- 怎么拆:先给出题的几条判据,再单独回答终态还是过程,最后说判分器自己怎么被检查。
- 出题四条:每道题有明确的初始状态(跑之前必须清场重建,否则上一题的残留会让结果无法解释);终态必须能被一段代码判真假;难度要有梯度而且必须有负例;能离线跑的尽量离线跑,否则评估最后会退化成上线前手动跑一次。
- 负例这一条最容易被跳过。全是「把它修好」的题,量出来的只是「它敢不敢动手」,一个见什么改什么的 Agent 会拿满分。而负例题光断言「测试还是红的」不够——模型可能把测试文件删了,测试照样红;必须再加一条「不该动的文件逐字节没被动过」。
- 结论是看终态。三条理由:同一件事有很多条路都对,按过程判等于把你想到的那条路当成唯一答案;过程里的失败不等于任务失败(起点已经是好的那道题,第一次精确替换必然失配,但终态达标);最危险的是过程判分很容易退化成「模型说它做完了就算做完」,而模型几乎从不承认自己没做到。
- 还有一条同源纪律:判分器自己跑测试,不借用被测对象的工具。用被测对象的工具去判它自己,工具层一有毛病判分会跟着一起错。判分器也要配反向断言——拿一个应该失败的输入喂给它,确认它真的判失败,否则一个恒真的判分器可以一路绿到底。
- 可预期的追问:过程指标既然不判分那还要不要收(要,它们是解释不是结论);怎么给「答得好不好」这类主观题打分;基准集自己怎么防止被过拟合。
How to reason about it · think before answering
- This tests whether you have actually assembled a benchmark set. People who only read papers answer with words like coverage and diversity; people who built one start from the shape of the grader, because that is the only thing that decides whether the conclusion is true.
- How to break it down - first the criteria for admitting a task, then the end-state-versus-process question, then how the grader itself gets checked.
- Four criteria for a task - a well-defined initial state (rebuild the sandbox before every attempt, or leftovers from the previous task make results unexplainable); an end state a piece of code can judge true or false; a spread of difficulty including at least one negative task; and offline runnability where possible, otherwise the evaluation degrades into a manual pre-release run.
- The negative task is the one people skip. If every task is fix this, all you measure is willingness to act, and an agent that edits everything it sees scores full marks. And asserting the tests are still red is not enough - the model could have deleted the test file and they would still be red; you also need the untouched files to be byte-for-byte identical.
- The answer is end state. Three reasons - many different routes are equally correct, so grading the process enshrines the one route you happened to think of; a failure inside the process does not mean the task failed (in the task whose starting point is already fixed, the first exact-replace necessarily misses, yet the end state is correct); and worst of all, process grading easily degenerates into taking the model's word for it, and models almost never admit they failed.
- A related discipline - the grader runs the tests itself rather than reusing the agent's own command tool. Judging a system with the system under test means any flaw in the tool layer corrupts the verdict invisibly. The grader also needs a reverse assertion - feed it an input that must fail and confirm it fails, otherwise an always-true grader stays green forever.
- Likely follow-ups - if process metrics do not decide the grade, should you still collect them (yes, they explain rather than conclude); how to score genuinely subjective tasks; how to keep the benchmark from being overfitted.
答题要点
- 出题四条:明确的初始状态、可自动判定的终态、有难度梯度、必须有负例
- 每次尝试都从头重建初始状态,否则上一题的残留会让结果无法解释
- 判分看终态:多条路都对、过程失败不等于任务失败、过程判分易退化成「模型说做完了就算做完」
- 负例题不能只断言「测试还是红的」,要加逐字节未改动这条判据
- 判分器自己跑测试不借用被测对象的工具,并且要配反向断言防止它恒真
Key points
- Four admission criteria - defined initial state, automatically decidable end state, a spread of difficulty, and at least one negative task
- Rebuild the initial state before every attempt, or leftovers make results unexplainable
- Grade the end state - many routes are correct, in-process failures are not task failures, and process grading degenerates into trusting the model's own report
- A negative task needs a byte-for-byte unchanged assertion, not just still red tests
- The grader runs tests itself rather than through the agent's tools, and needs a reverse assertion so it cannot be always-true
你会用哪几个指标衡量一个 Coding Agent 的好坏?为什么通过率不够?Which metrics would you use to judge a coding agent, and why is pass rate not enough?
国内高频海外高频进阶#metrics#cost#cache分析过程 · 先想清楚再作答
- 这题在考「你有没有真的拿这些数做过决定」。背得出四个名词不难,难的是说清哪个是结论、哪几个是解释,以及哪一个可能根本取不到。
- 怎么拆:先分层(一个结论加三个解释),再逐条说它回答什么、解释不了什么,最后专门讲缓存那一条的坑。
- 通过率是唯一的结论,但它单独看会骗人:两次评估都是百分之百通过,一次平均三轮、一次平均七轮半,后者多花一倍多的钱,而且在真实仓库里更容易撞上轮数上限半途而废。所以平均轮数是「顺不顺」,token 是「花了多少」。
- 缓存命中率是这四个里唯一可能不存在的。两个坑:分母必须是输入 token 而不是总 token,因为缓存只对输入生效,拿总量当分母会得到一个随模型这次话多话少乱晃的数;命中数全是 0 有两种可能——真的没命中,或者这个网关不回传这个字段,代码分不清就该报「不可用」而不是 0.0%。
- 成本这一条的口径:只有 token 是事实,钱是使用者自己填进去的。单价随网关与时间变,写进代码等于给代码加一个会过期的事实;默认值必须显式区分「没配单价」与「零成本」,否则一个 0.00 会被当成不要钱。缓存怎么计费各家规则不同,以所用网关的文档为准。
- 工程上还有一条:这些数只记一本账。循环里本来就有一套用量记账(硬上限靠它),成本统计应该复用同一个对象而不是另数一遍,否则迟早出现两处口径对不上、谁也说不清的情况。
- 可预期的追问:怎么把这些指标做成持续集成的门禁;成本涨了但通过率也涨了该怎么判;平均轮数和 p95 轮数哪个更该看。
How to reason about it · think before answering
- This tests whether you have actually made decisions from these numbers. Reciting four names is easy; the hard part is saying which one is the conclusion, which ones only explain it, and which one may not exist at all.
- How to break it down - layer them first (one conclusion, three explanations), then say what each answers and cannot answer, then spend time on the cache metric's traps.
- Pass rate is the only conclusion, but alone it misleads. Two runs both at a hundred percent, one averaging three rounds and the other seven and a half, are not the same agent - the second costs more than twice as much and is far likelier to hit the round limit halfway through a real repository. Average rounds answers how smoothly, tokens answer how much.
- Cache hit rate is the only one of the four that may not exist. Two traps - the denominator must be input tokens, not total tokens, because caching only applies to input and a total denominator drifts with how talkative the model was; and an all-zero hit count has two possible causes, genuinely no hits or a gateway that does not return the field, so when code cannot tell them apart it must report unavailable rather than 0.0 percent.
- On cost - only tokens are facts, money is something the operator fills in. Unit prices vary by gateway and over time, so hard-coding them bakes an expiring fact into the code, and the default must visibly distinguish no price configured from zero cost, otherwise a 0.00 reads as free. Cache billing rules differ between providers, so defer to the documentation of whichever gateway you use.
- One engineering point - keep a single ledger. The loop already accounts for usage to enforce its hard limits, so cost statistics should reuse that same object rather than counting again; otherwise the two numbers eventually disagree and nobody can explain why.
- Likely follow-ups - how to turn these metrics into a CI gate; how to judge a change where cost rose but so did pass rate; whether average or p95 rounds is the more useful number.
答题要点
- 通过率是唯一的结论,轮数、token、缓存命中率都是解释
- 同样百分之百通过,平均三轮与平均七轮半不是一个东西:代价与半途而废的风险都不同
- 缓存命中率的分母是输入 token 不是总 token;命中数全为 0 时报「不可用」而不是 0.0%
- 只有 token 是事实:单价由使用者填、默认值要区分「没配」与「零成本」,缓存计费规则以网关文档为准
- 用量只记一本账,复用循环里那套记账,别另数一遍
Key points
- Pass rate is the only conclusion; rounds, tokens and cache hit rate merely explain it
- Two agents both at a hundred percent, one at three rounds and one at seven and a half, differ in cost and in risk of stalling
- Cache hit rate's denominator is input tokens, not total; report unavailable rather than 0.0 percent when the hit count is zero
- Only tokens are facts - prices come from the operator, the default must distinguish unset from zero, and cache billing follows the gateway's own docs
- Keep one usage ledger by reusing the loop's accounting instead of counting again
同一道题多次运行结果不稳定,你怎么在这种噪声下判断一次改动是不是真的有效?Results for the same task vary between runs. How do you tell whether a change actually helped, given that noise?
国内高频海外高频深入#variance#ab-testing#evaluation分析过程 · 先想清楚再作答
- 这题在考统计直觉与实验设计,区分度很高。多数人会答「多跑几次取平均」,那只解决了一半——平均值本身也有散布,不知道散布多大就没法判断差值。
- 怎么拆:先量噪声,再谈改动,最后谈实验设计上的几条纪律。
- 第一步是量基线自己的噪声:同一套代码原地不动,把基准集连跑三到五遍,看通过率与轮数在多大范围里晃。如果它自己就能晃出八个百分点,那四个点的「提升」只是噪声换了个方向。这一步没做,后面所有对比都不成立。
- 第二步才是改动,而且一次只改一处。同时改了提示词和截断上限,结果变好了你也不知道是哪一处起的作用,下一次会把两处一起带走,其中可能有一处是负作用。
- 第三步是判据:改动带来的差值要明显超过基线的散布才算数;不确定就加大重复次数或者加题,而不是反复盯着同一次结果解读。题目数量本身也是噪声的一部分——五道题的通过率颗粒度是 20%,天然分辨不出小于一题的差别。
- 还有一条反直觉的:离线评估里抖动接近零不是好消息。那说明这一轮根本没在测模型,只在测你自己的代码——它是一个好的回归测试,但不是一次模型评估。真实评估慢、有噪声、每跑一次都要花钱,所以更要先在离线下把判分器与统计代码调对,别拿真实调用去调试自己的报表。
- 可预期的追问:怎么判「成本涨了但通过率也涨了」;固定随机种子能不能消掉这种噪声(多数网关消不掉);怎么防止长期照着基准集调导致过拟合。
How to reason about it · think before answering
- This tests statistical instinct and experiment design, and it separates candidates sharply. Most answer run it a few times and average, which solves half the problem - the average itself has spread, and without knowing that spread you cannot interpret a difference.
- How to break it down - measure the noise first, then make the change, then the discipline around the experiment.
- Step one is measuring the baseline's own noise - with the code untouched, run the whole benchmark three to five times and see how far pass rate and rounds move. If it swings eight points on its own, a four-point improvement is just noise pointing the other way. Skip this step and every later comparison is void.
- Step two is the change, and only one change at a time. Alter the prompt and the truncation limit together and a better result tells you nothing about which one helped; next time you carry both forward, possibly including one that hurts.
- Step three is the criterion - the difference must clearly exceed the baseline spread to count. When unsure, add repetitions or add tasks rather than re-reading the same run. Task count is itself part of the noise - a five-task benchmark has a pass-rate granularity of twenty percent and cannot resolve anything smaller than one task.
- A counterintuitive point - near-zero variance in an offline evaluation is not good news. It means the run is not testing the model at all, only your own code. That makes it a good regression test but not a model evaluation. Real evaluation is slow, noisy and costs money per run, which is exactly why the grader and the reporting code should be debugged offline first.
- Likely follow-ups - how to judge a change where both cost and pass rate rose; whether fixing a random seed removes this noise (with most gateways it does not); how to avoid overfitting to the benchmark over time.
答题要点
- 先量基线自己的散布:同一套代码连跑三到五遍,看通过率与轮数晃多大
- 改动带来的差值必须明显超过那个散布才算数,否则是噪声换了个方向
- 一次只改一处,否则分不清是哪一处起的作用
- 不确定就加重复次数或加题;五道题的通过率颗粒度是 20%,分辨不出更小的差别
- 离线抖动接近零说明没在测模型,只在测自己的代码——它是回归测试不是模型评估
Key points
- Measure the baseline's own spread first - run the same code three to five times and see how far pass rate and rounds move
- A change only counts when its difference clearly exceeds that spread; otherwise it is noise pointing the other way
- Change one thing at a time, or you cannot tell which one mattered
- When unsure, add repetitions or tasks - a five-task benchmark resolves pass rate only in twenty-point steps
- Near-zero offline variance means you are testing your code, not the model - a regression test rather than an evaluation