1. 当 Qwen 下载量成为谈资,OpenRouter 调用记账才是成本分析师的入口
当 Qwen 系列在 Hugging Face 的累计下载量成为行业谈资之后,OpenRouter 上的调用记账反而更容易暴露一个老问题:同一个应用今天走 A 上游、明天走 B 上游,prompt_tokens 和 completion_tokens 能对上,项目成本却对不上。要去 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=openrouter_cost_intro)拿 Key,Base URL 填 https://taotoken.net/api,先把调用日志做成可审计的账本。
站在成本分析师视角,模型下载量、开源榜单、分发优势都只是背景。真正影响月度报表的,是四个字段:谁在调用、调用哪个模型、输入输出 token 各是多少、这笔调用应该落到哪个项目。OpenRouter 的问题不是不能看账单,而是当你的调用同时来自本地脚本、Claude Code、Codex、自动化任务和临时验证时,账单维度很容易被上游聚合层吃掉。TaoToken 的价值在这里不是“再做一个中转”,而是把 OpenAI 兼容入口、Key 管理、调用日志和 token 记账拉到同一个 Base URL 下,减少成本归因的断层。
如果你只关心“模型能不能返回”,那么任何能出结果的端点都够用。但如果你要回答“这个月 Qwen 类模型在 OpenRouter 调用链里贡献了多少成本”“Claude Code 的长上下文缓存有没有被重复计费”“Codex 的任务重试是不是把 token 拉高了”,就必须把日志落到本地账本。下面从拿 Key、跑通调用、Claude Code 配置、Codex 配置、CC Switch 三件套,一路写到调用日志和 token 记账示例。
2. 从 TaoToken 官网拿 Key:先统一 Base URL 和项目标签
第一步不是写业务代码,而是把入口固定下来。打开 TaoToken 官网,登录后进入控制台,在 API Keys 页面创建一个项目级 Key。不要所有项目共用一个 Key,否则后期只能按模型拆账,没法按项目拆账。建议按“项目名 + 环境”命名,例如csdn-ugc-prod、csdn-ugc-test、local-cost-lab。
创建后复制 Key,后续所有示例都用YOUR_API_KEY代替。Base URL 固定为:
https://taotoken.net/api注意这个 Base URL 不加 UTM 参数。UTM 只用于官网链接和 deep link,工具配置里不要带。你可以先用一条最小 curl 请求验证 Key、Base URL 和模型 ID 是否匹配:
export TAOTOKEN_API_KEY="YOUR_API_KEY" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="YOUR_MODEL_ID" curl -sS "$TAOTOKEN_BASE_URL/chat/completions" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "'"$TAOTOKEN_MODEL"'", "messages": [ {"role": "user", "content": "用一句话说明 token 记账为什么要记录项目标签"} ], "temperature": 0.2 }'如果返回 401,优先检查 Key 是否复制完整、是否带了多余空格。如果返回 404,检查 Base URL 是否误写成其他路径,或者模型 ID 是否在当前 Key 的可用范围内。如果返回 429,先不要急着换 Key,先把本地日志打开,确认是不是重试逻辑导致短时间重复请求。成本分析师最怕的不是限流,而是限流后的重试没有幂等记录,最后账单里多出一批“幽灵 token”。
在控制台里给 Key 加备注之外,还建议在应用侧固定一个project字段。这个字段不一定要求网关支持,本地日志必须支持。因为很多团队最后对不上账,不是网关缺功能,而是应用调用时没有把项目、任务类型、发起人写进日志。
3. Claude Code 接入:settings.json 与 ANTHROPIC_* 的记账口径
Claude Code 的配置要单独对待。它使用settings.json和ANTHROPIC_*环境变量,不要把这些变量套到 Codex 上。典型配置如下:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "YOUR_API_KEY", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY" } }如果你的 Claude Code 版本只认其中一个鉴权变量,保留可用的那个即可。配置完成后,在项目目录里运行一次小任务,例如让它解释一个函数,然后立刻查看 TaoToken 控制台和本地日志。这里的关键不是“有没有返回”,而是 usage 里有没有cache_creation_input_tokens、cache_read_input_tokens这类字段。Claude Code 在长上下文、反复读取文件、连续修改代码时,缓存命中会明显影响成本。如果本地记账只记prompt_tokens和completion_tokens,很容易把缓存读取成本高估或低估。
Claude Code 的建议记账字段:
request_id created_at project tool = claude_code model prompt_tokens completion_tokens cache_creation_input_tokens cache_read_input_tokens total_tokens retry_count如果你还没有配置好 Claude Code,可以直接看 Claude Code 文档,里面会涉及ANTHROPIC_BASE_URL、Key 和模型映射。这里再强调一次:Claude Code 用ANTHROPIC_*,Codex 不要抄这一套。
4. Codex 接入:config.toml 与 OpenAI 兼容 provider 的 token 日志
Codex 走的是另一套配置。不要写ANTHROPIC_*,而是使用config.toml。一个可复制的 provider 配置如下:
model_provider = "taotoken" model = "YOUR_MODEL_ID" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"然后在 shell 里设置:
export TAOTOKEN_API_KEY="YOUR_API_KEY"如果你使用多个模型,可以在config.toml里准备多个 profile,但不要让 Codex 读取 Claude Code 的ANTHROPIC_*变量。成本分析时,Codex 的 token 结构通常和普通聊天不同:它可能包含较长的系统提示、文件上下文、工具调用结果和多次重试。建议在本地日志里单独打tool = codex标签,并按任务类型再拆一层,例如task_type = refactor、task_type = test_fix、task_type = explain。
Codex 的记账重点是重试去重。很多 Codex 任务失败后会自动重试,第二次请求的 prompt 可能比第一次更长。如果只看总 token,你会以为模型变贵了;拆开retry_count和request_id后,才发现是失败重试造成的。建议在调用层生成一个稳定的trace_id,重试时复用这个trace_id,但每次请求保留独立request_id。这样既能看单次成本,也能看任务总成本。
5. CC Switch 三件套:Base URL、API Key、模型映射不要混用
如果你用 CC Switch 管理不同编码工具,建议把配置拆成三件套:
第一件是供应商配置。新建一个 TaoToken 供应商,Base URL 填https://taotoken.net/api。不要带 UTM,不要带/v1或遗漏/api,以控制台和文档显示为准。更多入口可以在 TaoToken 官网 找到。
第二件是 API Key。每个工具、每个项目尽量用不同 Key。Claude Code 用一个 Key,Codex 用一个 Key,临时脚本用一个 Key。这样后期在控制台按 Key 过滤,就能快速定位成本来源。Key 统一写成YOUR_API_KEY,不要把真实 Key 提交到仓库。
第三件是模型映射。把主模型、快速模型、推理模型分别映射到 TaoToken 可用的模型 ID。不要假设模型名称在所有工具里完全一致。Claude Code 的模型配置、Codex 的model字段、普通 OpenAI 兼容脚本的model参数,最好从一份本地models.env或配置中心读取,避免手写错模型名导致请求失败或落到错误价格档位。
CC Switch 场景下的推荐目录结构:
config/ claude-settings.json codex-config.toml ccs-profile-tao-token.json models.env logs/ token_ledger.db token_ledger.jsonl核心原则只有一句话:Claude Code 的ANTHROPIC_*只给 Claude Code,Codex 的config.toml只给 Codex,普通 OpenAI 兼容调用用OPENAI_API_KEY或自定义TAOTOKEN_API_KEY。混用配置是后期账单对不上的高发原因。
6. 调用日志与 token 记账示例:Python + SQLite 本地账本
下面给一个本地可运行的记账示例。它不连接生产库,不连接 Oracle,只把调用结果写入本地 SQLite 和 JSONL。你可以直接复制到本地脚本里,把YOUR_MODEL_ID换成控制台可用模型。
先安装依赖:
pip install openai初始化本地账本:
import json import os import sqlite3 import time import uuid DB_PATH = "logs/token_ledger.db" def init_db(): os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) conn = sqlite3.connect(DB_PATH) conn.execute(""" CREATE TABLE IF NOT EXISTS llm_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, trace_id TEXT, request_id TEXT, created_at TEXT, source TEXT, project TEXT, tool TEXT, model TEXT, prompt_tokens INTEGER, completion_tokens INTEGER, total_tokens INTEGER, cache_read_tokens INTEGER DEFAULT 0, cache_write_tokens INTEGER DEFAULT 0, retry_count INTEGER DEFAULT 0, raw_usage TEXT ) """) conn.commit() return conn if __name__ == "__main__": init_db() print("token ledger ready")发起调用并落账:
import json import os import sqlite3 import time import uuid from openai import OpenAI DB_PATH = "logs/token_ledger.db" JSONL_PATH = "logs/token_ledger.jsonl" client = OpenAI( base_url="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"] ) def record_call(row): conn = sqlite3.connect(DB_PATH) conn.execute(""" INSERT INTO llm_calls ( trace_id, request_id, created_at, source, project, tool, model, prompt_tokens, completion_tokens, total_tokens, cache_read_tokens, cache_write_tokens, retry_count, raw_usage ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( row["trace_id"], row["request_id"], row["created_at"], row["source"], row["project"], row["tool"], row["model"], row["prompt_tokens"], row["completion_tokens"], row["total_tokens"], row["cache_read_tokens"], row["cache_write_tokens"], row["retry_count"], json.dumps(row["raw_usage"], ensure_ascii=False) )) conn.commit() conn.close() with open(JSONL_PATH, "a", encoding="utf-8") as f: f.write(json.dumps(row, ensure_ascii=False) + "\n") def chat_and_log(project="csdn_ugc", tool="local_script", model="YOUR_MODEL_ID"): trace_id = str(uuid.uuid4()) started = int(time.time() * 1000) resp = client.chat.completions.create( model=model, messages=[ {"role": "system", "content": "你是成本分析助手,回答要短。"}, {"role": "user", "content": "解释 OpenRouter 调用为什么要拆输入 token 和输出 token。"} ], temperature=0.2 ) usage = resp.usage raw_usage = usage.model_dump() if hasattr(usage, "model_dump") else dict(usage) row = { "trace_id": trace_id, "request_id": getattr(resp, "id", ""), "created_at": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), "source": "taotoken", "project": project, "tool": tool, "model": model, "prompt_tokens": getattr(usage, "prompt_tokens", 0) or 0, "completion_tokens": getattr(usage, "completion_tokens", 0) or 0, "total_tokens": getattr(usage, "total_tokens", 0) or 0, "cache_read_tokens": raw_usage.get("cache_read_input_tokens", 0) or raw_usage.get("prompt_tokens_details", {}).get("cached_tokens", 0) or 0, "cache_write_tokens": raw_usage.get("cache_creation_input_tokens", 0) or 0, "retry_count": 0, "raw_usage": raw_usage, "latency_ms": int(time.time() * 1000) - started } record_call(row) return row if __name__ == "__main__": init_db() result = chat_and_log() print(json.dumps(result, ensure_ascii=False, indent=2))这段代码的重点不是“封装得多漂亮”,而是把source、project、tool、model和raw_usage一起落盘。raw_usage原始保存非常重要,因为不同模型、不同上游的 usage 字段可能略有差异,后续修正计费公式时不用重新跑请求。
本地价格表和成本查询可以这样写:
CREATE TABLE IF NOT EXISTS model_price ( model TEXT PRIMARY KEY, input_per_m REAL, output_per_m REAL, cache_read_per_m REAL DEFAULT 0, cache_write_per_m REAL DEFAULT 0 ); INSERT OR REPLACE INTO model_price VALUES ('YOUR_MODEL_ID', 0.0, 0.0, 0.0, 0.0);按项目汇总成本:
SELECT c.project, c.model, SUM(c.prompt_tokens) AS input_tokens, SUM(c.completion_tokens) AS output_tokens, SUM(c.total_tokens) AS total_tokens, ROUND( SUM( c.prompt_tokens / 1000000.0 * p.input_per_m + c.completion_tokens / 1000000.0 * p.output_per_m + c.cache_read_tokens / 1000000.0 * p.cache_read_per_m + c.cache_write_tokens / 1000000.0 * p.cache_write_per_m ), 6 ) AS estimated_cost FROM llm_calls c LEFT JOIN model_price p ON p.model = c.model WHERE c.source = 'taotoken' GROUP BY c.project, c.model ORDER BY estimated_cost DESC;这里input_per_m、output_per_m等价格必须从 TaoToken 控制台或模型页维护到本地,不要写死在业务代码里。成本分析师应该把价格表当作配置,而不是魔法数字。
7. OpenRouter 历史调用如何并入同一本 token 账
如果你之前已经在 OpenRouter 上积累了调用记录,不需要把历史数据丢掉。可以把 OpenRouter 导出的 CSV 导入同一个本地库,用source字段区分。OpenRouter 侧通常能导出请求时间、模型、输入 token、输出 token、费用等字段,你只需要映射到本地表。
示例导入脚本:
import csv import sqlite3 import uuid DB_PATH = "logs/token_ledger.db" CSV_PATH = "openrouter_usage.csv" conn = sqlite3.connect(DB_PATH) with open(CSV_PATH, "r", encoding="utf-8-sig", newline="") as f: reader = csv.DictReader(f) for row in reader: conn.execute(""" INSERT INTO llm_calls ( trace_id, request_id, created_at, source, project, tool, model, prompt_tokens, completion_tokens, total_tokens, cache_read_tokens, cache_write_tokens, retry_count, raw_usage ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( str(uuid.uuid4()), row.get("request_id") or row.get("id") or "", row.get("created_at") or row.get("date") or "", "openrouter", row.get("project") or "openrouter_import", "openrouter_export", row.get("model") or "", int(float(row.get("prompt_tokens") or row.get("input_tokens") or 0)), int(float(row.get("completion_tokens") or row.get("output_tokens") or 0)), int(float(row.get("total_tokens") or 0)), int(float(row.get("cache_read_tokens") or 0)), int(float(row.get("cache_write_tokens") or 0)), 0, "{}" )) conn.commit() conn.close() print("openrouter rows imported")导入后,你可以做两件事:
第一,按时间窗口对比 OpenRouter 和 TaoToken 的调用量,确认切换后是否有异常增长。比如某个 Codex 任务在 OpenRouter 上平均输出 token 较少,但切到新入口后输出突然变长,可能是模型映射或系统提示变了。
第二,按模型维度合并总成本。OpenRouter 的账单可能已经包含上游费用,而 TaoToken 的本地账本需要你维护价格表。两者不要直接相加,而是先统一 token 口径,再用同一套公式估算。否则一个含加价、一个不含加价,报表会误导决策。
如果你现在还在用 OpenRouter 跑部分实验,也可以让应用侧逐步切到 TaoToken 的 Base URL:https://taotoken.net/api。切流时不要一次性全量,先选一个非关键项目,用同一个project标签同时记录两套来源,跑一周再比较。
8. 成本分析师的检查清单与常见坑
第一,Key 必须分项目。一个 Key 跑所有项目,控制台只能看到总量。你最终还是要回到日志里手工拆,成本分析效率很低。
第二,Base URL 不要随意改。工具配置里统一写https://taotoken.net/api,不要一会儿加/v1,一会儿漏/api。地址不一致会导致部分工具走旧通道,账本出现空洞。
第三,Claude Code 和 Codex 配置严格分开。Claude Code 用settings.json和ANTHROPIC_*,Codex 用config.toml和TAOTOKEN_API_KEY。把ANTHROPIC_*抄到 Codex,是最常见也最浪费时间的一类配置错误。
第四,流式响应要确认 usage。有些流式接口只在最后一个 chunk 返回 usage,有些需要额外参数。如果本地脚本只读中间 chunk,就会记成 0 token。建议先用非流式请求校准一次,再决定流式日志怎么补。
第五,重试要幂等。用trace_id串联任务,用request_id区分单次请求。重试次数单独记录,否则失败重试会被误判为模型涨价。
第六,缓存 token 不要漏。Claude Code、长文档问答、代码库分析都可能有缓存读取。缓存读取价格通常低于普通输入,但漏记会导致成本估算偏差。最稳妥的方式是把raw_usage原样保存,后续用 SQL 或 Python 重新计算。
第七,价格表本地维护。把模型单价写在代码常量里,短期方便,长期一定会过期。建议每个月初从控制台或模型页更新model_price表,并保留版本记录。
第八,先小流量验证,再扩大。任何新入口、新 Key、新模型映射,都先用一个project = cost_lab的测试项目跑通。确认日志、usage、模型 ID、价格都正确后,再切正式业务。
最后给一条成本分析师的实践路径:先用 模型对话 做一次最小调用,确认 Key 和 Base URL;如果你的主场景是高频编码任务,去 Coding Plan 看额度与计费方式;再到 API Keys 创建项目级 Key;Claude Code 用户直接看 Claude Code 文档。更多入口和说明在 TaoToken 官网。把调用日志和 token 记账做起来之后,Qwen 下载量是不是热点已经不重要,重要的是你终于知道每一笔 OpenRouter 调用最终落到了哪个项目、哪个工具、哪个模型上。