From 7185f25dec8afd34d3e170e95913d63bf1fcd666 Mon Sep 17 00:00:00 2001 From: moranzhi Date: Wed, 7 Oct 2026 23:49:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E4=B8=8A=E8=B4=B9=E7=94=A8=E6=A0=B8?= =?UTF-8?q?=E5=AF=B9=E4=B8=8E=E7=BC=93=E5=AD=98=E5=AE=A1=E8=AE=A1=E6=9D=90?= =?UTF-8?q?=E6=96=99=EF=BC=8C=E6=96=B9=E4=BE=BF=E5=90=8E=E7=BB=AD=E6=8C=89?= =?UTF-8?q?=E7=BB=93=E8=AE=BA=E7=BB=A7=E7=BB=AD=E5=8E=8B=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E6=88=90=E6=9C=AC=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 交接说明自包含,不依赖聊天记录也能接着做优化。 Co-authored-by: Cursor --- docs/briefs/prompt-cache-audit-2026-10-07.md | 128 +++++++++++ docs/briefs/token-cost-report-2026-10-06.md | 219 +++++++++++++++++++ docs/briefs/token-optimization-handoff.txt | 132 +++++++++++ 3 files changed, 479 insertions(+) create mode 100644 docs/briefs/prompt-cache-audit-2026-10-07.md create mode 100644 docs/briefs/token-cost-report-2026-10-06.md create mode 100644 docs/briefs/token-optimization-handoff.txt diff --git a/docs/briefs/prompt-cache-audit-2026-10-07.md b/docs/briefs/prompt-cache-audit-2026-10-07.md new file mode 100644 index 0000000..3c50b12 --- /dev/null +++ b/docs/briefs/prompt-cache-audit-2026-10-07.md @@ -0,0 +1,128 @@ +# 提示缓存核对:记账是否可信、首填为何仍≈0 + +> 状态:核对结论。承接 `docs/briefs/token-cost-report-2026-10-06.md` §1.5 / §4.P2。 +> 记录:2026-10-07。 +> 疑问:若 OpenRouter 上 DeepSeek 缓存能挂住,是否说明「首填几乎不吃缓存」是本地记账错了,或粘滞/TTL 才是主因? + +## 1. 结论(先看这个) + +1. **缓存记账可信。** 我们只是把 OpenRouter 的 `usage.prompt_tokens_details.cached_tokens`(及 DeepSeek 的 `prompt_cache_hit_tokens`)原样写入 `~/.writing-agent/stats`;不是自估、不是漏字段。 +2. **「首填≈0、retry 高」是上游真值,不是漏记。** 同轮对照里,绝大多数是 fill 几乎无缓存、fill-retry 立刻上到八成上下。若解析坏了,两边应同假。 +3. **DeepSeek 探测成功,不能推翻 Gemini 首填账。** 花销报告窗口主模型是 `google/gemini-3.*-flash`;DeepSeek 只证明「同 baseUrl + 同解析链路」在另一模型上能读到非零缓存。 +4. **粘滞/`session_id` 不是当前主账缺口。** 同会话内 fill→retry 已能吃到缓存,说明粘到了同一上游且前缀可复用;贵在 **每一轮第一枪大前缀仍全价**,以及 **重试常态**。 +5. **优化优先级不变:** 仍以 P0(fill 去工具循环、消灭 retry)为主;P2(跨 select/fill 稳定前缀)对 Gemini 有意义,但不是「先修记账」或「再抠 TTL」。 + +--- + +## 2. 记账链路(我们记了什么) + +| 层 | 行为 | +|---|---| +| OpenRouter 响应 | `prompt_tokens` / `prompt_tokens_details.cached_tokens` / 可选 `cache_write_tokens` | +| `parseUsage`(`src/llm/client.ts`) | 读 `cached_tokens`;DeepSeek 另认 `prompt_cache_hit_tokens` | +| stats jsonl | `cachedTokens` / `cacheWriteTokens` 等落盘 | +| 报告命中率 | `cached / prompt`(按请求平均或合计) | + +单元测试覆盖 OpenAI details、DeepSeek hit/miss、cache write。探测脚本与生产走同一 `complete` → `parseUsage`。 + +**原始对照(2026-10-07,Gemini 探测):** 直接 `fetch` OpenRouter,两次固定大前缀请求的 `usage` 均为: + +```json +"prompt_tokens_details": { + "cached_tokens": 0, + "cache_write_tokens": 0 +} +``` + +落盘若记成 0,与上游一致。 + +--- + +## 3. 生产窗口再切一刀(缓存行为) + +范围:`~/.writing-agent/stats` 中 2026-10-03、10-04、10-06、10-07 的 `dictate_agent`(与花销报告同一量级窗口,略含 10-07)。 + +| step | 请求 | 有缓存占比 | 均命中率 | 中位命中 | +|---|---:|---:|---:|---:| +| select | 79 | 1.3% | ~0.8% | 0% | +| **fill** | 79 | **1.3%** | **~0.5%** | **0%** | +| **fill-retry** | 83 | **84.3%** | **~71.9%** | **~85%** | +| reply | 30 | 0% | 0% | 0% | + +同轮 `fill` + 至少一次 `fill-retry` 的配对约 **45**: + +| 模式 | 对数 | +|---|---:| +| 首填≈0 且 retry 命中 >40% | **34** | +| 两者都≈0 | 11 | +| 首填命中 >20% | **0** | + +样本:同轮 `fill` `cached=0`、随后 `fill-retry` `prompt≈90k / cached≈82k`(命中约 91%)。这只能解释为:**上游在 retry 上报了高缓存,我们照记。** + +模型侧:窗口几乎全是 `google/gemini-3.7-flash` / `gemini-3.8-flash`;与花销报告一致。 + +--- + +## 4. 探测:DeepSeek vs Gemini(同 OpenRouter) + +核对方式:固定大前缀连打 OpenRouter,读 `usage.prompt_tokens_details.cached_tokens`(脚本已删,结论仍有效)。 + +### 4.1 `deepseek-v4-pro` + +| 调用 | prompt | cached | 命中 | +|---|---:|---:|---:| +| A#1 | 10602 | 7650 | 72.2% | +| A#2~A#4 | 10602 | 9743 | 91.9% | +| 等 60s 后 B#1~B#2 | 10602 | 9743 | 91.9% | + +→ 隐式缓存可用;短间隔爬升;**约 1 分钟间隔未掉光**。说明「同 baseUrl + 粘滞 + 记账」这条链对 DeepSeek 成立。 + +### 4.2 `google/gemini-3.8-flash` + +| 调用 | prompt | cached | 命中 | +|---|---:|---:|---:| +| A#1~A#3(间隔 5s) | ~12.7k~15k | **0** | **0%** | + +原始 `usage` 同上,`cached_tokens=0` 且 `cache_write_tokens=0`。 + +→ **不是我们没记上**,是这次探测形态下上游就报 0。与生产里「仅当 fill 刚跑过、retry 几乎同前缀」才高频命中,并不矛盾:retry 复用的是**刚发过的整包 fill 前缀**;探测的「新会话 + 纯文本 system」未复现那种命中。 + +OpenRouter 文档对 Gemini 同时提到隐式缓存与 `cache_control` 显式断点;本仓库**未打** `cache_control`。生产 retry 仍能命中,说明至少部分 Gemini 路由在「极近、极同前缀」下会报 `cached_tokens>0`;**不能**因此假设跨 select→fill、或跨轮首填也会自动吃到折扣。 + +--- + +## 5. 对花销报告结论的修正与保留 + +| 命题 | 判定 | +|---|---| +| 首填缓存几乎失效 | **保留**(记账复核后仍成立) | +| 贵主要因为 fill 大前缀 ×(1+retry) | **保留** | +| 怀疑「其实有缓存但没记」 | **否定** | +| 怀疑「DeepSeek 能缓存 ⇒ 当前账单不该贵」 | **否定**(账单模型是 Gemini;且 DeepSeek≠本窗口主路径) | +| 怀疑「再抠 TTL / session 就能救首填」 | **弱化**:同会话 retry 已证明粘滞可用;缺口在 **select≠fill 前缀** 与 **每轮第一枪**,不是「完全没粘住」 | +| P2 共享稳定前缀 | **仍值得做**(对 Gemini 跨步命中),但收益次于 P0 去循环 | +| 换 DeepSeek 指望隐式缓存 | **可作实验**,质量/工具/价格需另验;不能单靠本次探测定案 | + +相对花销直觉(未缓存=1、缓存≈0.25):retry 已打折,**未打折的是几乎每次的首填大包**。消灭 retry 或把首填变小,都比「怀疑仪表」更贴账单。 + +--- + +## 6. 建议(本核对后的落地顺序) + +与 `token-cost-report-2026-10-06.md` §5 对齐,本文件只钉缓存相关验收: + +| 步 | 动作 | 验收 | +|---|---|---| +| A | **不改记账**;需要时用原始 `usage` 或 `/generation` 抽查 | 抽查与 jsonl `cachedTokens` 一致 | +| B | P0:fill 改为一次 structured,去掉写工具循环 | 每轮调用 p50→2;fill-retry 占比 <10%(主收益) | +| C | P2:select/fill 共用不变字节前缀,易变块置尾 | 同会话第 2 轮起 **fill 首次** `cached/prompt` 明显上升(Gemini 目标仍可订 ≥40%,以实测为准) | +| D | 若坚持 Gemini 显式缓存 | 单独评估 OpenRouter `cache_control`;先小流量探测,再决定是否进主路径 | +| E | 可选:用 DeepSeek 等隐式缓存友好模型做同书对照周 | 比的是 **有效单价 × 质量**,不是单看 hit% | + +**明确不做:** 为「修缓存」去动 `parseUsage` 估命中;用 `provider.order` 换 `session_id`;把探测脚本的 DeepSeek 命中率写进创作账单当成已省钱。 + +--- + +## 7. 一句话答复原疑问 + +> 缓存记录是正确的。DeepSeek 能挂缓存,只说明链路和仪表没坏;当前创作窗口里 Gemini **首填几乎不打折是上游如实返回**,retry 高命中反而证明记对了。钱仍主要花在「每轮第一枪大前缀 + 填写真的在重试」,优先去 Agent 循环,而不是先怀疑统计或死磕 TTL。 diff --git a/docs/briefs/token-cost-report-2026-10-06.md b/docs/briefs/token-cost-report-2026-10-06.md new file mode 100644 index 0000000..8f1f0d1 --- /dev/null +++ b/docs/briefs/token-cost-report-2026-10-06.md @@ -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 调用占比 **<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 太大」两件事。 diff --git a/docs/briefs/token-optimization-handoff.txt b/docs/briefs/token-optimization-handoff.txt new file mode 100644 index 0000000..b4f5444 --- /dev/null +++ b/docs/briefs/token-optimization-handoff.txt @@ -0,0 +1,132 @@ +这份文档是自包含的。接收方看不到代码仓库、聊天记录和作品正文。请只根据本文做事。 + +请你做两件事,都要写成我们可以逐条核对的文字: + +1. 列出业界在「长上下文 Agent、提示缓存、工具循环」上的常见优化方案。每条写清:做法、它节省的是请求次数、未缓存输入、缓存折扣价还是输出,以及它依赖什么前提(例如上游必须支持前缀缓存、模型必须能一次返回多个工具)。 +2. 针对下面描述的这个程序,给出你建议采用的组合方案。说明每一步落在哪一次模型请求、上下文里留哪些块、块的顺序、相位机的哪一段执行。不要假装读过仓库。标出你不确定、需要我们核对的点。 + +我们会用仓库和现有规则检查你的方案。与本文第三节冲突的做法请单独标出,不要藏进推荐里。 + + +一、这是什么程序 + +这是一个写作代理,分两段使用。 + +创作期:用户用自然语言描述想要的作品,不手动点选节点。模型决定这一轮要落实哪些「能力」,把结果写成黑板上的产物。产物是规格(舞台、规则、变量、正文如何呈现、开场),不是游玩回合的正文。聊天只做确认和追问。清空对话后产物还在,产物才是创作期的持久记忆。 + +游玩期:另一套 Worker 按已经写好的规格往下演。一次用户输入通常对应一次生成,不是创作那种多步工具循环。 + +主要渠道是 OpenRouter 上的 Gemini 与 OpenAI 兼容模型。也有直连的单一上游做对照。费用近似为: + +请求次数 ×(未缓存输入 × 全价 + 缓存输入 × 折扣价 + 输出 × 输出价) + +缓存命中不是免费的,只是更便宜。输出大约是 prompt 的 3% 到 9%,单价更高,但当前主账仍是输入。最近约三天(2026-09-28 10:35 到 2026-10-01 10:35,东八区)创作落盘 416 次请求、prompt 1199 万 token,游玩 29 次、prompt 80 万。创作大约是游玩的 14 倍。优化主战场是创作。 + + +二、会有哪些上下文 + +下面这些块都会进模型,但不是每一次请求全有。 + +创作落盘,一句用户话开始时拼一次,工具循环中途只在末尾追加: + +- 系统提示:身份、写入规则、推进原则。尚无本局方案时附整份能力短目录(何时用、何时不用、档位)。已有方案时改为方案进度:每个能力一行,已落或未落、档位、理由、缺口。不含产物正文。样本里系统提示 6695 字,其中方案进度 4392 字。 +- 一条用户消息:全部已有产物,按相对顺序,然后全量对话。样本 25 份产物共 60518 字(约 69%),全量对话 19975 字(约 23%)。没有一份产物独大,最大的正文组成 8879 字,其次变量规则 6005、实现机制 4569,其余多为两三千字。会话中段产物已经约 5.6 万字。 +- 工具定义:每次都带,约 4917 字,一轮内不变。工具有读取能力、读取库条目、准备开场、写入产物、删除、声明变量、声明映射、清空对话。 +- 预设:包在系统侧的文风或格式片段。当前选用预设启用正文约 362 字,可忽略。换长预设时它变成稳定前缀,应放在易变内容之前。 +- 工具结果:循环后半才出现。读取能力的返回体是该能力的任务、原则、产物规格、评分和库目录,一份约 5000 字。库条目正文同理,目录里只有摘要。这些全文一开始不在上下文里。 + +游玩一次生成: + +- 预设与该 Worker 的系统说明(相对稳定)。 +- 槽位人设。 +- 静态规格投影(舞台、规则、叙事、正文组成等,开局后很少改)。 +- 对话历史(每轮变长,按投影裁剪)。 +- 变量当前值与本轮用户输入(每轮都变)。 + +总管工具循环(决定何时调用哪个 Worker)另有一套 message 历史。最近三天的账几乎不在这条上,优化时不要把它和创作落盘混成同一次请求。 + +能力的完整写法存放在各自的提示文件里,不在系统目录里。不把所有能力的完整提示放进第一次请求。完整写法只在已经确定本轮要写哪几项之后进入上下文。 + + +三、相位机之内,程序可以动哪里 + +运行分两层。 + +业务阶段:创作(设计规格)然后游玩。这是作品生命周期,不是每一次模型调用。 + +运行相位:idle、running、waiting_user、done、error。相位机是纯函数:给定会话和事件,决定谁在场、哪些工具可用、何时必须等用户、产物何时算数。它不决定「下一步叫哪个能力」。下一步由 running 里的模型工具循环决定。 + +一句用户话是硬事件:相位进入 running,这一轮的模型调用次数从零计。创作落盘在这次 running 里跑工具循环,上限大约三十多步。循环开始时拼好开头,之后只追加助手工具调用和工具结果。模型不再欠请求、或写出给用户的回复之后,相位回到 waiting_user,等待下一句。 + +因此: + +- 减少「读一份、写一份、再读下一份」必须发生在同一次 running 里,也就是两次用户话之间。 +- 不能把相位机改成按固定工序自动跑完全部节点。用户不手选节点,方案也不是执行顺序。 +- Worker 执行时总管暂停。游玩的那一次生成是 Worker 自己的请求,上下文按该 Worker 的投影拼,不复用创作工具循环的消息历史。 +- 程序可以在两次模型请求之间插入确定性步骤:注入选中能力的当前提示、核对版本哈希、逐条校验、只重试失败的那一条。这些不是相位变化。 + + +四、缓存优化 + +目标是稳定前缀留在同一上游的提示缓存里。不追求两次请求的整包字节相同。Agent 每一轮消息都会变。 + +已经接上:OpenRouter 请求体带本局 session_id(最长 256 字)。直连其它渠道不带,避免未知字段被拒绝。有 session_id 后,粘滞从该会话第一次成功请求就开始,不必先看到缓存命中,也不再因为第一条用户消息被重写就换上游。不要发送 provider.order,它会关掉粘滞。 + +接上之前的测量(不要把「再做一遍粘滞」当成主体): + +- 创作工具循环间隔约 7 到 15 秒。Gemini 上同一次循环内缓存从约 3800 token 按大约 4000 一块爬到 7600、11500、15000,整段 prompt 约 50% 到 68% 命中。每个 burst 的第一下经常是 0。中途偶尔整段掉回 0,是换了上游。 +- 游玩在 OpenRouter 上 28 次、prompt 1.8 万到 3.4 万,间隔约 5 到 22 分钟,缓存为 0。直连单一上游时,间隔 8 到 30 分钟仍常见命中 80% 到 91%。有一轮从约 1.1 万掉到 6656 并停住,说明前缀中段改过,只保住开头。 +- Gemini 隐式缓存大约 4000 token 一块记账。前缀差几百 token,账单上可以差一整块。 + +session_id 只保证落到同一上游。命中到第几个 token,取决于前缀从哪一字开始和上一次不同。 + + +五、上下文编排顺序 + +原则:越少改的越靠前,每轮都变的放在最后。一处改动只作废它后面的缓存。 + +游玩已按此排列:人设与静态规格、对话历史、变量与本轮输入。排序本身在直连上游上有效;OpenRouter 上曾经是 0,是因为当时没有粘住上游,不是因为顺序反了。 + +创作当前顺序是:系统提示(含会随落盘改写的方案进度)、然后一条用户消息里先全部产物再全量对话、工具定义在消息之外、工具结果接在末尾。产物任何一份被覆盖,如果它不在整包的末尾,它后面的产物和对话都会离开缓存。工具定义一轮内稳定,适合留在前缀可缓存的区域。方案进度放在系统提示里,方案一改,系统提示之后全部重计。 + +选择与填写若拆成两次请求,稳定的能力目录应在选择请求的前部,方案进度、上一问和本轮用户输入在末尾。填写请求里,少改的产物在前,本轮新注入的节点提示和最近对话在后。节点提示每轮都会换,不能放在全部产物之前,否则一换节点,后面整包产物都按未缓存计。 + + +六、上下文发送哪些 + +用同一本书量过,不发请求。当前一次创作请求的 messages 约 88218 字,末次实际 prompt 约 46897 token(大约 2 字 1 token)。 + +- 选择若只带方案进度加用户最后一句:约 4522 字。再加助手上一问仍然很小。产物正文不要放进选择。只看用户这句原文不够,否则回答「第二个」或修改已有地图时会对错节点,已写过的能力会被再选。 +- 填写若仍带全部产物,对话只留最近约两轮(样本 1770 字):约 6.2 万字。砍掉全量对话大约只省两成。 +- 再注入一份节点提示约 5000 字。一次注入两三份,是在产物之上加一两成,而且只发送一次。 + +所以「少发对话」省得有限。「少发哪些产物」才碰到大头,但填写时不带相关产物就会和已有设定冲突。哪些产物是这份能力的材料、哪些只是其它规格,本文没有一份可执行的白名单。常见的相关性裁剪、摘要、检索如果要用,必须说明误裁后怎样发现。 + +模型在看见该能力当前版本的任务、原则、规格、评分之前,写出来的正文不算数。程序用这些段落的哈希做版本。同一轮生成里先调用读取、同时把正文交上去,不算看见过写法。不要求先读的只有 narrative、variable-context、context-order、worker-spec、refine 和标记为自动的程序步骤。库条目正文同样要先看见再写进产物,默认 1 到 2 条。 + + +七、程序上的优化,仍在同一次 running 里 + +现在的倍数来自工具循环,不是来自相位来回切换。 + +同一项内容能力至少两次模型请求:第一次凭短说明选定并读取,第二次看见写法之后写入。提示要求一轮通常写这句已经说清的 2 到 3 个能力。模型常一次只返回一个工具,于是变成读甲、写甲、读乙、写乙,最后再单独回用户一句。驱动程序其实会在同一步执行该次响应里的全部工具,日志里偶发一次写两份。 + +讨论过、尚未定案的程序形状,供你对照业界做法,不是已经同意的设计: + +- 同一次 running 里两次模型请求。第一次只输出能力 id 和库条目 id。程序注入对应提示并记下版本。第二次一次交回多份产物和一段给用户的回复。某一条校验失败只重写那一条。 +- 若模型一次只能调用一个工具,用一次结构化输出携带多条产物,程序在本地切开。这只去掉写入侧的重复前缀。规格说明仍须在这次生成之前就在上下文里。 +- 写开场前有一次程序准备:编译信息可见范围、游玩执行方式、上下文排序和运行配置。这一步不应再变成一次让模型自由发挥的长上下文请求。 + +必须仍然成立的行为:用户不手选节点;方案不是执行顺序;没说清的「必须」和「有必要」本轮不写,先问 2 到 3 个选项;同轮多份产物只给一次综合回复,不把完整 JSON 贴进回复;变量和映射不走普通写入。 + + +八、请你列出的常见方案,以及针对我们的组合 + +常见方案请覆盖至少这四类,每类不要只写一句口号。我们没给仓库,所以请把业界做法写全,包括你最后不推荐的。我们会删掉不适合的。 + +1. 缓存:粘滞路由、前缀稳定性、缓存块粒度、显式缓存断点、把变化赶出前缀。session_id 已经有了,请写它解决不了的部分。 +2. 编排顺序:稳定段、历史、本轮输入的常见排序,以及中段一改、后段全失效时业界怎么处理。 +3. 发送哪些:全文产物、摘要、按能力取材料、检索、滑动窗口、把目录和正文拆开。请写误删材料的后果。 +4. 程序:一次多工具、结构化输出一次交多份、选择与填写拆请求、把规格预注入从而取消读取轮、限制工具循环次数。请写哪种依赖模型能力,哪种只依赖程序。 + +然后给出你建议我们采用的组合:一句用户话几次请求、每次上下文块的顺序、预计相对现在少发送几次大前缀、产物那约 6 万字还在不在填写请求里。故意不做的项写原因。和第三节、第六节冲突的地方单独列出,不要写进推荐步骤。