工具调用出错捕获提升程序健壮性

工具调用出错捕获提升程序健壮性

一、这是什么?(概念解释)

在工具调用过程中,可能会出现各种错误:参数类型错误、参数缺失、工具执行失败等。如果不进行错误处理,程序会直接崩溃。

解决方案:通过三种策略提升程序健壮性:

  1. 错误捕获(Try-Except):捕获工具执行异常并返回友好提示
  2. 回退处理(Fallbacks):当主方案失败时,自动切换到备用方案
  3. 错误反馈重试:将错误信息反馈给 LLM,让其自我纠正并重试

二、有什么用?(应用场景)

场景 说明
参数错误 LLM 传递了错误的参数类型或缺失必需参数
API 失败 外部 API 调用失败(网络错误、超时等)
模型切换 当一个模型失败时,自动切换到备用模型
自我纠正 让 LLM 根据错误信息自动修正参数重试
降级处理 当高级功能失败时,降级到简单方案
用户体验 避免程序崩溃,给用户友好的错误提示

三、策略一:错误捕获(Try-Except)

python 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import Any
import dotenv
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()

# 定义工具
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
    """使用复杂工具进行复杂计算操作"""
    return int_arg * float_arg

# 定义带错误处理的工具执行函数
def try_except_tool(tool_args: dict, config: RunnableConfig) -> Any:
    try:
        return complex_tool.invoke(tool_args, config=config)
    except Exception as e:
        # 捕获错误并返回友好提示
        return f"""调用工具时使用以下参数:

{tool_args}

引发了以下错误:

{type(e).__name__}: {e}"""

# 创建模型并绑定工具
llm = ChatOpenAI(model="moonshot-v1-8k", temperature=0)
llm_with_tools = llm.bind_tools([complex_tool])

# 创建链:模型 -> 提取参数 -> 执行工具(带错误处理)
chain = (
    llm_with_tools |
    (lambda msg: msg.tool_calls[0]["args"]) |
    try_except_tool
)

# 调用
result = chain.invoke("使用复杂工具,对应参数为5和2.5")
print(result)
# 如果缺少 dict_arg 参数,会返回:
# 调用工具时使用以下参数: {'int_arg': 5, 'float_arg': 2.5}
# 引发了以下错误: TypeError: missing 1 required positional argument: 'dict_arg'

四、策略二:回退处理(Fallbacks)

python 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()

# 定义工具
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
    """使用复杂工具进行复杂计算操作"""
    return int_arg * float_arg

# 创建两个模型:主模型和备用模型
llm = ChatOpenAI(model="moonshot-v1-8k").bind_tools([complex_tool])
better_llm = ChatOpenAI(model="moonshot-v1-32k").bind_tools([complex_tool])

# 创建两个链:主链和备用链
better_chain = (
    better_llm |
    (lambda msg: msg.tool_calls[0]["args"]) |
    complex_tool
)

chain = (
    llm |
    (lambda msg: msg.tool_calls[0]["args"]) |
    complex_tool
).with_fallbacks([better_chain])  # 关键:设置回退链

# 调用:如果 llm 失败,自动使用 better_llm
result = chain.invoke("使用复杂工具,对应参数为5和2.1,不要忘记了dict_arg参数")
print(result)

回退策略适用场景

  • 主模型失败,切换到备用模型
  • 高级工具失败,降级到简单工具
  • 主 API 失败,切换到备用 API

五、策略三:错误反馈重试(Self-Correction)

python 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import Any
import dotenv
from langchain_core.messages import ToolCall, AIMessage, ToolMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()

# 1. 定义自定义异常
class CustomToolException(Exception):
    def __init__(self, tool_call: ToolCall, exception: Exception) -> None:
        super().__init__()
        self.tool_call = tool_call
        self.exception = exception

# 2. 定义工具
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
    """使用复杂工具进行复杂计算操作"""
    return int_arg * float_arg

# 3. 定义带自定义异常的执行函数
def tool_custom_exception(msg: AIMessage, config: RunnableConfig) -> Any:
    try:
        return complex_tool.invoke(msg.tool_calls[0]["args"], config=config)
    except Exception as e:
        # 抛出自定义异常,携带工具调用信息和原始异常
        raise CustomToolException(msg.tool_calls[0], e)

# 4. 定义异常转换函数:将异常转换为消息列表
def exception_to_messages(inputs: dict) -> dict:
    exception = inputs.pop("exception")

    # 构造消息历史:
    # - AIMessage: 模型决定调用工具
    # - ToolMessage: 工具返回错误信息
    # - HumanMessage: 提示模型不要重复犯错
    messages = [
        AIMessage(content="", tool_calls=[exception.tool_call]),
        ToolMessage(
            tool_call_id=exception.tool_call["id"],
            content=str(exception.exception)
        ),
        HumanMessage(
            content="最后一次工具调用引发了异常,请尝试使用更正的参数再次调用该工具,请不要重复犯错"
        ),
    ]
    inputs["last_output"] = messages
    return inputs

# 5. 创建 Prompt(包含占位符,用于插入错误信息)
prompt = ChatPromptTemplate.from_messages([
    ("human", "{query}"),
    ("placeholder", "{last_output}")  # 占位符,会被错误信息替换
])

