# 函数调用出错处理提升程序健壮性

在执行函数调用的过程中,错误几乎是不可避免的,无论是大语言模型错误地传递了参数,还是工具内部抛出的错误,如果错误不做任何处理操作,都会让程序变得异常脆弱,所以可以考虑在链中构建错误处理/捕获来减轻这些故障出现的概率。

# 01. try/except 捕获工具错误

首先我们先来构建一个 复杂工具 ,并让 LLM 尝试调用这个工具,并故意引发一些错误,示例代码如下:

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="gpt-3.5-turbo-16k", temperature=0)
llm_with_tools = llm.bind_tools([complex_tool])


chain = llm_with_tools | (lambda msg: msg.tool_calls[0]["args"]) | complex_tool


print(chain.invoke("使用复杂工具,对应参数为5和2.1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

输出内容如下:

Traceback (most recent call last):
  File "D:\Project\llmops\llmops-api\study\41-工具调用\1.错误捕获.py", line 29, in <module>
    print(chain.invoke("使用复杂工具,对应参数为5和2.1"))
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\runnables\base.py", line 2875, in invoke
    input = step.invoke(input, config)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\tools.py", line 427, in invoke
    return self.run(tool_input, **kwargs)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\tools.py", line 615, in run
    raise error_to_raise
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\tools.py", line 578, in run
    tool_args, tool_kwargs = self._to_args_and_kwargs(tool_input)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\tools.py", line 501, in _to_args_and_kwargs
    tool_input = self._parse_input(tool_input)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\langchain_core\tools.py", line 454, in _parse_input
    result = input_args.parse_obj(tool_input)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\pydantic\v1\main.py", line 526, in parse_obj
    return cls(**obj)
  File "D:\Project\llmops\llmops-api\venv\lib\site-packages\pydantic\v1\main.py", line 341, in __init__
    raise validation_error
pydantic.v1.error_wrappers.ValidationError: 1 validation error for complex_toolSchema
dict_arg
  field required (type=value_error.missing)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在上面的示例中,因为工具的描述并不清晰,大语言模型只针对前 2 个参数生成了对应的数值,第 3 个参数并没有生成,所以引发了 pydantic 数据校验错误,对于这类错误,通用的处理方法是在工具调用步骤上使用 try/except 进行错误的捕获,并在出错时返回游泳的提示信息,修正代码部分如下:

def try_except_tool(tool_args: dict, config: RunnableConfig) -> Any:
    try:
        return complex_tool.invoke(tool_args, config)
    except Exception as e:
        return f"调用工具时使用以下参数:\n\n{tool_args}\n\n引发了以下错误:\n\n{type(e)}: {e}"

chain = llm_with_tools | (lambda msg: msg.tool_calls[0]["args"]) | try_except_tool
1
2
3
4
5
6
7

输出内容:

调用工具时使用以下参数:

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

引发了以下错误:

<class 'pydantic.v1.error_wrappers.ValidationError'>: 1 validation error for complex_toolSchema
dict_arg
  field required (type=value_error.missing)
1
2
3
4
5
6
7
8
9

在这种模式下,如果将 工具返回的错误消息 重新返回给 LLM 时,LLM 会重新判断并继续执行 函数调用 ,从而将参数补全,让程序正常执行,也是最推荐的一种错误处理方案,即在 工具内部 进行错误的捕获,并将错误信息独立返回。

# 02. 回退与重试处理

前文中,掌握 Runnable 可运行组件时,我们掌握了如果 Runnable 可运行组件出错,可以执行 回退 和 重试 两种策略,在函数调用中也可以使用这两种策略来处理,例如:

  • 在函数调用参数生成错误时,可以考虑回退到一个更好的模型,例如平时使用 gpt-3.5-turbo-16k 模型,在出错时,回退到 gpt-4o 模型上进行重新试验。

  • 亦或者是触发重试机制,并且在重试的时候,携带上错误信息,让 LLM 强大的自然语言处理功能进行自我纠正。

使用更好的模型进行回退,操作起来非常简单,只需要定义一个更强大的模型,并创建一条新链,然后使用 Runnable可运行组件 的 .with_fallbacks() 即可绑定对应的回退策略,示例代码如下:

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="gpt-3.5-turbo-16k").bind_tools([complex_tool])
better_llm = ChatOpenAI(model="gpt-4o").bind_tools(
    [complex_tool], tool_choice="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])


print(chain.invoke("使用复杂工具,对应参数为5和2.1"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

回退策略 并不一定是万能的,有的时候哪怕使用了参数更大的模型,生成的 函数调用 参数依旧不符合规范,这种情况就要考虑下工具的相关描述、Prompt 编写得是否存在问题。

另外一种优化策略是 携带错误信息的重试策略 ,让 LLM 纠正其行为,只需要在抛出错误时,将错误信息一起携带给 LLM,让其重新操作即可,示例代码如下:

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()


class CustomToolException(Exception):
    """自定义的工具错误异常"""

    def __init__(self, tool_call: ToolCall, exception: Exception) -> None:
        super().__init__()
        self.tool_call = tool_call
        self.exception = exception


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


def tool_custom_exception(msg: AIMessage, config: RunnableConfig) -> Any:
    try:
        return complex_tool.invoke(msg.tool_calls[0]["args"], config)
    except Exception as e:
        raise CustomToolException(msg.tool_calls[0], e)


def exception_to_messages(inputs: dict) -> dict:
    
    exception = inputs.pop("exception")
    
    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



prompt = ChatPromptTemplate.from_messages([
    ("human", "{query}"),
    ("placeholder", "{last_output}"),
])


llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools=[complex_tool])


chain = prompt | llm | tool_custom_exception
self_correcting_chain = chain.with_fallbacks(
    [exception_to_messages | chain], exception_key="exception",
)


print(self_correcting_chain.invoke({"query": "使用复杂工具,对应参数为5和2.1"}))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

输出内容:

10.5
1

# 最新版 LangChain 用法提示

  • 新版 LangChain 更推荐用 LCEL、Runnable、ChatModel.bind_tools()、结构化输出和 LangGraph 来组织复杂链路;老式 Chain、部分 AgentExecutor 写法可以读懂,但新项目应优先选择更清晰的图或 Runnable 编排。

  • 工具调用相关代码要区分两层:模型是否原生支持 tool/function calling,以及业务侧如何定义工具 schema、参数校验、错误兜底和观测日志。

  • 如果示例中的导入路径和你当前安装版本不同,优先查当前版本包内导出位置;常见迁移方向是从 langchain 拆到 langchain-core、langchain-community、langchain-openai、langgraph 等包。

# 拓展

  • 工具或插件不要只看能不能调通,更要看是否可观测、可限流、可重试、可审计。联网类工具还要处理超时、空结果、搜索噪声和结果时效性。

  • Agent 场景里,Prompt 只是调度策略的一部分;工具描述、参数 schema、历史状态、错误反馈、停止条件和人工介入点同样会影响最终稳定性。

# 常见问题

  • 为什么模型没有调用工具?常见原因是工具描述不清晰、参数 schema 过宽或过窄、用户问题不需要工具、模型本身不支持工具调用,或者工具绑定位置不对。

  • 为什么工具调用后回答仍然不准?先看工具返回是否正确,再看工具结果是否被放回模型上下文,最后检查输出解析、历史消息和异常兜底是否覆盖了真实错误。

# 面试题

  • 解释函数调用、工具调用和 Agent 的区别。

  • LangChain 中 tool schema 的作用是什么?为什么参数校验对生产环境很重要?

  • ReACT Agent 和 tool-calling Agent 的核心差异是什么?分别适合什么场景?

  • LangGraph 相比 LCEL 更适合解决哪些复杂编排问题?

# 生产问题排查

问题 常见原因 处理方式
工具没有被调用 工具描述弱、绑定失败、模型不支持 打印绑定后的模型配置,补充工具描述,换用支持工具调用的模型
参数格式错误 schema 设计不清晰,模型生成字段不稳定 使用 Pydantic/JSON Schema 校验,失败后把错误反馈给模型重试
联网结果不可用 搜索为空、接口超时、命中低质量页面 增加超时、重试、结果过滤、来源白名单和降级回答
Agent 循环不停止 缺少终止条件或工具返回被误判 设置最大迭代次数,记录每轮 thought/action/observation,增加停止规则
线上难以复现 缺少输入、工具请求和模型响应日志 给每次调用加 trace id,记录工具入参、出参、耗时和异常