在 Xiaomi Agent(MiMoCode)里统计第三方 API 的 Token 用量
从「只有小米订阅才有用量通知」到「任意 OpenAI 兼容网关都能本地记账 + 按模型分色报表」
背景:为什么官方通知不够用
用 Xiaomi Agent / MiMoCode 时,有一个很实际的问题:
- 走小米官方订阅时,平台侧通常能告诉你大概用了多少 token;
- 一旦你把模型切到 第三方 API(例如 OpenCode Go、OpenRouter、自建兼容网关),官方账单/通知往往就对不上了——因为流量根本没走小米计费通道。
于是需求变得很具体:
- 无论对接什么平台的 API,都要能统计 token;
- 能记到 缓存命中 / 未命中(这直接决定花费);
- 能 配置模型单价,看到预估人民币费用;
- 能按 天 出图:Token 用量、费用,并且 按模型区分颜色。
最终落地形态是一个很轻的 MiMoCode 插件:
| 产物 | 路径 | 作用 |
|---|---|---|
| 原始明细 | data/usage.csv |
持久化、可 Excel 打开 |
| 可视化日报 | report/report.html |
默认「今天」,下拉切换 7/30 天 |
| 价目表 | plugin/prices.json |
元 / 百万 tokens |
下面按「问题 → 方案 → 实现 → 使用」完整讲一遍。
一、方案选型:为什么是插件,而不是代理或平台账单
可选路径大致有三条:

