关注腾讯云开发者,一手技术干货提前解锁👇
最近很火的 OpenClaw 的出镜率是越来越高了,内外网的技术文章,新产品的问世,Mac Mini 的涨价,自媒体的宣传层出不穷。作者是国外一个叫 Peter Steinberger(现已经被奥特曼高薪挖到 OpenAI ),说是花了一个周末就烹饪完成的“小龙虾”。 随着司内 OpenClaw 也支持了,我也在企微中增加了对应的机器人,给它取名「靓靓蒸虾🦞」,协助处理一些需求管理上的事情。我一直也在做上下文相关的事情,现在我们就拨开虾外壳,看看它内部详细是如何调味的(进行上下文窗口管理的)。
仓库: github.com/openclaw/openclaw 本文基于该仓库源码进行分析。
在 AI Agent 的长会话场景中,上下文窗口溢出是一个绕不开的问题。当对话越来越长、工具调用结果越来越多,LLM 的上下文窗口终将被填满。OpenClaw 为此设计了一套多层防御系统,从"尽量不溢出"到"溢出了也能恢复",覆盖了整个上下文生命周期。本文将从架构到实现细节,完整剖析这套方案。
整体架构
OpenClaw 的上下文管理分为三个阶段,形成递进的防御纵深:
一个关键的设计原则是渐进式降级:先做轻量级裁剪(只丢弃冗余数据),再尝试 LLM 摘要(有损但保留语义),最后才是暴力截断(只保留头部内容)。每一层只在前一层不够用时才会介入。
第一层:预防性裁剪(发送 LLM 之前)
这一层的目标是:在消息发送给 LLM 之前,尽可能裁剪掉不再需要的冗余内容,避免触发上下文溢出。
2.1 会话历史轮次限制(History Turn Limit)
文件:src/agents/pi-embedded-runner/history.ts
这是最简单、最粗粒度的保护——直接限制保留的用户对话轮次数。
工作原理
limitHistoryTurns() 函数从消息列表的末尾向前遍历,计数 的消息。当计数超过 limit 时,丢弃更早的所有消息:
注意这里的截断边界是 lastUserIndex——即被计数的最后一个 user 消息的位置。这意味着截断点始终在一个完整的 user 轮次边界上,不会把一个 user-assistant-toolResult 的三元组截断成碎片。
配置解析
限制数值通过 getHistoryLimitFromSessionKey() 从配置中解析,支持多级覆盖:
这个分层设计使得运营者可以为特定的高频用户设置更严格的限制,同时保持其他用户的默认行为。session key 的 kind 字段(dm/direct/channel/group)决定走哪条解析路径。
2.2 Context Pruning 扩展(渐进式裁剪旧 Tool Results)
文件:src/agents/pi-extensions/context-pruning/
如果说 History Turn Limit 是"剪头",Context Pruning 就是"瘦身"。它是一个运行时扩展(extension),注册在 context 事件上,在每次构造 LLM 请求时拦截消息列表。
触发机制
扩展采用 cache-ttl 模式运行,默认 TTL 为 5 分钟。这意味着:
两级裁剪策略
核心逻辑在 pruneContextMessages() 中,使用字符占比作为触发条件:
其中 charWindow = contextWindowTokens × 4(使用 1 token ≈ 4 字符的粗略估算)。
Soft Trim 的截断实现很精巧——它不是简单地 slice,而是在文本块(text content block)的层面操作,分别从头部和尾部取字符,保持换行符的完整性:
裁剪范围与保护规则
并非所有消息都会被裁剪,有几条硬性保护规则:
Hard Clear 还有一个额外的安全阈值:只有当可裁剪的工具结果总量超过 minPrunableToolChars(默认 50,000 字符)时才会执行,避免对少量工具结果做无意义的清理。
默认参数一览
2.3 单条 Tool Result 截断
文件:src/agents/pi-embedded-runner/tool-result-truncation.ts
Context Pruning 处理的是"很多旧的 tool result 累加起来太大"的情况,而单条截断处理的是另一种场景——单条工具返回了巨量内容(比如读取了一个大文件或执行了一个产生大量输出的命令)。
大小限制
最终限制取两者中的较小值:
对于一个 200K token 上下文窗口的模型,单条 tool result 最大约为 200K × 0.3 × 4 = 240K 字符。对于 2M 上下文的模型,理论值 2.4M 会被 HARD_MAX_TOOL_RESULT_CHARS 限制到 400K。
截断策略
截断时保留内容的头部(开头通常包含最重要的信息),并尽量在换行符处断开以避免切断行中间:
截断后追加一段提示信息,引导模型通过 offset/limit 等参数获取更多内容:
多文本块的比例分配
一个 tool result 可能包含多个 text content block。此时截断预算会按各 block 的原始长度比例分配:
两种截断模式
持久级截断的实现很有意思——它通过 Session Manager 的分支机制(branching)来修改历史:
这种方式避免了直接修改已有的 entry,保持了 session 文件的 append-only 语义。
第二层:Compaction(基于 LLM 的主动压缩)
这是 OpenClaw 最核心的上下文压缩机制——用另一次 LLM 调用来生成对话历史的摘要,用摘要替代原始消息。
核心文件:
3.1 触发时机
Compaction 在两种场景下触发:
3.2 Compaction Safeguard 协调流程
compaction-safeguard 扩展监听 session_before_compact 事件,协调整个 compaction 流程。它不只是简单地做摘要,而是一个完整的信息保留+压缩 pipeline:
摘要失败保护
整个流程用 try-catch 包裹,任何异常都会导致 { cancel: true }——取消 compaction,保留原始历史。这是一个重要的设计决策:宁可让上下文溢出(进入溢出恢复流程),也不要因为摘要失败而丢失历史。
3.3 摘要生成算法详解
分段摘要(summarizeInStages)
当消息量较大时,不能一次性把所有消息送给 LLM 做摘要(摘要请求自身也有上下文窗口限制)。summarizeInStages 采用分而治之的策略:
关键参数:
splitMessagesByTokenShare 的分割算法按 token 总量均分,确保每个 chunk 的 token 数接近 totalTokens / parts。分割点在消息边界上,不会切断单条消息。
自适应 chunk 大小
如果消息平均体积很大(比如用户频繁读取大文件),固定的 chunk 比例可能仍然导致单个 chunk 溢出摘要模型。computeAdaptiveChunkRatio 会动态缩小 chunk 比例:
举例:如果平均消息占上下文窗口的 20%(avgRatio = 0.2),那么 reduction = 0.4,chunk ratio 被缩小到 MIN_CHUNK_RATIO = 0.15。每个 chunk 只会放 15% 上下文窗口大小的内容。
超大消息的三级降级(summarizeWithFallback)
对于包含极大单条消息的情况,摘要可能直接失败。summarizeWithFallback 实现了三级降级:
每一级都能产出一个结果,不会让 compaction 因为单条超大消息而完全中断。
摘要调用的容错
每个 chunk 的摘要调用通过 retryAsync 封装,具有内建重试机制:
最多重试 3 次,退避延迟从 500ms 到 5000ms,附加 20% 的抖动避免雷同重试。只有 AbortError(用户主动取消)不重试。
3.4 历史裁剪预处理(pruneHistoryForContextShare)
在开始摘要之前,如果待摘要的消息总量太大,需要先做一轮预裁剪。这发生在新内容(摘要后需要保留的部分)已经占用了超过 maxHistoryShare(默认 50%)的上下文窗口时。
裁剪算法:
这里的 repairToolUseResultPairing 至关重要——丢弃消息后,可能出现 tool_result 的对应 tool_use(在 assistant 消息中)已被丢弃的情况。Anthropic 的 API 会严格检查配对关系,孤立的 tool_result 会导致 "unexpected tool_use_id" 错误。修复函数会:
3.5 Compaction Summary 的结构化输出
最终生成的 summary 不仅仅是对话摘要。OpenClaw 在摘要文本后附加了结构化信息,确保 compaction 后 AI 仍然知道"自己做过什么":
这些附加信息的意义:
3.6 安全保护机制
Compaction 涉及将对话历史送入 LLM 处理,有专门的安全考虑:
第三层:溢出后恢复
文件:src/agents/pi-embedded-runner/run.ts
即使有了预防性裁剪和主动压缩,仍然可能出现上下文溢出——比如模型的实际 token 计数与估算的 chars/4 启发式有较大偏差,或者 SDK 自动 compaction 后上下文仍然超限。
溢出检测
通过 isLikelyContextOverflowError() 检测 LLM 返回的错误是否为上下文溢出。检测逻辑覆盖两个来源:
恢复决策树
恢复约束
Token 估算策略
OpenClaw 使用 chars / 4 的启发式方法估算 token 数量(即 1 token ≈ 4 字符),这是一个有意为之的简化:
在 chunkMessagesByMaxTokens 中:
在 compaction-safeguard 中,计算历史裁剪阈值时也会应用安全系数:
此外,stripToolResultDetails() 在估算前移除 toolResult.details,避免不可信的大体积附加数据干扰估算和摘要。
配置项汇总
全景流程图
核心设计思路
附录:上下文管理对 Provider KV Cache 的影响分析
上下文管理方案不可避免地会改变发送给 LLM 的消息序列。而主流 LLM Provider(Anthropic、OpenAI、Google 等)都提供了 Prompt Caching 机制——如果新请求的 prompt 前缀与前一次请求相同,Provider 可以复用已有的 KV Cache,大幅降低延迟和计费。
以 Anthropic 为例:cache read 价格仅为普通 input 的 10%,cache write 则为 125%。一次 cache miss 可能导致成本翻倍。
9.1 OpenClaw 对 Provider Cache 的感知
OpenClaw 明确知晓并利用了 Provider 的 Prompt Caching 能力:
9.2 各层操作对 KV Cache 的影响
1. History Turn Limit — 对 cache 无直接影响
这个操作只会在首次构建消息列表时截断最老的轮次。由于每次 LLM 调用的消息列表都是从 session 文件重建的,截断行为在每次请求间是一致的。只要 limit 不变,每次调用的 prompt 前缀是稳定的,不会导致 cache miss。
但如果 limit 触发了截断(消息数超过限制),被截断的那一次请求的 prompt 前缀会与前一次完全不同——这一次一定是 cache miss。不过这通常只发生在长时间运行的会话中。
2. Context Pruning — 会导致 cache 失效,但有刻意的缓解设计
这是 cache 影响最大的操作。Soft Trim 和 Hard Clear 会修改旧 tool result 的内容,改变 prompt 中间的文本。由于 KV Cache 是严格前缀匹配的,一旦修改了 prompt 中间的某条消息内容,从修改点到末尾的所有 token 都会 cache miss。
OpenClaw 的缓解设计——Cache TTL 对齐:
Context Pruning 的 5 分钟 TTL 不是随意选择的。它与 Anthropic 的 "short" cache retention(也是 5 分钟)精确对齐:
设计意图是:
此外,isCacheTtlEligibleProvider() 确保只有支持 cache 的 Provider 才启用这个基于 TTL 的 pruning 模式。不支持 cache 的 Provider(如 OpenAI、DeepSeek)不会使用 cache-ttl 模式,因此不受 TTL 约束。
Cache 失效时的成本影响:
当 pruning 确实执行时:
但由于 pruning 只在 cache 已过期时执行,这次 miss 的额外成本仅是"cache write"(比普通 input 贵 25%),而不是"本可以 cache read 却 miss 了"(cache read 便宜 90%)。
3. 单条 Tool Result 截断 — 内存级无影响,持久级会 cache miss
4. Compaction — 完全重建 prompt,cache 完全失效
Compaction 是最极端的操作——用一段摘要替代了大量历史消息。compaction 后的 prompt 与之前完全不同,KV Cache 必然 100% miss。
这是一个有意接受的 trade-off:
9.3 成本影响量化估算
以 Anthropic Claude 的定价为例(claude-opus-4-6):
假设一次 100K token 的请求:
关键观察:Compaction 虽然导致 cache 完全失效,但由于 prompt 大幅缩短,即使全量 cache write 的成本也远低于溢出前每次请求的成本。
9.4 总结
OpenClaw 的设计在 cache 效率 和 上下文管理 之间取得了合理的平衡。最关键的缓解机制是 Context Pruning 的 TTL 与 Provider Cache 周期对齐——这确保了最频繁的上下文修改操作不会浪费有效的 cache。而 Compaction 虽然代价最大,但它本身就是一个"救命"操作,执行后 prompt 缩短带来的长期成本节约远超一次 cache miss 的损失。
猜你所想彩蛋时刻
-End-
原创作者|杨柏
感谢你读到这里,不如关注一下?👇
你对本文内容有哪些看法?同意、反对、困惑的地方是?欢迎留言,我们将邀请作者针对性回复你的评论,欢迎评论留言补充。我们将选取1则优质的评论,送出腾讯云定制文件袋套装1个(见下图)。3月11日中午12点开奖。
扫码领取腾讯云开发者专属服务器代金券!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/212479.html