工具调用是Agent能力的核心放大器
一个没有工具的LLM,只能生成文本。给它配上工具,它能:查询数据库、调用API、读写文件、发送邮件、执行代码——本质上,工具让LLM从"说话者"变成了"行动者"。
但工具设计是门手艺。同样的功能,工具设计得好,Agent能精准调用;设计得差,Agent要么乱用,要么根本不知道该调哪个。
本文总结一套经过大量实践验证的工具设计原则,帮你的Agent变得更可靠。
原则一:单一职责,不要瑞士军刀
坏的设计:
# 一个函数做太多事——LLM不知道什么时候该用它
@tool
def manage_database(operation: str, table: str, data: dict = None,
filter: dict = None, order_by: str = None):
"""
数据库操作:支持增删改查,operation可以是
'insert', 'update', 'delete', 'select', 'count', 'aggregate'
"""
if operation == 'select':
...
elif operation == 'insert':
...
# 更多逻辑...
好的设计:
@tool
def query_users(filter_criteria: dict, limit: int = 20) -> list[dict]:
"""
查询用户列表。
filter_criteria示例:
- {"status": "active"} 查询活跃用户
- {"age_min": 18, "city": "北京"} 查询北京18岁以上用户
"""
...
@tool
def create_user(name: str, email: str, role: str = "user") -> dict:
"""创建新用户账户,返回创建的用户信息(含用户ID)"""
...
@tool
def update_user_status(user_id: str, new_status: str) -> bool:
"""更新指定用户的状态,status可以是active/suspended/deleted"""
...
为什么:LLM在选工具时做的是语义匹配。工具描述越聚焦,LLM越容易判断该调哪个、不该调哪个。
原则二:描述是工具的说明书,要写给LLM看
工具的描述(docstring/description)是LLM决定是否调用这个工具的唯一依据。要写得清晰、具体、包含使用示例:
# 不好的描述
@tool
def send_notification(user_id: str, message: str):
"""发送通知""" # 太简略!
...
# 好的描述
@tool
def send_email_notification(
user_id: str,
subject: str,
message: str,
priority: str = "normal"
) -> dict:
"""
向指定用户发送邮件通知。
Args:
user_id: 接收通知的用户ID(如 "user_123")
subject: 邮件主题(50字以内)
message: 邮件正文(支持Markdown格式)
priority: 优先级,可选 "normal"(普通)或 "urgent"(紧急,会触发短信备用)
Returns:
dict: {"success": bool, "message_id": str, "delivered_at": str}
使用场景:
- 用户完成重要操作后发确认邮件
- 系统异常需要通知用户时
- 定时提醒类通知
注意:不适合发送营销邮件,仅用于事务性通知
"""
...
原则三:参数类型明确,善用枚举
LLM生成参数时容易产生"创意"——它可能传"YES"而不是True,传"北京市"而不是"beijing"。明确的类型定义和枚举能大幅减少这种错误:
from enum import Enum
from pydantic import BaseModel, Field
from typing import Literal
class NotificationChannel(str, Enum):
EMAIL = "email"
SMS = "sms"
PUSH = "push"
WECHAT = "wechat"
class SendNotificationInput(BaseModel):
user_id: str = Field(description="用户ID,格式为 'user_' 加数字,如 user_123")
channel: NotificationChannel = Field(
description="通知渠道:email(邮件)/sms(短信)/push(推送)/wechat(微信)"
)
message: str = Field(max_length=500, description="通知内容,最多500字")
urgent: bool = Field(default=False, description="是否紧急消息,true时会立即发送")
@tool(args_schema=SendNotificationInput)
def send_notification(user_id: str, channel: str, message: str, urgent: bool = False):
"""向用户发送通知"""
...
原则四:返回结构化、信息丰富的结果
工具的返回值是LLM下一步推理的输入。返回值要:
- 明确说明成功/失败
- 在失败时给出可操作的错误信息
- 包含LLM可能需要的关联信息
# 不好的返回
@tool
def create_order(product_id: str, quantity: int) -> str:
try:
order = db.create_order(product_id, quantity)
return "成功" # LLM不知道订单ID,无法后续操作
except Exception as e:
return "失败" # LLM不知道为什么失败,无法处理
# 好的返回
@tool
def create_order(product_id: str, quantity: int) -> dict:
"""创建订单,返回订单详情"""
try:
# 先验证库存
stock = db.get_stock(product_id)
if stock < quantity:
return {
"success": False,
"error_code": "INSUFFICIENT_STOCK",
"error_message": f"库存不足:当前库存{stock}件,需要{quantity}件",
"suggestion": "可以减少购买数量,或查询其他可用商品"
}
order = db.create_order(product_id, quantity)
return {
"success": True,
"order_id": order.id,
"order_number": order.number, # 人类可读的订单号
"total_amount": order.total,
"estimated_delivery": order.eta,
"next_steps": ["可调用 get_order_status 查询订单状态",
"可调用 cancel_order 取消订单"]
}
except Exception as e:
return {
"success": False,
"error_code": "SYSTEM_ERROR",
"error_message": str(e),
"suggestion": "请稍后重试,或联系客服"
}
原则五:幂等性设计
Agent可能因为种种原因重试工具调用(超时、误判等)。写操作工具应该尽可能是幂等的:
@tool
def send_welcome_email(user_id: str) -> dict:
"""向新用户发送欢迎邮件(幂等:同一用户只发一次)"""
# 检查是否已发送
if db.email_log.exists(user_id=user_id, type="welcome"):
return {
"success": True,
"skipped": True,
"reason": "欢迎邮件已于之前发送,避免重复发送"
}
# 发送邮件
email_service.send(
to=db.get_user_email(user_id),
template="welcome"
)
# 记录日志
db.email_log.insert(user_id=user_id, type="welcome")
return {
"success": True,
"skipped": False,
"sent_at": datetime.now().isoformat()
}
原则六:危险操作需要二次确认机制
删除、支付等不可逆操作,应该设计两步式工具:
@tool
def prepare_delete_user(user_id: str) -> dict:
"""
【第一步】准备删除用户账号,返回确认码和影响分析。
需要用户(或后续流程)提供确认码后,才能执行实际删除。
"""
user = db.get_user(user_id)
if not user:
return {"success": False, "error": "用户不存在"}
# 分析删除影响
orders = db.count_orders(user_id)
files = db.count_files(user_id)
confirmation_code = generate_confirmation_code()
# 将确认码存储(5分钟有效)
cache.set(f"delete_confirm_{user_id}", confirmation_code, ttl=300)
return {
"success": True,
"confirmation_code": confirmation_code,
"impact_analysis": {
"user_name": user.name,
"orders_affected": orders,
"files_to_delete": files,
"warning": "此操作不可逆!数据将被永久删除"
},
"next_step": "调用 execute_delete_user 并提供此确认码"
}
@tool
def execute_delete_user(user_id: str, confirmation_code: str) -> dict:
"""
【第二步】执行用户删除。必须先调用 prepare_delete_user 获取确认码。
"""
stored_code = cache.get(f"delete_confirm_{user_id}")
if not stored_code or stored_code != confirmation_code:
return {
"success": False,
"error": "确认码无效或已过期,请重新调用 prepare_delete_user"
}
# 执行删除
db.delete_user(user_id)
cache.delete(f"delete_confirm_{user_id}")
return {"success": True, "deleted_user_id": user_id}
原则七:工具集要有合理的粒度层级
把工具组织成粗粒度的高层工具和细粒度的底层工具:
# 高层工具:Agent优先使用
@tool
def process_customer_refund(order_id: str, reason: str) -> dict:
"""处理客户退款申请(端到端流程:验证→退款→通知)"""
...
# 底层工具:高层工具不够用时才用
@tool
def validate_order_refundable(order_id: str) -> dict:
"""检查订单是否可退款"""
...
@tool
def process_payment_refund(payment_id: str, amount: float) -> dict:
"""执行支付退款(底层操作)"""
...
@tool
def update_order_status(order_id: str, status: str) -> dict:
"""更新订单状态"""
...
这种设计让Agent在简单场景用高层工具一步完成,在复杂场景(如高层工具失败)能灵活组合底层工具。
原则八:控制工具集的大小
给LLM太多工具是个陷阱。研究表明,当工具数量超过20个时,LLM的工具选择准确率会显著下降。
按任务动态选择工具:
class DynamicToolSelector:
"""根据当前任务动态提供相关工具子集"""
def __init__(self, all_tools: dict):
self.all_tools = all_tools
self.tool_embeddings = {
name: embed_text(tool.__doc__)
for name, tool in all_tools.items()
}
def get_relevant_tools(self, task: str, max_tools: int = 10) -> list:
"""检索与当前任务最相关的工具"""
task_emb = embed_text(task)
scores = {
name: cosine_sim(task_emb, emb)
for name, emb in self.tool_embeddings.items()
}
# 始终包含的核心工具
core_tools = ["get_current_time", "ask_user_clarification"]
# 动态选择最相关的工具
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
dynamic_tools = [name for name, _ in ranked[:max_tools - len(core_tools)]]
return [self.all_tools[name] for name in core_tools + dynamic_tools]
实战检查清单
设计每个工具时,过一遍这份清单:
- 职责单一:这个工具只做一件事
- 描述清晰:包含使用场景、参数说明、返回值说明
- 参数明确:使用强类型,枚举可选值
- 返回规范:始终返回 success 字段,失败时给出 suggestion
- 幂等设计:写操作能安全重试
- 危险防护:不可逆操作有二次确认
- 错误处理:捕获所有异常并返回结构化错误信息
结语
工具设计是AI Agent工程中最容易被低估的环节。大家往往花大量时间在LLM选型和Prompt设计上,却用一个下午草草定义了20个工具。
但实践证明,工具的质量直接决定了Agent的可靠性上限。一个设计精良的工具集,能让一个普通的LLM表现得出乎意料地好;反之,再强的LLM也会在劣质工具集前束手无策。
投资工具设计,是提升Agent可靠性回报率最高的事情之一。