1. 长会话为什么会“越聊越傻”:OpenClaw 上下文压缩要解决的真实问题
如果你用 OpenClaw 跑过超过 50 轮的任务型对话,大概率遇到过这种情况:前面明明说清楚了需求,聊到后面它开始重复问同样的问题,或者把早就确认过的参数又改回去。这不是模型变笨了,而是上下文窗口被塞满了。
OpenClaw 是一个支持长对话的个人 AI 助手平台,它的核心矛盾很直接:LLM 的上下文窗口有限(4K 到 200K tokens 不等),但真实任务对话可能持续上百轮。工具调用返回的日志、文件内容、命令输出会迅速吃掉预算。一旦接近窗口上限,就会出现两个后果——要么请求直接报超限错误,要么触发“Lost in the Middle”现象:关键信息落在上下文中间位置时,模型的召回能力显著下降。
OpenClaw 的解法不是简单粗暴地砍历史,而是一套多层防御体系。从底层的提示缓存保留,到中层的延迟轮次维护,再到上层的 Compaction(上下文压缩)与工具结果截断,每一层各司其职。这篇内容聚焦最核心的 Compaction 层:分块摘要怎么分、工具结果怎么截、压缩阈值怎么触发,以及如何用 TaoToken 统一 Key 把整条链路接起来验证。
适合谁看:正在给 OpenClaw 配置长会话能力的开发者、被上下文超限报错卡住的运维同学、以及想理解压缩算法工程落地的 AI 应用工程师。下面从配置骨架开始,一步步把可复制的方案搭出来。
2. 接入前的准备:用 TaoToken 统一 Key 打通模型通道
OpenClaw 的压缩链路里,摘要生成、质量审计、模型回退都需要调用 LLM。如果每个环节单独配 Key,管理成本会很高。TaoToken 提供统一 Key 和 API 通道,把模型调用收敛到一个入口,压缩配置里只需要引用同一个 provider 即可。
先拿到访问凭证。打开控制台创建 API Key:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console创建完成后,Key 的管理页面在这里,后续轮换或查看用量都从这进:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keysAPI 的基础地址是https://taotoken.net/api,注意这个地址不带任何查询参数,直接作为 base_url 使用。接入文档在:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc如果你用的是 Claude Code 这类编码 Agent,Anthropic 兼容通道的说明单独有一页:
https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=ClaudeCodeAnthropic注意:压缩专用模型建议选一个便宜且响应快的,因为摘要调用频率高。把压缩模型和主对话模型分开配置,能显著降低长会话的总体成本。
环境变量先设好,后面 config.toml 里直接引用:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"3. 可复制的压缩配置:config.toml 与 settings.json 骨架
OpenClaw 的压缩行为由两层配置驱动:config.toml管全局默认值和阈值,settings.json管会话级覆盖。下面这份骨架是我实测下来比较稳的组合,你可以直接抄。
3.1 config.toml 核心段落
[agents.defaults] # Agent 上下文 token 封顶,超过就进入压缩判断 contextTokens = 120000 [agents.defaults.compaction] # 压缩专用模型,走 TaoToken 统一通道 model = "claude-3-5-haiku" thinkingLevel = "low" # 压缩超时,单位秒,默认 180 timeoutSeconds = 180 # 后压缩索引同步模式:off / async / await postIndexSync = "async" [agents.defaults.compaction.safeguard] # 保留最近轮次原文,默认 3,最大 12 recentTurnsPreserve = 3 # 质量守卫,开启后会对摘要做审计重试 qualityGuardEnabled = true qualityGuardMaxRetries = 1 [agents.defaults.context-pruning] # 上下文修剪模式,cache-ttl 按缓存过期时间修剪工具结果 mode = "cache-ttl" ttl = "5m" [agents.defaults.context-pruning.hardClear] enabled = true placeholder = "[Old tool result content cleared]" [agents.defaults.context-pruning.tools] # 哪些工具的结果允许被修剪,deny 优先于 allow deny = ["read_file", "list_dir"] allow = ["*"]3.2 settings.json 会话级覆盖
{ "compaction": { "safeguard": { "recentTurnsPreserve": 5, "maxHistoryShare": 0.5, "identifierPolicy": "preserve", "customInstructions": "保留所有文件路径、函数名和错误码,不要改写标识符。" }, "contextWindowTokens": 120000 }, "contextPruning": { "mode": "cache-ttl", "ttl": "5m", "hardClear": { "enabled": true, "placeholder": "[Old tool result content cleared]" } } }3.3 关键阈值对照表
| 配置项 | 默认值 | 作用 |
|---|---|---|
compaction.timeoutSeconds | 180 | 压缩超时,超时中止 |
safeguard.recentTurnsPreserve | 3 | 保留最近轮次原文 |
safeguard.maxHistoryShare | 0.5 | 历史占上下文最大份额 |
context-pruning.ttl | 5m | 工具结果缓存过期时间 |
contextTokens | 无 | Agent 上下文封顶 |
提示:
recentTurnsPreserve不要设太大。设成 12 时,最近 12 轮原文可能本身就占满预算,压缩等于没压。3 到 5 是实测比较平衡的区间。
4. 压缩算法拆解:分块摘要、自适应比例与工具结果截断
配置只是开关,真正决定压缩效果的是底层算法。OpenClaw 的压缩实现里有三个算法值得单独讲清楚,理解了它们,你调参才有方向。
4.1 分块摘要算法
当历史太长无法一次性摘要时,OpenClaw 采用分块独立摘要再合并的策略。核心函数是summarizeChunks,它带重试机制(3 次尝试,延迟 500ms 到 5000ms,带 0.2 抖动)。摘要指令里有一条很关键:标识符保留指令,要求压缩时保留文件路径、函数名、错误码这类关键标识符。这就是为什么压缩后任务还能接得上——模型知道之前改的是哪个文件。
4.2 自适应分块比例
固定分块比例在消息大小不均时会翻车。OpenClaw 用computeAdaptiveChunkRatio动态调整,关键常量如下:
export const BASE_CHUNK_RATIO = 0.4; // 默认分块比例 export const MIN_CHUNK_RATIO = 0.15; // 自适应下限 export const SAFETY_MARGIN = 1.2; // token 估算缓冲 export const SUMMARIZATION_OVERHEAD_TOKENS = 4096; // 摘要开销逻辑是:先估算消息平均大小,当平均消息超过上下文窗口的 10% 时,降低分块比例,避免单条超大消息把摘要撑爆。如果某条消息超过窗口的 50%,直接走buildOversizedFallbackPlan回退计划。
4.3 工具结果截断
工具返回的超大结果是最容易撑爆上下文的元凶。截断算法保留头部加尾部,中间用省略标记。关键逻辑是hasImportantTail检测:如果尾部匹配到 error、exception、failed、traceback 这些关键词,就保留尾部预算(budget 的 30%,上限 4000 字符)。因为报错信息通常在输出末尾,砍掉尾部等于砍掉最有价值的部分。
聚合层还有一道防线:resolveLiveToolResultAggregateMaxChars取perResultMaxChars * 4和contextWindowTokens * 4 * 0.5的较大值。截断后会插入通知:
export function formatContextLimitTruncationNotice(truncatedChars: number): string { return `[... ${Math.max(1, Math.floor(truncatedChars))} more characters truncated; rerun with narrower args if needed]`; }这条通知很重要,它告诉模型“结果被截断了,需要更窄的参数重跑”,而不是让模型以为结果就这么多。
5. 验证压缩链路:从请求到成功结果
配置写完后,必须验证整条链路真的通了。分两步:先验证 TaoToken 通道能正常调用模型,再验证压缩触发后任务连续性没断。
5.1 验证模型通道
先用一个最小请求确认 Key 和 base_url 正确:
curl -s https://taotoken.net/api/v1/messages \ -H "x-api-key: $TAOTOKEN_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-3-5-haiku", "max_tokens": 64, "messages": [{"role": "user", "content": "回复 OK 两个字母"}] }'返回里能看到content字段带OK,说明通道正常。如果返回 401,检查 Key 是否复制完整;返回 404,检查 base_url 有没有多写路径。
想直接在网页里对比不同模型的表现,可以用模型对话页:
https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat5.2 验证压缩触发
启动 OpenClaw 后,跑一段会持续产生工具调用的任务,比如让它连续读取多个文件并总结。观察日志里是否出现压缩相关记录:
[compaction] estimateMessagesTokens: 118400, threshold: 120000 [compaction] below_threshold, skip [compaction] estimateMessagesTokens: 124800, threshold: 120000 [compaction] acquireSessionWriteLock [compaction] captureSnapshot checkpoint [compaction] summarizeChunks: 3 chunks, retryAsync attempts=1 [compaction] estimateTokensAfterCompaction: 41200 [compaction] persistCompactionCheckpoint [compaction] armPostCompactionLoopGuard看到estimateTokensAfterCompaction从 124800 降到 41200,说明压缩生效。压缩后继续追问之前任务里的细节,比如“刚才第三个文件的函数名是什么”,如果模型能答对,说明标识符保留指令起作用了,任务连续性保住了。
5.3 长期编码场景的通道选择
如果你跑的是长时间编码 Agent,压缩调用频繁,建议用 Coding Plan 通道,配额和稳定性更适合持续任务:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan6. 压缩常见报错排查
压缩链路出问题时,报错信息往往比较隐晦。下面是我踩过的几个坑和对应解法。
6.1 压缩后模型重复调用同一工具
这是典型的压缩死循环:压缩丢了信息,模型以为工具没执行过,重复调用。OpenClaw 有post-compaction-loop-guard守卫,观察窗口默认 3 次,检测到“相同工具名 + 相同参数哈希 + 相同结果哈希”重复 3 次就中止,抛出PostCompactionLoopPersistedError。
如果你看到这个错误,说明压缩摘要丢了关键状态。解法是提高recentTurnsPreserve,或者在customInstructions里明确要求保留工具调用结果的状态描述。
6.2 压缩超时中止
日志出现timeout分类,说明摘要模型 180 秒没返回。先检查 TaoToken 通道的响应延迟,再考虑把timeoutSeconds调大,或者换一个更快的压缩模型。注意超时后压缩会中止,但检查点快照还在,不会丢数据。
6.3 压缩被判定为良性跳过
日志里below_threshold、already_compacted、deferred_background这些属于良性跳过,不是故障。deferred_background表示压缩被延迟到后台 lane 执行,主流程没阻塞。只有summary_failed、provider_error_4xx、provider_error_5xx才是真正需要处理的失败。
6.4 工具结果截断后模型答非所问
如果模型抱怨“信息不完整”,检查截断通知是否正常插入。截断通知缺失时,模型不知道结果被砍过,会基于残缺信息硬答。确认context-truncation-notice没有被自定义配置覆盖掉。
6.5 上下文窗口硬下限报错
CONTEXT_WINDOW_HARD_MIN_TOKENS是 4000,CONTEXT_WINDOW_WARN_BELOW_TOKENS是 8000。如果你配的contextTokens低于这些值,会直接报错。压缩预算再紧,也别把窗口压到 8000 以下,否则压缩本身的开销(摘要提示 4096 tokens)都放不下。
7. 把压缩链路接稳:下一步做什么
压缩配置调通之后,建议做两件事巩固效果。第一,把压缩模型和主模型分开,压缩用便宜快的,主对话用能力强的,成本能降一大截。第二,定期看压缩日志里的estimateTokensAfterCompaction数值,如果压缩后 token 还是接近阈值,说明maxHistoryShare或recentTurnsPreserve需要往下调。
接入凭证和文档入口再放一次,方便你直接跳转:
API Keys: https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys 接入文档: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc 模型对话: https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan最后留一个实用技巧:压缩指令里的identifierPolicy设成preserve之后,再在customInstructions里补一句“保留所有错误码和文件路径”,双保险。我试过在长任务里去掉这条,压缩后模型把ERR_CONN_TIMEOUT改写成“连接超时错误”,虽然意思对,但后续代码里匹配错误码就失效了。标识符保留不是可选项,是长会话任务连续性的底线。