补上费用核对与缓存审计材料,方便后续按结论继续压创作成本。

交接说明自包含,不依赖聊天记录也能接着做优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 23:49:51 +08:00
co-authored by Cursor
parent 66ed1eaca8
commit 7185f25dec
3 changed files with 479 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
# 创作对话 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 太大」两件事。