☰
剑桥ZeroBench视觉推理基准实测:用TaoToken统一Key跑通多模型评测
2026/10/7 20:05:22 网站建设 项目流程

1. 为什么我要自己跑一遍 ZeroBench

ZeroBench 是剑桥大学团队在 2025 年 3 月放出的视觉推理基准,100 道主题目、334 道子题目,设计目标就一个:让当时最强的 20 个多模态模型在单次作答下全部拿 0 分。这个结果在圈子里传得很广,但真正动手复现的人不多,原因也很现实——要横向对比十几个模型,你得同时维护十几套 API Key、十几份 SDK 配置,光是环境切换就够劝退。

我关心的不是「谁又得了 0 分」这个结论,而是想验证两件事:第一,ZeroBench 的题目到底难在哪,是视觉识别失败还是推理链断裂;第二,用统一入口跑多模型评测,能不能把「换模型」这件事的成本压到接近零。第二点对做评测的人来说价值更大,因为基准会过时,但一套顺手的多模型调用流程可以一直用下去。

这篇内容面向的是需要横向对比多款 AI 模型视觉推理能力的开发者。我会给出通过 TaoToken 统一 Key 接入多模型、批量跑 ZeroBench 题目的可复制配置,以及记录各模型得分与失败案例的验证动作。你跟着做,能搭出一套自己的视觉推理评测流程,而不是只看别人的结论。

先说清楚 ZeroBench 的结构,这决定了你的评测脚本怎么写。主题目是 100 道,答案大多是数值或短字符串,评测时用精确匹配;子题目 334 道,是把主题目拆成中间步骤,用来定位模型卡在哪一环。题目类型覆盖计数、空间关系、镜像反射、逻辑电路追踪、图形导航等。官方在论文里嵌了金丝雀字符串,目的是防止训练数据污染,你复现时不用管这个,但要知道它的存在意味着「网上抄答案」这条路是堵死的。

我实测下来,最省事的做法不是去 clone 官方仓库再改,而是自己写一个薄薄的评测壳:题目和图片放本地,模型调用走统一 API,答案解析和打分自己控制。这样你换模型、换提示词、换评分规则都不用动底层。下面从接入准备开始。

2. TaoToken 统一 Key 的前置准备

多模型评测最烦的就是「每个厂商一套鉴权」。OpenAI 一套、Anthropic 一套、Google 一套,有的还要走不同的 SDK 版本,装依赖都能装出冲突。TaoToken 的思路是提供一个兼容 OpenAI 格式的统一入口,你用一套 Key、一个 Base URL,就能调用不同厂商的模型,模型差异只体现在请求里的 model 字段。

对评测场景来说,这个设计刚好对味:你的评测脚本只需要维护一份客户端配置,模型列表变成一个数组,循环里改 model 名就行。得分记录、失败案例、成本统计都能挂在同一个数据结构上。

准备工作分三步。第一步是拿 Key,去控制台创建,地址是 https://taotoken.net/api-keys ,创建后复制保存,后面所有请求都用它。第二步是确认你要评测的模型 ID,不同厂商的命名不一样,比如 Anthropic 系列是 claude-* 这种形式,OpenAI 系列是 gpt-* 或 o* 这种形式,具体以文档里的模型列表为准,文档在 https://taotoken.net/doc 。第三步是准备运行环境,Python 3.10 以上,装 openai 这个包就够,因为统一入口兼容 OpenAI 的 SDK 协议。

这里有个容易踩的点:很多人以为「统一入口」意味着所有模型的参数完全一致。实际上不同模型对 temperature、max_tokens、图像输入格式的支持程度有差异。比如推理型模型可能不支持你手动设 temperature,图像分辨率上限也各不相同。所以评测脚本里要做参数兜底,别把某一家的默认参数硬套到所有模型上。

关于成本,我不在这里编造具体价格,因为模型定价会变。你要做的是在评测脚本里记录每次调用的 token 用量,跑完一轮后自己算。ZeroBench 只有 100 道主题目,即使加上子题目,单模型全量跑一轮的调用次数也是可控的,适合先小批量试跑再全量。

如果你打算长期做多模型评测,甚至跑 Agent 类的批量任务,可以考虑 Coding Plan 这类长期方案,地址是 https://taotoken.net/coding-plan ,它更适合高频、持续的调用场景,而不是一次性试跑。单次评测用按量计费就够了。

3. 可复制的多模型评测配置

这一节是核心,给你能直接抄的配置和脚本骨架。先看统一客户端的初始化,我用环境变量存 Key,避免硬编码:

import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api" )

Base URL 就是 https://taotoken.net/api ,注意不要加多余的路径后缀。Key 从环境变量读,别写进代码提交到仓库。

接下来是模型清单。我建议用一个 JSON 文件管理,方便增删和记录元信息:

