Files
writing-agent/docs/briefs/token-cost-report-2026-10-06.md
T
chenmoranzhiandCursor 7185f25dec 补上费用核对与缓存审计材料,方便后续按结论继续压创作成本。
交接说明自包含,不依赖聊天记录也能接着做优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 23:49:51 +08:00

220 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 创作对话 Token 花销报告与优化建议
> 状态:体验核对结论。数据来自本机 `~/.writing-agent/stats`(2026-10-02~10-06),对照 `docs/briefs/creation-round-cost.md` 旧基线与当前 `src/dictate/round.ts` 实现。
> 记录:2026-10-06。
> 诉求:对话不要继续走「自由 Agent 工具循环」,花销过大。
## 1. 结论(先看这个)
1. **主战场仍是创作落盘,不是游玩。** 本窗口 `dictate_agent` 约占总 prompt 的 **99%+**;游玩 Worker 几乎可忽略。
2. **已经不是旧版「读甲写甲再读乙」自由 Agent**,而是 `select → 程序注入 → fill 工具循环(可 retry)→ 偶发 lean reply`。但 **fill 仍是 agent 形态**(`toolChoice: required` + 多工具 + 最多 4 次重试),这是当前贵的原因。
3. **一句用户话平均约 3.3 次模型请求**,中位 3、P90 5、最多 6。其中 **60% 的轮次至少 1 次 fill-retry**,约 **29% 要 2 次及以上重试**。
4. **相对花销(未缓存=1、缓存=0.25、输出=4)**:fill **53%**、fill-retry **28%**、select **15%**、reply **4%**。去 Agent 的收益应打在 **fill 一次写完、消灭重试、砍历史**,而不是再抠游玩。
5. **首填缓存几乎失效**:fill 首次命中率约 **1%**,retry 才爬到约 **70%**。大前缀每一轮至少全价付一遍。
---
## 2. 本窗口花销账
统计范围:本地 jsonl,2026-10-02、10-03、10-04、10-06(10-05 无落盘)。共 **298** 条用量记录。
### 2.1 按 caller
| 路径 | 请求 | prompt | 其中缓存 | 命中率 | completion |
|---|---:|---:|---:|---:|---:|
| `dictate_agent` | 279 | **636.7 万** | 202.6 万 | 31.8% | 57.9 万 |
| `worker:*` | 13 | 2.5 万 | ~0.1 万 | — | 0.3 万 |
| `main_agent` | 6 | 0.06 万 | — | — | — |
创作 : 游玩(prompt)≈ **250 : 1**(本窗口游玩样本很少;方向与旧基线「创作远大于游玩」一致)。
### 2.2 按日(仅创作)
| 日 | 请求 | prompt(百万) | 缓存(百万) | completion(千) |
|---|---:|---:|---:|---:|
| 10-02 | 37 | 0.30 | 0.04 | 117 |
| 10-03 | 86 | 2.25 | 0.76 | 187 |
| 10-04 | 23 | 0.31 | 0.07 | 45 |
| 10-06 | 133 | 3.50 | 1.15 | 229 |
### 2.3 创作按 step(关键)
| step | 请求 | prompt 合计 | **单次均 prompt** | 缓存命中 | completion |
|---|---:|---:|---:|---:|---:|
| **fill-retry** | 89 | 284.1 万 | **31.9k** | **70.1%** | 16.4 万 |
| **fill** | 82 | 242.5 万 | **29.6k** | **1.1%** | 33.7 万 |
| select | 85 | 89.6 万 | 10.5k | 0.9% | 5.0 万 |
| reply | 23 | 20.5 万 | 8.9k | 0% | 2.7 万 |
要点:
- retry 请求数 **多于** 首次 fill(89 > 82)→ 重试是常态,不是边角。
- 首次 fill 几乎不吃缓存;钱主要花在「每一轮第一枪大前缀」。
- select 已不带产物正文(实现正确),但仍带全量更早对话,均约 10k prompt。
- lean reply 只覆盖约 **28%** 有 fill 的轮次;其余多在 fill 同请求里 `reply_user`(设计意图),或失败后走 prose 回退。
### 2.4 按用户轮次(`turnId`)
| 指标 | 值 |
|---|---:|
| 轮次数 | 85 |
| 每轮模型调用 avg / p50 / p90 / max | **3.28 / 3 / 5 / 6** |
| 每轮 prompt 合计 avg / p50 / p90 / max | **7.5 万 / 6.0 万 / 14.9 万 / 25.9 万** |
| 无 retry | 34(40%) |
| ≥1 次 retry | 51(60%) |
| ≥2 次 retry | 25(29%) |
### 2.5 上下文块(`segmentChars`,字符,非 token)
| step | n | rules | history | **products** | injected | tail | 均 prompt tok |
|---|---:|---:|---:|---:|---:|---:|---:|
| fill | 82 | ~2.1k | ~9.3k | **~23.0k** | ~5.6k | ~5.0k | 29.6k |
| fill-retry | 89 | ~2.1k | ~8.7k | **~21.9k** | ~6.3k | ~4.9k | 31.9k |
| select | 85 | ~0.3k | ~9.0k | **0** | 0 | ~4.9k | 10.5k |
| reply | 23 | ~0.5k | 0 | ~6.7k(仅本轮) | 0 | ~6.3k | 8.9k |
近期 fill 样本(10-06):products 已到 **4.3~5.0 万字**,history **2.6~3.2 万字**,单次 prompt **5.5~6.9 万 token**,且 `cached=0`。
### 2.6 模型
| 模型 | 请求 | prompt(百万) | 命中率 |
|---|---:|---:|---:|
| `google/gemini-3.8-flash` | 171 | 4.13 | 29.6% |
| `google/gemini-3.7-flash` | 102 | 2.17 | 36.4% |
| `openai/gpt-6.1-sol` | 6 | 0.07 | 16.1% |
金额粗算(按 Flash 量级:未缓存输入 $0.15/M、缓存 $0.0375/M、输出 $0.60/M,**仅作数量级**):本窗口创作约 **$1.1**,约 **$0.013/用户轮**。体感「贵」主要来自 **轮次×大前缀×重试**,以及若换成更贵模型会线性放大;不是输出写爆。
### 2.7 与旧基线对照
旧账(`creation-round-cost.md`,约 9/20 起、创作 416 次 / prompt 1198 万):自由工具循环、先 `read_module` 再 `insert`。
| | 旧自由 Agent | 本窗口(已 planned) |
|---|---|---|
| 形状 | 读/写交织、工具定义常驻 | select + 注入 + fill 循环 |
| 同轮请求倍数 | 常「每节点×2」再加回复 | 中位 **3**(已封顶感更好) |
| 产物进选题 | 会进(旧整包) | **已去掉** |
| 填写大头 | 全产物 + 全对话 | **仍是全产物 + 近乎全对话** |
| 首填缓存 | 依赖粘滞 | **仍≈0**(select/fill 前缀形状不同) |
**程序已省下「读模块那一轮」和选题侧产物**;**未省下「大前缀×(1+重试)」**。用户觉得「还是 Agent、还是贵」,与数据一致。
---
## 3. 当前链路(体验视角)
```text
用户一句
→ select(小上下文,强制 select_nodes) ≈1 次,~10k prompt
→ 程序注入本轮节点写法 / 库正文(确定性,不计模型)
→ fill(大上下文,强制工具:insert/delete/变量/映射 + reply_user)
└─ 未过则 append 工具结果,再 fill-retry(最多约 4)
→ 若填完却未 reply_user → lean reply(只带本轮产物) ≈9k
```
与「不要 Agent」的落差:
| 已做到 | 仍像 Agent |
|---|---|
| 选题与填写拆开 | fill 仍多工具 + required |
| 规格由程序注入,不再 `read_module` | 校验失败靠模型再跑一整轮大前缀 |
| 回复可瘦身 | 变量/映射/删除仍走 tool loop |
| `session_id` 粘滞已接 | select→fill 系统提示与正文布局不同,首填缓存打不穿 |
---
## 4. 优化建议(按性价比)
### P0 — 对话填写去 Agent(最贴合诉求)
**目标形状:一句用户话 = 2 次模型请求(澄清时 1 次),禁止 fill 工具循环。**
1. **select**:保持现状(或改为纯 JSON structured output,去掉 tools 税)。只出 `module_id[]` / 澄清题。
2. **fill**:改为 **一次 structured complete**:`{ products: [...], reply, declares?, deletes? }`,程序本地切开落盘与校验。
3. **失败只重写失败 tag**:上下文 = 规则摘要 + 该 tag 注入规格 + 相关产物白名单 + 错误信息;**不要**再塞全黑板 + 全历史重跑。
4. **变量/映射**:优先由程序从 structured 字段落盘,或极窄工具面;不要与长正文 insert 共用大循环。
预期(相对本窗口相对花销):
- 去掉 fill-retry 常态 → 相对花销约 **-25%~35%**
- 首填仍大,但每轮从 3.3 枪降到 ~2 枪 → 再 **-20%~30%**(视澄清/失败率)
- 合计数量级:**创作输入花销降到现在的一半左右**(前提:一次交多份成功率够高)
与相位机约束不冲突:仍在同一次 `running` 内、用户不手选节点、方案不是执行序。
### P1 — 砍 history(实现便宜、收益稳)
现状:select/fill 的「更早对话」均值约 9k 字,会话后期到 **3 万字+**,占填写真份额。
建议:
- select:方案进度 + **最近 2~4 轮** + 末句(旧 brief 已论证「只留末句会误选」,不能只留一句)。
- fill:同样只留最近对话;产物冲突靠产物正文,不靠早期闲聊。
- 清空对话能力保持;长记忆另走总结产物,不靠无限 chat。
预期:fill/select 各再 **-10%~20%** prompt;会话越长收益越大。
### P2 — 稳首填缓存前缀
`session_id` 只保证同上游;**select 与 fill 的 system + 前部布局不一致 → 首填命中≈0**。
建议(不改业务语义):
- 抽出 **跨 select/fill 不变的规则前缀**(身份、写入规则骨架),两边共用同一字节前缀。
- 易变块(方案进度、checklist、注入、末句)固定在末尾(现已部分如此)。
- 工具定义:若 fill 改 structured,则工具 JSON 不再每轮跟着大请求。
预期:在粘滞有效时,同一会话后续轮次的「规则+稳定壳」可从全价变折扣;**不降低发送体积,但降低未缓存账单**。对 OpenRouter+Gemini 块粒度(约 4k)敏感,前缀差几百 token 可能整块失效。
### P3 — 产物相关性(大头,风险高)
products 占 fill 字符大头(样本约一半以上)。旧 brief 已说明:误裁会导致冲突设定。
建议分两档:
1. **安全档**:按本轮 `module_id` 维护「必带 tag」白名单(依赖边写在 catalog);其余只带 **标题+一行摘要+hash**。
2. **激进档**:检索/摘要。必须配套:写入前冲突扫描、失败则自动补全文重试(否则质量回退会被当成「省钱事故」)。
预期:做好可再砍 fill **30%~50%**;做不好会表现为反复 rewrite,把 P0 收益吃掉。
### P4 — 游玩与总管
本窗口不必优先。游玩保持「一次生成、非创作 tool loop」。OpenRouter 上游玩缓存曾为 0,靠 `session_id` 已具备前提;前缀顺序现已正确。总管 `main_agent` 可忽略。
### 明确不做 / 慎做
| 项 | 原因 |
|---|---|
| 改回用户手选节点以省一轮 | 与产品「自然语言推进」冲突 |
| 相位机按固定工序跑完全部节点 | 与「方案不是执行序」冲突 |
| 选题带全产物 | 已证明浪费;会毁掉 select 的小请求优势 |
| 用 `provider.order` 代替 `session_id` | 会关掉粘滞 |
| 只砍输出 | 输出约是 prompt 的 3%~9%,不是主账 |
---
## 5. 建议落地顺序(可验收)
| 步 | 改动 | 验收指标(同书同对话长度) |
|---|---|---|
| A | fill 改为一次 structured,去掉 write tool loop | 每轮模型调用 p50→**2**;fill-retry 调用占比 **&lt;10%** |
| B | select/fill history 窗口 | fill `segmentChars.history` 中位下降 **≥50%** |
| C | 共享稳定 system 前缀 | 同会话第 2 轮起 fill 首次 `cached/prompt` **≥40%**(Gemini/OpenRouter) |
| D | 产物白名单/摘要 | fill 均 prompt **再降 ≥25%**,且 insert 拒绝率不升高 |
体验验收(主观):
- 用户感知仍是「说一句 → 落几块 → 一段综合回复」,无「工具来回思考很久」的 Agent 感。
- 澄清轮仍可 1 次请求结束(仅选题提问)。
- 失败时只看到「某几块重写」,而不是整轮长时间空转。
---
## 6. 和「不要 Agent」的直接答复
- **创作对话:应该停止以 Agent tool loop 作为填写主路径。** 选题可以极窄工具或 JSON;填写应程序注入规格 + 一次结构化交卷 + 本地校验。
- **游玩:已经不是创作那种 Agent**;继续单次 Worker 生成即可。
- **总管 Agent:不是当前账单**;可维持。
若只做一件事:做 **P0(fill 去循环)**。它同时回应「体感像 Agent」和「token 太大」两件事。