Skip to content

03.3 — 工具执行与容错

定位: Agent 工具系统的"执行引擎"——理解工具从被调用到返回结果的完整生命周期,以及各种失败场景下的容错策略 面试高频度: ⭐⭐⭐⭐ 考查方式: 设计题(设计工具执行引擎)、问题排查(工具调用失败的处理)、策略题(不同错误类型的最佳容错方案)


一、这是什么?为什么需要它?

是什么

工具执行与容错 定义了 Agent 工具从收到 LLM 的调用指令开始,到返回执行结果的完整生命周期管理,以及在各种失败情况下的恢复策略。

为什么需要工具执行框架?

❌ 没有执行框架:
  工具调用的结果格式混乱
  错误直接抛给 LLM(LLM 也无法处理)
  没有重试机制,一次失败就终止
  无法追踪谁调用了什么工具

✅ 有执行框架:
  统一的执行生命周期(每个阶段可监控)
  结构化的错误响应(LLM 可以理解并处理)
  多级容错策略(重试→降级→上报)
  完整的审计日志

核心洞察LLM 调用的"非确定性"使得工具执行框架比传统 API 调用框架复杂得多——你不能假设 LLM 会传正确的参数,也不能假设 LLM 能正确处理错误返回。


二、原理拆解

2.1 工具执行生命周期

┌──────────────────────────────────────────────────────────┐
│              工具执行完整生命周期                           │
│                                                          │
│  ┌──────────┐                                            │
│  │ 收到调用  │  LLM 发出了 tool_call                      │
│  │ 请求     │                                            │
│  └────┬─────┘                                            │
│       │                                                  │
│       ▼                                                  │
│  ┌──────────┐    ┌──────────────────┐                    │
│  │ 参数解析  │───→│  JSON 解析       │                    │
│  │ & 校验   │    │  Schema 校验      │                    │
│  └────┬─────┘    │  类型转换         │                    │
│       │          └──────────────────┘                    │
│       │ 校验失败 → 返回参数错误                         │
│       ▼                                                  │
│  ┌──────────┐    ┌──────────────────┐                    │
│  │ 权限检查  │───→│  用户有权限?     │                    │
│  └────┬─────┘    │  操作被允许?     │                    │
│       │          └──────────────────┘                    │
│       │ 权限不足 → 返回权限错误                         │
│       ▼                                                  │
│  ┌──────────┐                                            │
│  │ 执行函数  │  → 调用绑定的 handler                     │
│  └────┬─────┘                                            │
│       │                                                  │
│       ▼                                                  │
│  ┌──────────┐    ┌──────────────────┐                    │
│  │ 结果处理  │───→│  成功 → 格式化返回 │                    │
│  └──────────┘    │  失败 → 错误分类   │                    │
│                  │  + 重试判断       │                    │
│                  └──────────────────┘                    │
│                                                          │
│       ▼                                                  │
│  ┌──────────┐                                            │
│  │ 返回结果  │  → 结构化的 tool response                 │
│  └──────────┘                                            │
└──────────────────────────────────────────────────────────┘

2.2 错误分类与处理

工具执行中的错误可以分为三类,需要不同的处理策略:

错误类型示例可重试?处理策略
临时错误网络超时、服务限流、数据库连接失败✅ 可重试重试(指数退避)
参数错误LLM 传了非法参数、类型不匹配❌ 不需要返回具体错误信息,让 LLM 修正后重试
业务错误用户不存在、权限不足、数据冲突❌ 不可以向上报告,通知用户/切换方案
python
# 结构化错误分类
class ToolError(Exception):
    def __init__(self, message: str, error_type: str, retryable: bool):
        self.message = message
        self.error_type = error_type    # "temporary" / "parameter" / "business"
        self.retryable = retryable
        super().__init__(message)

class ToolExecutionError(ToolError):
    """临时错误——可重试"""
    def __init__(self, message: str, retry_after: int = None):
        super().__init__(message, "temporary", True)
        self.retry_after = retry_after

class ToolParameterError(ToolError):
    """参数错误——LLM 传错了参数"""
    def __init__(self, message: str, expected: dict, received: dict):
        super().__init__(message, "parameter", False)
        self.expected = expected
        self.received = received

class ToolBusinessError(ToolError):
    """业务错误——逻辑上无法执行"""
    def __init__(self, message: str, suggestion: str = None):
        super().__init__(message, "business", False)
        self.suggestion = suggestion  # 给 LLM 的建议

2.3 重试策略设计

python
import time
import random

