
关注腾讯云开发者,一手技术干货提前解锁👇
最近很火的 OpenClaw 的出镜率是越来越高了,内外网的技术文章,新产品的问世,Mac Mini 的涨价,自媒体的宣传层出不穷。作者是国外一个叫 Peter Steinberger(现已经被奥特曼高薪挖到 OpenAI ),说是花了一个周末就烹饪完成的“小龙虾”。 随着司内 OpenClaw 也支持了,我也在企微中增加了对应的机器人,给它取名「靓靓蒸虾🦞」,协助处理一些需求管理上的事情。我一直也在做上下文相关的事情,现在我们就拨开虾外壳,看看它内部详细是如何调味的(进行上下文窗口管理的)。

仓库: github.com/openclaw/openclaw 本文基于该仓库源码进行分析。
在 AI Agent 的长会话场景中,上下文窗口溢出是一个绕不开的问题。当对话越来越长、工具调用结果越来越多,LLM 的上下文窗口终将被填满。OpenClaw 为此设计了一套多层防御系统,从"尽量不溢出"到"溢出了也能恢复",覆盖了整个上下文生命周期。本文将从架构到实现细节,完整剖析这套方案。
整体架构
OpenClaw 的上下文管理分为三个阶段,形成递进的防御纵深:
┌────────────────────────┐ ┌─────────────────────┐ ┌──────────────────────────┐
│ LLM 调用前(预防) │ │ LLM 调用中(自动) │ │ LLM 调用后(溢出恢复) │
│ │ │ │ │ │
│ 1. History Turn Limit │ │ 3. SDK 自动 │ │ 5. Overflow 错误检测 │
│ 2. Context Pruning │ │ Compaction │ │ 6. 显式 Compaction │
│ (Tool Result 裁剪) │ │ │ │ 7. 超大 Tool Result 截断 │
│ │ │ │ │ 8. 重试 / 放弃 │
└────────────────────────┘ └─────────────────────┘ └──────────────────────────┘一个关键的设计原则是渐进式降级:先做轻量级裁剪(只丢弃冗余数据),再尝试 LLM 摘要(有损但保留语义),最后才是暴力截断(只保留头部内容)。每一层只在前一层不够用时才会介入。
第一层:预防性裁剪(发送 LLM 之前)
这一层的目标是:在消息发送给 LLM 之前,尽可能裁剪掉不再需要的冗余内容,避免触发上下文溢出。
2.1 会话历史轮次限制(History Turn Limit)
文件:src/agents/pi-embedded-runner/history.ts
这是最简单、最粗粒度的保护——直接限制保留的用户对话轮次数。
工作原理
limitHistoryTurns() 函数从消息列表的末尾向前遍历,计数 role === "user" 的消息。当计数超过 limit 时,丢弃更早的所有消息:
export function limitHistoryTurns(
messages: AgentMessage[],
limit: number | undefined,
): AgentMessage[] {
if (!limit || limit <= 0 || messages.length === 0) {
return messages;
}
let userCount = 0;
let lastUserIndex = messages.length;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === "user") {
userCount++;
if (userCount > limit) {
return messages.slice(lastUserIndex);
}
lastUserIndex = i;
}
}
return messages;
}注意这里的截断边界是 lastUserIndex——即被计数的最后一个 user 消息的位置。这意味着截断点始终在一个完整的 user 轮次边界上,不会把一个 user-assistant-toolResult 的三元组截断成碎片。
配置解析
限制数值通过 getHistoryLimitFromSessionKey() 从配置中解析,支持多级覆盖:
per-DM 用户级别覆盖
→ channels.*.dms[userId].historyLimit
provider 级别 DM 默认
→ channels.*.dmHistoryLimit
provider 级别群组/频道默认
→ channels.*.historyLimit这个分层设计使得运营者可以为特定的高频用户设置更严格的限制,同时保持其他用户的默认行为。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 分钟。这意味着:
// extension.ts
if (runtime.settings.mode === "cache-ttl") {
const ttlMs = runtime.settings.ttlMs;
const lastTouch = runtime.lastCacheTouchAt ?? null;
if (!lastTouch || ttlMs <= 0) return undefined;
if (ttlMs > 0 && Date.now() - lastTouch < ttlMs) return undefined;
}两级裁剪策略
核心逻辑在 pruneContextMessages() 中,使用字符占比作为触发条件:
阶段 | 触发条件 | 行为 | 效果 |
|---|---|---|---|
Soft Trim | totalChars / charWindow > 0.3 | 对超过 4000 字符的旧 tool result,保留首 1500 + 尾 1500 字符,中间用 ... 替代 | 保留关键信息(开头通常是命令/文件头,结尾通常是结论/错误),丢弃中间冗余 |
Hard Clear | totalChars / charWindow > 0.5 | 将整个 tool result 替换为 "[Old tool result content cleared]" | 彻底释放空间,仅保留"这里曾经有一个工具调用"的标记 |
其中 charWindow = contextWindowTokens × 4(使用 1 token ≈ 4 字符的粗略估算)。
Soft Trim 的截断实现很精巧——它不是简单地 slice,而是在文本块(text content block)的层面操作,分别从头部和尾部取字符,保持换行符的完整性:
function softTrimToolResultMessage(params) {
const parts = collectTextSegments(msg.content);
const rawLen = estimateJoinedTextLength(parts);
if (rawLen <= settings.softTrim.maxChars) return null;
const head = takeHeadFromJoinedText(parts, headChars);
const tail = takeTailFromJoinedText(parts, tailChars);
const trimmed = `${head}\n...\n${tail}`;
const note = `\n\n[Tool result trimmed: kept first ${headChars} chars and last ${tailChars} chars of ${rawLen} chars.]`;
return { ...msg, content: [asText(trimmed + note)] };
}裁剪范围与保护规则
并非所有消息都会被裁剪,有几条硬性保护规则:
Hard Clear 还有一个额外的安全阈值:只有当可裁剪的工具结果总量超过 minPrunableToolChars(默认 50,000 字符)时才会执行,避免对少量工具结果做无意义的清理。
默认参数一览
// settings.ts
{
mode: "cache-ttl",
ttlMs: 5 * 60 * 1000, // 5 分钟缓存 TTL
keepLastAssistants: 3, // 保护最近 3 条 assistant 消息的 tool results
softTrimRatio: 0.3, // 总字符占比 > 30% 时触发 soft trim
hardClearRatio: 0.5, // 总字符占比 > 50% 时触发 hard clear
minPrunableToolChars: 50_000, // hard clear 前需要可裁剪 tool result ≥ 50K 字符
softTrim: {
maxChars: 4_000, // tool result 超过 4K 字符才触发 soft trim
headChars: 1_500, // 保留首 1500 字符
tailChars: 1_500, // 保留尾 1500 字符
},
hardClear: {
enabled: true,
placeholder: "[Old tool result content cleared]",
},
}2.3 单条 Tool Result 截断
文件:src/agents/pi-embedded-runner/tool-result-truncation.ts
Context Pruning 处理的是"很多旧的 tool result 累加起来太大"的情况,而单条截断处理的是另一种场景——单条工具返回了巨量内容(比如读取了一个大文件或执行了一个产生大量输出的命令)。
大小限制
MAX_TOOL_RESULT_CONTEXT_SHARE = 0.3 // 单条 tool result 最多占上下文窗口的 30%
HARD_MAX_TOOL_RESULT_CHARS = 400_000 // 绝对上限 400K 字符(约 100K tokens)
MIN_KEEP_CHARS = 2_000 // 截断时至少保留前 2000 字符最终限制取两者中的较小值:
export function calculateMaxToolResultChars(contextWindowTokens: number): number {
const maxTokens = Math.floor(contextWindowTokens * MAX_TOOL_RESULT_CONTEXT_SHARE);
const maxChars = maxTokens * 4; // 1 token ≈ 4 chars 估算
return Math.min(maxChars, HARD_MAX_TOOL_RESULT_CHARS);
}对于一个 200K token 上下文窗口的模型,单条 tool result 最大约为 200K × 0.3 × 4 = 240K 字符。对于 2M 上下文的模型,理论值 2.4M 会被 HARD_MAX_TOOL_RESULT_CHARS 限制到 400K。
截断策略
截断时保留内容的头部(开头通常包含最重要的信息),并尽量在换行符处断开以避免切断行中间:
export function truncateToolResultText(text, maxChars, options) {
const keepChars = Math.max(minKeepChars, maxChars - suffix.length);
let cutPoint = keepChars;
// 尝试在 80% 范围内找到最近的换行符
const lastNewline = text.lastIndexOf("\n", keepChars);
if (lastNewline > keepChars * 0.8) {
cutPoint = lastNewline;
}
return text.slice(0, cutPoint) + suffix;
}截断后追加一段提示信息,引导模型通过 offset/limit 等参数获取更多内容:
⚠️ [Content truncated — original was too large for the model's context window.
The content above is a partial view. If you need more, request specific sections
or use offset/limit parameters to read smaller chunks.]多文本块的比例分配
一个 tool result 可能包含多个 text content block。此时截断预算会按各 block 的原始长度比例分配:
// truncateToolResultMessage() 中
const blockShare = textBlock.text.length / totalTextChars;
const blockBudget = Math.max(
minKeepChars + suffix.length,
Math.floor(maxChars * blockShare),
);两种截断模式
模式 | 函数 | 场景 | 持久化 |
|---|---|---|---|
内存级 | truncateOversizedToolResultsInMessages() | LLM 调用前的预防性守卫 | 不修改 session 文件 |
持久级 | truncateOversizedToolResultsInSession() | 溢出恢复时的最后手段 | 修改 session 文件(通过分支重写) |
持久级截断的实现很有意思——它通过 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:
session_before_compact 事件触发
│
├── 1. 前置检查
│ ├── 解析 model(ctx.model → fallback runtime.model)
│ └── 获取 API key → 任一缺失则 cancel,保留原始历史
│
├── 2. 收集元数据
│ ├── 文件操作记录:read/edited/written → readFiles + modifiedFiles
│ └── 工具失败记录:最多 8 条,每条 ≤ 240 字符
│
├── 3. 历史预裁剪(可选)
│ └── 当新内容 > maxHistoryShare(50%) 的上下文窗口时
│ ├── 丢弃最老的 chunk
│ ├── 修复 tool_use/tool_result 配对
│ └── 对被丢弃消息单独做摘要 → droppedSummary
│
├── 4. 分段摘要(summarizeInStages)
│ └── messagesToSummarize → chunks → per-chunk 摘要 → 合并
│
├── 5. Split Turn 处理(可选)
│ └── 如果是分裂轮次,额外摘要 turnPrefixMessages
│
└── 6. 组装最终 summary
├── 对话摘要文本
├── Tool Failures 列表
├── <read-files> / <modified-files> 文件操作
└── <workspace-critical-rules> AGENTS.md 关键规则摘要失败保护
整个流程用 try-catch 包裹,任何异常都会导致 { cancel: true }——取消 compaction,保留原始历史。这是一个重要的设计决策:宁可让上下文溢出(进入溢出恢复流程),也不要因为摘要失败而丢失历史。
3.3 摘要生成算法详解
分段摘要(summarizeInStages)
当消息量较大时,不能一次性把所有消息送给 LLM 做摘要(摘要请求自身也有上下文窗口限制)。summarizeInStages 采用分而治之的策略:
消息列表
→ splitMessagesByTokenShare (按 token 均分为 N 个 chunk)
→ 逐 chunk 调用 summarizeWithFallback 生成摘要
→ 如果产生多个 partial summaries
→ 将各摘要作为 user 消息送入 LLM 做合并摘要
→ 使用 MERGE_SUMMARIES_INSTRUCTIONS 指导合并关键参数:
const DEFAULT_PARTS = 2; // 默认分 2 段
const BASE_CHUNK_RATIO = 0.4; // 每个 chunk 最多占上下文窗口的 40%
const MIN_CHUNK_RATIO = 0.15; // 最小 15%
const SAFETY_MARGIN = 1.2; // 20% 安全裕量
const SUMMARIZATION_OVERHEAD_TOKENS = 4096; // 预留 4096 tokens 给摘要 prompt 本身splitMessagesByTokenShare 的分割算法按 token 总量均分,确保每个 chunk 的 token 数接近 totalTokens / parts。分割点在消息边界上,不会切断单条消息。
自适应 chunk 大小
如果消息平均体积很大(比如用户频繁读取大文件),固定的 chunk 比例可能仍然导致单个 chunk 溢出摘要模型。computeAdaptiveChunkRatio 会动态缩小 chunk 比例:
export function computeAdaptiveChunkRatio(messages, contextWindow) {
const avgTokens = totalTokens / messages.length;
const safeAvgTokens = avgTokens * SAFETY_MARGIN; // 乘以 1.2 安全系数
const avgRatio = safeAvgTokens / contextWindow;
// 当平均消息大小 > 上下文窗口的 10% 时,开始缩小
if (avgRatio > 0.1) {
const reduction = Math.min(avgRatio * 2, BASE_CHUNK_RATIO - MIN_CHUNK_RATIO);
return Math.max(MIN_CHUNK_RATIO, BASE_CHUNK_RATIO - reduction);
}
return BASE_CHUNK_RATIO; // 默认 0.4
}举例:如果平均消息占上下文窗口的 20%(avgRatio = 0.2),那么 reduction = 0.4,chunk ratio 被缩小到 MIN_CHUNK_RATIO = 0.15。每个 chunk 只会放 15% 上下文窗口大小的内容。
超大消息的三级降级(summarizeWithFallback)
对于包含极大单条消息的情况,摘要可能直接失败。summarizeWithFallback 实现了三级降级:
Level 1: 尝试全量摘要
↓ 失败(超时/溢出/API 错误)
Level 2: 剔除超大消息(单条 > 50% 上下文窗口),只摘要剩余小消息
+ 追加 "[Large assistant (~150K tokens) omitted from summary]" 标注
↓ 仍然失败
Level 3: 返回兜底文本
"Context contained N messages (M oversized). Summary unavailable due to size limits."每一级都能产出一个结果,不会让 compaction 因为单条超大消息而完全中断。
摘要调用的容错
每个 chunk 的摘要调用通过 retryAsync 封装,具有内建重试机制:
summary = await retryAsync(
() => generateSummary(chunk, model, reserveTokens, apiKey, signal, customInstructions, summary),
{
attempts: 3,
minDelayMs: 500,
maxDelayMs: 5000,
jitter: 0.2,
label: "compaction/generateSummary",
shouldRetry: (err) => !(err instanceof Error && err.name === "AbortError"),
},
);最多重试 3 次,退避延迟从 500ms 到 5000ms,附加 20% 的抖动避免雷同重试。只有 AbortError(用户主动取消)不重试。
3.4 历史裁剪预处理(pruneHistoryForContextShare)
在开始摘要之前,如果待摘要的消息总量太大,需要先做一轮预裁剪。这发生在新内容(摘要后需要保留的部分)已经占用了超过 maxHistoryShare(默认 50%)的上下文窗口时。
裁剪算法:
1. 计算 budgetTokens = contextWindowTokens × maxHistoryShare
2. while (消息总 token > budgetTokens):
a. 将消息按 token 均分为 N 段(默认 2)
b. 丢弃第一个(最老的)chunk
c. 调用 repairToolUseResultPairing 修复孤立的 tool_result
d. 统计丢弃量
3. 对所有被丢弃的消息做单独摘要 → droppedSummary
4. droppedSummary 作为 previousSummary 传递给后续的主摘要流程这里的 repairToolUseResultPairing 至关重要——丢弃消息后,可能出现 tool_result 的对应 tool_use(在 assistant 消息中)已被丢弃的情况。Anthropic 的 API 会严格检查配对关系,孤立的 tool_result 会导致 "unexpected tool_use_id" 错误。修复函数会:
3.5 Compaction Summary 的结构化输出
最终生成的 summary 不仅仅是对话摘要。OpenClaw 在摘要文本后附加了结构化信息,确保 compaction 后 AI 仍然知道"自己做过什么":
[对话摘要文本]
## Tool Failures ← 工具失败记录(最多 8 条)
- bash (exitCode=1): command not found...
- read_file (status=error): file too large
<read-files> ← 已读文件列表
src/foo.ts
src/bar.ts
</read-files>
<modified-files> ← 已修改文件列表
src/baz.ts
</modified-files>
<workspace-critical-rules> ← AGENTS.md 中的关键规则
...Session Startup / Red Lines 内容... (限制 ≤ 2000 字符)
</workspace-critical-rules>这些附加信息的意义:
3.6 安全保护机制
Compaction 涉及将对话历史送入 LLM 处理,有专门的安全考虑:
永远不将 toolResult.details 送入 LLM 做摘要。details 可能包含不可信的 payload(如外部 API 返回的原始数据),防止 prompt injection第三层:溢出后恢复
文件:src/agents/pi-embedded-runner/run.ts
即使有了预防性裁剪和主动压缩,仍然可能出现上下文溢出——比如模型的实际 token 计数与估算的 chars/4 启发式有较大偏差,或者 SDK 自动 compaction 后上下文仍然超限。
溢出检测
通过 isLikelyContextOverflowError() 检测 LLM 返回的错误是否为上下文溢出。检测逻辑覆盖两个来源:
恢复决策树
检测到 context overflow 错误
│
├── 分支 A: 本次 attempt 内 SDK 已自动 compaction?
│ └── 是 → 增加 overflowCompactionAttempts 计数
│ └── 直接重试 prompt(不再额外 compact,避免重复压缩)
│
├── 分支 B: 本次 attempt 内无 auto-compact?
│ └── overflowCompactionAttempts < 3?
│ ├── 是 → 执行显式 compaction(trigger: "overflow")
│ │ ├── 成功 → 重试 prompt
│ │ └── 失败 → 进入 Fallback
│ └── 否 → 进入 Fallback
│
├── Fallback: 检测是否有超大 tool result
│ └── sessionLikelyHasOversizedToolResults()
│ ├── 有 → truncateOversizedToolResultsInSession()
│ │ ├── 截断成功 → 重试 prompt
│ │ └── 截断无效 → 放弃
│ └── 无 → 放弃
│
└── 所有手段用尽 → 返回错误:
"Context overflow: prompt too large for the model.
Try /reset (or /new) to start a fresh session,
or use a larger-context model."恢复约束
Token 估算策略
OpenClaw 使用 chars / 4 的启发式方法估算 token 数量(即 1 token ≈ 4 字符),这是一个有意为之的简化:
在 chunkMessagesByMaxTokens 中:
const effectiveMax = Math.max(1, Math.floor(maxTokens / SAFETY_MARGIN));在 compaction-safeguard 中,计算历史裁剪阈值时也会应用安全系数:
const maxHistoryTokens = Math.floor(contextWindowTokens * maxHistoryShare * SAFETY_MARGIN);此外,stripToolResultDetails() 在估算前移除 toolResult.details,避免不可信的大体积附加数据干扰估算和摘要。
配置项汇总
配置路径 | 说明 | 默认值 |
|---|---|---|
agents.defaults.contextTokens | 上下文窗口上限覆盖 | 模型默认值 |
agents.defaults.compaction.reserveTokens | compaction 后为新回复保留的 token | 20,000 |
agents.defaults.compaction.reserveTokensFloor | reserveTokens 下限 | 20,000 |
agents.defaults.compaction.keepRecentTokens | 保留最近消息的 token 数 | SDK 默认 |
agents.defaults.contextPruning.mode | pruning 模式 | "cache-ttl" |
agents.defaults.contextPruning.ttl | 缓存 TTL | "5m" |
agents.defaults.contextPruning.keepLastAssistants | 保护最近 N 条 assistant 消息 | 3 |
agents.defaults.contextPruning.softTrimRatio | soft trim 触发阈值 | 0.3 |
agents.defaults.contextPruning.hardClearRatio | hard clear 触发阈值 | 0.5 |
channels.*.dmHistoryLimit | DM 会话历史轮次限制 | 无限制 |
channels.*.historyLimit | 群组会话历史轮次限制 | 无限制 |
全景流程图
┌─────────────────────────────────┐
│ 用户消息进入会话 │
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ History Turn Limit │ ← 最简粗粒度截断
│ (只保留最近 N 轮用户对话) │
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ Context Pruning (soft/hard) │ ← 渐进式裁剪旧 tool results
│ - Soft: 保留 head+tail │ 5 分钟 TTL 节流
│ - Hard: 替换为占位符 │ ratio 阈值 0.3/0.5
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ 单条 Tool Result 截断 │ ← 内存级预防守卫
│ (单条 ≤ 30% 上下文窗口) │ ≤ 400K 字符硬顶
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ 发送给 LLM │
└────────────┬────────────────────┘
│
成功 ◄──┤──► 溢出错误
│
┌────────────▼────────────────────┐
│ Compaction(LLM 生成摘要) │
│ 1. 自适应 chunk 大小 │
│ 2. 分段摘要 + 摘要合并 │
│ 3. 超大消息三级降级 │
│ 4. 附加文件操作 + 工具失败信息 │
│ 5. 附加工作区关键规则 │
│ 6. 修复 tool_use/result 配对 │
└────────────┬────────────────────┘
│
成功 ◄──┤──► 仍然溢出?
│
┌────────────▼────────────────────┐
│ 截断超大 Tool Results │ ← 最后手段
│ (持久级修改 session 文件) │ 通过 branching 重写
└────────────┬────────────────────┘
│
成功 ◄─┤─► 放弃(提示用户 /reset)核心设计思路
附录:上下文管理对 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 分钟)精确对齐:
// Context Pruning 默认 TTL
ttlMs: 5 * 60 * 1000 // 5 分钟
// Anthropic cache retention 默认
cacheRetention: "short" // 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):
场景 | input 单价 | cache read 单价 | cache write 单价 |
|---|---|---|---|
正常(cache hit) | $5/M | $0.5/M | — |
Cache miss(新内容) | $5/M | — | $6.25/M |
假设一次 100K token 的请求:
操作 | Cache Hit 率 | 成本估算 |
|---|---|---|
正常对话(无 pruning/compaction) | ~90%+ | ~$0.10(90K cache read + 10K input) |
Pruning 执行后的第一次请求 | 0%(全量 write) | ~$0.63(100K cache write) |
Compaction 后(prompt 缩至 30K) | 0%(全量 write) | ~$0.19(30K cache write) |
关键观察:Compaction 虽然导致 cache 完全失效,但由于 prompt 大幅缩短,即使全量 cache write 的成本也远低于溢出前每次请求的成本。
9.4 总结
操作 | Cache 影响 | 缓解措施 | 成本评估 |
|---|---|---|---|
History Turn Limit | 低(一致性截断) | 无需特殊处理 | 几乎无额外成本 |
Context Pruning | 中等(修改中间内容) | TTL 对齐 Provider cache 周期 | 仅在 cache 过期后触发 |
Tool Result 截断 | 中等(单次 miss) | 只在溢出恢复时使用 | 一次性成本,可接受 |
Compaction | 高(完全重建) | prompt 大幅缩短补偿 | 长期看反而降低总成本 |
OpenClaw 的设计在 cache 效率 和 上下文管理 之间取得了合理的平衡。最关键的缓解机制是 Context Pruning 的 TTL 与 Provider Cache 周期对齐——这确保了最频繁的上下文修改操作不会浪费有效的 cache。而 Compaction 虽然代价最大,但它本身就是一个"救命"操作,执行后 prompt 缩短带来的长期成本节约远超一次 cache miss 的损失。
猜你所想彩蛋时刻
-End-
原创作者|杨柏
感谢你读到这里,不如关注一下?👇

你对本文内容有哪些看法?同意、反对、困惑的地方是?欢迎留言,我们将邀请作者针对性回复你的评论,欢迎评论留言补充。我们将选取1则优质的评论,送出腾讯云定制文件袋套装1个(见下图)。3月11日中午12点开奖。

扫码领取腾讯云开发者专属服务器代金券!





