上下文溢出 3 层恢复模式

OpenClaw 实现了一个复杂的 3 层恢复策略,用于处理 agent 执行期间的上下文溢出错误。

位置

src/agents/pi-embedded-runner/run.ts:684-845

常量

// 放弃前的最大自动压缩尝试次数
const MAX_OVERFLOW_COMPACTION_ATTEMPTS = 3;

// 令牌估计的安全边际(20% 缓冲)
const SAFETY_MARGIN = 1.2;

// 压缩的最小保留令牌数
const DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR = 20_000;

恢复层级

第 1 层:检测

const contextOverflowError = (() => {
  if (!promptError) return null;

  const errorText = describeUnknownError(promptError);
  if (isLikelyContextOverflowError(errorText)) {
    return {
      source: "provider",
      text: errorText,
    };
  }
  return null;
})();

检测溢出错误来自:

  • 提供商错误响应
  • SDK 自动压缩失败
  • 令牌限制超出消息

第 2 层:自动压缩

最多 3 次尝试压缩会话:

if (overflowCompactionAttempts < MAX_OVERFLOW_COMPACTION_ATTEMPTS) {
  overflowCompactionAttempts++;
  log.warn(
    `检测到上下文溢出(尝试 ${overflowCompactionAttempts}/${MAX_OVERFLOW_COMPACTION_ATTEMPTS});` +
    `尝试为 ${provider}/${modelId} 自动压缩`
  );

  const compactResult = await compactEmbeddedPiSessionDirect({
    sessionId,
    sessionKey,
    sessionFile,
    trigger: "overflow",
    diagId: overflowDiagId,
    attempt: overflowCompactionAttempts,
    maxAttempts: MAX_OVERFLOW_COMPACTION_ATTEMPTS,
    // ... 其他参数
  });

  if (compactResult.compacted) {
    autoCompactionCount += 1;
    log.info(`自动压缩成功;重试提示`);
    continue; // 重试请求
  }
}

压缩策略:

  • 使用 trigger: "overflow" 调用 compactEmbeddedPiSessionDirect()
  • 递增全局尝试计数器
  • 成功时:重试提示
  • 失败时:进入第 3 层

第 3 层:工具结果截断

对于有超大工具结果的会话:

if (!toolResultTruncationAttempted) {
  const contextWindowTokens = ctxInfo.tokens;
  const hasOversized = sessionLikelyHasOversizedToolResults({
    messages: attempt.messagesSnapshot,
    contextWindowTokens,
  });

  if (hasOversized) {
    toolResultTruncationAttempted = true;
    log.warn(
      `[context-overflow-recovery] 尝试为 ${provider}/${modelId} 截断工具结果 ` +
      `(contextWindow=${contextWindowTokens} 令牌)`
    );

    const truncResult = await truncateOversizedToolResultsInSession({
      sessionFile,
      contextWindowTokens,
      sessionId,
      sessionKey,
    });

    if (truncResult.truncated) {
      log.info(
        `[context-overflow-recovery] 截断了 ${truncResult.truncatedCount} 个工具结果;重试提示`
      );
      continue; // 重试而不重置溢出计数器
    }
  }
}

截断策略:

  • 识别超过上下文窗口显著部分的工具结果
  • 截断超大结果,同时保留元数据
  • 重置 overflowCompactionAttempts(保持全局上限强制执行)

第 4 层:中止并报错

如果所有恢复尝试都失败:

const kind = isCompactionFailure ? "compaction_failure" : "context_overflow";
return {
  payloads: [
    {
      text:
        "上下文溢出:提示对模型来说太大。" +
        "尝试 /reset(或 /new)开始新会话,或使用更大上下文的模型。",
      isError: true,
    },
  ],
  meta: {
    durationMs: Date.now() - started,
    agentMeta: { sessionId, provider, model: model.id },
    systemPromptReport: attempt.systemPromptReport,
    error: { kind, message: errorText },
  },
};

面向用户的消息:

  • 清晰解释问题
  • 可操作的建议:/reset/new 或更大模型
  • 保留错误元数据以供调试

诊断日志

每个溢出事件生成一个诊断 ID:

const overflowDiagId = createCompactionDiagId();
log.warn(
  `[context-overflow-diag] sessionKey=${sessionKey} ` +
  `provider=${provider}/${modelId} source=${contextOverflowError.source} ` +
  `messages=${msgCount} sessionFile=${sessionFile} ` +
  `diagId=${overflowDiagId} compactionAttempts=${overflowCompactionAttempts} ` +
  `error=${errorText.slice(0, 200)}`
);

交叉引用