{ "models": [ { "name": "claude-sonnet", "model_id": "claude-sonnet-4-20250514", "supports_temperature": true, "max_image_side": 1568 }, { "name": "gpt-4o", "model_id": "gpt-4o", "supports_temperature": true, "max_image_side": 2048 }, { "name": "o-series", "model_id": "o4-mini", "supports_temperature": false, "max_image_side": 2048 } ] }

注意 supports_temperature 这个字段,推理型模型通常不接受自定义温度,你传了可能报错或被忽略。评测脚本里要根据这个字段决定是否带上 temperature 参数。

然后是单题调用的函数。ZeroBench 的题目是「图片 + 问题文本」,答案要求放在花括号里,所以提示词要固定格式:

import base64 def encode_image(path): with open(path, "rb") as f: return base64.b64encode(f.read()).decode("utf-8") def ask_model(client, model_cfg, image_path, question): b64 = encode_image(image_path) messages = [ { "role": "user", "content": [ {"type": "text", "text": question + "\nLet's think step by step and put the final answer in curly braces."}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}} ] } ] kwargs = {"model": model_cfg["model_id"], "messages": messages, "max_tokens": 4096} if model_cfg.get("supports_temperature"): kwargs["temperature"] = 0 resp = client.chat.completions.create(**kwargs) return resp.choices[0].message.content

这段代码里有两个关键点。一是图像用 base64 内联,避免外链失效;二是提示词里明确要求把最终答案放进花括号,方便后面用正则提取。max_tokens 给足,因为推理型模型会生成很长的思考链,给太小会导致答案被截断,直接判错。

批量跑的时候,把题目组织成一个列表,每项包含 image_path、question、answer,然后双层循环:外层遍历模型,内层遍历题目。每跑完一题就把结果追加写入 JSONL,这样中途断了也能续跑:

import json, re def extract_answer(text): m = re.findall(r"\{([^}]*)\}", text) return m[-1].strip() if m else None def run_eval(client, model_cfg, items, out_path): with open(out_path, "a", encoding="utf-8") as f: for it in items: try: raw = ask_model(client, model_cfg, it["image_path"], it["question"]) pred = extract_answer(raw) record = { "model": model_cfg["name"], "qid": it["qid"], "pred": pred, "gold": it["answer"], "correct": pred == it["answer"], "raw_len": len(raw) } except Exception as e: record = {"model": model_cfg["name"], "qid": it["qid"], "error": str(e)} f.write(json.dumps(record, ensure_ascii=False) + "\n")

这个骨架故意写得很朴素,没有并发、没有重试。原因是我建议你先用 5 到 10 道题小批量验证流程通不通,确认答案提取、打分、写盘都正常,再考虑加并发。一上来就上多线程,出错时你分不清是模型问题还是并发问题。

如果你用的是 Claude Code 这类工具做辅助开发,接入时同样需要三件套:Base URL 填 https://taotoken.net/api ,Key 用你创建的,Model ID 填你要用的模型名。这三项缺一不可,很多人只填了 Key 忘了改 Base URL,结果请求打到默认地址上,报鉴权失败。

4. 验证请求与成功结果

配置写好后,先别急着跑全量。用一道题做冒烟测试,确认链路通。我建议单独写一个最小脚本:

resp = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Reply with the word ok in curly braces."}], max_tokens=50 ) print(resp.choices[0].message.content)

如果返回类似{ok}的内容,说明 Key、Base URL、网络都正常。这一步能过滤掉大部分低级错误。

冒烟测试通过后,跑一道真实的 ZeroBench 题目。你会看到不同模型的表现差异很大:有的模型输出很长的推理过程,最后给出一个数值;有的模型直接给答案,推理链很短;还有的模型会跑偏,答非所问。这些都是正常现象,评测的价值就在于把这些差异记录下来。

成功跑通一轮后,你的 JSONL 文件里会有类似这样的记录:

{"model": "claude-sonnet", "qid": "zb_001", "pred": "12", "gold": "12", "correct": true, "raw_len": 1832} {"model": "gpt-4o", "qid": "zb_001", "pred": "11", "gold": "12", "correct": false, "raw_len": 640}

然后写一个统计脚本,按模型聚合正确率:

from collections import defaultdict stats = defaultdict(lambda: {"total": 0, "correct": 0}) with open("results.jsonl", encoding="utf-8") as f: for line in f: r = json.loads(line) if "error" in r: continue s = stats[r["model"]] s["total"] += 1 s["correct"] += int(r["correct"]) for model, s in stats.items(): acc = s["correct"] / s["total"] if s["total"] else 0 print(f"{model}: {s['correct']}/{s['total']} = {acc:.2%}")

跑完主题目后,你会得到一张各模型的正确率表。按 ZeroBench 论文的结论,单次作答下主题目正确率会非常低,甚至为 0。如果你跑出来某个模型正确率明显偏高,先别高兴,大概率是答案提取出了问题,比如模型把答案写在了花括号外面,或者你的正则匹配到了推理过程中的中间值。这时候要回去看 raw 输出,人工核对几道题。

