8.OpenClaw源码解析——三层洋葱重试

上节课我们学习了可靠消息投递,当大模型回复完消息之后,会将消息进行chunk切分(这里主要还是防止消息过长)并写入tmp.json, 之后后台会启一个线程,并扫描特定文件夹下的*.json文件,再发送,如果成功则删除该临时JSON文件。 但是如果大模型本身就有问题怎么办? 比如API Key被限流,Key失效,上下文超长,请求超时等。 今天我们来讲三层洋葱重试,他是保障OpenClaw能够正常调用的机制。 分别使用了key轮换压缩上下文agent执行

1.三层重试

1.1. 🧅 三层结构总览

层级 名称 职责
Layer 3 Agent 执行层 最核心、最基础,直接调用 LLM,不负责容错,异常向上抛出
Layer 2 上下文压缩层 处理上下文过长问题(截断 tool_result、压缩旧对话为摘要)
Layer 1 Key 轮换层 处理认证/计费/限流/超时等故障,将失败 Profile 置入冷却并切换下一可用 Key

调用顺序:Layer 3 →(若异常)→ Layer 2 →(若仍异常)→ Layer 1,就像剥洋葱,外层为内层兜底。

1.2. Layer3: Agent执行层

这是最内层,也是实际执行层 。它直接向 LLM 发送消息并处理工具调用,自身不捕获任何 API 异常,所有异常都会原样向外层传播。

python 复制代码
# Layer 3: 标准工具调用循环
# 运行 s01/s02 中的 while True + stop_reason 模式
# end_turn 时返回 (final_response, updated_messages)
# 任何 API 异常都向外层传播

current_messages = list(messages)
iteration = 0

while iteration < self.max_iterations:
    iteration += 1

    # 1. 将内部消息格式转换为 LangChain 消息格式
    from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
    
    lc_messages = [SystemMessage(content=system)]
    for msg in current_messages:
        if msg["role"] == "user":
            lc_messages.append(HumanMessage(content=msg["content"]))
        elif msg["role"] == "assistant":
            ai_msg = AIMessage(content=msg["content"])
            if "tool_calls" in msg and msg["tool_calls"]:
                ai_msg.tool_calls = msg["tool_calls"]
            lc_messages.append(ai_msg)  
        elif msg["role"] == "tool":
            lc_messages.append(ToolMessage(content=msg["content"], tool_call_id=msg.get("tool_call_id", "")))
    
    # 2. 绑定工具,发起调用(此处可能抛出 API 异常)
    client_with_tools = api_client.bind_tools(tools)
    response = client_with_tools.invoke(lc_messages)

    # 3. 处理响应 ------ 工具调用 或 直接回复
    if hasattr(response, 'tool_calls') and response.tool_calls:
        # 有工具调用请求
        tool_calls_info = []
        for tool_call in response.tool_calls:
            tool_calls_info.append({
                'id': tool_call.get('id', ''),
                'name': tool_call['name'],
                'args': tool_call['args']
            })
        
        # 保存助手消息(含工具调用请求)
        assistant_content = response.content if response.content else ""
        current_messages.append({
            "role": "assistant", 
            "content": assistant_content, 
            "tool_calls": tool_calls_info
        })
        
        # 执行工具调用,收集结果
        tool_results = []
        for tool_call in response.tool_calls:
            result = process_tool_call(tool_call['name'], tool_call['args'])
            tool_results.append({
                "role": "tool",
                "tool_call_id": tool_call.get('id', ''),
                "name": tool_call['name'],
                "content": result
            })
        # 将工具结果作为 user 角色消息追加,继续循环
        current_messages.append({
            "role": "user",
            "content": tool_results,
        })
        continue  # 进入下一轮,让 LLM 处理工具结果

    elif hasattr(response, 'content') and response.content:
        # 无工具调用,直接返回最终文本回复
        assistant_content = response.content
        current_messages.append({
            "role": "assistant",
            "content": assistant_content,
        })
        return response, current_messages

    else:
        # 意外响应(如空内容),视为 end_turn
        return response, current_messages

# 超过最大迭代次数仍无结果,抛出运行时错误
raise RuntimeError(
    f"Tool-use loop exceeded {self.max_iterations} iterations"
)
  • 单纯执行:不处理任何重试或降级。
  • 异常上抛 :无论 invoke 抛出的认证错误、限流错误、超时错误还是上下文过长错误,都直接抛出,交给外层。
  • 工具循环:支持多轮工具调用,直至得到最终文本回复或达到迭代上限。

Layer 2:上下文压缩层

