tools 拓展了 agent 的能力,使其能够获取实时数据、执行代码、查询外部数据库,并在现实环境中执行操作。 底层来看,tools 是具备规范输入与输出的可调用函数,会传递给对话模型。模型会根据对话上下文决定何时调用工具,以及提供哪些输入参数。
1. 怎样创建 tools
创建工具最简单的方式是使用
@tool装饰器。默认情况下,函数的文档字符串会作为工具的描述,帮助模型判断何时调用该工具,需要注意的是 类型提示是必需的,因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁,以帮助模型理解工具的用途,以及是否调用这个 tool;
@tool(name?:str, des?:str) 或者 @tool(args_schema=PydanticModel | JsonSchema) 是一个语法糖装饰器,它的核心作用是:把一个普通的 Python 函数,瞬间变成大模型能看懂、能调用的“工具”对象(Tool)。
具体体现在三个方面:
-
自动提取元数据(Schema):它会自动读取函数的名字、函数文档(Docstring,极其重要!)以及参数的类型注解(Type Hints),并自动转换成大模型 API 需要的 JSON 格式描述。
-
标准化接口:将函数封装为 LangChain 的 BaseTool 实例,方便无缝接入 Agent、LangGraph 或直接绑定到 Chat Model。
-
参数校验:底层基于 Pydantic 自动对大模型传过来的参数进行校验,防止类型错误
# lc-tools-an-memory.ipynb
# 创建 chat model 并绑定 agent
import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
from langchain.tools import tool
load_dotenv()
aliyun_base_url = os.getenv("ALIYUN_BASE_URL")
aliyun_api_key = os.getenv("ALIYUN_API_KEY")
model = init_chat_model(
model="qwen3.7-flash-2026-07-15",
model_provider="openai",
base_url=aliyun_base_url,
api_key=aliyun_api_key,
)
@tool
def love_answer(username: str) -> str:
# 类型提示是必需的,因为它们定义了工具的输入模式。文档字符串应信息丰富且简洁,以帮助模型理解工具的用途,让模型知道这个工具是否需要调用,如何使用这个工具
"""如果有用户提问有人爱他吗,那么就直接返回 `这个世界上有很多人爱你,更甚者山川河流也爱你!`
Args:
username (str): 用户名
"""
return f"这个世界上有很多人爱你 {username},更甚者山川河流也爱你!"
agent = create_agent(
model=model,
system_prompt="兼爱非攻、仁者爱人",
tools=[love_answer],
)
response = agent.invoke(
{"messages": [HumanMessage("天明问有人爱他吗?")]},
)
print("\nresponse tool_calls: \n", response['messages'][1].tool_calls)
print("\nresponse content: \n", response['messages'][-1].content)
从 agent.invoke 可以看到 message 中的 tool_calls 可以将 tool 解析初 name 和 args 还有一些 tool 的元数据。
@tool 的 description 参数覆盖自动生成的工具描述,为模型提供更清晰的指引:
@tool 的第一个参数 默认情况下,工具名称取自函数名。当你需要更具描述性的名称时可对其进行重写
⚠️:
config/runtime不可用作工具参数。使用这些名称会引发运行时错误。
2. 怎么做 tools 参数验证
Pydantic 模型用作 tools 的数据校验
# 其他的 tools 使用 Pydantic 模型或 JSON 模式定义复杂输入
from pydantic import BaseModel, Field
from typing import Literal
class WeatherInput(BaseModel):
"""Input for weather queries."""
location: str = Field(description="City name or coordinates")
units: Literal["celsius", "fahrenheit"] = Field(
default="celsius",
description="Temperature unit preference"
)
include_forecast: bool = Field(
default=False,
description="Include 5-day forecast"
)
@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
return "something"
JSON schemas 用作 tools 的数据校验
weather_schema = {
"type": "object",
"properties": {
"location": {"type": "string"},
"units": {"type": "string"},
"include_forecast": {"type": "boolean"}
},
"required": ["location", "units", "include_forecast"]
}
@tool(args_schema=weather_schema)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
"""Get current weather and optional forecast."""
return "something"
3. 大模型调用工具(Tool Calling)的过程是怎样的?
大模型调用工具并不是由大模型自己在服务器上直接运行你的 Python 代码,而是一个“人机协作、分步交互”的过程。
整个标准流程分为 5 个步骤:
- 绑定(Binding) 开发者在代码中定义好工具(使用 @tool),然后将这些工具通过 .bind_tools() 传递给大模型:
# @tool 的 description 参数覆盖自动生成的工具描述,为模型提供更清晰的指引:
# @tool 的第一个参数 默认情况下,工具名称取自函数名。当你需要更具描述性的名称时可对其进行重写
@tool("get_weather", description="根据 city 获取天气")
def get_weather_by(city: str) -> str:
"""获取天气(优先级没有 @tool 的 description 高) """
return f"{city}的天气是晴天,25℃"
在这个阶段,大模型的系统提示词(System Prompt)里会被悄悄注入这些工具的定义,让模型知道:“我有 get_weather 这个工具可以用”。
- 用户提问与模型意图识别(Model Request) 用户向大模型发起提问,例如:“帮我查一下北京今天天气怎么样?” 大模型收到提问后进行思考:
“这个问题需要查实时天气,我不会,但我手头有一个叫 get_weather 的工具正好能干这事。”
- 模型返回“工具调用请求”(Tool Call Response) 大模型不会直接回答最终结果,而是返回一个特殊的结构化响应(在 LangChain 中表现为带有 tool_calls 属性的 AIMessage):
模型输出:我要调用 get_weather 函数,传入参数 city="北京"。
- 本地拦截与执行(Local Execution) 你的后端代码(或 LangChain 的 Agent 框架)截获了大模型的这个请求:
代码解析出大模型要调用的函数名是 get_weather,参数是 {"city": "北京"}。
你的程序在本地安全环境中真正执行了你写的 Python 函数 get_weather(city="北京")。
函数返回结果:"北京的天气是晴天,25℃"。
- 反馈结果与生成最终回答(Final Response) 你的代码把刚才函数执行的结果打包成一个特殊的“工具消息”(ToolMessage),再次发送给大模型:
输入:用户提问 + 大模型的Tool Call请求 + 本地工具的执行结果
大模型思考:哦,结果出来了,是“北京的天气是晴天,25℃”。
模型输出最终答案:“根据查询,北京今天的天气是晴天,气温为 25℃ 噢!”
4. tools 具备访问 context 的能力
工具在能够获取对话历史、用户数据、持久化内存等运行时信息时,才能发挥最大能力, Tools 通过 ToolRuntime 参数可以访问到运行时的更多信息,ToolRuntime 提供了如下信息 为你将图片内容整理为中文 Markdown 表格如下:
| 组件名称 (Component) | 描述 (Description) | 应用场景 (Use case) |
|---|---|---|
| State(状态) | 短期记忆 - 仅存在于当前对话中的可变数据(如消息记录、计数器、自定义字段) | 访问对话历史记录、追踪工具调用次数 |
| Context(上下文) | 在调用时传入的不可变配置(如用户 ID、会话信息) | 根据用户身份提供个性化响应 |
| Store(存储) | 长期记忆 - 在跨对话中持久保存的数据 | 保存用户偏好设置、维护知识库 |
| Stream Writer(流式写入器) | 在工具执行期间发射实时更新 | 展示长时间运行操作的执行进度 |
| Execution Info(执行信息) | 当前执行的身份与重试信息(如线程 ID、运行 ID、尝试次数) | 访问线程/运行 ID,并根据重试状态调整行为 |
| Server Info(服务器信息) | 在 LangGraph Server 上运行时的特定服务器元数据(如助手 ID、图 ID、已认证用户) | 访问助手 ID、图 ID 或已认证的用户信息 |
| Config(配置) | 当前执行的 RunnableConfig 配置 | 访问回调函数(callbacks)、标签(tags)和元数据(metadata) |
| Tool Call ID(工具调用 ID) | 当前工具调用的唯一标识符 | 将工具调用与日志及模型调用关联起来 |
访问短期记忆 (State)
状态是对话存续期间有效的短期记忆。它包含消息历史记录以及你在 Graph State(LangGraph 中) 中定义的所有自定义字段。
将 runtime: ToolRuntime 添加到你的工具签名中以访问 state。调用时,ToolNode 会自动注入该值;该参数不会包含在发送给模型的工具定义中。使用 runtime.state 读取当前对话状态,比如如下使用 runtime.state["messages"] 来获取当前的会话 list
from langchain.tools import tool, ToolRuntime
@tool
def get_last_user_message(runtime: ToolRuntime, username: str) -> str:
"""Get the most recent message from a specific user by name.
Args:
username: The name of the user whose last message you want to find.
"""
messages = runtime.state["messages"]
# 倒序查找指定用户的最后一条消息
for message in reversed(messages):
print("DEBUG message:::::: ", message) # 方便在终端观察
# 确保是 HumanMessage 且 name 匹配
if isinstance(message, HumanMessage) and getattr(message, "name", None) == username:
return message.content
return "No user messages found"
# 别忘了导入 HumanMessage
agent = create_agent(
model=model,
tools=[get_last_user_message],
system_prompt=SystemMessage("如果需要查找特定角色的发言,请使用对应的工具。"),
)
response = agent.invoke({
"messages": [
HumanMessage(
content="如果有一天,你会来看一看我吗?",
name="天明",
id="user-unique-id",
),
HumanMessage(
content="看你做什么呢,天明?",
name="高月",
id="qianlong-unique-id",
),
HumanMessage(
content="姬如千泷不论山高路远,必来!",
name="高月",
id="qianlong-unique-id",
),
HumanMessage(
content="高月的最后一句话是啥?",
name="墨子",
id="mozi-unique-id",
),
]
})
print("\nresponse content: \n", response["messages"][-1].content)
更新短期记忆状态 (State)
使用 Command 更新 agent 的 state。这对于需要更新自定义状态字段的工具很有用。更新时需要用到
ToolMessage,这样模型就能看到工具调用的结果,如下展示更新短期记忆 state 的 age 和 name:
from langchain.agents import AgentState
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command
# 1. 定义自定义状态
class CustomState(AgentState):
user_name: str
user_age: int
# 2. 定义可以通过 Command 修改状态的工具
@tool
def set_user_info(new_name: str, new_age: int, runtime: ToolRuntime) -> Command:
"""Set the user's name in the conversation state."""
return Command(
update={
"user_name": new_name,
"user_age": new_age,
"messages": [
ToolMessage( # 更新状态必须使用 ToolMessage
content=f"User name successfully set to {new_name}.",
tool_call_id=runtime.tool_call_id,
)
],
}
)
# 别忘了导入 HumanMessage
agent = create_agent(
model=model,
tools=[set_user_info],
state_schema=CustomState, # 关联自定义状态
system_prompt=SystemMessage(
content=(
"你是一个智能助手。如果用户要求设置用户信息时,请调用 set_user_info 工具;"
)
),
)
response = agent.invoke(
{
"user_name": "未知", # 初始化 CustomState 中的字段
"user_age": 0, # 初始化 CustomState 中的字段
"messages": [
HumanMessage(
# 此处更改的是 agent 的 user_name 状态字段
content="如果有一天,你会来看一看我吗?,我要求设置我的名字为: '荆天明',age 18 岁",
name="天明",
id="user-unique-id",
),
],
}
)
print("\n--- 最终回复内容 ---")
print(response["messages"][-1].content)
print("\n--- 最终状态 (State) ---")
print("user_name:", response.get("user_name"))
print("user_age:", response.get("user_age"))
tools 访问 context
context 提供调用时传入的不可变配置数据。可用于存放用户ID、会话信息,或是对话过程中不应发生变更的应用专属配置(比如context_schema、checkpointer)
- 比如
context_schema指定 agent 的 上下文约束 - 比如指定 checkpointer,
checkpointer的作用:- 实现多轮对话记忆与状态持久化(Persistence):每次 Agent 执行完一步(或者一次 invoke 结束),checkpointer 都会自动把当前的完整状态(包括对话历史 messages、自定义的状态变量等)保存到存储介质中(如保存到内存中
checkpointer=InMemorySaver()、数据库等) - 支持“人工干预”(Human-in-the-loop):利用 checkpointer,Agent 可以在执行到危险步骤时暂停(Interrupt),把当前状态安全地存下来;等人类管理员点击“同意”后,Agent 可以从断点处直接恢复执行,而不需要重新从头跑一遍。
- **时间旅行与调试(Time Travel & Replay):**因为每一个步骤的状态都被 checkpointer 记录了下来,你可以随时查看历史某一个时刻的“现场”。
- 容错与故障恢复: 如果后端服务在执行某个复杂的工具调用时突然崩溃(例如网络超时、服务器重启),只要配置了持久化的 checkpointer(如 Redis、PostgreSQL 存盘),系统就可以从上一个成功的检查点恢复,而不至于丢失所有的上下文和进度
- 实现多轮对话记忆与状态持久化(Persistence):每次 Agent 执行完一步(或者一次 invoke 结束),checkpointer 都会自动把当前的完整状态(包括对话历史 messages、自定义的状态变量等)保存到存储介质中(如保存到内存中
from dataclasses import dataclass
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.utils.uuid import uuid7
# 模拟数据库 个人资产信息表
USER_DATABASE = {
"user123": {
"name": "Alice Johnson",
"account_type": "Premium",
"balance": 5000,
"email": "alice@example.com",
},
"user456": {
"name": "Bob Smith",
"account_type": "Standard",
"balance": 1200,
"email": "bob@example.com",
},
}
# 定义上下文, user_id 代表当前用户标识,不会改变
@dataclass
class UserContext:
user_id: str
# 2. 定义获取用户信息 的 tool,
@tool
def get_account_info(runtime: ToolRuntime[UserContext]) -> str:
"""Get the current user's account information."""
# runtime.context 获取 context 的当前用户信息
user_id = runtime.context.user_id
if user_id in USER_DATABASE:
# 模拟数据库查询
user = USER_DATABASE[user_id]
return (
f"Account holder: {user['name']}\n"
f"Type: {user['account_type']}\n"
f"Balance: ${user['balance']}"
)
return "User not found"
agent = create_agent(
model=model,
tools=[get_account_info],
context_schema=UserContext, # 定义 context 的类型
checkpointer=InMemorySaver(), # 创建一个内存保存器,它负责把每一次对话的状态(包括消息历史、中间变量等)实时保存到内存(RAM),让大模型不会 “失忆”,程序重启后数据就会丢失。
# checkpointer=InMemorySaver 标识在生产环境中,通常会替换为 Redis、PostgreSQL 等持久化 Checkpointer。
system_prompt=SystemMessage(content=("You are a financial assistant.")),
)
"""
thread_id 的作用:
1. 确保每个对话的会话状态(如用户名、年龄等)不会相互干扰。如果你有多个用户在同时和机器人聊天,你可以为每个用户生成不同的 thread_id。这样他们的聊天记录和状态就会被 InMemorySaver 隔离开,互不干扰
2. 关联记忆(Thread ID),thread_id 就像是一个“聊天房间号”或“会话 ID”。
当你调用 agent.invoke(..., config=config) 时,会拿着这个 thread_id 去 InMemorySaver/Redis/DB 中寻找 里查找:“有没有这个房间之前的聊天记录?”。如果有,就加载进来;如果没有,就新建一个。
"""
thread_id = str(uuid7())
config = {"configurable": {"thread_id": thread_id}}
context = UserContext(
user_id="user123"
) # 加入当前 context 中的 user 的 id 是 Alice Johnson 的 user_id
# 调用的时候指定 context 和 配置(比如 thread_id)
res1 = agent.invoke(
{
"messages": [
HumanMessage(
"What's my current balance?",
),
],
},
config=config,
context=context,
)
print("res1.content:", res1["messages"][-1].content)
print("res1.token_usage", res1["messages"][-1].response_metadata["token_usage"])
res2 = agent.invoke(
{
"messages": [
HumanMessage(
"What's my current balance?",
),
],
},
config=config,
context=context,
)
print("res2.content:", res2["messages"][-1].content)
print("res2.token_usage", res2["messages"][-1].response_metadata["token_usage"])
tools 访问 store (长期记忆)
BaseStore提供可跨会话保留的持久化存储。不同于State(短期记忆),保存到该Store中的数据在后续会话中仍然可用。通过
runtime.store访问该存储。此存储采用命名 namespace/key 的模式来组织数据⚠️ ⚠️ ⚠️ 对于生产环境部署,请使用持久化存储实现,例如 PostgresStore、MongoDBStore 或 RedisStore,而不要使用 InMemoryStore
from langgraph.store.memory import InMemoryStore
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI
# store.get访问 Store 长期记忆
@tool
def get_user_info(user_id: str, runtime: ToolRuntime) -> str:
# 注释文档帮助 LLM 理解 tool 怎么使用
"""查找用户信息"""
store = runtime.store
# users 是一个 namespace, 可以理解为数据库中的表
user_info = store.get("users", user_id)
return str(user_info.value) if user_info else "Unknown user"
# 保存/更新 Store 长期记忆
@tool
def save_user_info(
user_id: str, name: str, age: int, email: str, runtime: ToolRuntime
) -> str:
"""保存用户信息"""
store = runtime.store
store.put("users", user_id, {"name": name, "age": age, "email": email})
return "Successfully saved user info."
# 定义一个统一的 config(方便在同一线程或上下文中管理)
config = {"configurable": {"thread_id": "session_1"}}
agent = create_agent(
model=model,
tools=[get_user_info, save_user_info],
store=InMemoryStore(), # 创建一个内存存储器(不是 InMemorySaver), 生产环境中通常会替换为 Redis、PostgreSQL 等持久化存储。
system_prompt=SystemMessage(content=("You are a assistant.")),
)
# 要求保存用户信息
agent.invoke(
{
"messages": [
HumanMessage(
"保存这个用户的信息 userid: abc123, name: kejuqu, age: 18, email: kejuqu@langchain.dev"
)
]
},
config=config,
)
# 要求获取用户信息
res = agent.invoke(
{"messages": [HumanMessage("请获取用户信息 userid: abc123")]},
config=config,
)
print("res: ", res["messages"][-1].content)
自定义 Stream writer (流写入器)
在长时间运行的任务中向用户反馈进度, 利用工具 runtime 中流式输出实时更新信息。
即使用 runtime.stream_writer 发送自定义更新信息:
from pydantic import BaseModel
class SatelliteConnection(BaseModel):
status: str
message: str
def today_weather(self) -> str:
return "晴天,25度"
def connect_satellite() -> SatelliteConnection:
return SatelliteConnection(status="success", message="已连接")
@tool
def get_weather(city: str, runtime: ToolRuntime) -> str:
"""Get weather for a given city with real-time progress updates."""
# 获取流式写入器
writer = runtime.stream_writer
# 1. 模拟步骤一:正在连接卫星/API
satellite_obj = connect_satellite()
writer(f"[进度1] 正在连接气象卫星,定位城市: {city}...")
# 2. 模拟步骤二:正在抓取数据
weather_data = satellite_obj.today_weather()
writer(f"[进度2] 已锁定目标,正在抓取 {city} 的气象数据...")
return f"It's always {weather_data} in {city}!"
# 创建带有该工具的 Agent
agent = create_agent(
model=model,
tools=[get_weather],
system_prompt=SystemMessage(
content=(
"You are a helpful assistant. When users ask for weather, "
"use the get_weather tool."
)
),
)
# 使用 agent.stream() 实时捕获并打印中间的自定义更新流
print("=== 开始执行 Agent ===")
query = "帮我查一下杭州的天气"
# 注意:为了能接收到 runtime.stream_writer 发出的数据,必须使用 agent.stream() 而不是 agent.invoke()
for chunk in agent.stream(
{"messages": [HumanMessage(content=query)]},
stream_mode=["updates", "custom"], # 或使用 "custom" / "updates" 模式接收流
):
print("--- 收到流式块 (Chunk) ---")
print(chunk)
print("\n=== 执行结束 ===")
在 tool 内部获取执行信息
可在 tool 内部通过 runtime.execution_info 获取线程 ID、运行 ID 以及重试状态
@tool
def log_execution_context(runtime: ToolRuntime) -> str:
"""Log execution identity information."""
# 获取执行上下文信息
info = runtime.execution_info
print(f"\n[Tool Execution Log] Thread: {info.thread_id}, Run: {info.run_id}")
print(f"[Tool Execution Log] Attempt: {info.node_attempt}")
return "Execution context logged successfully."
# 创建带有 checkpointer 的 Agent(thread_id 依赖于 checkpointer 生效)
agent = create_agent(
model=model,
tools=[log_execution_context],
checkpointer=InMemorySaver(),
system_prompt=SystemMessage(
content="You are a helpful assistant. Use the log_execution_context tool when asked."
),
)
# 生成一个唯一的 thread_id
thread_id = str(uuid7())
config = {"configurable": {"thread_id": thread_id}}
print(f"=== 开始执行 Agent (Thread ID: {thread_id}) ===")
# 调用 Agent
response = agent.invoke(
{"messages": [HumanMessage(content="请帮我记录一下当前的执行上下文信息。")]},
config=config,
)
print("\nAgent 最终回复: ", response["messages"][-1].content)
5. tool 的返回值的不同含义
tool 的返回值 类型不同代表者模型的处理不同,具体情况请看
# tool 的返回值
@tool
def tools_return_str(pstr: str) -> str:
"""工具的文档描述,供大模型选择是否调用该 tool"""
# 特点
# 1. 默认情况下,tool 返回值会被转换为 ToolMessage 对象
# 2. 默认不会直接修改 Agent 的状态(除非模型或其他工具后续触发修改)
return f"字符串返回值 {pstr}会自动转换为 ToolMessage 对象"
@tool
def tools_return_dict() -> dict:
"""工具的文档描述,供大模型选择是否调用该 tool"""
# 直接返回字典对象, 适用场景:当工具产生需要大模型进行检查和解析的结构化数据时
# 对象会被序列化后作为工具输出传回, 大模型可以读取特定字段并进行推理。
return {
"name": "kejuqu",
"age": "129",
}
@tool
def tools_return_multimodal_content() -> list:
"""工具的文档描述,供大模型选择是否调用该 tool"""
# 返回多模态内容 (list[dict]),当模型支持多模态时,工具可以返回包含文本、图片、音频等多种媒体的内容块。
# 需确保所用模型支持对应的模态。
return [
{"type": "text", "text": "Screenshot of the current page:"},
{"type": "image", "url": "https://example.com/page.png"},
]
@tool
def tools_return_command(language: str, runtime: ToolRuntime) -> list:
"""工具的文档描述,供大模型选择是否调用该 tool"""
# 当工具不仅要返回数据,还需要直接写入/修改 Agent 的图状态(例如更新偏好设置或应用状态)时
# 当针对当前图时,Command 的 update 中必须包含一个 ToolMessage,其 tool_call_id 必须与当前的 runtime.tool_call_id 匹配(否则 ToolNode 会报错抛出 ValueError)
return Command(
update={
"preferred_language": language,
"messages": [
ToolMessage(
content=f"Language set to {language}.",
tool_call_id=runtime.tool_call_id,
)
],
}
)
# (return_direct=True) 适用场景: 当工具的输出已经是完整且用户可读的最终答案,不需要大模型再做总结、重新措辞或进行链式调用时。
@tool(return_direct=True)
def tools_return_command_with__return_direct(language: str, runtime: ToolRuntime) -> list:
"""工具的文档描述,供大模型选择是否调用该 tool"""
# 当工具要返回多模态内容时,Command 的 update 中必须包含一个 ToolMessage,其 tool_call_id 必须与当前的 runtime.tool_call_id 匹配(否则 ToolNode 会报错抛出 ValueError)
return Command(
update={
"preferred_language": language,
}
)
需要注意的是 使用 @tool(return_direct=True) 时,行为特点在不同的场合意义不同:
-
单工具调用:Agent 会立刻将工具的输出作为最终结果返回给调用方,跳过后续的模型循环。
-
并行工具调用(全员 return_direct=True):所有工具执行完毕后,Agent 直接结束,最终响应会包含该步骤中所有被调用工具的 ToolMessage 输出。
-
混合并行调用:如果在同一步骤中,既有
return_direct=True的工具,也有普通的工具,Agent 不会直接退出,而是会将所有工具的 ToolMessage 返回给模型,让模型继续进行推理。 -
结合 Command 使用:return_direct=True 的工具也可以返回 Command 来更新图状态(同样需要包含匹配的 ToolMessage)。
6. 错误处理
借助 LangChain 智能体 [中间件](https://docs.langchain.com/oss/python/langchain/middleware) 处理工具错误,对失败的工具调用进行重试,或者返回自定义错误信息:
# 错误处理:
# 借助 LangChain 智能体 中间件 处理工具错误,对失败的工具调用进行重试,或者返回自定义错误信息:
from collections.abc import Callable
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage
from langchain.tools.tool_node import ToolCallRequest
# ==========================================
# 1. 定义中间件:捕获异常并转为 ToolMessage 友善返回
# ==========================================
@wrap_tool_call
def handle_tool_errors(
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
"""Convert tool exceptions into ToolMessages the model can handle."""
try:
print(f"[Middleware] 正在拦截工具调用: {request.tool_call['name']}")
return handler(request)
except Exception as e:
print(f"[Middleware] 捕获到工具异常,已安全转为 ToolMessage: {e}")
return ToolMessage(
content=f"Tool error: Please check your input and try again. ({e})",
tool_call_id=request.tool_call["id"],
)
# ==========================================
# 2. 定义两个工具
# ==========================================
# 【情况 A:有 handler/中间件保护的工具】
@tool
def robust_divide(a: int, b: int) -> str:
"""Divide two numbers. Will fail if b is zero."""
if b == 0:
raise ValueError("Division by zero is not allowed.")
return f"The result is {a / b}"
# 【情况 B:没有 middleware 保护的裸工具】
@tool
def fragile_divide(a: int, b: int) -> str:
"""Divide two numbers without error middleware."""
if b == 0:
raise ValueError("Unprotected division by zero crash!")
return f"The result is {a / b}"
# ==========================================
# 3. 创建测试 Agent
# ==========================================
# 我们先测试【有 handler 保护】的工具
agent_with_middleware = create_agent(
model=model,
tools=[robust_divide],
middleware=[handle_tool_errors], # 注册了中间件
system_prompt=SystemMessage(
content="You are a helpful assistant. Use robust_divide for calculations."
),
)
# ==========================================
# 4. 运行演示
# ==========================================
thread_id = str(uuid7())
config = {"configurable": {"thread_id": thread_id}}
print("=== 测试 1:【有 handler (middleware)】的工具出错 ===")
try:
res1 = agent_with_middleware.invoke(
{"messages": [HumanMessage(content="请帮我计算 10 除以 0")]},
config=config,
)
print("\n--- 带有 Handler 的 Agent 最终回复 ---")
# 因为 agent_with_middleware agent 有 错误 middleware 处理,所以不会进入到。except Exception as e 块, 除非 下一句 print 里有语法错误
print("agent_with_middleware res1: ", res1["messages"][-1].content)
except Exception as e:
print("抛出异常了:", e)
print("\n" + "=" * 50 + "\n")
print("=== 测试 2:【没有 handler (middleware)】的工具出错 ===")
agent_without_middleware = create_agent(
model=model,
tools=[fragile_divide],
# 没有注册 middleware
system_prompt=SystemMessage(
content="You are a helpful assistant. Use fragile_divide for calculations."
),
)
try:
res2 = agent_without_middleware.invoke(
{"messages": [HumanMessage(content="请帮我计算 100 除以 0")]},
config=config,
)
print("\n--- 无 Handler 的 Agent 最终回复 ---")
print("agent_without_middleware res2: ", res2["messages"][-1].content)
except Exception as e: # 没有 handler 的中间件捕获到异常,会抛出异常, 因此会打印 如下的错误信息
print(
"🔥 成功捕获崩溃:由于没有中间件拦截,异常直接导致整个 Agent 运行时崩溃报错!"
)
print(f"异常详情: {e}")