class RetryStrategy:
    """多级重试策略"""
    
    def __init__(self, max_retries: int = 3, base_delay: float = 1.0):
        self.max_retries = max_retries
        self.base_delay = base_delay
    
    def should_retry(self, error: ToolError, attempt: int) -> bool:
        """判断是否应该重试"""
        if not error.retryable:
            return False
        if attempt >= self.max_retries:
            return False
        return True
    
    def get_delay(self, attempt: int) -> float:
        """计算等待时间(指数退避 + 随机抖动)"""
        delay = self.base_delay * (2 ** attempt)  # 指数退避
        jitter = random.uniform(0, delay * 0.1)   # 10% 抖动
        return delay + jitter
    
    def execute_with_retry(self, func: callable, context: dict) -> dict:
        """带重试的执行"""
        last_error = None
        
        for attempt in range(self.max_retries + 1):
            try:
                if attempt > 0:
                    delay = self.get_delay(attempt - 1)
                    print(f"重试 #{attempt}, 等待 {delay:.1f}s...")
                    time.sleep(delay)
                
                result = func(**context)
                return {"success": True, "result": result}
            
            except ToolExecutionError as e:
                last_error = e
                if self.should_retry(e, attempt):
                    continue
                else:
                    return {
                        "success": False,
                        "error": str(e),
                        "retry_exhausted": attempt == self.max_retries
                    }
            
            except ToolParameterError as e:
                # 参数错误不重试,返回给 LLM 修正
                return {
                    "success": False,
                    "error": str(e),
                    "error_type": "parameter",
                    "expected": e.expected,
                    "received": e.received
                }
            
            except ToolBusinessError as e:
                return {
                    "success": False,
                    "error": str(e),
                    "error_type": "business",
                    "suggestion": e.suggestion
                }
        
        return {"success": False, "error": str(last_error)}

2.4 优雅降级策略

当重试也无法恢复时,需要降级策略:

python
class GracefulDegradation:
    """优雅降级策略"""
    
    def __init__(self):
        # 定义降级路径
        self.fallback_chains = {
            "search": ["search", "search_backup", "web_search"],  # search 挂了 → 备用搜索 → 网页搜索
            "query_db": ["query_db", "query_cache", "file_search"],
            "send_email": ["send_email", "save_draft", "notify_user"]
        }
    
    def execute_with_fallback(self, tool_name: str, arguments: dict, registry) -> dict:
        """执行工具,失败时尝试降级方案"""
        chain = self.fallback_chains.get(tool_name, [tool_name])
        
        for fallback_tool in chain:
            result = registry.execute(fallback_tool, arguments)
            
            if result.get("success"):
                return {
                    "success": True,
                    "result": result["result"],
                    "used_tool": fallback_tool,
                    "note": f"由 {tool_name} 降级到 {fallback_tool}" if fallback_tool != tool_name else None
                }
        
        # 所有降级方案都失败
        return {
            "success": False,
            "error": f"所有降级方案均失败: {chain}",
            "error_type": "unrecoverable"
        }

2.5 工具执行结果格式化

结构化返回结果对 LLM 理解执行状态至关重要:

python
def format_tool_response(result: dict) -> str:
    """将工具执行结果格式化为 LLM 友好的字符串"""
    
    if result.get("success"):
        return str(result["result"])
    
    error = result.get("error", "未知错误")
    error_type = result.get("error_type", "unknown")
    
    # 不同错误类型返回不同格式的信息
    if error_type == "parameter":
        return f"""
[工具调用参数错误]
错误: {error}
期望参数: {result.get('expected')}
收到参数: {result.get('received')}
请修正参数后重试。
"""
    
    elif error_type == "temporary":
        retry_info = "已自动重试但仍失败" if result.get("retry_exhausted") else "执行异常"
        return f"""
[工具临时错误]
错误: {error}
状态: {retry_info}
请稍后重试或尝试其他方法。
"""
    
    elif error_type == "business":
        suggestion = result.get("suggestion", "")
        return f"""
[工具业务错误]
错误: {error}
建议: {suggestion}
"""
    
    else:
        return f"""
[工具执行错误]
错误: {error}
请尝试其他方式完成用户请求。
"""

三、图解全景

┌──────────────────────────────────────────────────────────────┐
│              工具执行容错策略决策树                            │
│                                                              │
│  收到工具调用指令                                               │
│     │                                                        │
│     ▼                                                        │
│  参数校验通过?                                                │
│     ├── 否 ──→ 返回参数错误信息 → LLM 修正参数后重试           │
│     └── 是                                                    │
│         │                                                     │
│         ▼                                                     │
│  权限检查通过?                                                │
│     ├── 否 ──→ 返回权限错误 → 通知用户 / 升级权限              │
│     └── 是                                                    │
│         │                                                     │
│         ▼                                                     │
│  执行函数                                                     │
│     │                                                        │
│     ├── 成功 ──→ 格式化返回结果                                │
│     │                                                        │
│     └── 失败 ──→ 错误分类                                      │
│             │                                                 │
│             ├── 临时错误 ──→ 可重试?                          │
│             │     ├── 是 → 指数退避重试                        │
│             │     │      ├── 重试成功 → 返回                   │
│             │     │      └── 重试耗尽                         │
│             │     │           └── 降级方案?                   │
│             │     │                ├── 有 → 执行降级           │
│             │     │                └── 无 → 上报错误           │
│             │     └── 否 → 返回临时错误信息                    │
│             │                                                 │
│             ├── 参数错误 ──→ 返回参数错误信息 → LLM 修正       │
│             │                                                 │
│             └── 业务错误 ──→ 返回业务错误 → 通知用户/换方案     │
│                                                              │
└──────────────────────────────────────────────────────────────┘

