OpenCode 夜间代码改进:96 消息/54 工具的深度优化实践
本文最后更新于 2026年7月14日 凌晨
凌晨三点,OpenCode 在跑一个大型重构任务时突然崩溃了。不是代码逻辑有 bug,而是它把 128k 上下文窗口里的每一个 token 都吃光了,最后一条消息被截断在关键的 JSON 尾部。我盯着满屏红色报错,意识到一个问题:如果 AI 编程工具不能像人类一样”忘记”不需要的信息,它永远只能在玩具项目里打转。
这已经是本周第三次夜间任务翻车。白天交互式使用一点问题都没有,一旦切换到夜间批量模式——把几十个文件的修改、测试、重构一股脑塞进去,对话历史就会膨胀成一只吞噬上下文窗口的巨兽。折腾了几个通宵,我终于把整条链路调通了:96 条消息、54 次工具调用,稳稳跑完不报错,Qwen3.5 在两张魔改 RTX 2080 Ti 上跑出 104 t/s 的稳定速度。这篇文章会把踩过的坑、调优的配置和背后的原理都写出来,省得你再走一遍。
实验环境
先亮家底,方便你对照自己的硬件。
- 服务器:ModelBase(自建 GPU 服务器)
- 显卡:2 × RTX 2080 Ti 魔改版,单卡 22 GB 显存,双卡合计 44 GB(Turing 架构,compute capability 7.5,无原生 FP8/BF16,无 NVLink)
- 推理引擎:llama.cpp,models-preset 多模型模式
- 代理层:new-api(中转 API 网关)
- 模型:Qwen3.5-35B-A3B-Ornith,Q4_K_M 量化,实测生成速度 104 t/s
- OpenCode 版本:夜间构建版(v1.3.0+),支持 GitLab 集成和 Node.js 工具链
夜间任务为什么会爆炸
白天你跟 OpenCode 交互,一问一答,消息是分散的,上下文缓慢增长,很难触达极限。但夜间模式会把白天攒下来的任务打包执行,改进 bug、写测试、重构代码,一次运行可能堆到 96 条消息,中间穿插 54 次工具调用。
每个工具调用的输入输出都算 token,叠起来就是几万 token 的上下文,三个问题同时爆发:
- 上下文窗口不够用。Qwen3.5-35B-A3B 默认的上下文配置在 llama.cpp 里可能没开够。96 条消息加上工具调用的膨胀,轻松突破 32K token,甚至 50K+。
- 工具定义本身吃 token。OpenCode 注册的每个工具都有一段 JSON Schema 描述,54 个工具的定义堆在 system prompt 里,还没开始对话就吃掉好几千 token。
- 显存分配打架。上下文窗口越大,KV Cache 占的显存越多。44 GB 显存中,模型本身 Q4_K_M 量化大约占 20 GB,剩下 24 GB 要同时容纳 KV Cache 和计算缓冲,稍不留神就 OOM。
优化一:消息上下文分层管理
OpenCode 在长任务中容易遇到”上下文遗忘”问题,因为消息列表没有做分层管理,历史记录全部平铺,很快就会把窗口填满。特别是工具调用历史,每次调用都会增加两条消息(请求 + 响应),十个文件就能积累 20 条,很快占据几十 K token。
我的做法是把消息分成常驻层和滚动层:
- 常驻层:系统提示 + 任务指令,始终保留,不参与压缩。
- 滚动层:工具调用历史,只保留最近 N 条(默认 32 条),更早的消息会被渐进式折叠。
- 输出层:单独缓存模型的生成内容,用于后续验证,但不占用核心上下文空间。
核心逻辑在 context_manager 中实现,大致如下:
1 | |
光分层还不够,还需要对消息内容做智能压缩。连续的工具调用会被合并,比如 5 次 read_file 只保留最后一次完整内容,前面的用摘要替代。
失败的重试只保留最终成功的结果,前两次失败尝试压缩成一句摘要。系统消息去重,只保留关键变化。测试下来,96 条消息通常能缩到 20-25 条,token 从 45K 降到 12K 左右,模型注意力更集中,回复质量反而更高。
优化二:工具调用预算与智能截断
批量任务中,工具调用的顺序和频率直接影响输出质量。我观察到两个典型问题:过早写入(模型还没分析完就直接写文件,导致修改不完整)和重复调用(同一个文件反复读取,浪费上下文空间)。
为了解决这个问题,我给工具调用加了一套预算机制:
1 | |
核心规则是”写入前必须有一次分析步骤”,强制模型先理解代码结构再生成修改。同时,对工具输出做智能截断:read_file 不再返回整个文件,只保留被修改过的区域和模型在思考链中关注过的代码行。
execute_command 只保留命令的退出码和最后 20 行输出,构建日志、测试输出等噪音在命令执行完成后立即丢弃。这一层平均能砍掉 60% 的冗余内容。
当对话历史超过 64 条消息时,还会触发一次”项目快照”——用一个独立的 prompt 让模型生成当前项目的状态摘要,包括修改过的文件列表、关键架构决策、当前阻塞的问题。这个摘要大约 1500 tokens,作为一条特殊系统消息插入对话历史中部,每 32 条消息更新一次,相当于给模型一个”断点续传”的 checkpoint,即使前面 200 条消息被压缩成几条摘要,模型依然能通过最新快照理解当前状态。
优化三:工具定义瘦身与 system prompt 分层
OpenCode 默认注册了一堆工具:文件读写、终端执行、搜索、Git 操作、代码格式化……每个工具的定义是一段 JSON Schema,54 个工具的 schema 堆在一起,光定义就吃掉 4000-6000 token。
每一轮对话这些定义都会跟着 system prompt 发给模型,推理时模型需要”扫描”所有定义来决定调用哪个工具,负担很重。
夜间任务往往只用到极少数工具,比如跑代码重构 + 测试生成,只需要读文件、写文件、搜索代码、运行测试四个工具。我把其他十几个工具全部禁掉,工具定义 token 从 5000 直接降到 400 左右。配置上分两套:白天用全量工具 opencode.json,夜间用精简版 opencode-nightly.json,通过启动参数切换。
1 | |
system prompt 也做了分层处理。全局编码规范、工具列表等不变内容放在最前面的 system 消息里,只在对话开始时发一次;任务特定的指令作为 user 消息的第一个 token 段,跟在后面。这样模型对全局规范有稳定的 KV Cache,只对任务描述做增量处理,96 条消息场景的首 token 延迟从 8 秒降到了 4 秒左右。
硬件调优:显存、KV Cache 与双卡分配
两张魔改 2080 Ti 总共 44 GB 显存,Qwen3.5-35B-A3B Q4_K_M 模型加载后大约占 20 GB,剩下 24 GB 要同时容纳 KV Cache 和计算缓冲。KV Cache 的大小与上下文长度成正比。
Qwen3.5 使用 GQA(Grouped Query Attention),KV Cache 比传统 MHA 小不少,但 96 条消息加上工具调用膨胀,实际 token 数可能达到 40K-50K。我实测把上下文窗口设到 64K token,KV Cache 大约占 12-14 GB,加上模型共 32-34 GB,留出 10 GB 缓冲,刚好安全。
双卡分配策略也大有讲究。Turing 架构没有 NVLink,卡间通信走 PCIe,延迟较高。默认的行分割意味着每层的前向传播都要跨卡通信;改为层分割后,前半部分层在卡 0,后半部分层在卡 1,只有层间边界需要一次通信,大幅减少了跨卡开销。最终 llama.cpp 启动参数如下:
1 | |
改成层分割后,Qwen3.5-35B-A3B 的实测速度从之前的 80 多 t/s 提升到了 104 t/s,提升主要来自跨卡通信开销的降低。
实测效果
在同一个重构任务上跑了三个版本:OpenCode v1.2.27(无压缩)、v1.3.0(基础压缩)、以及最终优化后的夜间构建版。任务是对一个 5000 行的 Python 项目做全面重构,包括错误处理、类型标注、测试覆盖,自动拆分成 12 个子任务。
| 指标 | v1.2.27 | v1.3.0 | 夜间构建(优化后) |
|---|---|---|---|
| 完成任务所需消息数 | 187 | 134 | 96 |
| 工具调用次数 | 54 | 54 | 54 |
| 峰值上下文占用 | 118k tokens | 76k tokens | 41k tokens |
| 因上下文溢出导致的任务失败 | 3 次 | 1 次 | 0 次 |
| 最终代码质量(人工评分) | 7.2/10 | 7.8/10 | 8.5/10 |
| 平均文件处理时间 | 45s | — | 28s |
最让人意外的是最后一行:代码质量反而提升了。翻看对话记录才发现,之前版本的工具输出噪音太严重,模型经常被无关信息分散注意力,压缩后的上下文更干净,模型的注意力集中度反而更高。
避坑指南
这套优化不是银弹,几个容易踩的坑需要提前预判。
压缩阈值不能太激进。如果把滚动层限制从 32 条消息改成 16 条,模型会开始重复调用 read_file 读取已经读过的文件——因为之前的文件内容被过早折叠掉了。32 条是在 Qwen3.5-35B-A3B 上反复调参的结果,换其他模型可能需要重新调整。
项目状态摘要的生成时机很关键。如果在项目处于”中间状态”(比如某个文件改了一半)时触发摘要,后续模型可能会基于错误的前提做决策。目前的解决方法是:摘要只在一次完整的”指令-响应”循环结束后才生成,不会在工具调用链的中途触发。
夜间任务的其他注意事项:
- 磁盘空间:批量写入文件时,临时文件可能占满磁盘,建议跑之前检查可用空间。
- 模型重启:llama.cpp 长时间运行可能触发显存碎片化,建议每 50 个文件重启一次引擎。
- Git 冲突:如果仓库有其他人在修改,夜间任务可能产生冲突,建议先执行
git pull --rebase。 - 工具配置分开:白天用全量工具,夜间用精简版,避免工具禁用导致白天无法正常使用 Git 等操作。
结语
消息分层、工具预算、智能压缩、硬件调优这一套组合拳下来,96 条消息、54 次工具调用不再是我凌晨三点爬起来看日志的噩梦。这套优化思路的核心是:不要让上下文窗口成为瓶颈,而是让它成为可控的资源。如果你的项目规模在 50 个文件以下、单次任务工具调用不超过 20 次,可能还感觉不到明显提升;但当你做跨模块重构、大型代码迁移、或者批量测试修复时,上下文压缩就会从”nice to have”变成”没有它不行”。
在 ModelBase 服务器上,44 GB 显存跑 Qwen3.5-35B-A3B 时,开启整套压缩机制后,单次任务的上限从大约 180 次工具调用提升到了 300 次以上。这意味着你可以让 AI 在夜间无人值守时完成更复杂的任务——这正是”夜间代码改进”这个说法的由来。
参考文献