Error: Lexical error on line 9. Unrecognized text. ...-> H[仅该平台] C -. 推荐:轻量、跨平台 .-> G ----------------------^
| 方案 | 优点 | 缺点 |
|---|---|---|
| 插件(最终选择) | 不改主程序、不改流量路径;换 provider 仍生效 | 依赖响应里回传 usage |
| 本地反向代理 | 在 HTTP 层抄 usage,最「底层」 |
要改 baseURL、多维护一跳服务 |
| 只靠平台账单 | 零开发 | 换第三方 API 就断了 |
MiMoCode 兼容 OpenCode 的插件体系(@mimo-ai/plugin)。关键在于:客户端每次 LLM 调用结束后,assistant 消息轨迹里已经带了 token 明细,不需要破解上游协议,也不需要劫持流量。
因此结论很干净:
统计点放在 Agent 本地出口(插件钩子),而不是某个平台的订阅后台。
二、数据从哪来:插件钩子与 tokens 结构
2.1 用哪两个钩子
| 钩子 | 时机 | 本插件职责 |
|---|---|---|
session.userQuery.post |
每一步 LLM 结束后 | 唯一写入 CSV 的地方(避免重复记账) |
session.post |
整段 session 结束 | 只刷新 HTML 报表 |
刻意不在 session.post 再写一遍 CSV:它的 trajectory 是全量轨迹,和逐步钩子叠在一起会 double count。
2.2 trajectory 里的 tokens 长什么样
每条 assistant 消息大致有:
{
role: "assistant",
providerID: "opencodego", // 或 xiaomi / openrouter / ...
modelID: "mimo-v2.6-flash",
tokens: {
total?: number,
input: number, // 通常视为「未命中缓存」的输入
output: number,
reasoning: number,
cache: {
read: number, // 缓存命中读
write: number // 缓存写入
}
},
cost?: number // 系统若有定价可能已算过,币种/口径不一定符合你的价目表
}
要点:
- 缓存命中 token 是现成的(
cache.read),不必自己猜; - 命中率可在聚合时计算:
hitRate = cache.read / (input + cache.read); - 自定义价目时,不要盲目信任系统
cost字段的币种——我们自己按「元/百万 tokens」重算更可控。
2.3 CSV 落盘格式
每步追加一行(示意):
date,time,sessionID,agentID,step,providerID,modelID,input,output,reasoning,cache_read,cache_write,total,cost_cny
2026-09-24,11:45:04,ses_xxx,main,4,opencodego,mimo-v2.6-flash,8985,91,199,33152,0,42427,0.00511402
为什么选 CSV 而不是 TXT/SQLite:
- 结构固定,Excel / WPS 直接打开;
- 程序 append 简单;
- 个人本地统计不需要再引入数据库。
读入时会过滤:日期非法、模型为空、total<=0 的脏行。
三、费用怎么算:价目表与公式
3.1 单位约定
统一为:
元(CNY) / 百万 tokens(per 1M tokens)
3.2 已配置的小米官方价(示例)
| 模型 | 输入(未命中) | 输入(命中缓存) | 输出 |
|---|---|---|---|
mimo-v2.6-pro |
¥1.50 | ¥0.0125 | ¥3.00 |
mimo-v2.6-flash |
¥0.50 | ¥0.01 | ¥1.00 |
deepseek-flash 这类 分时段计价 的模型:先不写死单价(fallback: null),token 照记,费用记 0,避免用错价。
3.3 公式
cost_cny = (
input × 未命中价
+ (output + reasoning) × 输出价 // reasoning_as_output: true
+ cache.read × 命中价
+ cache.write × 缓存写价
) / 1_000_000
plugin/prices.json 示例:
{
"currency": "CNY",
"unit": "per_1m_tokens",
"models": {
"mimo-v2.6-pro": {
"input_miss": 1.5,
"output": 3,
"cache_read": 0.0125,
"cache_write": 0,
"reasoning_as_output": true
},
"mimo-v2.6-flash": {
"input_miss": 0.5,
"output": 1,
"cache_read": 0.01,
"cache_write": 0,
"reasoning_as_output": true
}
},
"fallback": null
}
3.4 系统侧 cost 是否也要写?
可以两边都写:
- 插件
prices.json:报表与 CSV 的权威人民币价目; - 全局
mimocode.jsonc里模型的cost:让运行时也有定价参考。
注意:系统消息上的 cost 文档口径常见为 USD 约定,和你的人民币价目 不一定同一币种。因此:
- 对账、博客里的「预计花费」以插件
cost_cny为准; - 系统
cost仅作补充,不混加。
配置片段示意:
// ~/.config/mimocode/mimocode.jsonc
"mimo-v2.6-flash": {
"name": "MiMo-V2.6-Flash",
"cost": {
"input": 0.5,
"output": 1,
"cache_read": 0.01
}
}
四、HTML 日报:交互与图表设计
报表是 单文件静态 HTML:生成时把「日期 × 模型」汇总嵌进页面,浏览器里过滤,切换范围不用重新写文件。
4.1 时间范围
- 默认:今天
- 下拉框可选:近 7 天 / 近 30 天
(不是一排分段按钮,更接近常见后台的「时间范围」选择)
切换后联动:
- 总卡片(Token / 费用 / 日均 / 调用数 / 模型数)
- 每个模型一张卡(Token、占比、费用、调用)
- Token 按模型堆叠柱状图
- 费用 按模型分色折线
- 缓存命中率折线
- 明细表(含模型列与色点)
4.2 为什么必须按模型分色
真实场景里经常是:
今天 flash 用了上千万 token,pro 只有几百万,deepseek 只有一点点。
若只画一条总曲线,大模型会把小模型压成噪声。按模型分色后:
- 堆叠柱一眼看出结构占比;
- 费用多线对比谁在烧钱;
- 模型卡片给出「今日 flash ¥x / pro ¥y」这种可读结论。
颜色按 modelID 稳定分配(排序后循环取调色板),刷新不会串色。
4.3 页面结构(逻辑示意)
[时间范围 ▾ 今天] ← select#rangeSelect
[图例:色块 + 模型名]
[总卡片] [总卡片] ...
[模型卡 flash] [模型卡 pro] ...
[Token 堆叠柱状图]
[费用分色折线]
[命中率折线]
[明细表:日期 | 模型 | ... | 费用]
五、完整接入步骤
5.1 目录结构
<YOUR_PROJECT_ROOT>/
├── plugin/
│ ├── index.js # 插件入口
│ └── prices.json # 价目表
├── data/
│ └── usage.csv # 调用明细(本地,勿随意公开)
├── report/
│ └── report.html # 日报
└── README.md
5.2 注册插件
编辑 全局 配置(对所有项目生效):
~/.config/mimocode/mimocode.jsonc
"plugin": [
["<YOUR_PROJECT_ROOT>/plugin/index.js", {
"csvPath": "<YOUR_PROJECT_ROOT>/data/usage.csv",
"reportPath": "<YOUR_PROJECT_ROOT>/report/report.html",
"pricesPath": "<YOUR_PROJECT_ROOT>/plugin/prices.json",
"autoReport": true
}]
]
路径按你的机器改成绝对路径(示例中的
<YOUR_PROJECT_ROOT>请替换)。
改完后 重启 MiMoCode 或新开会话,否则旧进程可能仍用内存里的旧代码,甚至把report.html刷回旧版。
5.3 正常使用
不需要额外命令:
- 正常聊天 / 跑 agent;
- 每步结束自动 append CSV;
- session 结束刷新 HTML;
- 浏览器打开
report/report.html。
5.4 校验是否生效
- CSV 是否出现
sessionID以ses_开头的新行; - HTML 是否有「时间范围」下拉框、模型分色卡片;
- 若一直为空:优先怀疑 配置改完没重启。
5.5 费用自检(可用样例)
以 pro、单位「元/百万」为例:
| 输入未命中 | 输出 | 缓存命中 | 计算 | 结果 |
|---|---|---|---|---|
| 1,000,000 | 100,000 | 2,000,000 | 1.5 + 0.3 + 0.025 |
¥1.825 |
| 500,000(flash) | 60,000 | 1,000,000 | 0.25 + 0.06 + 0.01 |
¥0.32 |
未配置价目的模型:cost_cny = 0,但 token 仍入账。
六、跨平台:为什么第三方 API 也能统计
因为插件读的是 MiMoCode 运行时已解析好的 tokens,不是某个平台的账单 API:
任意 OpenAI 兼容网关
│
▼
返回 usage(input/output/cache)
│
▼
AI SDK / 运行时写入 assistant.tokens
│
▼
插件 session.userQuery.post
│
├─► 本地价目 → cost_cny
└─► CSV + HTML
只要满足:
- 上游(或运行时归一化后)能给出 input / output / cache;
- 你在
prices.json里为 该 modelID 配了价(或接受费用为 0)。
就能统计。换 provider 时通常只需确认:
modelID字符串与价目表 key 一致(或你改表);- 响应确实带 usage(极少数流式接口不回 usage 会记不到——这在代理方案里同样躲不掉)。
七、开发中踩过的坑(博客里值得写)
-
重复记账
session.userQuery.post与session.post都写 CSV → 翻倍。
解法:逐步钩子写数据,结束钩子只出报表。 -
扁平行 vs 嵌套 tokens
CSV 行是cache_read,计费函数若只读tokens.cache.read会恒为 0。
解法:cache?.read ?? cache_read双兼容。 -
旧进程覆盖新报表
改完index.js但未重启,旧模块继续writeFile(report),表现为「下拉框总是消失」。
解法:改插件后必须重启/新开会话;排查时看report.html是否还有rangeSelect。 -
虚拟测试数据污染
开发时写入的sessionID=s样例会让金额虚高。
解法:只保留sessionID以ses_开头的真实行;测完删掉测试数据与备份。 -
币种与「每百万」
口径不写清楚会差 6 个数量级或币种错位。价目文件显式写unit: per_1m_tokens、currency: CNY。 -
分时段模型不要硬编码单价
deepseek 等按时段变价 →fallback: null,宁可费用 0,不要错账。
八、安全与隐私注意
- CSV/HTML 含你的 使用习惯、模型偏好、会话 ID,默认当本地隐私文件,不要随手公网分享;
- 配置里的 API Key 不要贴进博客/截图;
- 插件只读会话轨迹并写本地文件,不对外上传(除非你自己加)。
九、效果截图应拍什么
写博客配图时建议按这个顺序截:
- 全局配置里的
plugin注册片段(打码 key); prices.json价目表;- 报表顶部:时间范围下拉框 + 图例;
- 今日总卡片 + 分模型卡片(flash / pro 不同颜色左边条);
- Token 堆叠柱状图;
- 费用 分色折线;
- 明细表含 模型列;
- (可选)Excel 打开的
usage.csv。
十、总结
| 问题 | 答案 |
|---|---|
| 小米订阅外的第三方 API 怎么统计? | 本地插件读运行时 tokens,与平台账单解耦 |
| 能不能记缓存? | 能:cache.read / cache.write,并算命中率 |
| 能不能配价格看花费? | 能:prices.json 按模型配「元/百万」 |
| 能不能按天出图? | 能:默认今天,下拉 7/30 天 |
| Token 和费用要不要分模型? | 要:堆叠柱 + 分色折线 + 模型卡片 |
| 插件作用域? | 写在全局 mimocode.jsonc → 所有项目生效 |
一句话:
在 Xiaomi Agent 上统计第三方 token,正确层级不是「求平台多发一条通知」,而是 在 Agent 出口挂一个可插拔的记账插件——CSV 落真实数据,价目表算人民币,HTML 按模型和时间范围给你看结构。
如果你也在用多 provider 混跑,这套思路可以原样复用:只改 prices.json 和路径,就能覆盖你自己的网关组合。
附录 A:插件职责一览
tokenUsagePlugin(options)
├─ options.csvPath / reportPath / pricesPath / autoReport
├─ loadPrices()
├─ priceFor(modelID)
├─ calcCostCny(tokens, price)
├─ aggregateByDayModel(rows)
├─ writeReport() # 生成带下拉框的单文件 HTML
├─ session.userQuery.post # 提取 trajectory → append CSV → 可选刷报告
└─ session.post # 仅刷新报告
附录 B:价目与配置文件清单
- 插件价目:
plugin/prices.json - 系统模型
cost:~/.config/mimocode/mimocode.jsonc - 插件注册:同文件
plugin数组 - 数据:
data/usage.csv - 报表:
report/report.html
附录 C:常见问题 FAQ
Q:换了 OpenRouter / 自建网关,要改插件吗?
A:一般不用改代码;确认 modelID 并在 prices.json 补价即可。
Q:为什么有的行费用是 0?
A:该 modelID 未配置价目,或 token 全 0 被过滤前的历史行。
Q:报表和 CSV 对不上?
A:先看是否旧进程刚覆盖 HTML;强制用当前 index.js 再生成一次。
Q:可以按 session 导出吗?
A:CSV 已有 sessionID 列,Excel 筛选即可;也可自行加导出按钮。
附录 D:让 Xiaomi Agent 从零生成这个插件(完整 Prompt)
如果你读完文章想自己落地,不必手写全部代码。把下面整段 Prompt 粘贴给 Xiaomi Agent(MiMoCode),按提示改路径和价目即可。
使用前请先改三处占位符:
1)项目根目录;2)全局配置里要写入的插件绝对路径;3)你的模型与官方单价(元/百万 tokens)。
复制以下 Prompt
请在指定项目目录从零实现一个「MiMoCode / Xiaomi Agent 本地 Token 用量统计插件」,并完成注册与自检。请严格按下列规格实现,不要擅自简化。
【目标】
无论对接小米订阅还是任意 OpenAI 兼容第三方 API,都在 Agent 本地统计 token、缓存命中与预估费用,并生成可按时间范围筛选、按模型分色的 HTML 日报。
【项目路径】
- 项目根目录:`<YOUR_PROJECT_ROOT>`(例如 F:/GitHub/xxx)
- 必须创建:
- `<ROOT>/plugin/index.js`
- `<ROOT>/plugin/prices.json`
- `<ROOT>/data/usage.csv`(可先只有表头)
- `<ROOT>/report/report.html`(由插件生成)
- `<ROOT>/README.md`(简要使用说明)
- 插件将注册到全局配置 `~/.config/mimocode/mimocode.jsonc` 的 `plugin` 数组,路径使用绝对路径指向上面的 `plugin/index.js`。
【价目表 prices.json】
- currency: "CNY"
- unit: "per_1m_tokens" // 元 / 百万 tokens,必须写死这个单位
- 按下面表格写入 models(仅这些模型计费;其它模型 fallback: null,即 token 照记、费用记 0):
- mimo-v2.6-pro: input_miss=1.5, output=3, cache_read=0.0125, cache_write=0, reasoning_as_output=true
- mimo-v2.6-flash: input_miss=0.5, output=1, cache_read=0.01, cache_write=0, reasoning_as_output=true
- 分时段计价模型(如 deepseek-flash)不要写死单价,保持 fallback 为 null。
- 可选:在 mimocode.jsonc 对应模型上写入 cost 字段(同一套每百万数值),但报表与 CSV 的人民币费用必须以 prices.json 为准,不要把系统 cost(可能是 USD 口径)和 cost_cny 混加。
【插件运行时行为】
1. 导出 ESM default 插件函数:`(input, options) => Promise<Hooks>`。
2. options 支持:csvPath、reportPath、pricesPath、autoReport(默认 true);缺省时用项目内相对路径。
3. 必须实现钩子:
- `session.userQuery.post`:从 `input.trajectory` 提取 assistant 消息的 tokens,**唯一**负责 append CSV;写入成功后若 autoReport 则刷新 HTML。
- `session.post`:**禁止再写 CSV**(全量 trajectory 会与逐步钩子重复记账);只刷新 HTML 报表。
- 可选 `config`:重载 prices.json。
4. 从 trajectory 提取字段:
- sessionID、agentID、step、providerID、modelID
- tokens.input / output / reasoning / tokens.cache.read / tokens.cache.write / total
- 费用用本地价目计算,写入 cost_cny;不要依赖系统 cost 币种。
5. 费用公式(必须实现):
cost_cny = ( input*input_miss + (output+reasoning)*output_price + cache.read*cache_read + cache.write*cache_write ) / 1e6
- 若 reasoning_as_output===false,则输出侧只用 output。
- 找不到价目(fallback null)则 cost_cny=0,但 token 仍写入。
6. 计费函数必须同时兼容两种缓存字段:
- trajectory 嵌套:tokens.cache.read / tokens.cache.write
- CSV 扁平行:cache_read / cache_write
(历史坑:只读 tokens.cache.read 会导致缓存价永远算不进费用。)
【CSV 规格】
- 表头固定:
date,time,sessionID,agentID,step,providerID,modelID,input,output,reasoning,cache_read,cache_write,total,cost_cny
- 每次 append 一行;文件不存在则先写表头。
- 读取生成报表时过滤脏行:date 必须匹配 YYYY-MM-DD;modelID 非空;total>0。
- 不要写入任何虚构/测试 session 数据;真实 sessionID 形如 ses_ 开头。若开发时需要造数,测试结束后必须删除测试行,只保留真实数据。
【HTML 日报表规格】
- 单文件静态 HTML,无外部 CDN/网络依赖;数据在生成时嵌入 JS(如 ALL_ROWS、COLORS、TODAY)。
- 顶部必须有 **下拉框**(不是分段按钮)选择时间范围:
- 默认选中「今天」(value=1)
- 可选「近 7 天」(7)、「近 30 天」(30)
- 使用 <select id="rangeSelect">,change 时调用 render(),不要用一排 button。
- 图例:每个模型固定颜色(按 modelID 排序后循环调色板,保证同模型同色)。
- 卡片:
- 总览:Token 合计、费用合计、日均 Token、日均费用、调用次数、模型数
- 每个模型一张卡:Token、占比%、费用、调用数;左边框用该模型颜色
- 图表(均按当前时间范围过滤后绘制):
1. Token:按天 **堆叠柱状图**,不同模型不同颜色(title 显示 模型: 数值)
2. 费用:按天 **多折线**,每模型一条线、颜色与图例一致
3. 缓存命中率:合计 hitRate = cache_read/(input+cache_read),单线即可
- 明细表列:日期、模型(带色点)、调用、输入、输出、推理、缓存读、缓存写、合计、命中率、费用
- 聚合粒度:date × modelID(不要只按天合并掉模型)
- 空范围显示「该时间范围内暂无数据」
【注册】
- 编辑 `~/.config/mimocode/mimocode.jsonc`:
- 追加/合并 `plugin` 数组:`["<ABS_PATH>/plugin/index.js", { csvPath, reportPath, pricesPath, autoReport:true }]`
- 不要覆盖已有 provider/mcp/apiKey 等无关字段;JSONC 注释尽量保留。
- 为 mimo-v2.6-pro / mimo-v2.6-flash 写入 cost(若不存在则只加 cost,不要乱编 limit/modalities)。
- 明确告知用户:改配置后必须 **重启 MiMoCode 或新开会话** 才会加载新插件。
【自检(必须做)】
1. 用 node 动态 import 插件,确认 default 是 function,能拿到 session.userQuery.post / session.post。
2. 用临时 trajectory 模拟一次调用写入 CSV:
- pro: input=1e6, output=1e5, cache.read=2e6 → cost_cny 必须为 1.825
- flash: input=5e5, output=5e4, reasoning=1e4, cache.read=1e6 → cost_cny 必须为 0.32
- 未配置价目模型 → 0
3. 生成 report.html 后断言包含:
- id="rangeSelect"、value="1" selected、value="7"、value="30"
- 不存在旧版分段按钮 data-range=
- 含 ALL_ROWS、modelCards、renderStackedBar
4. 自检用的虚拟 CSV 行 **全部删除**,只保留真实 ses_ 数据(若尚无真实数据则留表头)。
5. 删除临时测试文件/备份,避免残留假数据。
【明确禁止的坑(历史踩坑,务必避免)】
1. 禁止在 session.post 再次 append CSV(重复记账)。
2. 禁止计费只读 tokens.cache.read 而忽略 CSV 扁平 cache_read(缓存价丢失)。
3. 禁止改完插件后假设已生效:必须提示重启;生成后检查 HTML 是否被旧进程覆盖(旧版特征:出现「共 N 天」静态 meta、没有 rangeSelect)。
4. 禁止用分段按钮代替时间下拉框;禁止默认展示全部历史而非「今天」。
5. 禁止价目单位含糊:必须是 CNY + per_1m_tokens;禁止把未配置模型随便填一个假单价。
6. 禁止把 API Key 写进 HTML/README/日志;不要回显 mimocode.jsonc 里的 apiKey。
7. 禁止留下 sessionID=s / test-session 等测试数据;测完只留真实数据。
8. 禁止未确认就 npm/pip 全局安装依赖;本插件应仅用 Node 内置 fs/path/url。
9. 禁止整文件盲目覆盖用户已有 mimocode.jsonc;做最小编辑。
10. 禁止图表只按天汇总而不区分模型(必须 date×model 堆叠/分线)。
【交付物】
- 上述目录中的完整可运行文件
- README:注册片段、价目表、费用公式、重启说明、报表打开路径
- 自检结果摘要(费用数值、HTML 关键标记、CSV 是否仅真实数据)
请先读当前目录与全局配置,再实现;实现完成后按【自检】逐项汇报结果。若路径/价目与占位符不一致,先向我确认再写入全局配置。
使用这段 Prompt 时的建议
- 先建空目录再粘贴 Prompt,避免 Agent 扫到无关大仓库。
- 价目若与示例不同,只改 Prompt 里的表格,不要让 Agent「自行上网猜价」。
- 实现完成后务必 重启会话,再打开
report/report.html看下拉框与分色图。 - 若报表突然变回无下拉框的旧样式,按正文「旧进程覆盖」一节排查,而不是先改业务逻辑。
- 开发阶段允许 Agent 造临时数据,但 Prompt 已要求 测完删除;合并前再人工确认 CSV 只有
ses_真实行。
本文对应实现为本地 MiMoCode 统计插件实践,路径与价格表请按你的环境替换。