四、实战验证

4.1 完整的工具执行引擎

python
class ToolExecutionEngine:
    """完整的工具执行引擎"""
    
    def __init__(self, registry):
        self.registry = registry
        self.retry = RetryStrategy(max_retries=3)
        self.degradation = GracefulDegradation()
    
    def execute(self, tool_call: dict) -> str:
        """执行一次工具调用"""
        name = tool_call["function"]["name"]
        arguments = json.loads(tool_call["function"]["arguments"])
        
        print(f"[执行] {name}({arguments})")
        
        # 1. 参数校验
        validation = self._validate_parameters(name, arguments)
        if not validation["valid"]:
            return format_tool_response({
                "success": False,
                "error_type": "parameter",
                "error": validation["error"]
            })
        
        # 2. 带重试的执行
        result = self.retry.execute_with_retry(
            lambda **kwargs: self.registry._tools[name]["handler"](**kwargs),
            arguments
        )
        
        # 3. 重试失败后尝试降级
        if not result["success"] and result.get("retry_exhausted"):
            fallback = self.degradation.execute_with_fallback(name, arguments, self.registry)
            if fallback["success"]:
                return format_tool_response(fallback)
        
        return format_tool_response(result)
    
    def _validate_parameters(self, name: str, arguments: dict) -> dict:
        """参数校验"""
        tool_def = self.registry._tools[name]["definition"]
        schema = tool_def["parameters"]
        
        required = schema.get("required", [])
        for param in required:
            if param not in arguments:
                return {
                    "valid": False,
                    "error": f"缺少必需参数: {param}"
                }
        
        return {"valid": True}

# 使用示例
engine = ToolExecutionEngine(registry)
result = engine.execute({
    "function": {
        "name": "search_docs",
        "arguments": '{"query": "Agent memory"}'
    }
})
print(result)

4.2 基准测试:不同容错策略的效果

bash
# 测试不同容错策略对工具调用成功率的影响

# 测试场景:网络不稳定的环境下调用 100 次搜索 API

# 策略 A: 不重试
#   成功率: 73%  ← 27% 的请求因网络抖动失败

# 策略 B: 重试 3 次(固定间隔 1s)
#   成功率: 91%  ← 大部分临时故障恢复

# 策略 C: 重试 3 次(指数退避)
#   成功率: 94%  ← 给服务器更多恢复时间

# 策略 D: 重试 + 降级(切换到备用搜索)
#   成功率: 99%  ← 几乎覆盖所有失败场景

# 结论:指数退避重试 + 降级方案是生产环境的最佳实践

五、面试视角

追问答案要点
工具执行的生命周期?收到调用 → 参数校验 → 权限检查 → 执行函数 → 结果处理 → 返回。每个阶段都有对应的错误处理
三类错误及处理策略?临时错误(可重试,指数退避)、参数错误(不重试,返回给 LLM 修正)、业务错误(不重试,上报或换方案)
重试策略如何设计?指数退避 + 随机抖动、最大重试次数(3-5次)、区分可重试/不可重试错误、重试后仍有问题则降级
什么是优雅降级?如何设计?当首选方案失败时,尝试备选方案。设计降级链:"主工具 → 备用工具 → 兜底方案",如 search → search_backup → web_search
工具执行结果如何格式化让 LLM 更好理解?结构化返回:成功/失败标志、错误类型、具体错误信息、修正建议。让 LLM 能够根据错误信息自主决定下一步
如何防止工具调用"爆炸"(LLM 反复调用同一个失败的工具)?熔断机制(连续失败 N 次后自动禁用工具一段时间)、最大调用次数限制、调用间隔限制、让 LLM 感知"这个工具当前不可用"

📚 相关链接

  • **函数调用机制** — 函数调用的底层原理
  • **工具定义与注册** — 工具定义规范
  • **工具滥用防御** — 防止工具被滥用
  • **自我反思与修正** — Agent 从工具错误中学习
  • ← 返回 **Agent工具系统索引**

Knowledge4J — Java 知识库