# 函数调用出错处理提升程序健壮性
在执行函数调用的过程中,错误几乎是不可避免的,无论是大语言模型错误地传递了参数,还是工具内部抛出的错误,如果错误不做任何处理操作,都会让程序变得异常脆弱,所以可以考虑在链中构建错误处理/捕获来减轻这些故障出现的概率。
# 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"))
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)
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
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)
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"))
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"}))
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
# 最新版 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,记录工具入参、出参、耗时和异常 |