会自我轮转的 Agent:从"命中率 99.9% 还是很贵"到实测省 76%
我在 DeepSeek Harness(DSH)上做了一个上下文预算插件,用 1181 步真实数据校准了成本模型,并把长会话的每步成本压到 1/3.3。
这篇文章把四组实验、一个反例、一个优化和插件实现全部摊开,包括踩过的五个坑。
一、起点:一个很自然的错误直觉
我问过好几个模型同一个问题:上下文缓存命中率越高,是不是越省钱?
答案都是"是"。这句话也没错——错的是它容易被当成全部。
我自己的会话是这样:命中率99.6%,看起来很健康。但把账单拆开后就露馅了:
长会话(823 步) 平均上下文 381K 每步 40,321 等价 token而同期一个"没有记忆"的聊天机器人,每步只要 4,600。
命中率高 37 个百分点,成本贵 8 倍。
二、把成本拆成三个乘数
成本 ≈ 上下文大小 × 调用次数 × 缓存折扣用真实日志分别测出三个系数:
| 乘数 | 实测比值 | 说明 |
|---|---|---|
| 上下文大小 | 30× | 227K vs 7.5K |
| 调用次数 | 5× | agent 一步一件事,要跑 5 步 |
| 缓存折扣 | 10× | 命中价约为未命中的 1/10 |
大多数人只盯着第三个,而它已经到顶了(99.9%,剩 0.1% 空间)。
真正的杠杆是第一个。
三、实验一:记忆"够用"比记忆"多"更重要
设计
3 组上下文 × 8 道题 × 2 次 =48 次测量,每次都开全新会话,杜绝残留。
| 组 | 记忆内容 |
|---|---|
| A | 无(只有系统提示 + 工具定义) |
| B | MEMORY.md 全文(约 1.5K token) |
| C | MEMORY.md + 600 条背景摘要(约 20K token) |
结果
| 组 | 平均步数 | 每题成本 | 正确率 |
|---|---|---|---|
| A 无记忆 | 3.44 | 12,157 | 100% |
| B 有记忆 | 1.75 | 2,034 | 100% |
| C 长背景 | 1.75 | 5,340 | 100% |
| AstrBot 对照 | — | 4,628 | — |
B 组只要无记忆的 1/6,只要对照组的 44%,而且答案全对。
最贵的那一题
| 题目 | A 组 | B 组 |
|---|---|---|
| “AstrBot 的数据库文件叫什么” | 51,407 | 1,497 |
A 组不知道,去磁盘里搜——一次烧掉 5 万。B 组从记忆里直接答。
34 倍差距,全花在搜索上。
但记忆不是万能折扣
同一批题里,"数一下目录里有几个 .md 文件"这题:
A 组 2,412 ← 更便宜 B 组 2,551 ← 多带了一段用不上的记忆记忆只在"里面真有答案"时生效。
四、实验二:长任务里,记忆反而亏钱
同一个项目不能只报好消息。
任务:目录里已有 step1.txt=1、step2.txt=2,新建 step3.txt 等于两者之和。判分直接读磁盘。
| 组 | 平均步数 | 每步上下文 | 平均成本 |
|---|---|---|---|
| A 无记忆 | 5.7 | 6,615 | 5,726 |
| B 精确记忆 | 3.5 | 17,712 | 7,239 |
| C 模糊记忆 | 3.8 | 20,180 | 8,634 |
交叉
步数 5.7 → 3.5 省 39% ✅ 每步上下文 6.6K → 17.7K 贵 168% ❌ 净结果 反而贵 26%省下来的步数,被撑大的上下文吃掉了。
膨胀从哪来
记忆本身只有 50 个 token。膨胀发生在**"注入记忆"这个动作**上:
模型收到"你已经完成了前两步"之后,先去目录里确认了一遍——它不相信记忆。那一轮探索的输出从此留在会话前缀里,每一步都要重读。
给它记忆,它反而多跑了一趟。
五、实验三:一句话把亏损翻成收益
既然亏损来自"模型不信记忆",那就让它信。
五种注入方式,各 6 次,同一道长任务题:
| 组 | 怎么说的 | 步数 | 成本 |
|---|---|---|---|
| A 无记忆 | — | 5.7 | 5,726 |
| B 精确(旧) | 「你已经完成了前两步:step1 是 1,step2 是 2」 | 3.5 | 7,239 |
| C 模糊 | 「做了几步,但没记数值」 | 3.8 | 8,634 |
| E 静默 | 只给事实 | 4.3 | 5,819 |
| D 权威 | 「**【权威记录·无需核实】**直接用,不要再调工具确认」 | 2.5 | 4,368 |
B 精确(旧) +26% ← 亏 D 权威 −24% ← 赚同一份事实、同样的信息量,只改了措辞。
旧措辞"你已经完成了……“读起来像别人的说法,模型去核实;
新措辞"这是你自己的记录,权威、直接用”,它就不怀疑了。
但方差还在:D 组六次是 1、1、2、2、4、5 步——概率性地减少核实,不是永久生效。
六、实验四:真实轮转 355 步,省 76%
前面的实验都是受控的。这一组是真实使用:把长会话轮转成小上下文会话,各跑一段真活。
| 会话 | 步数 | 平均上下文 | 峰值 | 等价 token | 每步 |
|---|---|---|---|---|---|
| 长会话(未轮转) | 823 | 381K | 727K | 33.18M | 40,321 |
| 轮转 1 | 124 | 128K | 252K | 1.83M | 14,742 |
| 轮转 2 | 90 | 68K | 126K | 0.73M | 8,138 |
| 轮转 3(进行中) | 141 | 113K | 203K | 1.79M | 12,710 |
| 轮转后合计 | 355 | 107K | — | 4.35M | 12,261 |
每步成本 40,321 → 12,261,降到 1/3.3。
线性拟合
1181 步的单步成本对上下文做拟合:
全部 1181 步 equiv = 0.10403 × ctx + 809 R² = 0.56 长会话 823 步 equiv = 0.10527 × ctx + 182 R² = 0.44 轮转后 358 步 equiv = 0.10115 × ctx + 1,468 R² = 0.95三个系数几乎相同(0.101~0.105),说明成本确实主要由上下文决定,跟会话新旧无关。
即:每 1K 上下文 ≈ 104 等价 token,另外每步约 0.8~1.5K 固定开销。
反事实:如果不轮转
从长会话结束时的 401K 接着跑这 355 步,按实测的每步 +474 增长:
反事实 18,224,161 实际 4,352,608 省 13,871,553(76.1%)轮转本身贵不贵
不贵。三次轮转的首步成本分别是 2,614 / 3,054 / 3,529,都低于长会话首步的 4,665(因为长会话首步是冷启动,要全量 miss)。
真正决定账单的是轮转频率,不是轮转成本。
还能省多少
| 轮转间隔 | 平均上下文 | 每步等价 token |
|---|---|---|
| 每 40 步 | 21K | ~3,000 |
| 每 60 步 | 26K | ~3,500 |
| 每 120 步 | 40K | ~5,000 |
| 不轮转 | 107K | ~12,300 |
现在两次轮转的间隔是 90~124 步,还没吃到红利。按 40K 预算(约每 60 步)轮转,每步还能再降 70%。
七、插件怎么做
架构
每轮结束 → 记账(步骤数、上下文、命中率、等价成本) ↓ 上下文 > BUDGET? ↓ 是 agent 自己写交接摘要(工具参数) ↓ 用「权威措辞」注入新会话 + 挂进工作区 ↓ QQ 桥读 active_session.json,自动切过去三个关键 API(DSH cordis 插件)
ctx.on('session/event',(session,event)=>{// 每条 assistant/message 都带 usage,直接拿来记账})constcreated=awaitcontroller.create({cwd:'E:/DSH-work'})awaitcontroller.prompt({sessionId,content:[{type:'text',text:seed}]},newAbortController().signal)五个坑(每个都让插件静默失效或直接报错)
① bundle patch 必须用insert形式
# 错:这是"改已有条目",目标不存在会被静默跳过-id:my-pluginconfig:{...}# 对-insert:-id:my-pluginname:my-pluginconfig:{...}② package.json 必须导出补丁文件
"exports":{".":"./index.js","./cordis.patch.yml":"./cordis.patch.yml","./package.json":"./package.json"}少了中间那行 →ERR_PACKAGE_PATH_NOT_EXPORTED,插件永远挂不上。
验证:node -e "require.resolve('你的插件/cordis.patch.yml')"
③ ```prompt() 的第二个参数是 AbortSignal,必传
不传报Cannot read properties of undefined (reading 'throwIfAborted')。
④ 工具输出 schema 是additionalProperties: false
返回对象多一个没声明的字段就整轮失败:tool "x" returned invalid output: "value.y" is not a declared property
⑤render必须返回内容块数组,不是字符串
render:(v)=>JSON.stringify(v)// ❌ content.some is not a functionrender:(_args,v)=>[{type:'text',text:...}]// ✅⑥(这个最隐蔽)create()不传 cwd,会话会落在宿主的工作目录
第一次轮转,新会话跑到了--C-Users-white-.dsh-profiles-desktop--下,看不到任何项目文件。
必须显式传cwd,并且调workspaceRegistry.create(cwd).attachSession(id)挂进工作区。
一个意外的发现
新会话一开口就是20,528 token,而预算设的是 20,000。
系统提示 + 工具定义(约 19.5K)就是地板。交接摘要只占 1K。
所以预算必须设在 4 万以上,否则每次轮转完立刻"超预算"。
八、这套东西的边界
已验证:成本模型、记忆的收益与反例、措辞的影响、真实轮转的 355 步。
没验证:
- 质量损失。所有对比都只算 token,没算"压缩后答错率"。反事实是"同样步数",不是"同样效果"。
- 样本量。记忆实验 48 次、长任务 18 次、优化 30 次——够看方向,不够下断言。
- 单机单模型。系数 0.104 是 DeepSeek 的价格结构,换供应商要重测。
- 长任务的反例只测了一种题型(写一个文件),更长的多步任务没覆盖。
最该补的是第 1 条。省钱省到答错,就没有意义。
九、复现
# 从任意 DSH 会话日志模拟预算曲线python simulate.py<session.v4.jsonl.zstd>--csvturns.csv# 轮转前后对比python tools/rotation_report.py数据文件:bench/data/*.csv(48 次记忆实验、18 次长任务、30 次优化),共 1181 步轮转日志。
十、一句话
上下文不是记忆,是账单。
记住该记的,忘掉能忘的,比"记得更多"便宜六倍。
数据时间:2026-10-27 ~ 11-03 口径:DSH 会话日志assistant/message.usage,缓存单价系数 0.1,单位"等价 token"(未命中计 1,缓存计 0.1,输出计 1)。
下面是该插件,已在GitHub上开源:
https://github.com/whitelady0666-boop/dsh-context-budget