ARTICLE · 1168072
用 sqlite 查 OpenClaw 的 token 账单-心跳空转吃掉一半配额 — 成本审计实战
现象:刚充的月度配额,两天就空了
OpenClaw 突然报错:
bailian-cli (qwen3.8-max) returned a billing error —
your API key has run out of credits直接拿配置里的 key 打一发真实请求,确认不是误报:
curl -s -X POST "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"model":"qwen3.8-max","messages":[{"role":"user","content":"hi"}]}'返回 insufficient_quota,附带一行关键信息:1-month quota exhausted, reset at 11-10 16:00 UTC。刚买两天的月度包用光了。这不是欠费,充值也解决不了——是消耗速度出了问题。
第一步:别猜,直接对数据库算账
OpenClaw 的每一轮模型调用都把 usage 写进 agent 的 sqlite 转录表(~/.openclaw/agents/main/agent/openclaw-agent.sqlite 的 transcript_events),这比任何日志都精确。用 node 内置的 sqlite 驱动聚合:
const {DatabaseSync} = require("node:sqlite");
const db = new DatabaseSync(SQLITE_PATH, {readOnly: true});
const rows = db.prepare(
"SELECT session_id, event_json, event_zstd FROM transcript_events"
).all();两个坑:
- 大部分行的
event_json是 null,正文在 event_zstd列里,要用zlib.zstdDecompressSync(Buffer.from(col)).toString()解压。只读event_json会漏掉三分之二的行。
usage 字段名是 input/output/cacheRead/cacheWrite,不是 OpenAI 风格的prompt_tokens。取错字段会得到全零的假账。
按「触发来源」分桶后,结果一目了然:
| 来源 | 轮数 | token | 占比 | 模型输出 |
|---|---|---|---|---|
| 心跳轮询 | 111 | 15.44M | 53.1% | 0.04M |
| 真实对话 | 169 | 13.63M | 46.9% | 0.19M |
心跳烧掉 1544 万 token,产出是每轮一句 NO_REPLY——99.7% 的花费买了空气。
第二步:找到放大器——上下文只涨不跌
单看一轮心跳,成本公式是:每轮全量重送上下文。抓出来:
user: "[OpenClaw heartbeat poll]"
assistant: exec → curl 检查站点
toolResult: "site:200"
assistant: "NO_REPLY"四步的小活,但主会话上下文已经堆到 161K,每次全价重放。48 轮/天 × 161K ≈ 770 万/天,和实测完全对得上。上下文分布也印证:259 轮里 159 轮在 120K 以上。
结论:心跳频率是引信,不压缩的上下文才是炸药。
第三步:三刀止血,全部热生效
# 1) 心跳降频 + 换便宜模型(它只会 curl,不需要旗舰模型)
openclaw config patch --stdin <<JSON
{"agents":{"defaults":{"heartbeat":{"every":"2h","model":"dashscope/qwen-turbo"}}}}
JSON
# 2) 压缩也要指定模型——否则摘要调用会撞上已耗尽的主模型
openclaw config patch --stdin <<JSON
{"agents":{"defaults":{"compaction":{"model":"dashscope/qwen-turbo"}}}}
JSONconfig patch 带 schema 校验,先 --dry-run 再正式打。改完日志立刻出现 config hot reload applied——不用重启 gateway,NRestarts 保持 0,微信通道零感知。
第四步:压缩主会话,以及一个假失败
CLI 没有 compact 子命令,要走 RPC:
openclaw gateway call sessions.compact \
--timeout 180000 --params '{"key":"agent:main:main"}'第一次只给了默认 10 秒超时,客户端报 gateway timeout,看起来失败了——其实压缩在服务端已经成功。第二次重试返回 Already compacted,查库确认转录链末尾确实多了一条 type:"compaction" 记录。教训:对可能慢的 RPC,写操作超时后先查状态再重试,别盲目重打。
压缩效果:上下文 161K → 39.7K,降 75%。剩下的约 35K 是系统提示 + AGENTS.md 等固定注入,属于常态开销,压不掉。摘要本身保留了决策、待办、精确标识符、最近轮次等 8 个小节,信息没丢。
第五步:切回可用主模型并验证
止血只保护了心跳和压缩,对话仍会撞墙,所以把 primary 切到还剩配额的 provider:
openclaw config patch --stdin <<JSON
{"agents":{"defaults":{"model":{"primary":"dashscope/qwen-plus"}}}}
JSON验证不能只看配置文件,跑一轮真实调用(不带 --deliver,不会发到聊天频道):
openclaw agent --json -m "只回复两个字:成功"看返回里的 executionTrace.winnerProvider: "dashscope"、fallbackUsed: false,这才叫切过去了。顺带一个坑:agent --local 在 gateway 运行时会被拒绝,提示先 gateway stop——测模型走网关就行,别真停。
实战踩坑提醒
- 心跳是成本黑洞高危区
:它用主会话、全量上下文、按频率雷打不动地跑。上线自动心跳前先算一笔账:轮数 × 上下文大小 × 24/间隔小时。
- 定时任务别用旗舰模型
:心跳这种「检查+汇报」的活,qwen-turbo 级别足够,单价差一个数量级。
compaction.model是必配项:主模型配额耗尽时,连压缩都会失败,陷入死锁——上下文越滚越大、每次调用都报错。提前指一个便宜的备用模型。
- 慢 RPC 超时 ≠ 失败
: gateway call默认 10 秒,压缩/大查询要给--timeout,超时后先读状态验证。
- 审计数据就在 sqlite 里
: transcript_events每轮都带 usage,记得event_zstd要解压、字段名是cacheRead/cacheWrite。
- 改配置优先
config patch+--dry-run:手改 JSON 容易写出非法结构,patch 有 schema 兜底,而且热加载。
小结
这次审计的完整账单:两天 2900 万 token,心跳空转占 53%。三刀下去——心跳 30m→2h 并降级模型、压缩上下文 161K→39K、主模型切到可用 provider——预计心跳日耗从约 770 万降到不足 50 万,降幅超过 90%,而且全程零重启。
比「省了多少」更重要的是方法:先测量,再优化。没有那 20 分钟的数据库聚合,大概率会直接充值了事,然后下个月再爆一次。
关注公众号,获取更多 OpenClaw 实操技巧















































































