🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度
1. 先定位 OpenRouter 用量榜里的 Qwen3 条目
Key 和 Base URL 都从 TaoToken 拿,落地页是 https://taotoken.net/?utm_source=taotoken_aicg_blog_generate&utm_content= ,进去建一把 Key 就够用了。不过在动手写脚本之前,有一步比申请 Key 更容易被跳过:先把榜上那条 Qwen3 看清楚,再决定你要复现什么。这篇要做的不是给榜单加一句评论,而是把榜上一个名字,变成你本地文件里一行能核对的记录。
打开 OpenRouter 的 rankings 页面,切到模型维度,选一个时间窗(周榜或者月榜),然后在列表里找 Qwen3 系列的条目。这里有个坑:Qwen3 不是一条,而是一族。同一代里会有不同参数规模、不同激活方式、不同后缀的变体,榜上按调用量排序时它们是分开统计的。所以你复现的目标不是「Qwen3 这个概念」,而是榜上某一条具体的全名,复制下来,一个字都别改。
抄记录的时候至少留四个字段:抓取日期、时间窗、条目全名、页面来源 URL。抓取日期比什么都重要,用量榜按天滚动,今天的位置和三天后的位置可能差出一大截,没有日期的排名数字写进文章里就是一段无法验证的传闻。时间窗同理,周榜和月榜的口径不一样,你写「榜上第几」而不写周期,等于什么也没说。
| 记录字段 | 从哪里拿 | 填写方式 |
|---|---|---|
| 榜名 | OpenRouter rankings 页面标题栏 | 写清是模型维度还是应用维度 |
| 抓取日期 | 你打开页面的当天 | YYYY-MM-DD,自己填 |
| 时间窗 | 页面上的 week / month 切换 | 二选一,写死,别中途换 |
| 条目全名 | 榜单里 Qwen3 那一行 | 原样复制,禁止手打简写 |
| 页面来源 | 浏览器地址栏 | 带路径的完整 URL |
这张表本身没有分数,原因很直接:本文不含任何排行分数。我手上没有某个具体日期的榜单快照,凭印象写一句「Qwen3 系列进了前多少」「周调用量多少亿 token」,读起来很爽,但你按同一天去查对不上,那这篇文章的其余部分也就不可信了。榜单只当索引用,告诉你现在有哪些 Qwen3 变体在被真实调用,真正的一手数据由你自己跑出来。
还有一点要提前说清楚,免得后面读混:榜上记录的是模型的实际调用量,跟走哪条通道无关。同一个模型 ID,从不同的 API 兼容通道发出去,榜单不会替你做区分。所以这里的角色分工是——榜负责告诉你「大家现在在调什么」,统一网关负责给你 Key 和 Base URL,脚本负责产出延迟和 token 这一列数字。三者别混成一张表。
2. 把榜上的 Qwen3 全名对齐到模型广场 ID
从榜上抄下来的是展示名,能发出去的必须是一个请求 ID,这两者经常长得不像。OpenRouter 上的写法通常带厂商前缀、版本后缀、上下文长度标记,甚至带「thinking」「instruct」这类行为区分;而模型广场上的 ID 是接入方定义的字符串,可能更短,也可能多了量化或者推理模式的标识。直接把展示名塞进 model 字段,最常见的结局是 404。
所以第二步是去 TaoToken 的模型广场,找到与榜上同名的 Qwen3 条目,把 ID 复制出来。判断「是不是同一个模型」不要靠名字像不像,靠三件事:参数规模对不对得上,上下文长度写的是多少,以及有没有推理相关开关。这三项里任意一项不一致,你跑出来的 token 消耗就没有可比性,因为长上下文和推理模式会显著抬高 completion token 的数量。
| 对齐项 | 榜上你会看到 | 广场里你要确认 |
|---|---|---|
| 归属系列 | Qwen3 加一串后缀 | 是否同一代,别跨代 |
| 参数规模 | 常写在名称或标签里 | 与榜上一致才继续 |
| 上下文窗口 | 页面上标注的 token 数 | 与榜上一致,不一致单独备注 |
| 是否推理模式 | 名称里可能带标记 | 关掉或打开要写死在脚本里 |
| 模型 ID | 无,榜上不提供 | 从广场复制,本文一律写「以模型广场为准」 |
我在这里不写任何具体 ID,因为模型广场的条目会随上游更新而增减,写死的 ID 过两周就可能失效,读者照抄反而更容易报错。正确做法是把 ID 放进环境变量,脚本里只引用变量名。这样换模型时你只改一行,不会漏改散落在正文里的某处字符串。
对齐工作还有一个容易被忽略的细节:一次实验里只能有一个变量。要么固定模型 ID 换 prompt,要么固定 prompt 换模型 ID,两样同时动,你就没法解释延迟差异是模型造成的还是输入长度造成的。本文的做法是固定 prompt、固定 temperature=0、固定 max_tokens,只跑同一个 Qwen3 条目五次,看它自己的波动有多大。先摸清噪声,再谈横向对比。
3. curl 版:一条请求同时拿到延迟和 usage
先把三个变量导进 shell。Base URL 固定写 https://taotoken.net/api,末尾不要加 /v1,这是最容易犯的格式错误之一;请求路径里的 /v1 由你自己在 URL 路径上补,和 Base URL 本身结尾没有关系。Key 用你在控制台创建的那把,占位符统一写 YOUR_API_KEY,别把真 Key 贴进任何要提交的脚本。
export API_KEY="YOUR_API_KEY"
export BASE_URL="https://taotoken.net/api"
export MODEL_ID="以模型广场为准"
请求体里只放一条 user 消息,prompt 长度控制在一屏以内,方便你肉眼检查返回是不是完整。-o 把响应体写进文件,-w 把计时和状态码打到终端,两路分开,省得 later 解析时还要剥掉多余字符。time_total 是端到端耗时,time_starttransfer 更接近首字节时间,两个都记,因为流式和非流式场景下它们解释起来不一样。
curl -sS -X POST "$BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-o resp.json \
-w 'http=%{http_code} total=%{time_total}s ttfb=%{time_starttransfer}s\n' \
-d '{"model":"'"$MODEL_ID"'","messages":[{"role":"user","content":"用三句话解释什么是向量数据库,不要用比喻。"}],"temperature":0,"max_tokens":256}'
跑完先看状态码,200 再往下走。然后拆 usage,这一步是整篇文章的核心:prompt_tokens 是输入侧,completion_tokens 是输出侧,total_tokens 是两者之和。如果响应里额外带了推理 token 或者缓存命中的计数,单独开一列记,别和 completion 混在一起,否则你后面拿它跟榜单上「实际调用量」的概念对比时会自己对不上账。
jq '{model, finish: .choices[0].finish_reason, usage}' resp.json
单次结果没有任何代表性。网络抖动、上游排队、模型冷启动都会影响那一秒的数字,一次跑出三秒不代表模型慢,只能说明那一刻通道忙。我的做法是同一条命令连跑五次,中间不换 prompt、不换 Key、不换模型 ID,把五次结果并排放进记录里,取中位数而不是平均值——一次异常的长尾会把平均值拉得很难看,中位数更能反映「正常一次大概多久」。第一次运行单独标注,因为它可能包含连接建立的额外开销。
4. Node 脚本:五次批量跑完,直接落成对照表
手工跑五次太蠢,写个二十行脚本更省事。用 OpenAI 兼容 SDK 时,baseURL 填 https://taotoken.net/api,SDK 会自己在后面拼上 /v1/chat/completions,所以这里同样不要手写 /v1。SDK 版本用你项目里现有的即可,本文不指定版本号,避免版本更新后代码对不上。
import OpenAI from "openai";
import fs from "node:fs";
const client = new OpenAI({
apiKey: process.env.API_KEY,
baseURL: "https://taotoken.net/api",
});
const MODEL_ID = process.env.MODEL_ID;
const PROMPT = "用三句话解释什么是向量数据库,不要用比喻。";
const RUNS = 5;
const rows = [];
for (let i = 1; i <= RUNS; i++) {
const t0 = performance.now();
const resp = await client.chat.completions.create({
model: MODEL_ID,
messages: [{ role: "user", content: PROMPT }],
temperature: 0,
max_tokens: 256,
});
const ms = Math.round(performance.now() - t0);
const u = resp.usage ?? {};
rows.push({
run: i,
latency_ms: ms,
prompt_tokens: u.prompt_tokens ?? null,
completion_tokens: u.completion_tokens ?? null,
total_tokens: u.total_tokens ?? null,
finish_reason: resp.choices?.[0]?.finish_reason ?? null,
});
}
fs.writeFileSync("qwen3-runs.jsonl", rows.map((r) => JSON.stringify(r)).join("\n"));
console.log(rows);
顶层 await 需要 ESM,项目里 package.json 加上 "type": "module",或者运行时用 node --input-type=module。usage 取不到时写 null 而不是写 0,这一点看着吹毛求疵,实际很关键:0 会被后续求平均时当成真实数据算进去,null 会被跳过,前者污染结论,后者只是缺一格。finish_reason 也留着,如果是长度截断,说明 max_tokens 设小了,那次 completion token 不能拿来比。
跑完之后把 JSONL 转成贴得进文档的表:
jq -r '[.run,.latency_ms,.prompt_tokens,.completion_tokens,.total_tokens,.finish_reason] | @csv' qwen3-runs.jsonl
下面是这张表在跑之前的样子。空着不是我偷懒,而是这些格子只能由你在自己那把 Key 上跑出来;本文不填任何编造的延迟和 token 数,也不把榜单数值塞进来充数。填的时候把五次结果原样贴入,再补一行中位数。
| 运行序号 | 延迟 (ms) | prompt_tokens | completion_tokens | total_tokens | finish_reason |
|---|---|---|---|---|---|
| 1(含建连) | 待填 | 待填 | 待填 | 待填 | 待填 |
| 2 | 待填 | 待填 | 待填 | 待填 | 待填 |
| 3 | 待填 | 待填 | 待填 | 待填 | 待填 |
| 4 | 待填 | 待填 | 待填 | 待填 | 待填 |
| 5 | 待填 | 待填 | 待填 | 待填 | 待填 |
| 中位数(不计第 1 次) | 待填 | 待填 | 待填 | 待填 | — |
这份表只代表你这台机器、这个时间窗、这条 prompt 下的一次运行,不代表任何公榜。延迟和 token 消耗本身也不是模型质量的度量,一个模型回得快、用 token 少,只能说明它这次输出得短,跟它答得好不好是两件事。把这行字写在表下面,比在正文里强调十次「仅供参考」都有用。
5. 用 Claude Code、Codex、CC Switch 把同一个模型跑得更久一点
五次请求只能看出噪声,看不出趋势。如果你的目的是连续观察同一个 Qwen3 条目在真实编码任务里的 token 消耗,那 curl 和教学脚本就不够用了,得把它接进日常用的客户端里,让它在你写代码的过程中自然累积调用记录。这条路和上面的脚本用的是同一把 Key、同一个 Base URL,区别只在于谁来发请求。
Claude Code 走的是 Anthropic 协议那套环境变量,三件套分别是 ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL。写进 ~/.claude/settings.json 的 env 字段,比每次开终端导出更稳,尤其你同时开了好几个窗口的时候。模型那一项填「以模型广场为准」的对应 ID,不要填展示名。
{
"env": {
"ANTHROPIC_BASE_URL": "https://taotoken.net/api",
"ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY",
"ANTHROPIC_MODEL": "以模型广场为准"
}
}
不想手改配置的,可以用命令行工具一步到位:
npm install -g @taotoken/taotoken
taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m YOUR_MODEL_ID
Codex 是另一套配置,千万别把 ANTHROPIC_* 那一组变量套过去,它是 TOML 文件加 provider 字段的结构。写在 ~/.codex/config.toml 里,base_url 同样是 https://taotoken.net/api,Key 通过环境变量读取,不写进文件明文。
model = "以模型广场为准"
model_provider = "taotoken"
[model_providers.taotoken]
name = "taotoken"
base_url = "https://taotoken.net/api"
env_key = "API_KEY"
CC Switch 这类切换器解决的是另一个问题:你手上不止一个供应商,来回改配置文件容易改错。做法是在里面新建一个自定义供应商,填四项——Base URL、Key、模型 ID、协议类型。切过去之后,在客户端里发一条最简单的消息,看它是否正常返回,再回切换器的控制面板确认当前生效的是这一条。切换生效的判断标准不是界面上的对勾,而是那条消息真的回来了。
有一点必须提醒:这类客户端接的是你自己的开发环境,让它们读日志、读报错、生成命令都没问题,但不要让它们替你在生产库或者生产机器上直接执行破坏性操作。让模型给出命令和 SQL,你本地跑完,再把输出贴回对话里继续分析。想长期用同一套配置做重复实验,可以先看 TaoToken 上 Coding Plan 的额度口径,再决定是把实验放在客户端里跑还是在脚本里跑。
6. 排障与对账:401、404、模型名,以及这笔调用有没有入账
接通道时遇到的报错,九成集中在四个状态码上,而且几乎都和模型本身无关。把它们按出现顺序排一遍,能省掉大量来回试的时间。
401 通常意味着 Key 有问题:前后带了空格、复制时漏了字符、或者用了别家平台的 Key。请求头里 Bearer 和 Key 之间必须有一个空格,这个细节在复制粘贴时经常被吃掉。确认无误后还是 401,就回控制台重新建一把,用新 Key 再试一次,不要在同一把 Key 上反复重试。
404 基本是两个原因。一是模型 ID 手打错了,展示名和请求 ID 不是一回事,必须从模型广场复制。二是路径拼错了,Base URL 只写到 https://taotoken.net/api,请求路径上补 /v1/chat/completions,如果两边都写 /v1,就会拼出重复路径。400 一般来自参数:temperature 超范围、max_tokens 超过该模型上限,或者消息结构不合法。429 和超时则是并发太高,把并发压回 1,每次之间加个短暂停顿,先确认单条能稳定成功。
排完错之后做一次对账,这一步最容易被跳过。把本地 JSONL 里的 total_tokens 求和,与控制台用量页面对应时间段的记录比对。差异一般有三个来源:重试产生的重复请求(失败的通常不计入)、你中途换过模型 ID、以及客户端后台自己发的短请求。对账不是要凑到分毫不差,而是要确认「我记录的这笔消耗,和计费侧看到的是同一批请求」。对不上的时候,先怀疑自己的脚本重跑了,再怀疑配置。
| 现象 | 最可能的原因 | 处理顺序 |
|---|---|---|
| 401 | Key 复制不全或含空格 | 重建 Key,检查 Bearer 后空格 |
| 404 | 模型 ID 错或路径重复 /v1 | 从广场复制 ID,检查路径拼接 |
| 400 | 参数越界或消息结构不合法 | 先固定 temperature 与 max_tokens |
| 429 / 超时 | 并发过高 | 降到单并发,加间隔重跑 |
| 200 但 usage 缺失 | 响应结构差异 | 记 null,不要补 0 |
跑完这一轮,你手上应该有三样东西:一张记录榜上条目的定位表(不含分数)、一份能重复执行的 curl 与 Node 脚本、一张由你自己五次运行填出来的延迟与 token 对照表。封面数字一个都没有,但每一格都能追溯到某次真实调用。想让这套东西持续跑下去,先把 模型对话 打开,用同一个模型 ID 发一条短请求,看控制台用量页是不是立刻记上这一笔;如果对照表要跑一整天、来回切模型,Coding Plan 的额度口径更合适;本地还没 Key 的,直接在 控制台 建一把,Claude Code 与 CC Switch 的三件套对齐方式见 接入文档。
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度




被折叠的 条评论
为什么被折叠?



