上下文按规则、历史、产物分段拼装,便于吃提示缓存并核对费用。 Co-authored-by: Cursor <cursoragent@cursor.com>
6.4 KiB
创作轮次与前缀花费
状态:结论已记录。缓存粘滞已接入:本局
sessionId在 OpenRouter 请求体写成session_id。不手选节点导致的「先选再写」尚无做法。
记录:2026-10-01。用量取自本地~/.writing-agent/stats,2026-09-20 起。
相关:src/dictate/prompt.ts、src/dictate/turn.ts、src/dictate/read-module.ts、src/llm/client.ts。
已记录的花费
9 月 20 日以来,OpenRouter 上的 token 主要在创作落盘的输入,不在游玩输出。
| 路径 | 请求 | 会话约 | prompt | 其中缓存 | 未缓存 | 输出 |
|---|---|---|---|---|---|---|
创作落盘 dictate_agent |
416 | 5 | 1198 万 | 782 万 | 417 万 | 38 万 |
游玩 worker:world-simulator |
28 | 2 | 77 万 | 0 | 77 万 | 7 万 |
输出大约是 prompt 的 3%–9%。输出单价更高,仍然要算,但排在输入后面。缓存命中仍按折扣价计,缩体积会同时降低全价和折扣价。
每轮花费可以看成:
请求次数 ×(未缓存输入 × 全价 + 缓存输入 × 折扣价 + 输出 × 输出价)
创作这 416 次是最大的一块。大约五个会话,一句用户话后面跟着一串读和写。
缓存粘滞:必做
OpenRouter 请求体现由统计层带上本局 sessionId,字段名 session_id(最长 256 字)。直连其它渠道不写这个字段。有 session_id 后,粘滞从该会话第一次成功请求就开始,不再等缓存命中,也不再因为第一条 user 被重写而换上游。provider.order 仍不发送,它会关掉这层粘滞。
同一条前缀,落点稳定时能命中,换上游就是 0:
- 创作工具循环间隔大约 7–15 秒。Gemini 上同一轮里缓存从约 3.8k 爬到 7.6k、11.5k、15k,整段 prompt 大约 50%–68% 命中。burst 的第一下经常是 0。中间偶尔整段掉回 0,是换了上游。
- 游玩 world-simulator 在 OpenRouter 上 28 次全是 0。gemini-3.7 间隔中位数约 5.5 分钟,gemini-3.8 约 22 分钟,prompt 1.8 万–3.4 万。
- 直连 glm-5.3 的游玩,间隔 8–30 分钟仍常见 80%–91%。某一轮从约 11k 掉到 6656 并停住,说明前缀中段改过,只保住开头约 6.6k。
因此缓存粘滞是必做项。不传稳定的 session_id,OpenRouter 每一轮按新开头重新选上游,游玩前缀缓存等于没有。provider.order 会关掉这层粘滞,不能拿来代替 session_id。
上下文排序对游玩前缀是有效的:人设和静态产物在前,历史居中,变量和本轮输入在后。它只在同一上游上表现得出来。
未决:不手选节点,选和写至少两轮
用户不手动指定本轮写哪个能力。系统提示里只有能力目录和短说明(何时用、何时不用、档位)。prompt.md 里的任务、原则、产物规格、评分不在这条提示里。
模型选定能力后调用 read_module。程序把这些段落放进工具结果,并在 运行.对话落盘.已读能力 记下该能力当前版本。版本是这些段落的哈希。随后的 insert 要核对这个账:没读过,或磁盘上的提示已经改过,就拒绝写入。self_score 也按这次读到的评分维度检查。narrative、variable-context、context-order、worker-spec、refine 以及 auto 能力不走这道门,其余内容能力都走。库条目同理:目录只是摘要,正文要等 read_library_entry 回来。
读的那一轮,完整写法还不在上下文里,模型给不出合格正文。同一项能力至少是:
第 1 次请求:凭短说明选定,并 read_module
第 2 次请求:看见任务 / 原则 / 规格 / 评分之后 insert
前缀发送两遍。第二遍还多带上那份写法。手选节点时,程序可以把该节点的完整写法放进唯一一次请求,模型当场写完。不手选时,第一次请求不能把所有节点的完整提示都放进去,这第二次省不掉。
一次只返回一个工具时,倍数再乘上去。提示要求一轮通常写 2~3 个已经说清的能力,模型常做成「读甲、写甲、读乙、写乙」,最后再单独回用户一句。每一拍都把已经变长的前缀再送一遍。「选和写拆成两次」是翻倍的原因;「每次只动一个节点」把两倍变成好几倍。
驱动程序已经会在同一步执行模型这一次返回的全部工具。接口没有禁止一轮多个 insert。日志里偶尔同轮两次写入。多数时候模型一次只吐一个工具。
结构化输出可以把一次生成拆成多份产物:一份 JSON 里多条 { position, content },程序再落盘,用户可见回复也可以放在同一份里。这省的是写入侧重复发送前缀。规格说明仍须在生成这批产物之前到达模型;否则模型是在没看见写法的情况下填内容。读和写如何合成更少的请求,本文不规定做法。
一本书上的拆账
本机作品 4c13b2b4 的当前会话(25 份产物、22 轮用户话)按现在的 buildDictateMessages 拼出来,不发请求。字数大约按 2 字 1 token 对照:同书末次 OpenRouter 请求 prompt 为 46897 token。
| 块 | 字数 | 占当前 messages |
|---|---|---|
| 已有产物合计 | 60518 | 69% |
| 全量对话 | 19975 | 23% |
| 系统提示(含方案进度 4392) | 6695 | 8% |
| 工具定义 JSON | 4917 | 另计,约等于系统提示 |
| 当前选用预设里启用的正文 | 362 | 可忽略 |
产物没有一份独自占满。最大的是 设计.正文组成 8879 字,其次变量规则 6005、实现机制 4569。其余多为两三千字,加在一起才是大头。会话中段的检查点(第 119 条消息之前)产物已经是 56102 字,和结尾同一量级。
按前面两段收窄再量同一份材料:
| 方案 | 字数 |
|---|---|
| 选择:方案进度 + 用户最后一句(130 字) | 4522 |
| 填写若仍带全部产物 + 最近 4 条对话(1770 字) | 约 6.2 万 |
| 再注入 1 份节点提示(正文约 5 千字,叙事指南 5557) | 约 6.7 万 |
| 现在这一次请求的 messages | 88218 |
选择请求很小,目录也稳定,缓存几乎打在整段上。填写请求的大头仍是已有产物,不是对话,也不是刚插入的节点提示。砍掉全量对话大约省两成,改不了产物那七成。节点提示每份约 5 千字,一次写两三份会在产物之上再加一两成,但只发送一次。