用例(User Case)
工具使用(ToolUse)
工具错误处理(ToolErrorHandling)

工具错误处理

使用模型调用工具存在一些明显的潜在故障模式。首先,模型需要返回一个可解析的输出。其次,模型需要返回有效的工具参数。

我们可以在我们的链中构建错误处理来减轻这些故障模式。

设置

我们需要安装以下软件包:

%pip install --upgrade --quiet langchain langchain-openai

并设置这些环境变量:

import getpass
import os
 
os.environ["OPENAI_API_KEY"] = getpass.getpass()
 
# 如果您想使用LangSmith,请取消下面的注释:
# os.environ["LANGCHAIN_TRACING_V2"] = "true"
# os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()

假设我们有以下(虚拟的)工具和工具调用链。我们将故意让工具复杂以尝试使模型出错。

# 定义工具
from langchain_core.tools import tool
 
 
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
    """使用复杂的工具做一些复杂的事情。"""
    return int_arg * float_arg
# 定义模型并绑定工具
from langchain_openai import ChatOpenAI
 
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
model_with_tools = model.bind_tools(
    [complex_tool],
    tool_choice="complex_tool",
)
# 定义链式调用
from operator import itemgetter
 
from langchain.output_parsers import JsonOutputKeyToolsParser
from langchain_core.runnables import Runnable, RunnableLambda, RunnablePassthrough
 
chain = (
    model_with_tools
    | JsonOutputKeyToolsParser(key_name="complex_tool", first_tool_only=True)
    | complex_tool
)

我们可以看到,即使我们尝试使用一个相当明确的输入调用此链,模型也无法正确调用工具(它忘记了dict_arg参数)。

chain.invoke(
    "使用复杂的工具。参数为5、2.1、空字典。不要忘记dict_arg"
)
ValidationError: 调用工具时出错。以下是引发的错误:

<class 'pydantic.v1.error_wrappers.ValidationError'>: 1个验证错误 for complex_toolSchemaSchema
dict_arg
  field required (type=value_error.missing)

使用try/except调用工具

更优雅地处理错误的最简单方法是在调用工具的步骤中使用try/except,并在发生错误时返回一条有用的消息。

from typing import Any
 
from langchain_core.runnables import RunnableConfig
 
 
def try_except_tool(tool_args: dict, config: RunnableConfig) -> Runnable:
    try:
        complex_tool.invoke(tool_args, config=config)
    except Exception as e:
        return f"使用以下参数调用工具:\n\n{tool_args}\n\n引发以下错误:\n\n{type(e)}: {e}"
 
 
chain = (
    model_with_tools
    | JsonOutputKeyToolsParser(key_name="complex_tool", first_tool_only=True)
    | try_except_tool
)
print(
    chain.invoke(
        "使用复杂的工具。参数为5、2.1、空字典。不要忘记dict_arg"
    )
)
使用以下参数调用工具:

{'int_arg': 5, 'float_arg': 2.1}

引发以下错误:

<class 'pydantic.v1.error_wrappers.ValidationError'>: 1个验证错误 for complex_toolSchemaSchema
dict_arg
  field required (type=value_error.missing)

回退

在工具调用错误的情况下,我们还可以尝试回退到更好的模型。在这种情况下,我们将回退到一个使用 gpt-4-1106-preview 而不是 gpt-3.5-turbo 的相同链。

chain = (
    model_with_tools
    | JsonOutputKeyToolsParser(key_name="complex_tool", first_tool_only=True)
    | complex_tool
)
better_model = ChatOpenAI(model="gpt-4-1106-preview", temperature=0).bind_tools(
    [complex_tool], tool_choice="complex_tool"
)
better_chain = (
    better_model
    | JsonOutputKeyToolsParser(key_name="complex_tool", first_tool_only=True)
    | complex_tool
)
 
chain_with_fallback = chain.with_fallbacks([better_chain])
chain_with_fallback.invoke(
    "使用复杂工具。参数为 5, 2.1, 空字典。不要忘记 dict_arg。"
)

观察这次链式运行的 Langsmith trace (opens in a new tab),我们可以看到第一个链式调用失败了,而回退成功了。

异常重试

为了进一步,我们可以尝试自动重新运行带有传入异常的链式调用,以便模型可以纠正其行为。

import json
from typing import Any
 
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnablePassthrough
 
 
class CustomToolException(Exception):
    """自定义 LangChain 工具异常。"""
 
    def __init__(self, tool_call: dict, exception: Exception) -> None:
        super().__init__()
        self.tool_call = tool_call
        self.exception = exception
 
 
def tool_custom_exception(tool_call: dict, config: RunnableConfig) -> Runnable:
    try:
        return complex_tool.invoke(tool_call["args"], config=config)
    except Exception as e:
        raise CustomToolException(tool_call, e)
 
 
def exception_to_messages(inputs: dict) -> dict:
    exception = inputs.pop("exception")
    tool_call = {
        "type": "function",
        "function": {
            "name": "complex_tool",
            "arguments": json.dumps(exception.tool_call["args"]),
        },
        "id": exception.tool_call["id"],
    }
 
    # 将历史消息添加到原始输入中,以便模型知道它在最后一次工具调用中犯了一个错误。
    messages = [
        AIMessage(content="", additional_kwargs={"tool_calls": [tool_call]}),
        ToolMessage(tool_call_id=tool_call["id"], content=str(exception.exception)),
        HumanMessage(
            content="上次工具调用引发了异常。请尝试使用更正的参数再次调用工具。"
        ),
    ]
    inputs["last_output"] = messages
    return inputs
 
 
# 在提示中添加一个 last_output MessagesPlaceholder,如果不传递它,它不会对提示产生任何影响,但是我们可以选择插入一个任意的消息列表到提示中,如果需要的话。我们将在重试时使用此功能插入错误消息。
prompt = ChatPromptTemplate.from_messages(
    [("human", "{input}"), MessagesPlaceholder("last_output", optional=True)]
)
chain = (
    prompt
    | model_with_tools
    | JsonOutputKeyToolsParser(
        key_name="complex_tool", return_id=True, first_tool_only=True
    )
    | tool_custom_exception
)
 
# 如果初始的链式调用失败,我们将使用传入的异常作为消息重新运行它。
self_correcting_chain = chain.with_fallbacks(
    [exception_to_messages | chain], exception_key="exception"
)
self_correcting_chain.invoke(
    {
        "input": "使用复杂工具。参数为 5, 2.1, 空字典。不要忘记 dict_arg"
    }
)

我们的链式调用成功了!观察这次链式运行的 LangSmith trace (opens in a new tab),我们可以看到我们的初始链式调用仍然失败了,只有在重试时链式调用才成功。