恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
tokscale 与 9Router 桥接实战:用 gjc 格式 JSONL 打通路由网关的用量、图表与成本估算
首页
资讯中心
/
tokscale 与 9Router 桥接实战:用 gjc 格式 JSONL 打通路由网关的用量、图表与成本估算
tokscale 与 9Router 桥接实战:用 gjc 格式 JSONL 打通路由网关的用量、图表与成本估算
发布时间:2026/10/12 3:03:53
开发工具CLI数据可视化LLMOps【免费下载链接】tokscale️ Track token usage across AI coding agents from your terminal. Global leaderboard with trillions of tokens tracked.项目地址https://gitcode.com/gh_mirrors/to/tokscale点击查看免费下载本文档围绕开源仓库 tokscale 中的docs/9router-bridge.md展开讲解如何通过一个 Python 桥接脚本将 9Router 网关的 SQLite 用量数据转换为 tokscale 可识别的 gjc 格式 JSONL 文件从而让 tokscale 的图表、分析与成本估算能力覆盖 9Router 的全部 API 调用。读完本文你将掌握桥接脚本的部署与合并自定义定价、scanner.extraScanPaths配置、成本字段策略paid/free 模型的分叉处理、Provider 推断规则以及基于 systemd 定时器的全自动运行方案。背景为什么需要一座桥9Router 是一个统一的路由网关它把来自 NVIDIA NIM、Kilocode、Cloudflare Workers AI、GitHub Models、OpenRouter、Gemini 等众多上游 Provider 的模型请求聚合到一处。这些调用在 9Router 自己的 SQLite 数据库~/.9router/db/data.sqlite中积累了详尽的 token 用量但 tokscale 的扫描器并不知道如何直接读取这份数据。解决方案是文档 9router-bridge.md 描述的桥接架构用一个纯 Python 脚本读取 9Router 数据库把用量记录改写成 tokscale 的 gjc 客户端解析器能够消费的 JSONL 文件再通过scanner.extraScanPaths.gjc把输出目录注册给 tokscale 扫描器。这样 9Router 的每一次调用都会以 client 名为9router的身份进入 tokscale 的统计、图表与定价体系。桥接方案涉及三个核心文件文件作用scripts/9router_tokscale_bridge_gjc.pyPython 桥接脚本读 9Router DB → 写 gjc 格式 JSONLscripts/9router_custom_pricing.json非免费模型的补充定价合并进 tokscale 自定义定价scripts/systemd/9router-tokscale-bridge.service 与 .timer每 10 分钟自动运行的 systemd 用户级定时器整体数据流How It Works桥接的完整链路如下也对应文档 How It Works 一节桥接脚本读取 9Router 的 SQLite 数据库~/.9router/db/data.sqlite同时还会遍历~/.9router/db/backups/upgrade-info/data.sqlite中的历史备份库见脚本中的discover_router_dbs从requestDetails表提取 token 用量该表把 tokens 嵌套在dataJSON 列里也从usageHistory表提取带独立cost列的记录按本地日期分组写出 gjc 格式 JSONL 文件到~/.local/share/9router-tokscale/sessions/文件名形如9router-YYYY-MM-DD.jsonltokscale 的 gjc 解析器crates/tokscale-core/src/sessions/gjc.rs读取这些文件再结合其内置定价数据库与你的自定义定价进行成本计算。这个流程有两个值得注意的实现细节按日期分组使用本地时区而非 UTC脚本中的_date_str_from_ms先把 Unix 毫秒时间戳转成本地时区再格式化为%Y-%m-%d为的是让桥接文件的时间分片与 tokscale--today/--since/--until的chrono::Local语义保持一致。备份库去重由最新优先保证discover_router_dbs按 mtime 倒序返回数据库列表当前库最先查询其 request ID 在seen_ids集合中优先获胜同时只有成功转换的行才会写入seen_ids因此当前库中一条损坏的行不会压制备份库中同一 ID 的有效副本脚本注释中称之为 backup-is-first-path-encountered 不变量。解析器的对应实现桥接脚本写入的 JSONL 由 gjc.rs 的parse_gjc_file消费。每行按type字段区分session头携带id与cwd不产生消息message行只保留role assistant且带有model与usage的记录service_tier_change及其他类型被跳过。message 对象中的source字段用于覆盖默认 client桥接脚本在所有消息里写入source: 9router因此这些记录在 tokscale 中以9router客户端身份呈现——这正是文档中--client 9router与--client gjc语义差异的根源gjc是超集同时包含原生 gjc 会话与桥接数据。// crates/tokscale-core/src/sessions/gjc.rs let client message .source .as_deref() .filter(|s| !s.trim().is_empty()) .unwrap_or(gjc);单元测试 test_source_field_overrides_client_id 直接用一条source:9router的消息断言messages[0].client 9router锁定了这一行为。部署四步走从合并定价到验证第一步合并自定义定价务必 merge不要覆盖9Router 的免费模型无需定价但付费模型需要价格才能被 tokscale 估算成本。scripts/9router_custom_pricing.json提供了这些模型的价格但文档对此给出了一条强烈警告不要用cp直接覆盖~/.config/tokscale/custom-pricing.json如果你已经有自己的覆盖项简单复制会静默摧毁它们。必须把 9Router 的条目合并进去如果文件还不存在合并脚本也会负责创建它。原因有二一是自定义定价是全局精确匹配的二是 9Router 定价文件中携带的通用模型 id如kimi-k2.5、gpt-oss-120b可能与你已有的条目冲突。文档给出的合并脚本保证了任何 key 冲突时你的既有条目优先mkdir -p ~/.config/tokscale if [ -f ~/.config/tokscale/custom-pricing.json ]; then # jq 的对象加法是右偏的key 冲突时右操作数获胜 # 因此用户的既有模型必须放在右操作数。 jq -s .[0].models (.[1].models .[0].models) | .[0] \ ~/.config/tokscale/custom-pricing.json scripts/9router_custom_pricing.json \ ~/.config/tokscale/custom-pricing.json.tmp \ mv ~/.config/tokscale/custom-pricing.json.tmp ~/.config/tokscale/custom-pricing.json else cp scripts/9router_custom_pricing.json ~/.config/tokscale/custom-pricing.json fijq -s把两个文件读成数组.[1].models .[0].models让右侧你的既有文件在 key 冲突时获胜最后.[0]保留第一个文件的顶层结构。如果你的环境没有 jq也可以手动打开~/.config/tokscale/custom-pricing.json把scripts/9router_custom_pricing.json中models键下的条目追加进去。定价文件的实际内容scripts/9router_custom_pricing.json覆盖了以下模型每条都给出输入/输出/缓存读取每百万 token 的美元价格并标注来源LiteLLM、Models.dev 或 OpenRouter模型 id输入 $/M输出 $/M缓存读 $/M来源deepseek-ai/deepseek-v4-flash0.140.280.0LiteLLM / tensormeshstepfun-ai/step-3.7-flash0.201.150.04Models.devmimo-v2.50.140.280.0028Models.dev / xiaomikimi-k2.50.603.000.10OpenRouter / moonshotaigpt-oss-120b0.030.150.0OpenRouter / openaicf/moonshotai/kimi-k2.50.603.000.10Cloudflare / moonshotaiopenai/gpt-oss-120b0.030.150.0OpenRouter注意cf/moonshotai/kimi-k2.5这条它是 Cloudflare 前缀限定形式而kimi-k2.5是通用形式两者都需要显式配置因为自定义定价是全局精确匹配的。第二步配置 tokscale 扫描器把桥接输出目录加入 tokscale 的设置文件。默认位置是~/.config/tokscale/settings.json可用TOKSCALE_CONFIG_DIR或XDG_CONFIG_HOME覆盖{ scanner: { extraScanPaths: { gjc: [/home/USER/.local/share/9router-tokscale/sessions] } } }从源码看extraScanPaths是 scanner.rs 中ScannerSettings的一个BTreeMapString, VecPathBuf字段键使用公开的 client id如codex、gemini、gjc保证 JSON 稳定、可手写维护extra_scan_paths_for会把这些路径转成扫描任务且路径是递归根目录。单元测试 test_scanner_settings_deserialize_extra_scan_paths_camel_case 验证了 camelCase 反序列化行为。第三步运行桥接脚本python3 scripts/9router_tokscale_bridge_gjc.py脚本执行后会在~/.local/share/9router-tokscale/sessions/下生成按日分组的 JSONL 文件并在 stdout 打印写入统计Files / Messages 数量同时再次给出 settings.json 片段与后续命令提示。如果找不到 9Router 数据库会输出9Router DB not found: path并安全退出。第四步验证tokscale graph --client 9router tokscale models --client 9router tokscale pricing deepseek-ai/deepseek-v4-flash桥接消息统一以 client9router打标--client 9router只显示桥接数据--client gjc是超集同时显示原生 gjc 会话与桥接数据因为 9Router 数据本身就是 gjc 格式。tokscale pricing则用来确认deepseek-ai/deepseek-v4-flash这类付费模型的价格已从自定义定价中正确解析。关键设计成本字段策略Cost Field Policy这是整座桥最精细的部分。理解它需要对 tokscale 的定价管线有一个前提认知gjc 解析器把任何存在的usage.cost.total哪怕显式是0.0都当作CostSource::ProviderReported权威成本从而让apply_pricing_if_available跳过重新计价反之成本字段缺失则返回(0.0, CostSource::Unknown)触发基于 tokens 定价数据库的重新计价。付费模型故意省略 cost 字段对于付费模型桥接脚本故意不在 JSONL 中输出usage.cost。因为一旦输出任何cost.totaltoksale 就会把它当作权威成本原样保留而 9Router 的requestDetails表本身并不提供精确价格成本只能由 tokscale 根据 tokens 乘以自定义定价推算。省略 cost 字段后embedded_cost返回(0.0, CostSource::Unknown)dispatch 层的定价保护逻辑就会从 tokens 定价数据重新计价。源码中embedded_cost的实现精确对应了这一语义gjc.rsfn embedded_cost(usage: CamelUsage) - (f64, CostSource) { match usage.cost.as_ref().and_then(|c| c.total) { Some(total) if total.is_finite() total 0.0 (total, CostSource::ProviderReported), _ (0.0, CostSource::Unknown), } }集成测试 test_gjc_explicit_zero_cost_is_preserved_while_absent_cost_reprices 在同一会话里放了两条消息一条显式cost:{total:0.0}成本保持 0.0一条完全省略 cost被重新计价为 0.2直接验证了显式零权威、缺失可重计价的规则。免费模型反向操作显式嵌入$0.00对免费层模型id 以-free或:free结尾不区分大小写桥接脚本反其道而行显式嵌入cost: {total: 0.0}。原因在于 tokscale 的定价查找在匹配前会剥离-free后缀如果省略成本kimi-k2.5-free会被按付费kimi-k2.5的费率重新计价。嵌入权威的$0.00后免费用量被钉死在零成本同时 tokens 照常计数。定价查找对免费后缀的归一化在 pricing/lookup.rs 的测试中有充分佐证lookup(kimi-k2.5-free)匹配到moonshotai/kimi-k2.5OpenRouterlookup(glm-4.7-free)匹配到z-ai/glm-4.7lookup(claude-sonnet-4-5-free)归一化到 LiteLLM 的claude-sonnet-4-5——后缀剥离、按付费基础模型计价的行为由此确认。usageHistory 表的特例usageHistory表自带一个独立的costREAL 列区别于requestDetails中嵌套在data里的 token 结构。桥接脚本对它的处理是大于 0 的有限成本值作为权威成本原样透传零值或缺失的成本则回落默认策略付费模型省略、免费模型写$0.00非数字或非有限值NaN/Infinity被归一化为None后同样按缺失处理。Provider 推断让定价查找命中正确的行tokscale 的定价查找需要 provider 线索才能准确命中定价行。桥接脚本读取 9Router 数据库的provider列当该列为空且模型 id 包含/时从第一个路径段推导 provider 提示脚本中model.split(/, 1)[0].lstrip()即去掉前缀后取第一段模型 idProviderdeepseek-ai/deepseek-v4-flashdeepseek-aistepfun-ai/step-3.7-flashstepfun-aicf/moonshotai/kimi-k2.5moonshotai来自 DB因为 9Router 直接提供了 providermimo-v2.5mimo-v2.5无/透传 DB 值当 9Router 直接提供 provider 时原样使用。另外桥接脚本为每条消息同时写入provider和api两个字段如果 provider 为空还会在 JSONL 中省略这两个字段此时 tokscale 的 gjc 解析器会调用inferred_provider_from_model从模型名推断见 provider_identity.rs如claude-*→anthropic、gpt-*→openai、gemini-*→google、deepseek-*→deepseek最终兜底为gjc。9Router 模型前缀速查表9Router 用不同前缀标记不同上游 Provider。桥接和定价配置中经常需要识别这些前缀文档给出的完整对照表如下前缀Providernvidia/NVIDIA NIMkc/Kilocodecf/Cloudflare Workers AIgh/GitHub Modelscx/Codexollama/Ollama本地openrouter/OpenRoutergemini/Google Geminigc/Gemini CLIbpm/BytePlus ModelArkcerebras/Cerebras以cf/moonshotai/kimi-k2.5为例cf前缀说明请求来自 Cloudflare Workers AI而 provider 列直接给出moonshotai因此它既能按 DB provider 命中定价也能被前缀表解释来源。免费 vs 付费模型的行为汇总免费层判定模型 id 以:free后缀或*-free后缀结尾不区分大小写见脚本中的is_free_model自定义定价文件只覆盖付费模型免费模型的每一行都会嵌入权威的cost: {total: 0.0}见上文成本字段策略这样在 tokscale 剥离-free后缀后仍显示$0.00而不是被按付费基础模型的费率计价。桥接输出的 JSONL 格式为了便于排查与二次开发这里给出桥接脚本实际写出的文件结构以某日文件为例。文件由两行类型构成{type:session,id:9router-2026-01-15,timestamp:2026-01-15T03:00:0000:00,cwd:/} {type:message,id:rd-12345,message:{role:assistant,model:deepseek-ai/deepseek-v4-flash,source:9router,timestamp:1736899200000,provider:deepseek-ai,api:deepseek-ai,usage:{input:1200,output:300,cacheRead:800,cacheWrite:0,totalTokens:2300}}}其中session 头的id为9router-日期timestamp取自当日第一条消息的毫秒时间戳转 ISO-8601cwd固定为/message 行的id前缀rd-来自requestDetails表、uh-来自usageHistory表用于跨表消歧与去重免费模型的消息会多出cost:{total:0.0}token 桶遵循 OpenAI 口径prompt_tokens已包含cached_tokens因此非缓存输入 max(prompt - cached, 0)脚本的compute_token_buckets同时把负数钳制为 0把字符串化数字安全转换为 int无 token、无时间戳或损坏dataJSON 的行会被跳过并打印原因缺失时间戳的行会在结束时汇总一次 warning。写入时采用临时文件 os.replace的原子替换方式且只覆盖本次扫描中确实有数据的日期文件历史文件不受影响。自动化systemd 用户定时器桥接脚本本身是一次性命令但文档推荐用 systemd 用户级定时器让它每 10 分钟自动运行让tokscale --today无需手动干预始终保鲜。一次性安装# 把桥接脚本安装到 systemd 单元期望的位置 mkdir -p ~/.local/share/9router-tokscale/ cp ~/Documents/Rust/tokscale/scripts/9router_tokscale_bridge_gjc.py ~/.local/share/9router-tokscale/9router_tokscale_bridge_gjc.py mkdir -p ~/.config/systemd/user/ cp ~/Documents/Rust/tokscale/scripts/systemd/9router-tokscale-bridge.{service,timer} ~/.config/systemd/user/ loginctl enable-linger $USER # 让定时器在没有活动登录会话时也能运行 systemctl --user daemon-reload systemctl --user enable --now 9router-tokscale-bridge.timer # 验证 systemctl --user list-timers | grep 9router systemctl --user start 9router-tokscale-bridge.service # 首次运行无需等待 journalctl --user -u 9router-tokscale-bridge.service -n 20定时器与服务单元仓库中的 .timer 与 .service 单元文件给出了生产级配置# 9router-tokscale-bridge.timer [Unit] DescriptionRun the 9Router → tokscale bridge every 10 minutes [Timer] OnBootSec2min OnUnitActiveSec10min AccuracySec30s Persistenttrue Unit9router-tokscale-bridge.service [Install] WantedBytimers.target# 9router-tokscale-bridge.service [Unit] Description9Router → tokscale gjc bridge (materialize todays usage) [Service] Typeoneshot ExecStart/usr/bin/python3 %h/.local/share/9router-tokscale/9router_tokscale_bridge_gjc.py StandardOutputjournal StandardErrorjournal各配置项的含义OnUnitActiveSec10min上次触发后每 10 分钟运行一次OnBootSec2min开机约 2 分钟后先补跑一次Persistenttrue跨重启持久化上次触发时间休眠/关机错过的运行会在下次开机后立即补上AccuracySec30s允许 ±30 秒的调度偏差避免定时器过度抢占Typeoneshot 日志全部进 journal适合一次性任务且方便journalctl排查。服务单元注释特别说明桥接脚本对 9Router DB 是只读的且只写自己的 JSONL 目录~/.local/share/9router-tokscale/sessions因此无需提权或网络访问即可安全运行。故障排查查看定时器状态systemctl --user status 9router-tokscale-bridge.timer查看最近一次运行日志journalctl --user -u 9router-tokscale-bridge.service -n 20需要重装单元重复上面的一次性安装命令即可单元文件会被就地覆盖已知限制文档明确了两条边界使用时需要心中有数Ollama 模型会被跳过当上游 API 不返回 usage 元数据时Ollama 模型行无法产出 token 统计桥接脚本将其省略陈旧的日期文件可能残留桥接只重写当前 DB 扫描中有对应行的日期文件某次运行时没有匹配行的日期文件保持原样因此一旦底层数据消失例如 DB 被清理或替换旧的日期文件会继续留在输出目录中。这是只覆盖、不删除策略的代价——好处是历史数据不会被误删。小结9Router 桥接把网关统一入口与用量统计前端两套体系粘合在了一起脚本侧的读库、去重、token 分桶、免费/付费成本策略与 tokscale 侧的 gjc 解析、Provider 推断、自定义定价查找一一对应。部署时牢记两条铁律——合并而非覆盖自定义定价、付费模型省略 cost 字段而免费模型显式写 0——再配合 systemd 定时器即可让 9Router 的全部调用自动进入 tokscale 的分析与成本估算体系。赞分享开发工具CLI数据可视化LLMOps【免费下载链接】tokscale️ Track token usage across AI coding agents from your terminal. Global leaderboard with trillions of tokens tracked.项目地址https://gitcode.com/gh_mirrors/to/tokscale点击查看免费下载相关推荐9Router Cursor 集成指南把 Cursor IDE 接入智能路由网关与多模型自由切换9Router Cursor 集成指南把 Cursor IDE 接入智能路由网关与多模型自由切换 导读 本指南以 9Router 官方文档的 Cursor人工智能LLM 网关AI 应用OpenClaw 接入 LiteLLM 网关实战统一模型路由、用量审计与图像生成配置指南OpenClaw 接入 LiteLLM 网关实战统一模型路由、用量审计与图像生成配置指南 LiteLLM 是一个开源的 LLM 网关通过统一 API 接入AI 应用AI Agent交互助手后端即时通讯网关9Router 配额跟踪与用量监控实战指南实时追踪 Token 消耗、成本估算与告警机制9Router 配额跟踪与用量监控实战指南实时追踪 Token 消耗、成本估算与告警机制 导读 本文围绕 9Router 的配额跟踪Quota Tracki人工智能LLM 网关AI 应用上一篇终极指南5步配置Buffalo结构化日志记录与监控方案下一篇开源项目 gls 常见问题解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考