ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

给 LLM Agent 测试时算力做 Elo-per-token,TaoToken Key 在哪一步拿

给 LLM Agent 测试时算力做 Elo-per-token,TaoToken Key 在哪一步拿 1. 401 与超时混在一起Elo-per-token 评测脚本的第一道坎如果你正在跑一个 LLM Agent 的测试时算力策略对比实验多半见过这种日志脚本启动三十秒先抛一排AuthenticationError: 401 invalid api key紧接着是几条ConnectTimeout最后评测结果文件是空的。很多人第一反应是模型接口不稳定但真正的原因往往在更前面一步客户端的base_url还指向默认地址Key 也没注入到运行时环境Agent 每一轮反思、每一次工具调用都在做无效重试。这类问题在普通对话场景里只是烦在 Elo-per-token 评测里是致命的。因为测试时算力策略的核心就是同一批任务跑不同规模的采样/反思/投票你需要的是一份可控且可归因的胜负记录和 token 消耗记录。Key 没配好重试和超时会被算进分母Elo 曲线会变得没有意义。我的处理顺序是固定的在准备调用模型之前先去 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentelo_intro 注册并创建 Key再把所有客户端的base_url统一指向https://taotoken.net/api。这一步做完后面所有关于 Elo、token、边际收益的计算才有讨论价值。本文按两条线展开一条是方法线用 Bradley-Terry 把任务内排序聚合成跨任务 Elo再除以 token 得到 Elo-per-token另一条是工程线Key 在哪一步拿、配置怎么写、token 怎么统计、收益递减对照表怎么生成。2. Elo-per-token 的量纲Bradley-Terry 聚合 token 分母先说清楚这个指标到底在量什么。传统 Elo 只回答谁更强不回答强这一点花了多少代价。而测试时算力策略多次采样后投票、多轮自我反思、Best-of-N 重排、树搜索等的本质是用推理成本换成功率。于是 Elo-per-token 的定义就很自然ΔElo(strategy, budget) Elo(strategy, budget) - Elo(baseline, budget0) Elo-per-token ΔElo / total_tokens 边际 Elo-per-token ΔElo(k) - ΔElo(k-1) / (tokens(k) - tokens(k-1))分母必须是全链路 token包含系统提示、工具调用返回、失败重试、反思轮、投票时被丢弃的分支。只统计最终那次成功调用的 token会系统性高估算力效率让曲线看起来一直是线性的。分子的来源是排序聚合。Agent 评测通常不会给绝对分数而是给同一任务下多个策略输出的相对胜负。Bradley-Terry 模型假设P(i 战胜 j) s_i / (s_i s_j)其中s_i是策略 i 的隐强度。用极大似然实践中用 MM 迭代拟合出s再映射到 Elo 尺度Elo_i 1000 (400 / ln 10) * ln(s_i / s_anchor)400 / ln 10 ≈ 173.7这个常数让 Elo 差 400 对应约 10 倍胜率比与传统棋类 Elo 的直觉一致。关键点在于任务内排序只在本任务内有可比性。任务 A 的 Elo 1000 和任务 B 的 Elo 1000 并不等价因为参与比较的策略集不同、题目难度不同。跨任务聚合必须做锚定这一步做错后面的收益递减结论会整体跑偏。3. 任务内排序怎么变成跨任务 Elo锚定策略与可运行代码聚合路径有三条常见做法复杂度递增做法思路适用场景全局池化把所有任务的胜负对扔进一个 BT 模型任务同质、策略集完全一致任务内拟合 锚点平移每任务单独拟合再以基准策略对齐后平均任务异构、策略集不完全一致分层贝叶斯任务作为随机效应共享先验任务数多、单任务样本少评测管线里性价比最高的是第二种。下面是可直接运行的实现依赖只有numpy# bt_elo.py import numpy as np from collections import defaultdict def fit_bradley_terry(results, max_iter1000, tol1e-10): results: [(winner, loser), ...] 返回未归一化强度 players sorted({p for pair in results for p in pair}) idx {p: i for i, p in enumerate(players)} n len(players) wins np.zeros(n) n_ij np.zeros((n, n)) for winner, loser in results: i, j idx[winner], idx[loser] wins[i] 1 n_ij[i, j] 1 n_ij[j, i] 1 p np.ones(n) / n for _ in range(max_iter): p_new np.zeros(n) for i in range(n): denom sum( n_ij[i, j] / (p[i] p[j]) for j in range(n) if n_ij[i, j] 0 ) p_new[i] wins[i] / denom if denom 0 else p[i] p_new p_new / p_new.sum() if np.max(np.abs(p_new - p)) tol: p, p_new p_new, p_new break p p_new return {players[i]: float(p[i]) for i in range(n)} def to_elo(strengths, base1000.0, scale400.0 / np.log(10)): vals np.array(list(strengths.values())) anchor float(np.exp(np.mean(np.log(vals)))) return {k: base scale * np.log(v / anchor) for k, v in strengths.items()} def aggregate_cross_task(task_results, anchor_policybaseline): task_results: {task_name: [(winner, loser), ...]} 以 anchor_policy 在每任务的 Elo 为原点做平移再跨任务平均。 cross defaultdict(list) for task, results in task_results.items(): elo to_elo(fit_bradley_terry(results)) if anchor_policy not in elo: continue offset elo[anchor_policy] for policy, v in elo.items(): cross[policy].append(v - offset) return {k: float(np.mean(v)) for k, v in cross.items()}跑起来是这样的python -c from bt_elo import aggregate_cross_task data { math_500: [(baseline,r1),(baseline,r3),(r1,r3),(baseline,r5)], code_repair: [(baseline,r1),(r3,baseline),(r3,r1),(r5,r3)], web_nav: [(baseline,r1),(baseline,r3),(r1,r3),(r5,r1)], } print(aggregate_cross_task(data)) 注意anchor_policy必须是所有任务都出现过的策略否则该任务会被跳过。缺失锚点是最常见的静默失败代码不报错结果里少了一个任务Elo 曲线照样画出来。4. Key 在哪一步拿TaoToken 接入时序与 base_url 指向回到工程侧。把 Key 的获取放到评测流程的哪一步直接决定了你浪费多少 GPU 时间和采样配额。我建议的顺序是写评测逻辑之前先到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentkey_step 完成注册进入控制台创建 Key。跑冒烟测试之前把 Key 写入环境变量不要把明文硬编码进仓库。启动 Agent 之前确认客户端base_url指向https://taotoken.net/api。批量评测之前先用一条最小请求验证鉴权和路径拼接。正式跑数之前确认 token 日志文件可写时区和字段名统一。环境变量建议单独放一份.env并被source到当前 shell# .env 加入 .gitignore export TAOTOKEN_API_KEYYOUR_API_KEY export OPENAI_API_KEY$TAOTOKEN_API_KEY export OPENAI_BASE_URLhttps://taotoken.net/apiPython 侧最简接入import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, timeout120.0, max_retries2, ) resp client.chat.completions.create( modelos.environ.get(AGENT_MODEL, 以控制台展示的模型名为准), messages[{role: user, content: 只回复 pong}], max_tokens16, ) print(resp.choices[0].message.content, resp.usage.total_tokens)命令行冒烟测试curl -sS https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: 以控制台展示的模型名为准, messages: [{role:user,content:ping}], max_tokens: 16 }如果这里返回 200 且usage字段完整说明鉴权链路通了可以开始跑评测。如果返回 404优先检查 SDK 拼接规则和你手写 URL 是否一致别急着改 Key。5. 三个客户端的可复制配置Claude Code / Codex / CC Switch评测 Agent 通常不只一种执行器Claude Code 跑交互式修复任务Codex 跑批处理脚本CC Switch 用来在两者之间切换供应商。三者的配置项完全不同混用是最常见的低级错误。5.1 Claude Codesettings.jsonANTHROPIC_*Claude Code 读~/.claude/settings.json走ANTHROPIC_*系列变量{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: 以控制台展示的模型名为准, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1 }, permissions: { allow: [Read, Edit, Bash(git diff:*), Bash(pytest:*)], deny: [Bash(rm -rf:*)] } }ANTHROPIC_AUTH_TOKEN是这里的关键字段写成ANTHROPIC_API_KEY有可能不被读取。改完配置要重启 Claude Code 进程热加载不一定生效。5.2 Codexconfig.tomlCodex 用~/.codex/config.toml走 provider 声明 env_key不要把ANTHROPIC_*塞进 Codex 的配置里它不认识model 以控制台展示的模型名为准 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat配套环境变量export TAOTOKEN_API_KEYYOUR_API_KEY codex --versionenv_key填的是环境变量名而不是 Key 本身这一点很容易写错。写错了 Codex 会提示找不到凭据而不是 401。5.3 CC Switch三件套字段对齐CC Switch 里新增一个供应商条目本质上是三件套Base URL、API Key、模型映射。它同时管理 Claude 侧和 Codex 侧两套下游配置所以同一个供应商要填两遍映射关系三件套字段Claude 侧落点Codex 侧落点Base URLANTHROPIC_BASE_URLmodel_providers.id.base_urlAPI KeyANTHROPIC_AUTH_TOKENmodel_providers.id.env_key指向的环境变量模型映射ANTHROPIC_MODELmodel 供应商默认模型一个容易忽略的细节切换供应商后之前跑了一半的评测批次不要接着用。新旧供应商的模型版本、上下文长度、返回字段可能不同混在一份 JSONL 里做 Elo 聚合等于把两种实验条件合并了。6. token 统计命令从 API usage 到策略级汇总Elo-per-token 的分母来自逐次调用的usage。评测脚本里不要只 print要落盘成 JSONL一行一次调用# runner.py import json, time, os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) def call_and_log(model, messages, strategy, task_id, out_pathruns.jsonl): t0 time.time() resp client.chat.completions.create(modelmodel, messagesmessages) usage resp.usage rec { ts: time.time(), task_id: task_id, strategy: strategy, model: model, prompt_tokens: usage.prompt_tokens, completion_tokens: usage.completion_tokens, total_tokens: usage.total_tokens, latency_s: round(time.time() - t0, 3), finish_reason: resp.choices[0].finish_reason, } with open(out_path, a, encodingutf-8) as f: f.write(json.dumps(rec, ensure_asciiFalse) \n) return resp.choices[0].message.content按策略汇总用jq一条命令就够jq -s group_by(.strategy) | map({ strategy: .[0].strategy, calls: length, prompt_tokens: (map(.prompt_tokens) | add), completion_tokens: (map(.completion_tokens) | add), p95_latency: (map(.latency_s) | sort | .[(length * 0.95 | floor)]) }) | map(. {total_tokens: (.prompt_tokens .completion_tokens)}) | sort_by(-.total_tokens) runs.jsonl按「任务 × 策略」二维汇总jq -s group_by([.task_id, .strategy]) | map({ task: .[0].task_id, strategy: .[0].strategy, tokens: (map(.total_tokens) | add) }) runs.jsonl tokens_by_task_strategy.json拿到这张表才可以和aggregate_cross_task输出的跨任务 Elo 做除法。强烈建议把两张表都存成 CSV方便在表格软件里做对照。7. Elo 评分与收益递减对照表模板与填充方法下面这张表是可复现产出的核心。表内数值为格式示意请用你自己的runs.jsonl和 Elo 脚本结果替换不要直接引用任何示意数字作为结论策略采样/反思规模平均 token/任务任务内 Elo 均值跨任务 EloΔEloElo/1k token边际 Elo/1k tokenbaseline1T1E10锚点000r12T2E2E2-E1Δ2Δ2/(T2/1000)首档边际值r34T3E3E3-E1Δ3Δ3/(T3/1000)(Δ3-Δ2)/((T3-T2)/1000)r58T4E4E4-E1Δ4Δ4/(T4/1000)(Δ4-Δ3)/((T4-T3)/1000)生成这张表的片段import json from bt_elo import aggregate_cross_task # 1) 从评测日志读胜负对按任务分组 task_results json.load(open(pairwise.json, encodingutf-8)) cross_elo aggregate_cross_task(task_results, anchor_policybaseline) # 2) 读 token 汇总 tok json.load(open(tokens_by_strategy.json, encodingutf-8)) tokens_per_task {r[strategy]: r[total_tokens] / r[tasks] for r in tok} # 3) 组装对照表 rows [] for s, elo in sorted(cross_elo.items(), keylambda kv: kv[1]): t tokens_per_task.get(s, float(nan)) rows.append({ strategy: s, cross_task_elo: round(elo, 2), tokens_per_task: round(t, 1), elo_per_1k_token: round(elo / (t / 1000), 4) if t t else None, }) for r in rows: print(r)读数时注意一点跨任务 Elo 是相对锚点平移后的平均值所以 baseline 那一行恒为 0。这不是baseline 得分为零而是其余策略以它为原点。写报告时要解释清楚否则读者会误读。8. 收益递减的三个读数拐点、方差与分母完整性拿到对照表之后怎么判断继续加算力还值不值我通常看三个信号。第一Elo/1k token 的衰减速度。把各档的Elo/1k token依次排列如果第二档已经掉到首档的三分之一以下而边际 Elo 仍在下降说明这条测试时算力策略已经进入平坦区。继续加倍预算换来的跨任务 Elo 提升很可能落在评测噪声范围内。第二跨任务方差。把每个任务的 Elo 单独列出来看提升是否集中在一两个任务上。如果数学题涨、代码修复不涨、网页导航反而跌这个策略就不是通用算力扩展而是特定题型增强。此时聚合后的平均 Elo 会掩盖真实分布建议在表里加一列任务间标准差。第三分母是否完整。三个高频漏项失败重试的 token 没记、工具调用的返回没记、投票里被丢弃的分支没记。这三项都会让分母偏小Elo-per-token 被高估。自检方式是拿一次完整运行的总 token 和账单/用量页面对一遍差异超过几个百分点就要查日志埋点。如果你想把这三个读数自动化可以加一个简单的判定函数def verdict(elo_per_1k, marginal): if len(elo_per_1k) 2: return 数据不足 first, last elo_per_1k[0], elo_per_1k[-1] if last first / 3 and all(m 0 for m in marginal[1:]): return 进入平坦区建议停止加码 if last first / 2: return 边际明显衰减按任务分桶复核 return 仍有扩展空间继续采样9. 上线前自检清单与常见报错映射跑正式评测前把这几项过一遍能省掉大量返工凭据TAOTOKEN_API_KEY在当前 shell 可见且不是空字符串。echo ${#TAOTOKEN_API_KEY}返回大于 0 的数字。地址客户端base_url是https://taotoken.net/api没有多余斜杠也没有重复拼/v1。模型名与 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_name_check 控制台里展示的名称一致不要凭记忆写。客户端分流Claude Code 用ANTHROPIC_*Codex 用config.tomlenv_key两者不交叉。日志runs.jsonl每行都有task_id、strategy、total_tokens三个字段。采样同一任务的多次运行使用相同随机种子设定避免把随机波动读成策略差异。报错映射表现象优先排查401 / invalid api keyKey 未注入、拼写错误、配置改完未重启进程404 / not foundbase_url路径拼接不符合所选 SDK 规则超时并发过高或单次max_tokens过大先降并发复测返回内容为空看finish_reason判断是长度截断还是内容过滤token 数与预期不符检查是否把多轮反思的调用算进了同一个任务10. 把评测管线接到模型入口整条链路总结成一句话评测逻辑开写之前拿 Key客户端指向https://taotoken.net/api每次调用落盘 usage用 Bradley-Terry 聚合任务内排序并锚定成跨任务 Elo最后用完整 token 做分母画收益递减曲线。顺序不能反先有可靠的 key 与 base_url才有可信的 Elo-per-token。需要动手时按这个顺序走一遍想先在浏览器里验证模型返回是否符合预期用模型对话试一轮最小 prompt。准备把 Agent 挂到长期评测管线里先看Coding Plan的配额与调用方式避免跑到一半断流。拿到凭据这一步在创建 API Key完成生成后直接写入.env不要提交到仓库。Claude Code 侧的字段名、settings.json结构、常见报错参考Claude Code 接入文档按文档字段逐项对齐。把YOUR_API_KEY换成你自己的 Key把base_url指向https://taotoken.net/api先跑一条ping再启动你的第一批 Agent 任务。等runs.jsonl里出现了带total_tokens的记录你的 Elo-per-token 对照表就可以开始填了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进