子题目的评测更有意思,因为它能告诉你模型卡在哪一步。子题目正确率通常比主题目高不少,但依然远低于传统基准。你可以把子题目的结果按题型分类,比如计数类、空间类、镜像类,看看哪一类是重灾区。这个分析比一个总分有用得多。

验证阶段还有一个动作值得做:同一道题让同一个模型跑 5 次,看答案是否稳定。ZeroBench 论文里提到,即使放宽到 5 次里对 1 次就算通过,得分率依然很低。你自己复现时,如果发现某模型偶尔答对某题,但 5 次里只对 1 次,说明它是蒙的,不是真会。这个稳定性指标比单次正确率更能反映模型的真实能力。

5. 本篇常见错误排查

多模型评测最容易在几个地方翻车,我把真实会遇到的报错和原因列出来。

第一个是 401 鉴权失败。报错信息通常是Error code: 401 - {'error': {'message': 'Invalid API key'}}。原因无非三种:Key 复制时带了空格、Key 已失效或被删、环境变量没读到。排查方法是先确认os.environ.get("TAOTOKEN_API_KEY")能打印出值,再确认这个值和控制台里的一致。如果 Key 没问题,检查 Base URL 是不是写成了 https://taotoken.net/api 而不是别的路径。

第二个是local proxy failed或连接超时。这类报错通常和你的运行环境网络配置有关,不是 API 本身的问题。检查你的请求是否走了不该走的网络路径,或者本地是否有拦截。这类问题我不展开,你按自己环境的网络规范处理即可。

第三个是reading 'choices'相关的报错,完整信息类似TypeError: 'NoneType' object is not subscriptable或KeyError: 'choices'。这通常意味着返回体结构和你预期的不一样,可能是请求被拒绝、模型名写错、或者参数不合法导致返回了错误对象。排查方法是把原始响应打印出来看,别直接取resp.choices[0]。模型名写错是很常见的原因,比如把claude-sonnet-4-20250514写成了别的版本号,或者用了文档里不存在的模型 ID。

第四个是 OAuth 或鉴权方式混淆。有些工具默认走 OAuth 流程,而你用的是 API Key,两者不能混。如果你在 Claude Code 或类似工具里配置,要明确选择 API Key 模式,Base URL 填 https://taotoken.net/api ,不要触发 OAuth 登录流程。Codex 的 auth.json 配置也是同理,里面要写的是 API Key 和 Base URL,不是 OAuth token。

第五个是图像相关的错误,比如image too large或unsupported image format。不同模型对图像尺寸和格式的支持不一样,有的上限是 1568 像素,有的是 2048。你的评测脚本里要根据模型配置做缩放,别把一张 4K 图直接丢给所有模型。格式上优先用 PNG 或 JPEG,base64 编码后注意别带换行。

第六个是答案提取失败。表现是模型明明答对了,但你的correct字段是 false。原因是模型没按你要求的格式把答案放进花括号,或者放了多个花括号导致正则匹配到了错误的那个。解决办法是取最后一个花括号内容,同时在提示词里强调格式。如果某模型经常不遵守格式,可以在解析失败时把整段输出存下来,人工看几道,再决定是改提示词还是改解析逻辑。

第七个是成本失控。推理型模型单题可能消耗几千甚至上万 token,如果你不加限制地跑全量,账单会很难看。建议先跑 10 道题估算单题成本,再决定是否全量。评测脚本里记录每题的 token 用量,跑完就能算出总成本。

6. 把评测流程固定下来

跑通一轮之后,真正有价值的是把这套流程固定成可重复的动作。我的做法是:题目和图片放一个目录,模型清单放一个 JSON,评测脚本和统计脚本分开,结果按「模型名 + 日期」命名存盘。这样你下次想加一个新模型,只需要在 JSON 里加一项,重跑一遍,就能和之前的结果对比。

如果你要验证某个模型在视觉推理上的具体表现,可以先用模型对话功能快速试几道题,地址是 https://taotoken.net/chat ,不用写代码就能看输出。确认值得深入评测后,再上批量脚本。

对于需要长期、高频跑评测的场景,按量计费可能不是最优解,Coding Plan 这类方案更适合持续调用,地址是 https://taotoken.net/coding-plan 。接入文档在 https://taotoken.net/doc ,API Key 管理在 https://taotoken.net/api-keys ,这两个页面建议收藏,配置和排障时都用得上。

最后说一个我踩过的坑:一开始我图省事,把所有模型的 temperature 都设成 0,结果推理型模型直接报参数不支持。后来改成按模型配置决定是否传 temperature,问题就没了。多模型评测的核心不是「统一所有参数」,而是「统一调用入口,尊重模型差异」。把这句话记住,你的评测流程会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询