AI Agent 工具设计实战:5 个核心模式与 5 个常见陷阱
很多 AI Agent 的问题看起来像是模型能力不够——选错工具、传错参数、错误处理不当。但事实上,模型通常只是在与它被给予的接口打交道。工具设计才是 Agent 失败的根本原因。
本文基于 Bala Priya C 在 Machine Learning Mastery 上的同名英文文章(June 15, 2026)改编,将原文关于 AI Agent 工具设计的最佳实践和失败模式整理为一份实用的中文参考指南。所有代码示例已根据中文开发者习惯做了适配。
为什么工具设计如此重要
一个模型只能从工具接口暴露的信息中推理:工具名、描述、参数 Schema、参数说明。这四个要素决定了模型如何理解意图、规划动作和执行任务。
当工具设计模糊、描述含糊、Schema 松散时,失败是可预测的。换一个更强的模型可以减少部分错误,但无法可靠地补偿一个有缺陷的接口。这不是模型的问题——是你设计的工具在「毒害」模型的推理能力。
5 个该做的(What Works)
1. 单一职责:一个工具只做一件事
最常见的反模式是做「瑞士军刀式」工具——一个函数通过 action 参数控制多种行为:
def manage_customer(
action: str,
customer_id: str | None = None,
data: dict | None = None
):
"""管理客户"""
pass # action: create | get | update | delete | suspend
模型必须先搞清楚该用哪种 action,然后才能解决实际任务。这让一个简单的操作变成了两步推理。
def create_customer(data: CustomerInput) -> Customer:
"""创建新客户记录。"""
pass
def get_customer(customer_id: str) -> Customer:
"""按 ID 查询客户。"""
pass
def suspend_customer(customer_id: str, reason: str) -> SuspensionResult:
"""暂停客户账户。"""
pass
单一职责工具给模型一个明确的函数信号,也让你的错误处理和可观测性变得简单。注意这不是万能的——shell、文件系统、浏览器等领域的工具可能天然需要多操作,但这是一个好的默认原则。
2. 紧 Schema:让非法状态不可表达
在工具调用中,模型通过推理你的 Schema 来构造参数。一个松散的 Schema 意味着模型需要猜测约束条件。一个紧密的 Schema 则把约束编码进去,不需要猜测:
from pydantic import BaseModel, Field
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class CreateTaskInput(BaseModel):
title: str = Field(
description="简短、可操作的任务标题。使用祈使句:'Review PR' 而非 'PR Review'",
min_length=5,
max_length=100
)
priority: Priority = Field(
description="任务优先级。仅阻止其他工作的阻塞项使用 HIGH",
default=Priority.MEDIUM
)
due_date: str = Field(
description="截止日期,ISO 8601 格式:YYYY-MM-DD。必须是未来日期",
pattern=r"^\d{4}-\d{2}-\d{2}$"
)
枚举(Enum)特别有用——对于只有少数有效值的字段,枚举彻底消除了「看似合理但实际无效」的输出。验证器(validators)和正则约束进一步压缩了模型的猜测空间。
3. 描述即文档:既要说明何时用,也要说明何时不用
工具描述是面向模型的文档。它需要做两件事:说明何时该用这个工具,以及说明何时不该用这个工具。大多数描述只做了第一件。
"""Search for documents in the knowledge base.""" """ Search the internal knowledge base for documents, policies, and reference material. Use this when the user asks about company procedures, product specs, or documented workflows. Do NOT use this for real-time data (prices, availability, current status) — use get_live_data() instead. Returns up to 5 results ranked by relevance. If no results are returned, the information is not in the knowledge base. """
没有边界说明时,模型从工具名字推断范围——这在规模扩大时是选择错误的可靠来源。
4. 结构化错误:告诉模型下一步怎么办
当工具失败时,模型读取错误来决定下一步。一个未处理的异常或堆栈跟踪会产生「噪音驱动的后续行为」——模型会反复重试、做出奇怪的决定,甚至编造数据。
class ToolError(BaseModel):
error_code: str # 机器可读,让模型分支决策
message: str # 人类可读的描述
recoverable: bool # Agent 可以重试吗?
suggested_action: str # 模型下一步该做什么
return ToolError(
error_code="RECORD_NOT_FOUND",
message="No user record found with ID 'usr_123'.",
recoverable=True,
suggested_action="Use list_users() to get valid user IDs before calling get_user()."
)
return ToolError(
error_code="QUOTA_EXCEEDED",
message="API quota for this tool has been reached for today.",
recoverable=False,
suggested_action="Notify the user and stop. Do not retry this tool today."
)
recoverable 和 suggested_action 字段才是真正改变 Agent 行为的关键。没有它们,模型会对不可重试的错误反复重试,或者对可恢复的错误直接放弃。
5. 幂等性:每个写操作都安全
任何改变状态的工具——创建记录、发送消息、转移资金——都必须能够安全地调用两次。实际上,Agent 在网络超时或不确定时会自动重试。
一个简单的做法是为每个写操作要求一个幂等键(idempotency key):
def send_email(
to: str,
subject: str,
body: str,
idempotency_key: str = Field(
description="此发送操作的唯一键。使用收件人+主题+时间戳的哈希值。"
"相同的键在重试时返回原始结果,不会重复发送。"
)
) -> dict:
"""发送邮件。幂等的:相同的 idempotency_key 不会触发第二次发送。"""
existing = idempotency_store.get(idempotency_key)
if existing:
return existing
result = email_service.send(to=to, subject=subject, body=body)
idempotency_store.set(idempotency_key, result, ttl=86400)
return result
没有幂等性保证,瞬态失败很容易变成重复操作。
5 个不该做的(What Doesn’t Work)
1. 裸 API 包装——Agent 不需要 REST 的全部
把 REST API 直接暴露为工具是最常见的捷径,也是生产环境中最常见的失败源。为开发者设计的 API 暴露了远超 Agent 实际需要的细节——响应中填充了数百个字段,分页需要手动处理,内部 ID 缺乏上下文。
一个专用的包装层需要在内部处理分页,只投射 Agent 需要的字段,并将 API 错误映射为结构化 ToolError 格式。当然,过度包装也有问题——如果每个端点都变成一个独立工具,工具表面就变得过于碎片化。
2. 全量加载——工具越多,精度越差
将所有工具加载到每个上下文中会消耗 Token 预算——LongFuncEval(2025 年的一项工具调用研究)发现,随着工具目录增大,性能显著下降,即使在 128K 上下文窗口中也不例外。
正确的做法是动态工具加载:确定当前步骤相关的工具,只包含这些:
STEP_TOOL_MAP = {
"research": ["search_documents", "search_web", "get_url_content"],
"write": ["create_document", "update_document", "format_text"],
"send": ["send_email", "post_to_slack", "create_calendar_event"],
}
def get_tools_for_step(step_type: str, available_tools: list) -> list:
relevant_names = STEP_TOOL_MAP.get(step_type, [])
return [t for t in available_tools if t.name in relevant_names]
3. 静默部分成功——Agent 不知道你失败了
当工具只完成了部分工作但返回看起来完全成功的结果时,问题就出现了。最常见的原因是工具抑制了内部失败:
def bulk_create_tasks(tasks: list) -> dict:
created = []
for task in tasks:
try:
result = task_api.create(task)
created.append(result.id)
except Exception:
pass # 静默失败:这是 bug
return {"created": created}
def bulk_create_tasks(tasks: list) -> BulkCreateResult:
created, failed = [], []
for task in tasks:
try:
created.append(task_api.create(task).id)
except TaskCreationError as e:
failed.append({"input": task.title, "reason": str(e)})
return BulkCreateResult(
created_ids=created,
failed_items=failed,
success=len(failed) == 0,
partial_success=len(created) > 0 and len(failed) > 0
)
partial_success 标志给了模型分支决策的依据:重试失败项、向用户展示部分结果、或者停止工作流。
4. 工具命名模糊——Agent 分不清两个「搜索」
当两个工具做相似的事情时,模型在每次调用时都要推理该用哪一个。这不仅消耗 Token,还引入错误。
常见的问题模式:
search_documents和find_documents— 用途相同fetch_user_profile和get_user_profile— 区别不明确create_task和add_task和new_task— 同一个操作三个名字
修正方法:每个工具的名字和描述应该做到不引用其他工具就能说清自己的用途。如果一个工具的用途需要拿另一个工具做对照才能说清楚,说明两个工具应该合并,或者职责边界需要重新划分。
5. 破坏性操作缺确认门——Agent 不问你直接删了
任何执行不可逆操作的工具——删除记录、给真实用户发消息、执行金融交易——都需要一道确认门。
最安全的模式是把「暂存」和「执行」分离为两次工具调用:
def stage_deletion(record_ids: list[str], reason: str) -> StagedDeletion:
"""暂存待删除的记录。不执行任何删除。
返回一个 60 秒后过期的确认令牌。
使用 confirm_deletion() 传入此令牌执行删除。"""
token = generate_deletion_token(record_ids)
staged_deletions[token] = {"ids": record_ids, "expires": now() + 60}
return StagedDeletion(
token=token,
records_to_delete=len(record_ids),
expires_in_seconds=60
)
def confirm_deletion(token: str) -> DeletionResult:
"""执行暂存的删除操作。不可逆!仅在用户明确确认后调用。"""
staged = staged_deletions.get(token)
if not staged or staged["expires"] < now():
raise ValueError("Token invalid or expired. Stage the deletion again.")
# 执行实际删除
...
两次调用意味着模型无法在单步推理中完成破坏性操作——这正是目的所在。
快速参考:设计决策一览
| 设计领域 | 该做的 | 不该做的 |
|---|---|---|
| 职责粒度 | 单一职责工具 | manage_database(action="create") 风格 |
| Schema | 枚举、验证器、类型字段 | 自由字符串、未类型化的 dict |
| 描述 | 包含边界说明和何时不该用 | 只写 happy path |
| 写操作 | 幂等键保证 | 发后不理,重试不安全 |
| 错误返回 | 结构化:recoverable + suggested_action | 未处理异常 |
| 工具选择 | 按步骤动态加载 | 所有工具塞进每个上下文 |
| API 包装 | 为 Agent 定制的包装层 | 裸露的 REST API |
| 部分成功 | 用 partial_success 字段显式报告 | 静默吞异常 |
| 破坏性操作 | 两阶段:暂存 + 确认 | 一步删除/发送/执行 |
| 工具重叠 | 语义区分明确,部署前审计 | 名称和描述相似混淆 |
总结
AI Agent 工具设计不是「让模型更聪明」的问题——而是让接口不再拖后腿的问题。五个该做的模式(单一职责、紧 Schema、边界描述、结构化错误、幂等性)和五个不该做的陷阱(裸 API、全量加载、静默失败、命名模糊、无确认门)构成了一个实用的工具设计清单。
下次你的 Agent 选错了工具,不要怪模型——先看看你给了它什么工具。
本文改编自 Bala Priya C 的文章《AI Agent Tool Design: What Works and What Doesn't》(Machine Learning Mastery, June 15, 2026),代码示例已根据中文开发者习惯适配。