# 6. 创建模型并绑定工具
llm = ChatOpenAI(model="moonshot-v1-8k", temperature=0).bind_tools(
    tools=[complex_tool],
    tool_choice="complex_tool"  # 强制使用该工具
)

# 7. 创建主链和回退链
chain = prompt | llm | tool_custom_exception
self_correcting_chain = chain.with_fallbacks(
    [exception_to_messages | chain],  # 失败时:转换异常 -> 重新调用链
    exception_key="exception"
)

# 8. 调用
result = self_correcting_chain.invoke({"query": "使用复杂工具,对应参数为5和2.1"})
print(result)

六、流程对比图

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                    三种错误处理策略流程对比                               │
└─────────────────────────────────────────────────────────────────────────┘


  ================== 策略一:错误捕获 ==================

  用户输入               LLM                  工具执行
     │                   │                      │
     ▼                   ▼                      │
  "调用工具" ────────────▶│ 返回 tool_calls      │
     │                   │                      │
     │ 提取参数           │                      │
     ▼                   │                      │
  try_except_tool() ────────────────────────────▶│
     │                                       执行工具
     │◀──────────────────────────────────────│
     │                   │                      │
     │ [成功]             │                      │
     ▼                   ▼                      ▼
  返回正常结果                                   ✓

  OR

     │ [失败]             │                      │
     │◀──────────────────────────────────────│ 抛出异常
     │                   │                      │
     ▼                   ▼                      ▼
  返回错误信息                                   友好提示


  ================== 策略二:回退处理 ==================

  用户输入               主LLM                工具执行
     │                   │                      │
     ▼                   ▼                      │
  "调用工具" ────────────▶│ 返回 tool_calls      │
     │                   │                      │
     │ 提取参数           │                      │
     ▼                   │                      │
  主链执行 ──────────────────────────────────────▶│
     │                                       执行工具
     │◀──────────────────────────────────────│
     │                   │                      │
     │ [失败]             │                      │
     │                   │                      ▼
  自动切换到备用链                               ✗ 失败
     │                   │
     ▼                   ▼
  备用LLM ──────────────▶│ 重新生成 tool_calls
     │                   │
     ▼                   │
  备用链执行 ──────────────────────────────────────▶│
     │                                       执行工具
     │◀──────────────────────────────────────│
     │                   │                      │
     ▼                   ▼                      ▼
  返回结果                                     ✓ 成功


  ================== 策略三:错误反馈重试 ==================

  用户输入               LLM                  工具执行
     │                   │                      │
     ▼                   ▼                      │
  "调用工具" ────────────▶│ 返回 tool_calls      │
     │                   │                      │
     │ 提取参数           │                      │
     ▼                   │                      │
  执行工具 ──────────────────────────────────────▶│
     │                                       执行工具
     │◀──────────────────────────────────────│
     │                   │                      │
     │ [失败]             │                      │
     │                   │                      ▼
  抛出 CustomToolException                      ✗ 失败
     │
     ▼
  exception_to_messages()
  (构造错误消息)
     │
     ▼
  将错误信息插入 Prompt
     │
     ▼                   │                      │
  重新调用 LLM ──────────▶│ 看到错误信息          │
     │                   │ "参数错误:缺失 dict_arg"
     │                   │                      │
     │                   ▼                      │
     │              重新生成 tool_calls          │
     │              (修正参数)                   │
     │                   │                      │
     │ 提取参数           │                      │
     ▼                   │                      │
  重新执行工具 ───────────────────────────────────▶│
     │                                       重新执行
     │◀──────────────────────────────────────│
     │                   │                      │
     ▼                   ▼                      ▼
  返回结果                                     ✓ 成功


  ┌─────────────────────────────────────────────────────────────────────────┐
  │                         策略选择建议                                     │
  └─────────────────────────────────────────────────────────────────────────┘

  场景                          推荐策略
  ─────────────────────────────────────────────────────────
  简单错误提示                   策略一:错误捕获
  模型/API 备份                  策略二:回退处理
  LLM 自我纠正                   策略三:错误反馈重试
  生产环境健壮性                 策略二 + 策略三 组合

相关推荐
天朝八阿哥1 小时前
使用Docker+vscode搭建离线的go开发调试环境
后端·docker·visual studio code
HelloReader1 小时前
Tauri 命令作用域(Command Scopes)精细化控制你的应用权限
前端
HelloReader1 小时前
Tauri 权限系统从零掌握 Permissions 与 Capabilities
后端
心在飞扬1 小时前
基于工具调用的智能体设计与实现(*)
前端·后端
心在飞扬1 小时前
函数调用快速提取结构化数据使用技巧
前端·后端
是你的小恐龙啊1 小时前
基于 Rust 与 DeepSeek 大模型的智能 API Mock 生成器构建实录:从环境搭建到架构解析
后端
用户020742201751 小时前
从零实现一个简易版 React:深入理解 Fiber 架构与协调算法
后端
心在飞扬1 小时前
不支持函数调用的大语言模型解决技巧
前端·后端
codingWhat1 小时前
如何实现一个「万能」的通用打印组件?
前端·javascript·vue.js