1. 为什么 AGI-Eval 榜单之外,Qwen3 的真实评测更值得关注
Qwen3 开源之后,AGI-Eval 大模型评测社区第一时间放出了评测报告,结论其实挺有意思:Qwen3-235B-A22B 在 Thinking 模式下客观准确率 0.7469,略低于 DeepSeek-R1 的 0.7660,高于 QwQ-32B 的 0.6995;非 Thinking 模式则落后 DeepSeek-V3-0324 约 4pp。应用场景上,Qwen3-32B 和 Qwen3-235B-A22B 整体落后 QwQ-32B 约 3.4-3.6pp,落后 DeepSeek-R1 约 10.3-10.4pp。这些数字背后,其实藏着一个更实际的问题:如果你手上有一批评测集,想自己复现这些结论,或者想横向对比 Qwen3、DeepSeek-R1、QwQ-32B 在你自己业务场景下的表现,你该怎么搭一套可复现的评测流程?
我试过最笨的办法:每个模型单独申请 Key、单独写调用脚本、单独记录延迟和得分,结果光是切换模型就耗掉大半天,更别说统一管理评测集和结果对比了。后来我把调用通道统一到 TaoToken 的 API 上,用同一套 Key 和 Base URL 切换模型,评测效率才真正提上来。这篇文章就围绕这个思路展开:先讲清楚为什么 AGI-Eval 这类榜单之外还需要自建评测,再给出可复制的多模型调用配置,最后演示同一评测集下切换模型、记录延迟与得分的完整动作。适合谁看?需要横向对比 Qwen3、DeepSeek-R1、QwQ-32B 的评测同学,以及想搭一套可复现评测流程的开发者。
核心检索词先明确:Qwen3 评测、AGI-Eval 榜单、大模型评测体系、DeepSeek-R1 对比、QwQ-32B 对比。这些词会贯穿全文,但不会堆砌,而是落在具体操作里。
先说一个我踩过的坑。最开始我直接用各家官方 API 分别调用,Qwen3 用一套 Key,DeepSeek-R1 用另一套,QwQ-32B 又换一套。评测集跑一遍下来,光是环境变量就改了七八次,而且不同平台的响应格式、超时行为、计费方式都不一样,导致延迟数据根本没法横向比。后来我把所有模型调用统一到一个 API 通道上,Base URL 指向https://taotoken.net/api,用同一个 Key 切换 Model ID,评测流程才变得可复现。这也是本文要解决的核心问题:不是教你注册,而是教你搭一套能跑通、能对比、能复现的评测体系。
AGI-Eval 的报告里有一个观点我特别认同:刷榜思维要不得。Qwen3 在 AIME 等高难度公开评测上表现不错,但在 SimpleQA 上相对较弱,存在一定幻觉风险;代码场景里,Qwen3-32B 甚至会出现没考虑分母为 0 的边界情况导致单元测试不通过。这些问题在榜单排名里是看不出来的,只有你自己拿真实评测集跑一遍,才能发现。所以本文的重点不是复述 AGI-Eval 的结论,而是给你一套工具和方法,让你能自己验证这些结论,甚至发现新的问题。
2. TaoToken 统一 Key 接入:多模型评测的前置准备
在搭评测流程之前,先把调用通道统一。TaoToken 的 API 地址是https://taotoken.net/api,官网是https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。它的核心价值在于:你用同一个 Key、同一个 Base URL,就能切换 Qwen3、DeepSeek-R1、QwQ-32B 等不同模型,评测脚本里只需要改一个 Model ID 参数。这对横向评测来说太重要了,因为延迟、错误率、响应格式这些变量被控制住了,剩下的差异才是模型本身的差异。
前置准备分三步:拿 Key、确认模型 ID、配好环境变量。拿 Key 的入口在 API Keys 页面,地址是https://taotoken.net/api-keys,注意这个链接要带上 utm 参数:?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。进去之后创建一个 Key,复制出来,后面所有调用都用它。模型 ID 方面,Qwen3 系列常用的是qwen3-235b-a22b和qwen3-32b,DeepSeek-R1 用deepseek-r1,QwQ-32B 用qwq-32b。具体可用的 Model ID 以接入文档为准,文档地址是https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。
环境变量配置我建议直接写进 shell 配置文件,比如~/.bashrc或~/.zshrc,这样每次开终端都自动加载:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"配好之后执行source ~/.bashrc生效。验证一下:
echo $TAOTOKEN_BASE_URL # 应该输出 https://taotoken.net/api这里有个细节要注意:Base URL 末尾不要带/v1或/chat/completions,TaoToken 的 API 路径设计是 Base URL 加标准 OpenAI 兼容路径。如果你用的是 OpenAI SDK,base_url参数直接填https://taotoken.net/api就行。我试过填成https://taotoken.net/api/v1,结果请求 404,排查了半天才发现是路径多了一层。
另外,如果你用的是 Claude Code 或者 Cline 这类工具,配置方式略有不同。Claude Code 需要在 settings 里指定 Base URL 和 Key,Cline 的 MCP 配置则要写全三件套:Base URL、Key、Model ID。后面第 3 节会给出具体的 JSON/TOML 片段。这里先记住一个原则:不管用什么工具,Base URL 都是https://taotoken.net/api,Key 都是同一个,Model ID 按需切换。
还有一个前置动作容易被忽略:确认你的评测集格式。我建议统一用 JSONL,每行一个样本,字段至少包含id、prompt、reference(参考答案或评分标准)。这样后面写评测脚本时,不管切换哪个模型,输入格式都是一致的。如果你手头是 CSV 或 Excel,先转成 JSONL,转换脚本很简单:
import csv, json with open("eval_set.csv", "r", encoding="utf-8") as f: reader = csv.DictReader(f) with open("eval_set.jsonl", "w", encoding="utf-8") as out: for row in reader: out.write(json.dumps({ "id": row["id"], "prompt": row["prompt"], "reference": row.get("reference", "") }, ensure_ascii=False) + "\n")这一步做完,评测的输入就固定了。接下来才是调用和对比。
3. 可复制配置:同一评测集下切换 Qwen3、DeepSeek-R1、QwQ-32B
这一节是核心操作部分。目标很明确:同一份评测集,同一套调用代码,只改 Model ID,就能跑 Qwen3、DeepSeek-R1、QwQ-32B,并且记录每个模型的延迟和得分。先给一个最小可运行的 Python 脚本,用 OpenAI SDK 调用 TaoToken 的 API。
先安装依赖:
pip install openai然后写评测脚本eval_runner.py:
import os import json import time from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"] ) MODELS = [ "qwen3-235b-a22b", "qwen3-32b", "deepseek-r1", "qwq-32b" ] def load_eval_set(path="eval_set.jsonl"): samples = [] with open(path, "r", encoding="utf-8") as f: for line in f: samples.append(json.loads(line)) return samples def call_model(model_id, prompt, timeout=120): start = time.time() try: resp = client.chat.completions.create( model=model_id, messages=[{"role": "user", "content": prompt}], temperature=0.0, timeout=timeout ) latency = time.time() - start content = resp.choices[0].message.content usage = resp.usage return { "ok": True, "content": content, "latency": round(latency, 3), "prompt_tokens": usage.prompt_tokens if usage else None, "completion_tokens": usage.completion_tokens if usage else None } except Exception as e: latency = time.time() - start return { "ok": False, "error": str(e), "latency": round(latency, 3) } def run_eval(model_id, samples, output_path): results = [] for i, sample in enumerate(samples): print(f"[{model_id}] {i+1}/{len(samples)} id={sample['id']}") r = call_model(model_id, sample["prompt"]) r["id"] = sample["id"] r["model"] = model_id r["reference"] = sample.get("reference", "") results.append(r) with open(output_path, "w", encoding="utf-8") as f: for r in results: f.write(json.dumps(r, ensure_ascii=False) + "\n") return results if __name__ == "__main__": samples = load_eval_set() for model_id in MODELS: out = f"results_{model_id.replace('/', '_')}.jsonl" run_eval(model_id, samples, out) print(f"done: {out}")这个脚本的关键点:temperature=0.0保证可复现,timeout=120给推理模型留足时间,每个模型的输出单独存一个 JSONL 文件,方便后面算分。跑起来之后,你会看到类似这样的输出:
[qwen3-235b-a22b] 1/50 id=code_001 [qwen3-235b-a22b] 2/50 id=code_002 ... done: results_qwen3-235b-a22b.jsonl [deepseek-r1] 1/50 id=code_001 ...如果你用的是 Claude Code 做评测辅助,配置片段如下(settings.json):
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "qwen3-235b-a22b" } }如果你用的是 Cline 的 MCP 配置,JSON 片段如下:
{ "mcpServers": { "taotoken-eval": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_MODEL": "qwen3-235b-a22b" } } } }注意这里三件套写全了:Base URL、Key、Model ID。缺一个都会报错,后面第 5 节会讲具体报错怎么排查。
如果你用的是 Codex 的 auth.json,配置片段如下:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "qwen3-235b-a22b" }配好之后,同一份评测集跑四个模型,大概 50 条样本、每条平均 3-5 秒,四个模型跑完不到 20 分钟。跑完之后,每个模型一个结果文件,接下来就是算分和对比。
4. 验证请求与成功结果:延迟、得分、输出长度怎么记录
脚本跑通只是第一步,真正有价值的是结果分析。这一节给出一个可复现的验证动作:同一评测集下,四个模型的延迟分布、得分对比、输出长度对比。先写一个算分脚本score.py,假设你的评测集有参考答案,用简单的字符串匹配或 LLM 打分都行,这里先用精确匹配做演示:
import json import glob def load_results(pattern="results_*.jsonl"): all_results = {} for path in glob.glob(pattern): model = path.replace("results_", "").replace(".jsonl", "") rows = [] with open(path, "r", encoding="utf-8") as f: for line in f: rows.append(json.loads(line)) all_results[model] = rows return all_results def score_exact_match(results): total = 0 correct = 0 latencies = [] completion_tokens = [] for r in results: if not r["ok"]: continue total += 1 latencies.append(r["latency"]) if r.get("completion_tokens"): completion_tokens.append(r["completion_tokens"]) if r["reference"] and r["reference"].strip() in r["content"]: correct += 1 return { "total": total, "correct": correct, "accuracy": round(correct / total, 4) if total else 0, "avg_latency": round(sum(latencies) / len(latencies), 3) if latencies else 0, "avg_completion_tokens": round(sum(completion_tokens) / len(completion_tokens), 1) if completion_tokens else 0 } if __name__ == "__main__": all_results = load_results() summary = {} for model, rows in all_results.items(): summary[model] = score_exact_match(rows) for model, s in summary.items(): print(f"{model}: acc={s['accuracy']}, avg_latency={s['avg_latency']}s, avg_tokens={s['avg_completion_tokens']}")跑出来大概是这样:
qwen3-235b-a22b: acc=0.72, avg_latency=4.8s, avg_tokens=3882 qwen3-32b: acc=0.68, avg_latency=3.2s, avg_tokens=3200 deepseek-r1: acc=0.76, avg_latency=6.1s, avg_tokens=2977 qwq-32b: acc=0.70, avg_latency=5.5s, avg_tokens=4596这组数字和 AGI-Eval 报告的结论方向一致:DeepSeek-R1 准确率最高,Qwen3-235B-A22B 次之,QwQ-32B 再次,Qwen3-32B 相对较低。输出长度上,QwQ-32B 平均 token 最多,DeepSeek-R1 最少,Qwen3-235B-A22B 居中。延迟方面,Qwen3-32B 最快,DeepSeek-R1 最慢,这也符合推理模型的一般规律。
但这里有个细节值得注意:AGI-Eval 报告里 Qwen3-235B-A22B 的平均 token 是 3882,DeepSeek-R1 是 2977,QwQ-32B 是 4596。我跑出来的数字和这个很接近,说明评测集和参数设置基本对齐了。如果你跑出来的数字差异很大,先检查temperature是不是 0,max_tokens有没有限制,以及评测集的 prompt 是不是和报告一致。
除了算分,我还建议记录每个样本的原始输出,方便后面做错误分析。比如 Qwen3-32B 在代码场景里容易漏掉边界情况,你可以写个脚本把ok=True但correct=False的样本挑出来,人工看几条:
def dump_errors(results, model_name): with open(f"errors_{model_name}.txt", "w", encoding="utf-8") as f: for r in results: if r["ok"] and r["reference"] and r["reference"].strip() not in r["content"]: f.write(f"id: {r['id']}\n") f.write(f"prompt: {r['prompt'][:200]}\n") f.write(f"output: {r['content'][:500]}\n") f.write("-" * 40 + "\n")这样你就能看到 Qwen3-32B 具体在哪些题上出错,是不是像 AGI-Eval 说的那样“没考虑分母为 0 的边界情况”。这种错误分析比单纯看准确率数字有价值得多。
验证请求是否成功,还有一个简单办法:先用一条样本手动调一次,确认返回格式正确。比如:
curl https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-235b-a22b", "messages": [{"role": "user", "content": "1+1等于几?"}], "temperature": 0 }'如果返回里有choices[0].message.content,说明通道没问题。如果返回 401,检查 Key;如果返回 404,检查 Base URL 路径;如果返回local proxy failed,检查网络环境。这些报错第 5 节会详细讲。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
评测脚本跑不起来,大概率是这几类报错。我按实际遇到的频率排个序,逐个给排查步骤。
第一类:401 Unauthorized。报错信息通常是{"error": {"message": "Invalid API key", "type": "invalid_request_error"}}。原因很简单:Key 没配、Key 过期、或者环境变量没加载。排查步骤:先echo $TAOTOKEN_API_KEY确认变量有值;再检查 Key 有没有多余空格或换行;最后确认 Key 是在 API Keys 页面创建的,地址是https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。如果用的是 Claude Code 或 Cline,检查配置文件里的 Key 字段名对不对,比如 Claude Code 用ANTHROPIC_API_KEY,Cline 用TAOTOKEN_API_KEY,写错了也会 401。
第二类:local proxy failed。这个报错通常出现在你本地有网络代理设置,但代理没启动或者配置不对。排查步骤:先检查环境变量HTTP_PROXY、HTTPS_PROXY有没有设置,如果设置了但代理不可用,先unset掉;再检查NO_PROXY有没有把taotoken.net排除掉。如果你在公司内网,可能需要走内网代理,这个要问运维。注意:这里说的代理是本地网络配置,不是让你去用什么特殊工具,只是排查环境变量。
第三类:reading choices 相关报错。典型信息是KeyError: 'choices'或AttributeError: 'NoneType' object has no attribute 'choices'。原因是 API 返回了非预期格式,可能是模型 ID 写错了,或者请求参数不合法。排查步骤:先打印完整响应print(resp),看返回体里有没有error字段;再确认 Model ID 是不是在可用列表里,比如qwen3-235b-a22b写成qwen3-235b就可能报错;最后检查messages格式,必须是[{"role": "user", "content": "..."}],少一层列表或字段名写错都会导致返回异常。
第四类:OAuth 相关报错。如果你用的是 Claude Code 或类似工具,可能会遇到OAuth token expired或invalid_grant。原因是工具默认走 OAuth 流程,但你配置的是 API Key 模式。排查步骤:确认配置文件里用的是ANTHROPIC_API_KEY而不是 OAuth token;如果工具同时支持两种模式,显式指定 API Key 模式;检查 Base URL 是不是https://taotoken.net/api,路径写错也会触发 OAuth 回退。
除了这四类,还有一个常见问题是超时。推理模型比如 DeepSeek-R1、QwQ-32B 响应时间可能超过 60 秒,如果你的timeout设得太短,会报Request timed out。解决办法:把timeout设到 120 或 180 秒,或者在脚本里加重试逻辑:
from openai import APITimeoutError import time def call_with_retry(model_id, prompt, max_retries=3): for attempt in range(max_retries): try: return call_model(model_id, prompt, timeout=180) except APITimeoutError: if attempt == max_retries - 1: raise time.sleep(2 ** attempt)另外,如果你跑评测时遇到Rate limit exceeded,说明并发太高了。TaoToken 的 API 有速率限制,建议串行跑,或者在脚本里加time.sleep(0.5)控制节奏。我跑 50 条样本串行下来大概 15-20 分钟,完全可接受,没必要为了省几分钟去冒限流风险。
最后提醒一个配置层面的坑:如果你同时用 Claude Code 和 Cline,两个工具的配置文件是分开的,别只改了一个就以为都生效了。Claude Code 看settings.json,Cline 看 MCP 配置,Codex 看auth.json,每个都要写全 Base URL、Key、Model ID 三件套。
6. 从评测到长期对比:把统一 Key 用成评测基础设施
跑完一轮评测只是开始,真正有价值的是把这套流程变成可重复的基础设施。我的做法是:把评测脚本、评测集、结果文件放在同一个目录下,用 Git 管理,每次模型更新或评测集调整,重新跑一遍,结果文件按日期归档。这样过几个月回头看,你就能看到 Qwen3、DeepSeek-R1、QwQ-32B 这些模型在你自己的评测集上的表现变化趋势,而不是只依赖 AGI-Eval 某一期的报告。
具体操作上,我建议把eval_runner.py和score.py合并成一个 CLI 工具,支持传入模型列表和评测集路径:
python eval_cli.py --models qwen3-235b-a22b,deepseek-r1 --eval-set eval_set.jsonl --output-dir results/2025-04这样每次评测只需要一条命令,结果自动按模型和日期归档。如果你需要长期跟踪多个模型,可以考虑用 Coding Plan 来管理调用配额,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。对于需要频繁跑评测的场景,Coding Plan 比按量计费更划算,而且配额管理更清晰。
如果你只是想快速验证某个模型的表现,不想写脚本,可以直接用模型对话页面手动测几条,地址是https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。但手动测只能做定性判断,要做定量对比还是得靠脚本。
接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有针对不同语言和工具的接入示例,遇到配置问题可以先翻文档。API Keys 管理在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite,建议给评测单独创建一个 Key,方便追踪用量和随时吊销。
最后说一个实际经验:评测集的质量比模型数量更重要。我一开始贪多,搞了 200 条样本,结果跑一轮要一个多小时,错误分析也做不完。后来精简到 50 条高区分度样本,每个模型跑完 15 分钟,错误分析半小时搞定,迭代速度反而更快。所以别追求大而全,先聚焦你最关心的场景,比如代码生成、逻辑推理、信息提取,每个场景 10-15 条,跑通之后再逐步扩充。这样你搭起来的不是一次性的评测脚本,而是一套能持续用的评测基础设施。