1. 为什么我要用统一 Key 跑 MT-Bench 人工判断评估器
做 LLM 应用的人迟早会撞上一个问题:模型换了一版,输出到底变好了还是变差了?靠肉眼看几十条回答根本看不出趋势,靠人工打分又贵又慢。MT-Bench 人工判断评估器基准测试就是解决这个问题的——它用一套已经有人类标注的成对比较数据集,去衡量「某个 LLM 当裁判」时,判得跟人类有多一致。
说白了,MT-Bench 人工判断数据集里存的是「同一个问题下,模型 A 和模型 B 的回答,人类觉得谁更好」。我们把 GPT、Gemini 这类模型当成评估器(Evaluator),让它去判同样的成对样本,再拿它的判断结果和人类标注对比,算出一致率。一致率越高,说明这个模型越适合当自动评估的裁判。
这套流程适合谁?三类人最需要:一是做模型选型的开发者,要在几个候选模型里挑一个当评估器;二是做 RAG 或 Agent 的团队,想量化每次改动的收益;三是写论文或做评测报告的人,需要可复现的基准数据。
但真正动手时,第一个坑往往不是算法,而是 Key。评估链路里要同时调 OpenAI、Gemini、Cohere 好几家,每家一个 Key、一套 SDK、一套计费,环境变量配到怀疑人生,跑一半某个 Key 额度没了还得重来。我试过把三家 Key 分别塞进环境变量,结果 Gemini 的 SDK 版本和 OpenAI 的异步调用互相打架,光调通就花了一下午。
所以这篇的做法是:用 TaoToken 的统一 Key 把多模型调用收敛到一个入口,Base URL 指向https://taotoken.net/api,模型 ID 按需切换。这样评估脚本里只维护一份 Key,换模型只改一个字符串。下面从环境准备到跑出基准表格,一步步来。
2. TaoToken 统一 Key 的前置准备与依赖安装
先说清楚 TaoToken 在这里扮演的角色:它是一个兼容 OpenAI 接口规范的模型调用入口,你拿一个 Key,就能通过同一个 Base URL 请求不同厂商的模型。对评估场景特别友好,因为评估脚本最怕的就是「每个模型一套调用代码」。
你需要准备的东西不多:
第一,一个 TaoToken 的 API Key。去官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后,进控制台 https://taotoken.net/console 创建。创建完在 API Keys 页面 https://taotoken.net/api-keys 能看到以sk-开头的密钥,复制保存好,后面配置要用。
第二,Python 环境,建议 3.10 以上。评估链路里 llama-index 对异步支持比较依赖新版本。
第三,装依赖。这里有个细节:llama-index 的 Gemini 集成包默认走 Google 官方 SDK,但我们用统一 Key 的话,其实可以统一走 OpenAI 兼容接口,少装一个 SDK 就少一个版本冲突源。不过为了跟原始案例对齐、方便你对照,我把两套都列出来,你按需选。
pip install llama-index-core pip install llama-index-llms-openai pip install llama-index-llms-gemini pip install llama-index-llms-cohere pip install pandas nest_asyncio如果你打算全部走统一入口,llama-index-llms-gemini和llama-index-llms-cohere可以先不装,直接用OpenAI类指定不同 model 即可。我实测下来,统一走 OpenAI 兼容接口最省心,因为PairwiseComparisonEvaluator内部对 LLM 的调用是标准 chat 接口,兼容层完全够用。
环境变量这块,传统做法是:
export OPENAI_API_KEY="sk-xxx" export GOOGLE_API_KEY="xxx"用统一 Key 后,你只需要一个:
export TAOTOKEN_API_KEY="sk-你的key"然后在代码里把它传给OpenAI类的api_key参数,api_base指向https://taotoken.net/api。注意 API 地址不带任何查询参数,就是干净的https://taotoken.net/api。
这里提醒一个容易忽略的点:llama-index 的OpenAI类默认会去读OPENAI_API_KEY环境变量,如果你系统里恰好有旧的 OpenAI Key,可能会串。所以显式传参最稳,别偷懒。
依赖装完,先跑个最小连通性测试,确认 Key 和 Base URL 没问题,再往下搭评估链路。这一步能帮你把「Key 错」和「代码错」分开,省很多排查时间。
from llama_index.llms.openai import OpenAI llm = OpenAI( model="gpt-4o-mini", api_key="sk-你的key", api_base="https://taotoken.net/api", temperature=0, ) print(llm.complete("用一句话说明什么是成对比较评估。"))能打印出结果,说明统一入口通了。如果报 401,先检查 Key 有没有复制全、有没有多余空格;如果报连接错误,检查api_base是不是写成了带路径的形式。
3. 可复制的评估脚本配置:JSON 与 Python 双份
这一节给你可以直接抄的配置。我习惯把模型配置抽成一个 JSON,脚本读它来构造评估器,这样加模型、换模型都不用改主逻辑。
先建一个eval_config.json:
{ "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "evaluators": [ { "name": "gpt-4o", "model_id": "gpt-4o", "temperature": 0 }, { "name": "gpt-4o-mini", "model_id": "gpt-4o-mini", "temperature": 0 }, { "name": "gemini-pro", "model_id": "gemini-1.5-pro", "temperature": 0 } ], "benchmark": { "batch_size": 20, "sleep_time_in_seconds": 0.5, "dataset_dir": "./mt_bench_data", "pack_dir": "./pack" } }注意model_id这一栏,它必须和 TaoToken 侧支持的模型标识一致。不同入口对模型名的写法可能不同,有的要models/gemini-pro前缀,有的直接gemini-1.5-pro。你可以在模型对话页面 https://taotoken.net/chat 里先手动选一次模型,看它实际发出的模型名是什么,照着填最保险。
然后是主脚本run_mtbench.py:
import os import json import asyncio import nest_asyncio import pandas as pd from llama_index.core.llama_dataset import download_llama_dataset from llama_index.core.evaluation import PairwiseComparisonEvaluator from llama_index.core.llama_pack import download_llama_pack from llama_index.llms.openai import OpenAI nest_asyncio.apply() with open("eval_config.json", "r", encoding="utf-8") as f: cfg = json.load(f) api_key = os.environ[cfg["api_key_env"]] # 1. 下载 MT-Bench 人工判断数据集 pairwise_dataset, _ = download_llama_dataset( "MtBenchHumanJudgementDataset", cfg["benchmark"]["dataset_dir"], ) # 2. 构造评估器 evaluators = {} for item in cfg["evaluators"]: llm = OpenAI( model=item["model_id"], api_key=api_key, api_base=cfg["base_url"], temperature=item["temperature"], ) evaluators[item["name"]] = PairwiseComparisonEvaluator(llm=llm) # 3. 下载基准测试包 EvaluatorBenchmarkerPack = download_llama_pack( "EvaluatorBenchmarkerPack", cfg["benchmark"]["pack_dir"], ) # 4. 逐个跑基准 async def run_all(): frames = [] for name, evaluator in evaluators.items(): print(f"=== benchmarking {name} ===") benchmarker = EvaluatorBenchmarkerPack( evaluator=evaluator, eval_dataset=pairwise_dataset, show_progress=True, ) df = await benchmarker.arun( batch_size=cfg["benchmark"]["batch_size"], sleep_time_in_seconds=cfg["benchmark"]["sleep_time_in_seconds"], ) df["evaluator_name"] = name frames.append(df) return pd.concat(frames, axis=0) final_df = asyncio.run(run_all()) print(final_df) final_df.to_csv("mtbench_result.csv", index=False)这份配置的关键设计点有三个。第一,api_key_env存的是环境变量名而不是 Key 本身,避免 Key 进版本库。第二,evaluators是数组,加模型只加一项。第三,batch_size和sleep_time放在配置里,因为不同模型的限流策略不一样,Gemini 那类可能要给更小的 batch 和更长的 sleep。
如果你更习惯 TOML,等价写法是这样:
base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" [[evaluators]] name = "gpt-4o" model_id = "gpt-4o" temperature = 0 [[evaluators]] name = "gemini-pro" model_id = "gemini-1.5-pro" temperature = 0 [benchmark] batch_size = 20 sleep_time_in_seconds = 0.5 dataset_dir = "./mt_bench_data" pack_dir = "./pack"读 TOML 用tomllib(Python 3.11+)或tomli,其余逻辑不变。选哪种看你项目习惯,重点是「Base URL + Key + Model ID」这三件套在配置里显式写清楚,别散落在代码各处。
4. 验证请求与成功结果:跑通一次完整基准
配置就绪后,先别急着全量跑。MT-Bench 人工判断数据集有 120 条样本,每条成对比较要调两次模型(原始顺序 + 翻转顺序),三个评估器就是 720 次调用。全量跑之前,先用小样本验证链路。
把batch_size临时改成 3,跑一次:
python run_mtbench.py你会看到进度条滚动,每个评估器跑完后打印一段 DataFrame。成功的话,输出里应该包含这些列:number_examples、invalid_predictions、inconclusives、ties、agreement_rate_with_ties、agreement_rate_without_ties。
我实测下来,小样本跑通后,输出大概长这样(数值仅示意,实际以你的运行为准):
| evaluator_name | number_examples | invalid_predictions | inconclusives | ties | agreement_rate_with_ties | agreement_rate_without_ties |
|---|---|---|---|---|---|---|
| gpt-4o | 3 | 0 | 1 | 1 | 0.67 | 0.75 |
| gpt-4o-mini | 3 | 0 | 1 | 2 | 0.33 | 0.50 |
| gemini-pro | 3 | 0 | 0 | 1 | 1.00 | 1.00 |
样本太小,数值没有参考意义,但能确认三件事:数据集加载成功、评估器能正常调用、结果能聚合。这三件事都过了,再改回batch_size=20全量跑。
全量跑的时候有几个观察点值得记下来。第一,invalid_predictions如果非零,说明评估器输出没法解析成「A 更好 / B 更好 / 平局」三选一,通常是模型没按格式返回,或者返回被截断。第二,inconclusives是位置偏差的度量——同一对样本正着问一次、反着问一次,如果两次判断不一致,就记为不确定。这个数越高,说明模型越容易被答案的呈现顺序带偏。第三,ties是模型判平局的次数,判太多平局说明它不够果断,判太少又可能过于武断。
跑完后mtbench_result.csv会落盘,你可以用 pandas 做透视:
import pandas as pd df = pd.read_csv("mtbench_result.csv") print(df.groupby("evaluator_name")[["agreement_rate_without_ties", "inconclusives"]].mean())这一步的意义在于:你得到的不再是「感觉 GPT-4 判得准」,而是一个可对比的数字。换模型、换 prompt、换 temperature,重跑一遍就能看出差异。这就是可复现评估流程的价值。
顺便说一句,如果你要长期跑这类评估,Coding Plan https://taotoken.net/coding-plan 那种按周期计费的方式会比按次调用更可控,尤其是评估任务动辄几百上千次调用的时候。
5. 本篇常见错误排查:401、local proxy failed 与解析异常
评估链路跑不通,九成问题集中在这几类。我按报错原文对照着说。
401 Unauthorized / invalid api key。最常见。原因通常是 Key 没传对,或者传了但被环境里旧的OPENAI_API_KEY覆盖。排查顺序:先确认os.environ["TAOTOKEN_API_KEY"]能打印出以sk-开头的值;再确认OpenAI(...)里显式传了api_key和api_base;最后确认api_base是https://taotoken.net/api,没有多余斜杠或路径。如果还报 401,去 API Keys 页面 https://taotoken.net/api-keys 重新生成一个,排除 Key 被禁用或额度耗尽。
local proxy failed / connection error。这类报错一般是网络层没通,或者api_base写错了。先确认api_base拼写,再确认本机能不能正常访问该地址。注意别在代码里配任何本地代理设置,评估脚本走直连最稳。如果公司网络有出口限制,找运维确认放行,别自己乱加代理配置。
reading 'choices' / KeyError: 'choices'。这个报错说明返回体里没有choices字段,通常是接口返回了错误结构,但 SDK 没正确抛出。根因往往是模型 ID 写错了——比如你写了gemini-pro,但入口期望gemini-1.5-pro,服务端返回一个错误对象,SDK 解析时找不到choices。解决办法是去模型对话页面 https://taotoken.net/chat 手动选一次目标模型,看实际模型名,照着改配置。
OAuth / authentication failed。如果你用的是 Gemini 官方 SDK 而不是统一入口,可能会撞上 OAuth 相关的报错。用统一 Key 走 OpenAI 兼容接口就能绕开这类问题,因为不再依赖 Google 的 OAuth 流程。这也是我推荐统一入口的原因之一。
invalid_predictions 偏高。这不是报错,但结果不可用。原因通常是评估器返回的文本没法解析成结构化判断。可以在PairwiseComparisonEvaluator里打开更详细的日志,看模型实际返回了什么。常见情况是模型加了一堆解释性文字,把判断结论淹没了。解决办法是换一个指令遵循更强的模型当评估器,或者在评估器构造时调整 prompt 模板。
inconclusives 异常高。说明模型位置偏差严重,正反两次判断老是不一致。可以尝试降低 temperature 到 0,或者换模型。如果某个模型 consistently 高,那它本身就不适合当评估器,这个结论本身就是有价值的。
排查时记住一个原则:先隔离变量。先用最小脚本测通「Key + Base URL + 一个模型」,再往评估链路里加复杂度。别一上来就跑全量,报错了都不知道是哪一层的问题。
6. 把评估链路固定下来:从一次性脚本到可复用流程
跑通一次不算完,真正省时间的是把它变成可重复执行的流程。我的做法是把上面那套脚本包成一个命令行工具,参数从命令行传,配置从 JSON 读,结果按时间戳落盘。
python run_mtbench.py --config eval_config.json --output runs/2024-xx-xx/这样每次改完 prompt 或换模型,重跑一条命令,结果自动归档,横向对比直接读多个 CSV。评估这件事,最大的成本不是跑一次,而是「想复现上次的结果却发现环境变了」。把配置和结果都落盘,这个问题就解决了。
另外两个实用技巧。第一,把batch_size和sleep_time做成按模型可配,因为不同模型的限流不一样,统一值要么太慢要么触发限流。第二,评估器数量多的时候,用asyncio.gather并发跑不同评估器,但同一个评估器内部保持串行,避免自己把自己限流。
如果你要把这套流程接到 CI 里,建议只跑固定的小样本子集做回归,全量跑留给手动触发。评估本身要调模型,成本和耗时都不低,别让它拖慢日常开发节奏。
最后留一个我踩过的坑:数据集下载目录和 pack 目录别放在临时目录里,download_llama_dataset和download_llama_pack每次都会检查本地缓存,放临时目录会导致每次重下,白白浪费时间。固定成项目内的./mt_bench_data和./pack,第二次跑就快很多。
到这一步,你已经有了一个用统一 Key 驱动、配置可复制、结果可对比的 MT-Bench 人工判断评估器基准测试流程。接下来换哪个模型当裁判、调什么参数,都是改配置重跑的事。