当 Layer 3 因上下文过长 (如 context_length_exceeded)而抛出异常时,外层会先进入本层进行处理。本层主要有两步:

  1. 截断过长的工具调用结果(tool_result
  • 如果某次工具调用返回的结果文本过长(超过预设阈值),则进行截断 (例如保留前 N 个字符,后加 ...(truncated))。
  • 这能有效压缩单条消息体积,避免因单条结果过大撑爆上下文窗口。
  1. 历史对话摘要压缩
  • 当整个对话历史累积过长时,将较旧的对话轮次 抽取出来,调用 LLM 生成一份简洁的摘要
  • 然后将摘要作为系统消息或用户消息替换掉原来的多轮详细对话,保留最新几轮完整上下文,其余用摘要替代。
  • 这样既保留了核心信息,又大幅降低了 token 消耗。

压缩后的消息会重新交给 Layer 3 重试,如果仍然过长,可递归压缩或最终交由 Layer 1 处理(但通常压缩后即可恢复)。

Layer1: Key 轮换层(处理认证、计费、限流、超时等)

当 Layer 2 也无法解决(或异常不属于上下文过长),或者压缩后依然报错,则由最外层 Key 轮换层 接管。

它负责识别故障类型,并将当前 Auth Profile 置入冷却(cooldown),然后自动切换到下一个可用 Profile(API Key / 端点)重试。

故障冷却

python 复制代码
elif reason in (FailoverReason.auth, FailoverReason.billing):
    # 认证失败(Key 无效/过期)或计费问题 → 冷却 5 分钟
    self.profile_manager.mark_failure(
        profile, reason, cooldown_seconds=300
    )
    break  # 尝试下一个 Profile

elif reason == FailoverReason.rate_limit:
    # 被限流(Rate Limit) → 冷却 2 分钟
    self.profile_manager.mark_failure(
        profile, reason, cooldown_seconds=120
    )
    break  # 尝试下一个 Profile

elif reason == FailoverReason.timeout:
    # 请求超时 → 冷却 1 分钟(通常网络波动,短冷却即可)
    self.profile_manager.mark_failure(
        profile, reason, cooldown_seconds=60
    )
    break  # 尝试下一个 Profile

else:
    # 未知故障 → 保守冷却 2 分钟
    self.profile_manager.mark_failure(
        profile, reason, cooldown_seconds=120
    )
    break

make failure的核心实现

python 复制代码
def mark_failure(
    self,
    profile: AuthProfile,
    reason: FailoverReason,
    cooldown_seconds: float = 300.0,
) -> None:
    """在失败后将配置置入冷却状态。

    默认冷却 5 分钟(适用于 auth/billing 类问题)。
    调用方可根据具体故障类型传入不同冷却时长(如超时用 60 秒)。
    """
    profile.cooldown_until = time.time() + cooldown_seconds
    profile.failure_reason = reason.value
    print_resilience(
        f"Profile '{profile.name}' -> cooldown {cooldown_seconds:.0f}s "
        f"(reason: {reason.value})"
    )

由此可知,整个pipeline的流程就是:

  1. 捕获 Layer 2/3 抛出的异常,解析出 FailoverReason
  2. 获取当前使用的 Auth Profile。
  3. 调用 mark_failure 将该 Profile 置入冷却(期间不会被选用)。
  4. break 跳出当前 Profile 的重试循环,自动选取下一个未冷却的 Profile。
  5. 使用新 Profile 重新从 Layer 3 开始执行
相关推荐
leeyi1 小时前
把软件装进不能上网的机房——一套建好了、还没上过战场的交付工程(第101篇)
docker·aigc·agent
一点一木2 小时前
🚀 2026 年 8 月 GitHub 十大热门项目排行榜 🔥
人工智能·github
ssshooter2 小时前
现在网页都能提供 MCP 了?!
前端·人工智能·程序员
京东云开发者2 小时前
Claude Code vs Codex 记忆体系深度对比
人工智能
SelectDB2 小时前
为什么 JSON 正在成为分析数据库新的竞争点?
数据库·json·agent
机械改造鹅2 小时前
从零开始拆解Pi系列——(7)Extension API
agent
plainGeekDev3 小时前
Agent代码审查与批量修复流水线
agent·ai编程·claude
不加辣椒3 小时前
第 4 章 什么是 Harness Engineering
人工智能
桃西西呀3 小时前
上下文窗口都卷到 100 万了,大模型为什么还在为"位置"发愁?
人工智能·llm·ai编程
阿拉斯攀登3 小时前
SpringBoot整合MQTT:后端订阅设备数据、解析传感器报文
人工智能