上一篇介绍了 Deep Agents 的 Planning 机制。通过任务拆解,Agent 能够明确目标、安排步骤,并跟踪执行进度。
但规划本身并不能完成任务。
Planning 仅负责安排任务,而 Tools 则负责执行具体操作。
例如,一个故障处理 Agent 可以制定这样的计划:查询未解决的工单、检查故障信息、更新工单状态,最后汇总处理结果。
要完成这些操作,它必须具备查询数据、访问系统和修改状态的能力。这些能力主要通过 Tools 提供。
本文介绍 Deep Agents 的工具机制、设计方法和实践要点,并通过一个完整示例,说明如何开发可用于实际业务的工具。
一、工具的基本定位
Tool 是 Agent 调用外部能力的接口。
它可以是一个普通的 Python 函数,也可以封装数据库查询、HTTP API、内部业务系统,或者通过 MCP 连接外部服务。
例如:
search_web:搜索网络资料。get_order:查询订单信息。update_ticket:修改工单状态。send_email:发送邮件。
这些工具虽然连接不同的系统,但对 Agent 来说,都包含几个基本要素:工具名称、功能描述、输入参数和返回结果。
需要分清的是,模型只负责选择工具并生成参数,实际执行由 Agent 的运行环境完成。
整个过程如下:
LLM
│
│ 选择工具、生成参数
▼
Tool Calling
│
│ 参数校验
▼
Tool Runtime
│
│ 执行函数
▼
External System
│
│ 返回结果
▼
LLM
│
▼
继续推理或完成任务
例如,模型决定调用:
{
"name": "get_order",
"args": {
"order_id": "ORD-1001"
}
}
这只是一次工具调用请求。
框架根据名称找到对应工具,校验参数并执行函数,再将执行结果作为 ToolMessage 返回给模型。
模型读取结果,决定继续调用其他工具,还是生成最终回答。
这就是 Tool Calling 的基本过程。
二、Deep Agents 的工具体系
Deep Agents 建立在 LangChain 的 Agent 机制之上,通过 create_deep_agent() 组织模型、工具和运行环境。
工具主要分为两类。
1. 内置工具
Deep Agents 提供了一组用于执行复杂任务的基础工具。
| 类别 | 工具 | 作用 |
|---|---|---|
| 文件管理 | ls、read_file、write_file、edit_file、glob、grep、delete | 文件操作 |
| 命令执行 | execute | 执行 Shell 命令 |
| 任务管理 | write_todos | 拆解任务、跟踪进度 |
| 任务委派 | task | 交给 Subagent 执行 |
并非所有工具在任何配置下都可用。
例如,execute 需要 Backend 提供命令执行能力,而 write_todos 从 Deep Agents 0.7.0 起改为按需启用。
如果需要使用 write_todos,可以通过 TodoListMiddleware 添加:
from deepagents import create_deep_agent
from langchain.agents.middleware import TodoListMiddleware
agent = create_deep_agent(
model="openai:gpt-5.5",
middleware=[TodoListMiddleware()],
)
启用后,Agent 可以创建和更新任务清单,跟踪 pending、in_progress、completed 等任务状态。
关于 TodoListMiddleware 的介绍以及具体细节可查看上一篇文章。
文件管理、命令执行和任务委派将在后续文章中详细介绍。本文主要关注自定义工具。
2. 自定义工具
如果希望 Agent 访问自己的业务系统,就需要注册自定义工具。
from deepagents import create_deep_agent
def get_order(order_id: str) -> dict:
"""根据订单编号查询订单信息。"""
return {
"order_id": order_id,
"status": "paid",
}
agent = create_deep_agent(
model="openai:gpt-5.5",
tools=[get_order],
)
这里通过 tools 注册业务工具。
Deep Agents 根据函数签名和文档字符串生成工具定义,供模型识别和调用。
需要注意,tools=[get_order] 只是增加一个业务工具,不会自动删除框架的内置工具。
Agent 最终能够使用哪些工具,还取决于框架配置和运行环境。
3. @tool 装饰器
除了直接注册普通 Python 函数,还可以使用 LangChain 提供的 @tool 装饰器。
from langchain.tools import tool
@tool
def get_order(order_id: str) -> dict:
"""根据订单编号查询订单信息。"""
return {
"order_id": order_id,
"status": "paid",
}
@tool 不是必需的。两种写法都可以注册到 Deep Agents。
区别在于,普通函数由框架在注册时转换为工具;使用 @tool 则会直接创建一个 LangChain Tool 对象。
因此,可以查看工具的定义:
print(get_order.name)
print(get_order.description)
print(get_order.args)
也可以通过 invoke() 单独执行:
result = get_order.invoke({
"order_id": "ORD-1001"
})
使用 @tool 后,还可以明确指定工具名称和描述:
@tool(
"lookup_order",
description="根据订单编号查询订单当前状态"
)
def get_order(order_id: str) -> dict:
return {
"order_id": order_id,
"status": "paid",
}
此时,模型看到的工具名称是 lookup_order,而不是 Python 函数名 get_order。
对于较复杂的输入,还可以通过 Pydantic 定义参数结构:
from pydantic import BaseModel, Field
class OrderInput(BaseModel):
order_id: str = Field(
description="订单编号,例如 ORD-1001"
)
@tool(args_schema=OrderInput)
def get_order(order_id: str) -> dict:
"""查询指定订单的信息。"""
return {
"order_id": order_id,
"status": "paid",
}
这里的 args_schema 用于定义工具的输入参数,包括字段类型、说明和校验规则。
简单工具可以直接使用普通函数。需要更明确地控制工具名称、描述和参数时,@tool 更方便。
三、工具的设计原则
普通程序中,函数的调用时机和参数通常由开发者确定。Agent 则需要模型自行判断,有时还会重复调用。
因此,工具需要明确职责、约束输入,并提供清晰的执行结果。
1. 职责明确
一个不太合理的工具示例:
def process_data(data: str) -> str:
...
process_data 无法说明具体用途,data 也缺乏明确含义。
当 Agent 拥有多个类似工具时,模型很难准确选择。
更好的方式是:
def get_order_status(order_id: str) -> str:
"""根据订单编号查询当前状态,不修改订单。"""
...
名称说明操作对象,描述补充具体用途和限制。
工具设计应遵循 SOLID 原则中的单一职责原则(SRP),让每个工具只负责一项明确的操作。
例如,查询订单与取消订单应分别定义为独立工具,避免将不同职责混在一个通用工具中。
2. 参数清晰
模型生成的参数不能直接当作可信输入。
例如:
def search_orders(
keyword: str,
limit: int = 10,
) -> dict:
...
即使 limit 声明为整数,模型仍可能传入极大的数值。因此,工具内部还需要限制查询数量。
对于固定选项,可以使用 Literal:
from typing import Literal
def get_orders(
status: Literal["pending", "paid", "closed"],
limit: int = 10,
) -> dict:
...
这样可以明确告诉模型,status 只能是三个值之一,并由工具框架校验。
复杂参数可以通过前面介绍的 Pydantic 定义取值范围和校验规则。
对于实际业务,还需要检查用户身份、操作权限和数据状态,不能只依赖参数类型。
3. 结果明确
工具执行后,应当让模型准确判断操作结果。
例如,查询失败时,不要只返回:
Something went wrong.
更合理的方式是返回明确的错误信息:
{
"ok": false,
"code": "NOT_FOUND",
"message": "订单不存在",
"retryable": false
}
成功时返回:
{
"ok": true,
"order_id": "ORD-1001",
"status": "paid"
}
这种结构可以让模型知道操作是否成功、失败原因是什么,以及是否需要重试。
对于修改类工具,还应返回修改前后的状态,方便判断操作是否真正完成。
4. 操作受控
不同类型的工具,需要不同的执行限制。
| 类型 | 示例 | 主要考虑 |
|---|---|---|
| 查询类 | 查询订单 | 权限、查询范围 |
| 修改类 | 更新工单 | 状态检查、重复修改 |
| 外部操作类 | 付款、发送邮件 | 人工确认、重复执行 |
例如,付款工具不能仅仅因为模型提供了金额和账户就直接执行。
程序必须检查用户权限、金额限制和付款状态。
模型负责提出操作请求,业务程序负责判断能否执行。
四、实战:可靠的工单工具
下面构建一个简单的故障工单 Agent。
它需要完成两个操作:查询指定工单,以及在权限和状态允许的情况下修改工单状态。
示例重点展示工具设计,其中的用到的数据会使用内存字典,以模拟从数据库获取。
安装依赖:
pip install -U deepagents "langchain[openai]"
完整代码如下:
from dataclasses import dataclass
from typing import Literal
from deepagents import create_deep_agent
from langchain.tools import tool, ToolRuntime
# 模拟业务数据库
INCIDENTS = {
"INC-1024": {
"title": "API 响应延迟升高",
"status": "open",
}
}
# 应用程序提供的用户信息
@dataclass
class AppContext:
user_id: str
can_update: bool
@tool
def get_incident(incident_id: str) -> dict:
"""根据编号查询故障工单,不修改数据。"""
incident = INCIDENTS.get(incident_id)
if incident is None:
return {
"ok": False,
"code": "NOT_FOUND",
}
return {
"ok": True,
"incident_id": incident_id,
**incident,
}
@tool
def update_incident_status(
incident_id: str,
new_status: Literal["investigating", "resolved"],
runtime: ToolRuntime[AppContext],
) -> dict:
"""更新故障工单状态,仅在用户明确要求修改时调用。"""
context = runtime.context
# 检查操作权限
if context is None or not context.can_update:
return {
"ok": False,
"code": "FORBIDDEN",
}
incident = INCIDENTS.get(incident_id)
if incident is None:
return {
"ok": False,
"code": "NOT_FOUND",
}
old_status = incident["status"]
# 状态相同,不重复修改
if old_status == new_status:
return {
"ok": True,
"changed": False,
"status": old_status,
}
# 检查状态能否变更
transitions = {
"open": {"investigating"},
"investigating": {"resolved"},
"resolved": set(),
}
if new_status not in transitions[old_status]:
return {
"ok": False,
"code": "INVALID_TRANSITION",
}
incident["status"] = new_status
return {
"ok": True,
"changed": True,
"old_status": old_status,
"new_status": new_status,
"updated_by": context.user_id,
}
agent = create_deep_agent(
model="openai:gpt-5.5",
tools=[get_incident, update_incident_status],
context_schema=AppContext,
system_prompt=(
"你是故障工单助手。"
"修改前先核实工单信息。"
"只有工具返回成功后,才能报告操作成功。"
),
)
result = agent.invoke(
{
"messages": [{
"role": "user",
"content": (
"查询 INC-1024。"
"如果状态是 open,"
"就改为 investigating。"
),
}]
},
context=AppContext(
user_id="operator-01",
can_update=True,
),
)
print(result["messages"][-1].content)
print(INCIDENTS)
这段代码有几个值得关注的地方。
首先,查询与修改分开。
get_incident 负责读取,update_incident_status 负责修改。两个函数都使用 @tool 定义,然后通过 tools 注册到 Agent。
其次,权限由程序提供。
context_schema=AppContext 声明上下文类型。执行时,应用程序通过 context 传入用户信息。
工具通过 runtime.context 读取这些信息,并检查 can_update。
ToolRuntime用于在工具内部获取运行时信息,例如当前用户、Agent State 和其他应用数据。
context_schema用于定义应用程序传给 Agent 的上下文结构。它们通常配合使用。应用程序通过
context传入用户信息,工具通过runtime.context读取。与普通工具参数不同,
runtime: ToolRuntime[...]由框架自动提供,不会暴露给模型。模型无法通过工具调用参数直接填写其中的用户身份或权限。
需要注意,示例中的用户身份和权限是模拟数据。实际项目应根据登录状态和权限系统生成,不能直接相信客户端提交的内容。
最后,状态变更受到限制。
工单只允许按以下顺序变化:
open
↓
investigating
↓
resolved
不符合规则的操作会返回 INVALID_TRANSITION。如果工单已经处于目标状态,也不会重复修改。
可以看到,模型负责判断何时调用工具,具体的权限和业务规则则由工具代码执行。
五、实际项目中的注意事项
上面的示例已经包含基本的权限和状态检查。真正接入业务系统时,还需要解决几个问题。
1. 超时与重复执行
外部 API 可能超时,但超时不一定意味着操作失败。
例如,付款请求可能已经执行,只是响应没有正常返回。此时直接重试,可能造成重复付款。
对于这类操作,应使用唯一请求编号记录执行结果。收到重复请求时,先检查之前的处理情况。
对于数据库修改,还需要使用事务和条件更新,避免多个请求同时修改同一条数据。
2. 工具范围与人工确认
不要向 Agent 提供超出任务需要的工具。
例如,只负责资料研究的 Agent,通常不需要删除数据或修改订单的能力。
对于删除、付款等重要操作,可以使用 Deep Agents 的 interrupt_on 配置执行前确认:
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
model="openai:gpt-5.5",
tools=[delete_order],
interrupt_on={
"delete_order": True,
},
checkpointer=MemorySaver(),
)
这里的 delete_order 是预先定义好的工具。
interrupt_on 指定哪些工具需要人工确认;checkpointer 保存执行状态,以便审核后继续运行。实际调用和恢复时,还需要使用相应的 thread_id。
示例使用内存保存状态。生产环境应使用可靠的持久化存储。
人工确认不能代替业务权限检查。即使审核通过,工具仍应检查操作是否合法。
3. 返回数据的数量
工具查询结果可能进入模型上下文。
如果一次返回几万条记录,不仅消耗大量 Token,也会增加模型处理信息的难度。
因此,查询工具应支持筛选、分页和数量限制。
例如,先返回工单编号、标题和状态,确定目标后再读取完整信息。
六、工具的测试与验证
工具开发完成后,需要同时检查函数本身和 Agent 的调用行为。
函数测试主要关注参数错误、权限不足、数据不存在、状态不合法以及重复执行等情况。
Agent 测试则关注另外三个问题:
工具是否选对? 用户只要求查询时,Agent 不应该调用修改工具。
参数是否正确? 调用时是否使用了正确的工单编号和目标状态。
结果是否可信? 工具返回失败后,Agent 不能错误地报告操作成功。
在实际项目中,最好准备一组固定任务,每次修改工具定义或提示词后重新测试,观察任务完成情况是否发生变化。
总结
Tools 让 Agent 具备访问外部系统和执行操作的能力。
在 Deep Agents 中,开发者可以直接注册普通 Python 函数,也可以通过 @tool 定义工具。模型负责选择工具并生成参数,实际操作由运行环境完成。
设计工具时,重点是职责明确、参数清晰、结果可判断。涉及数据修改时,还需要落实权限和业务规则。
模型可以决定尝试做什么,但真正能做什么、怎样执行,必须由程序控制。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。