Tools Design Best Practices - AI Agent 工具设计最佳实践 / Tools Design Best Practices for AI Agents
📅 创建时间:2026-07-28 🏷️ 标签:#ToolsDesign #Agent工程 #ToolCalling #工具设计 📚 前置知识:[[01-function-calling]]
📋 本章目标
- 理解"工具描述是 LLM 的用户界面"这一核心认知
- 掌握工具命名的动词_名词模式和业务语言原则
- 能够设计清晰、安全、LLM 友好的参数 schema
- 掌握工具粒度的判断框架:什么时候拆分、什么时候合并
- 理解错误处理如何让 LLM 具备自我纠正能力
- 能够设计多工具协作的依赖关系
- 建立安全第一的工具设计思维,知道什么不该暴露
第0部分:工具设计的核心认知——这不是 API 设计
0.1 两种读者,两种思维
你在设计 HTTP API 时,读者是人类程序员。他们会读文档、看示例、用 IDE 补全。你设计 RESTful 端点时用的是程序员熟悉的约定:GET /users/:id、POST /orders、资源嵌套、状态码。
但你在设计 Agent 工具时,读者是LLM。它不会"读文档",它只看你写的 name、description、parameters 这几个字段。这些字段就是它的全部"认知窗口"。
┌─────────────────────────────────────────────────────────────┐
│ API 设计 vs 工具设计 │
├─────────────────────────────────────────────────────────────┤
│ │
│ HTTP API(读者:人类程序员) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ GET /users/:id/orders?status=shipped │ │
│ │ 返回 200 { orders: [...] } │ │
│ │ │ │
│ │ 程序员看到 URL 路径就知道:查某用户的已发货订单 │ │
│ │ 看状态码就知道:200 成功, 404 用户不存在 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Agent Tool(读者:LLM) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "name": "list_orders", │ │
│ │ "description": "查询指定用户的所有订单,可按状态过滤",│ │
│ │ "parameters": { │ │
│ │ "user_id": { "description": "用户唯一标识符" }, │ │
│ │ "status": { "enum": ["paid","shipped","done"] } │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ LLM 看到的只有这三个字段。没有 URL 路径、没有文档、 │ │
│ │ 没有示例。它必须仅凭这些文字做出"要不要调"的判断。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘0.2 坏的工具定义长什么样
下面是一个真实的"反面教材"——你在很多项目中都能看到类似的工具定义:
# 反面教材:这样的工具 LLM 几乎必然用错
{
"name": "do_query", # 名字模糊
"description": "执行查询", # 什么查询?查什么?
"parameters": {
"type": "object",
"properties": {
"q": { # 参数名是缩写
"type": "string",
"description": "查询参数" # 什么参数?格式是什么?
},
"t": { # 又是缩写
"type": "string",
"description": "类型"
}
},
"required": ["q"]
}
}LLM 拿到这个工具后会怎样?它不知道 q 到底该填 SQL 语句、Elasticsearch DSL、还是自然语言关键词。它不知道 t 是 "user" 还是 "USER" 还是 "users"。最终它会猜——而 LLM 的猜测,十次有八次是错的。
0.3 好的工具定义长什么样
# 正面的工具定义
{
"name": "search_customers",
"description": "在客户数据库中按姓名或手机号搜索客户。"
"适用场景:用户询问某位客户的信息时调用。"
"返回匹配的客户列表(含 ID、姓名、手机号、注册时间)。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词。可以是客户姓名(如'张三')或手机号(如'13800138000')。"
"支持模糊匹配。"
},
"search_by": {
"type": "string",
"enum": ["name", "phone", "auto"],
"description": "搜索方式:'name'按姓名搜索,'phone'按手机号搜索,'auto'自动识别(默认)。"
}
},
"required": ["query"]
}
}LLM 拿到这个工具后,每个字段的含义都一清二楚。它知道什么时候调用、参数怎么填。
第1部分:工具描述即 UI——LLM 的"用户界面"
1.1 为什么说 description 是 UI
对于人类用户来说,UI 是按钮上的文字、输入框的 placeholder、下拉菜单的选项。好的 UI 让你看一眼就知道怎么操作,不需要读说明书。
对于 LLM 来说,name + description + parameters 就是它的全部 UI。它看不到你的代码实现、看不到数据库 schema、看不到 API 文档。它只能根据这段 JSON 来决定"要不要调这个工具"以及"参数该填什么"。
┌─────────────────────────────────────────────────────────────┐
│ 工具定义的三个"UI 元素" │
├─────────────────────────────────────────────────────────────┤
│ │
│ name ← 相当于"按钮上的文字" │
│ 用户看到"发送",就知道点了会发出去 │
│ LLM 看到 send_email,就知道这是发邮件 │
│ │
│ description ← 相当于"按钮旁边的说明文字" │
│ "点击后将订单提交到仓库系统" │
│ LLM 据此判断"用户的请求是否该触发这个工具" │
│ │
│ parameters ← 相当于"表单里的输入框" │
│ label、placeholder、下拉选项 │
│ LLM 据此决定每个槽位该填什么值 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 description 的四要素法则
一条好的 description 应该回答四个问题:
┌─────────────────────────────────────────────────────────────┐
│ description 四要素法则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 做什么(What) │
│ "在客户数据库中按姓名或手机号搜索客户" │
│ │
│ 2. 什么时候用(When) │
│ "适用场景:用户询问某位客户的信息时调用" │
│ "不要用于批量导出或报表生成(用 export_customers)" │
│ │
│ 3. 返回什么(Returns) │
│ "返回匹配的客户列表,含 ID、姓名、手机号、注册时间" │
│ "若无匹配结果返回空数组 []" │
│ │
│ 4. 有什么限制/副作用(Constraints) │
│ "单次最多返回 20 条结果" │
│ "调用后会记录审计日志" │
│ "不会发送任何通知给客户" │
│ │
└─────────────────────────────────────────────────────────────┘对比:
# 坏:只回答了一个问题(做什么),而且含糊
"description": "搜索客户"
# 好:回答了全部四个问题
"description": "在客户数据库中按姓名或手机号搜索客户。"
"适用场景:用户询问某位客户的信息时调用。"
"返回匹配的客户列表(ID、姓名、手机号、注册时间),最多 20 条。"
"无匹配结果时返回空数组。不会修改任何数据。"1.3 常见陷阱:description 里的"程序员语言"
description 是写给 LLM 看的,LLM 的理解方式更接近普通用户而非程序员:
# 陷阱 1:用技术实现代替业务含义
# ❌ 坏
"description": "对 user 表执行 SELECT 查询,JOIN orders 表,按 created_at 降序排列"
# ✅ 好
"description": "查询指定用户的历史订单列表,按时间从新到旧排列"
# 陷阱 2:用 API 术语代替自然语言
# ❌ 坏
"description": "POST /api/v2/tickets,创建工单资源"
# ✅ 好
"description": "在客服系统中创建新的工单。适用场景:用户反馈问题或提出需求时调用。"
# 陷阱 3:描述中包含对 LLM 无意义的信息
# ❌ 坏
"description": "调用内部 RPC 服务 ticket-service.create() 方法,超时时间 30s"
# ✅ 好
"description": "在客服系统中创建新的工单。返回工单编号和状态。"1.4 description 的"排除法"价值
description 除了告诉 LLM"什么时候该用",更重要的是告诉它"什么时候不该用":
{
"name": "search_knowledge_base",
"description": "搜索公司内部知识库中的技术文档和 FAQ。"
"注意:此工具只能搜索已发布的知识库文章,"
"无法搜索实时数据、客户订单或财务报表。"
"如果用户问的是订单相关问题,请使用 search_orders。"
"如果用户问的是财务数据,请使用 query_finance_db。"
}这种"排除法"能显著减少 LLM 选错工具的几率。你把工具的"能力边界"写清楚,LLM 就不会越界。
第2部分:命名设计——LLM 不是程序员
2.1 动词_名词模式
┌─────────────────────────────────────────────────────────────┐
│ 工具命名模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 标准模式:动词_名词 │
│ │
│ 动词(做什么) 名词(操作对象) │
│ ───────────── ────────────── │
│ search customers │
│ get weather │
│ create ticket │
│ send email │
│ update order_status │
│ delete cart_item │
│ list invoices │
│ cancel subscription │
│ │
│ 动词选择原则: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 查询类 → get / search / list / query │ │
│ │ 创建类 → create / add / register │ │
│ │ 更新类 → update / modify / set │ │
│ │ 删除类 → delete / remove / cancel │ │
│ │ 执行类 → execute / run / trigger │ │
│ │ 发送类 → send / notify / publish │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ get vs search vs list 的区别: │
│ • get_user(user_id) 精确获取一个,知道 ID │
│ • search_users(keyword) 模糊搜索,可能多个结果 │
│ • list_users(status) 列举,按条件过滤,通常分页 │
│ │
└─────────────────────────────────────────────────────────────┘2.2 不要做的事
# 1. 不要缩写
# ❌ 坏
"name": "srch_usr" # LLM 可能猜不出来
# ✅ 好
"name": "search_users"
# 2. 不要用技术术语做名词
# ❌ 坏
"name": "query_elasticsearch_index_v2"
# ✅ 好
"name": "search_customers"
# 3. 不要用含糊的动词
# ❌ 坏
"name": "handle_order" # handle 是"处理"——创建?更新?删除?
"name": "process_payment" # process 是"处理"——发起?退款?查询?
# ✅ 好
"name": "create_order"
"name": "refund_payment"
# 4. 不要把"怎么做"放进名字
# ❌ 坏
"name": "lookup_customer_by_phone_via_twilio_api"
# ✅ 好
"name": "lookup_customer" # description 里说明支持手机号查询2.3 命名一致性:建立"词汇表"
如果你的系统有 20 个工具,命名不一致会让 LLM 混乱:
┌─────────────────────────────────────────────────────────────┐
│ 命名一致性:坏 vs 好 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 坏(不一致): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ get_user ← 动词在名词前 │ │
│ │ order_create ← 名词在动词前!模式变了 │ │
│ │ fetchProduct ← camelCase! │ │
│ │ payment-refund ← kebab-case!完全是另一种风格 │ │
│ │ searchOrders ← 又回到 camelCase │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 好(一致): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ get_user ← 全部 snake_case │ │
│ │ create_order ← 全部 动词_名词 │ │
│ │ search_products ← LLM 看一眼就能学会这个"规则" │ │
│ │ refund_payment ← 模式清晰,猜测成本低 │ │
│ │ cancel_subscription ← 新工具命名有据可依 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 核心原则:选一种风格(推荐 snake_case),然后死守。 │
│ 你不必让名字"技术上准确"——你要让它"LLM 可预测"。 │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:参数设计——让 LLM 填对每一个"槽位"
3.1 参数数量:3-5 个为宜
这是经过大量实践验证的经验数字:
┌─────────────────────────────────────────────────────────────┐
│ 参数数量对准确率的影响(经验观察) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 0-2 个参数: │
│ ├── 优点:LLM 几乎不出错 │
│ └── 不足:工具能力有限,可能需要多次调用 │
│ │
│ 3-5 个参数:★ 最佳区间 │
│ ├── 优点:能力和易用性的平衡点 │
│ └── 准确率:大部分主流模型在此区间表现稳定 │
│ │
│ 6-8 个参数: │
│ ├── 优点:一个工具做更多事 │
│ └── 风险:LLM 开始漏填参数、填错类型、混淆字段 │
│ │
│ 9+ 个参数: │
│ ├── 实测:即使 GPT-4o 也开始频繁出错 │
│ └── 解决:拆成多个工具,或把高级参数移到"options"对象 │
│ │
└─────────────────────────────────────────────────────────────┘超过 5 个参数时的拆分策略:
# 坏:参数太多
{
"name": "create_campaign",
"parameters": {
"properties": {
"name": ...,
"budget": ...,
"start_date": ...,
"end_date": ...,
"target_audience": ...,
"platform": ...,
"bid_strategy": ...,
"creative_ids": ...,
"tracking_pixel": ...,
}
}
}
# 好:拆分 + 高级参数放入 options
{
"name": "create_campaign",
"parameters": {
"properties": {
"name": {"description": "广告系列名称", "type": "string"},
"budget": {"description": "日预算,单位元,如 '500.00'", "type": "number"},
"start_date": {"description": "开始日期,格式 YYYY-MM-DD", "type": "string"},
"options": {
"type": "object",
"description": "高级配置(可选)",
"properties": {
"platform": {"enum": ["google", "facebook", "tiktok"]},
"bid_strategy": {"enum": ["cpc", "cpm", "ocpm"]},
}
}
},
"required": ["name", "budget", "start_date"]
}
}3.2 必填 vs 可选:给 LLM 明确的"自由度"信号
┌─────────────────────────────────────────────────────────────┐
│ required 字段的"行为引导"作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ required 不仅是给程序看的校验规则——它直接影响 LLM 的行为: │
│ │
│ required 中的参数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 知道"这个必须填",它会: │ │
│ │ • 如果用户没提供 → 主动追问用户 │ │
│ │ • 如果可以从上下文推断 → 推断后填入 │ │
│ │ • 如果找不到也推断不出 → 返回澄清问题给用户 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 不在 required 中的参数: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 知道"这个可以不填",它会: │ │
│ │ • 如果用户明确提了 → 填入 │ │
│ │ • 如果用户没提 → 省略(用服务端默认值) │ │
│ │ • 不会主动追问(设计预期如此) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘3.3 enum 约束:给你能给的,锁住该锁的
enum 是参数设计中最被低估的约束。它同时做了三件事:指导 LLM 选值、防止非法输入、减少服务端校验负担。
# 没有 enum —— LLM 自己"发明"值
"status": {
"type": "string",
"description": "订单状态"
}
# LLM 可能填: "已发货"、"shipped"、"SHIPPED"、"发货中"、"已完成发货"
# 有 enum —— LLM 只能从列表中选
"status": {
"type": "string",
"enum": ["pending", "confirmed", "shipped", "delivered", "cancelled"],
"description": "订单状态。可选值:pending(待确认)、confirmed(已确认)、"
"shipped(已发货)、delivered(已签收)、cancelled(已取消)"
}
# LLM 填: "shipped" ← 干净、可预测enum 的 description 里应同时给出值和中文含义,因为用户表达可能是中文的,LLM 需要做"语义映射":用户说"帮我查已发货的订单",LLM 需要知道 "已发货" 对应 enum 里的 "shipped"。
3.4 参数 description 的三层信息
┌─────────────────────────────────────────────────────────────┐
│ 参数 description 的三层信息 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第一层:业务含义(必填) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "客户姓名" │ │
│ │ "订单状态" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第二层:格式/约束(强烈推荐) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "格式 YYYY-MM-DD,如 '2026-01-15'" │ │
│ │ "手机号,11 位数字,如 '13800138000'" │ │
│ │ "金额,单位为元,保留两位小数,如 '199.99'" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第三层:获取方式(帮助 LLM 从上下文提取) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "通常出现在用户消息中的'订单号:XXX'或'编号 XXX'" │ │
│ │ "用户提到'发给谁'或'收件人'后面的邮箱地址" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 分层示例(完整版): │
│ "customer_phone": { │
│ "type": "string", │
│ "description": "客户手机号。11 位数字,如'13800138000'。" │
│ "通常出现在用户消息中'手机号:'后面。" │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:工具粒度——在"太粗"和"太细"之间找平衡
4.1 粒度频谱
┌─────────────────────────────────────────────────────────────┐
│ 工具粒度频谱 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 太粗 ←──────────────────────────────────────────→ 太细 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ manage_order │ │ create_order │ │ set_order_ │ │
│ │ (一个工具做 │ │ cancel_order │ │ customer_id │ │
│ │ 增删改查) │ │ update_order │ │ set_order_ │ │
│ │ │ │ get_order │ │ product │ │
│ │ 问题:LLM │ │ │ │ set_order_ │ │
│ │ 不知道该工 │ │ 比较合适 │ │ quantity │ │
│ │ 具到底能做 │ │ │ │ │ │
│ │ 什么 │ │ │ │ 问题:LLM │ │
│ └──────────────┘ └──────────────┘ │ 做一件事要 │ │
│ │ 调 5-10 个 │ │
│ 百宝箱式工具: │ 工具,效率 │ │
│ { │ 极低 │ │
│ "action": "create|update|delete", └──────────────┘ │
│ "data": {...} ← 任何结构 │
│ } │
│ LLM 困惑:action 到底有哪些值?data 什么格式? │
│ │
└─────────────────────────────────────────────────────────────┘4.2 粒度决策框架
判断一个工具应该拆分还是合并,看三个维度:
┌─────────────────────────────────────────────────────────────┐
│ 粒度决策框架(三个维度) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 维度 1:操作类型是否不同? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 创建 ≠ 删除 ≠ 查询 → 拆成独立工具 │ │
│ │ 按状态过滤 vs 按日期过滤 → 同一个工具的不同参数 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 维度 2:参数集合是否显著不同? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ create_order(商品列表, 地址, 优惠码, 支付方式) │ │
│ │ cancel_order(订单号, 原因) │ │
│ │ → 参数完全不同,拆开更好 │ │
│ │ │ │
│ │ search_orders_by_status(status, page) │ │
│ │ search_orders_by_date(start, end, page) │ │
│ │ → 参数高度相似,合为一个 search_orders 更合适 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 维度 3:一个用户请求通常需要几步? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户说"帮我查张三的订单" │ │
│ │ → 先 search_customers("张三") 拿 ID │ │
│ │ → 再 search_orders(customer_id=...) 查订单 │ │
│ │ → 两步是合理的,因为"找客户"和"查订单"是不同的能力 │ │
│ │ │ │
│ │ 但如果需要 5 步才能完成一个常见任务 → 考虑封装 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 经验法则: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 一个工具 = 一个"业务动作" │ │
│ │ 一个业务动作 = 用户能用一句话描述的操作 │ │
│ │ │ │
│ │ ✅ "创建订单" → 一个工具 │ │
│ │ ✅ "取消订单" → 一个工具 │ │
│ │ ❌ "在订单表插入一行并更新库存并发送通知" → 太细 │ │
│ │ ❌ "管理订单" → 太粗 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.3 具体示例:create_user 怎么拆
# 太粗 —— 一个工具做所有用户相关的事
{
"name": "manage_user",
"description": "管理用户", # LLM 不知道能做什么
"parameters": {
"properties": {
"action": {"type": "string"}, # 没有 enum!LLM 会"发明"值
"user_data": {"type": "object"}, # 什么结构?完全不知道
}
}
}
# 太细 —— 每个字段一个工具
"set_user_name" # LLM 要调 5 次才能创建一个用户
"set_user_email"
"set_user_phone"
"set_user_department"
"activate_user"
"send_welcome_email"
# 恰好 —— 按业务动作拆分
"create_user" # 必填:name, email。可选:phone, department
"update_user" # 必填:user_id + 要改的字段
"deactivate_user" # 必填:user_id。可选:reason
"get_user" # 必填:user_id
"search_users" # 必填:query。可选:department, status4.4 何时"故意做粗":便利工具
有时候故意做一个"粗"工具是合理的——当它对应一个常见的复合操作时:
# 便利工具:封装常见的两步操作
{
"name": "find_customer_and_list_orders",
"description": "按姓名或手机号查找客户,并直接返回该客户的最近订单。"
"这是一个便利工具,相当于先调用 search_customers 再调用 list_orders。"
"适用场景:用户询问'帮我查张三的订单'时直接使用此工具。"
"注意:如果用户只想查客户信息(不关心订单),请用 search_customers。",
"parameters": {
"properties": {
"query": {
"type": "string",
"description": "客户姓名或手机号"
},
"order_limit": {
"type": "integer",
"description": "返回最近 N 个订单,默认 10"
}
},
"required": ["query"]
}
}关键在于 description 中清楚说明:这是一个便利工具、它做了什么、以及什么时候应该用更细粒度的工具。
第5部分:错误处理设计——让 LLM 从失败中自我纠正
5.1 核心认知:错误信息是给 LLM 看的,不是给日志看的
┌─────────────────────────────────────────────────────────────┐
│ 错误处理的两种受众 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 你的习惯(给程序员看): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ raise ValueError("Invalid status: 3") │ │
│ │ {"error": "E1005", "message": "DB_CONN_TIMEOUT"} │ │
│ │ 500 Internal Server Error │ │
│ │ │ │
│ │ 这些对 LLM 毫无意义。LLM 不知道 E1005 是什么, │ │
│ │ 不知道 500 是临时还是永久错误,不知道该怎么纠正。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 应该做的(给 LLM 看): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ { │ │
│ │ "success": false, │ │
│ │ "error": "订单状态 '已签收' 不在可选值范围内。 │ │
│ │ 可选值为:pending, confirmed, shipped, │ │
│ │ delivered, cancelled", │ │
│ │ "suggestion": "请使用 'delivered' 替代 '已签收'" │ │
│ │ } │ │
│ │ │ │
│ │ LLM 看到这个后可以直接修正参数并重试。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 错误返回的四种模式
# 模式 1:参数错误 → 返回修正建议(可重试)
# LLM 应收到:明确的错误原因 + 正确的格式/可选值
{
"success": False,
"error": "参数 'start_date' 格式错误:收到 '2026年1月15日',"
"期望格式为 YYYY-MM-DD(如 '2026-01-15')。",
"corrected_params": {"start_date": "2026-01-15"}
}
# 结果:LLM 通常在第 1-2 次重试内修正
# 模式 2:业务规则冲突 → 返回冲突说明(可能可重试)
# LLM 应收到:为什么被拒绝 + 可以怎么做
{
"success": False,
"error": "该用户已有 3 张未使用的优惠券(已达上限),无法再领取。",
"available_alternatives": [
"先使用现有优惠券后再领取新的",
"将此优惠券转赠给其他用户"
]
}
# 结果:LLM 向用户解释拒绝原因,并主动提供替代方案
# 模式 3:临时故障 → 返回可重试标记
# LLM 应收到:是不是暂时性的 + 建议等待多久
{
"success": False,
"error": "服务暂时不可用,可能是因为正在维护中。",
"retryable": True,
"retry_after_seconds": 5
}
# 结果:LLM 可以等 5 秒后重试,或告诉用户稍后再试
# 模式 4:永久失败 → 明确告知不可重试
# LLM 应收到:不可重试 + 原因 + 建议的下一步
{
"success": False,
"error": "指定的用户 ID 'usr_nonexistent' 在系统中不存在。"
"请先通过 search_users 查询正确的用户 ID。",
"retryable": False
}
# 结果:LLM 换一个工具或告诉用户提供更多信息5.3 抛异常 vs 返回错误 JSON
┌─────────────────────────────────────────────────────────────┐
│ 抛异常 vs 返回错误 JSON │
├─────────────────────────────────────────────────────────────┤
│ │
│ 抛异常: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具函数内部 raise Exception(...) │ │
│ │ → Agent 框架捕获异常,把异常信息放进 tool result │ │
│ │ │ │
│ │ 问题: │ │
│ │ • 异常信息通常是技术性的(stack trace, error code) │ │
│ │ • 框架不一定把异常转成 LLM 友好的格式 │ │
│ │ • LLM 可能拿到一坨 traceback 而不是有用的错误说明 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 返回错误 JSON:★ 推荐 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具函数永远 return 一个 dict,包含 success 字段 │ │
│ │ │ │
│ │ def create_order(data): │ │
│ │ try: │ │
│ │ # 业务逻辑 │ │
│ │ return {"success": True, "order_id": "..."} │ │
│ │ except ValidationError as e: │ │
│ │ return { │ │
│ │ "success": False, │ │
│ │ "error": str(e), ← 业务语言 │ │
│ │ "suggestion": "..." ← 修正建议 │ │
│ │ } │ │
│ │ │ │
│ │ 好处:LLM 拿到的永远是结构清晰的 JSON │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 黄金法则:每个工具函数都应返回一个包含 success: bool 的 dict │
│ │
└─────────────────────────────────────────────────────────────┘5.4 错误信息的四条设计原则
# 原则 1:说人话,不说机器话
# ❌ 坏
"error": "ERR_DB_001: constraint violation on fk_user_id"
# ✅ 好
"error": "无法删除该客户,因为该客户还有 3 笔未完成的订单。请先取消或完成这些订单后再试。"
# 原则 2:给出"下一步"
# ❌ 坏
"error": "查询无结果"
# ✅ 好
"error": "未找到姓名为'张伞'的客户。请检查姓名拼写,或尝试用手机号搜索。"
# 原则 3:区分"你错了"和"我挂了"
# ❌ 坏(混淆两类错误)
"error": "操作失败" # 是参数不对还是服务挂了?LLM 不知道
# ✅ 好
"error": "参数错误:start_date 不能晚于 end_date" # 这是"你错了"→ LLM 可以修正
"error": "支付网关暂时无响应,请稍后重试" # 这是"我挂了"→ LLM 应该等待/转述
# 原则 4:错误信息中不要泄露敏感信息
# ❌ 坏
"error": "数据库连接失败: mysql://admin:pass123@10.0.1.5:3306/prod_db"
# ✅ 好
"error": "数据库暂时不可用,请稍后重试。"第6部分:工具组合——让多个工具协同工作
6.1 工具之间三种关系
┌─────────────────────────────────────────────────────────────┐
│ 工具之间的三种关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 关系 1:独立并行 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ get_weather("北京") ←──→ get_weather("上海") │ │
│ │ ↓ ↓ │ │
│ │ 结果 1 结果 2 │ │
│ │ │ │
│ │ 特征:互不依赖,可以同时调用 │ │
│ │ 用户:"北京和上海今天分别多少度?" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 关系 2:顺序依赖(链式) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ search_customers("张三") │ │
│ │ ↓ customer_id = "c_456" │ │
│ │ list_orders(customer_id="c_456") │ │
│ │ ↓ order_id = "o_789" │ │
│ │ get_order_detail(order_id="o_789") │ │
│ │ │ │
│ │ 特征:下游工具的输入是上游工具的输出 │ │
│ │ 用户:"张三最近一个订单的详情是什么?" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 关系 3:条件分支 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ check_inventory(product_id="p_123") │ │
│ │ ↓ │ │
│ │ 库存 > 0 ──→ create_order(...) │ │
│ │ 库存 = 0 ──→ notify_restock_alert(...) │ │
│ │ │ │
│ │ 特征:后一步用哪个工具取决于前一步的结果 │ │
│ │ 用户:"帮我下单一个商品,没货就通知我补货时购买" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.2 如何让 LLM 理解工具之间的依赖
工具之间的依赖关系不是写在 JSON schema 里的——没有 "depends_on": "search_customers" 这样的字段。LLM 是通过以下信息来推断的:
┌─────────────────────────────────────────────────────────────┐
│ 让 LLM 理解依赖的三种方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方式 1:通过参数名建立"数据流线索" │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ search_customers 返回: {customers: [{id: "c_456"}]} │ │
│ │ list_orders 的参数: customer_id ← 同名! │ │
│ │ │ │
│ │ LLM 看到上一个返回里有个 id,下一个参数叫 customer_id, │ │
│ │ 就会自己建立关联。 │ │
│ │ │ │
│ │ 关键:保持参数名在上下游工具中一致 │ │
│ │ ✅ 都用 customer_id → LLM 自动关联 │ │
│ │ ❌ 一个叫 user_id,一个叫 cust_id → LLM 需要猜 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式 2:在 description 中显式声明 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "description": "查询指定客户的订单列表。" │ │
│ │ "参数 customer_id 可以从 │ │
│ │ search_customers 的返回结果中获取。" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方式 3:通过返回值的 description 提供上下文 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具返回中附带"可用作下一步"的线索: │ │
│ │ { │ │
│ │ "customer_id": "c_456", │ │
│ │ "_available_actions": [ │ │
│ │ "用此 customer_id 调用 list_orders 查看订单", │ │
│ │ "用此 customer_id 调用 update_customer 修改信息" │ │
│ │ ] │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘6.3 工具组合的常见模式
# 模式:资源查询 → 资源操作
# 这是最常���的组合模式
# 步骤 1: search_xxx → 拿到 ID
# 步骤 2: get/update/delete_xxx → 用 ID 操作
# 让这个模式更流畅的设计技巧:
# 技巧 1:search 工具的返回直接包含可用的 ID
{
"customers": [
{"id": "c_456", "name": "张三", "phone": "13800138000"}
]
}
# 技巧 2:在参数 description 中引用上游工具
"customer_id": {
"description": "客户 ID。你可以从 search_customers 的返回结果中获取此 ID。"
}
# 技巧 3:对于高频组合,提供便利工具(如 4.4 节示例)
"find_customer_and_list_orders"第7部分:安全和权限——不该暴露的绝不暴露
7.1 安全边界总览
┌─────────────────────────────────────────────────────────────┐
│ 工具安全的四层防线 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第 1 层:定义层 —— 什么不写进 tools 数组 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ❌ 不暴露:删除数据库、DROP TABLE、执行任意命令 │ │
│ │ ❌ 不暴露:读写系统文件、修改配置、访问凭证 │ │
│ │ ❌ 不暴露:发送短信/邮件的"自由文本"接口(防垃圾) │ │
│ │ ✅ 只暴露:参数明确、边界清晰的业务操作 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第 2 层:校验层 —— 参数校验必须在服务端 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ LLM 的输出是不可信的——它可能被 prompt injection 操纵 │ │
│ │ 所有参数必须在服务端重新校验: │ │
│ │ • 类型校验(string 真的是 string) │ │
│ │ • 长度校验(不能传 10MB 的字符串) │ │
│ │ • 范围校验(价格不能是负数) │ │
│ │ • 枚举校验(status 的值真的在允许列表中) │ │
│ │ • 权限校验(这个用户真的能操作这个订单吗?) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第 3 层:确认层 —— 敏感操作需要二次确认 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 哪些操作需要确认? │ │
│ │ • 涉及金额: 退款、转账、大额下单 │ │
│ │ • 不可逆操作: 删除、注销、封禁 │ │
│ │ • 影响他人: 群发消息、修改公共资源 │ │
│ │ • 外部效应: 发送真实短信/邮件、调用付费 API │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 第 4 层:审计层 —— 所有操作可追溯 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每��工具调用必须记录: │ │
│ │ • 谁触发的(user_id, session_id) │ │
│ │ • 什么工具(tool_name) │ │
│ │ • 什么参数(全部参数,敏感字段脱敏) │ │
│ │ • 什么结果(success/failure,耗时) │ │
│ │ • 什么上下文(触发此工具的对话轮次) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘7.2 敏感操作的确认机制
# 方案 A:两阶段调用(推荐)
# 阶段 1:LLM 调 preview_refund,返回预览信息
{
"name": "preview_refund",
"description": "预览退款信息(不执行实际退款)。"
"返回退款金额、手续费、预计到账时间。",
"parameters": {
"properties": {
"order_id": {"type": "string", "description": "订单号"}
},
"required": ["order_id"]
}
}
# → 返回: {"success": True, "amount": "199.00", "fee": "0.00", "eta": "1-3 个工作日"}
# 阶段 2:用户确认后,Agent 调 execute_refund
{
"name": "execute_refund",
"description": "执行实际退款操作。"
"注意:仅在用户明确确认退款金额和条件后调用。"
"建议先调用 preview_refund 展示退款预览。",
"parameters": {
"properties": {
"order_id": {"type": "string", "description": "订单号"},
"confirmation_code": {
"type": "string",
"description": "用户确认码。由系统在 preview_refund 后生成,用户回复确认后填入。"
}
},
"required": ["order_id", "confirmation_code"]
}
}
# 方案 B:require_confirmation 参数
{
"name": "send_sms",
"description": "向指定手机号发送短信。"
"当 require_confirmation 为 true 时,"
"系统会返回一条预览消息,需要用户在对话中确认后才实际发送。"
"建议对群发或营销类短信始终开启确认。",
"parameters": {
"properties": {
"phone": {"type": "string", "description": "手机号"},
"message": {"type": "string", "description": "短信内容"},
"require_confirmation": {
"type": "boolean",
"description": "是否要求用户确认后再发送。默认为 true。"
}
},
"required": ["phone", "message"]
}
}7.3 参数校验:防线必须在自己手里
┌─────────────────────────────────────────────────────────────┐
│ 参数校验的正确位置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ LLM 输出参数 │
│ │ │
│ ▼ │
│ ┌──────────────────┐ 校验失败 → 返回错误让 LLM 修正 │
│ │ 你的校验层 │ │
│ │ (必须在这里) │ │
│ │ │ │
│ │ 1. 类型检查 │ │
│ │ 2. 格式检查 │ │
│ │ 3. 范围检查 │ │
│ │ 4. 权限检查 │ │
│ │ 5. 业务规则检查 │ │
│ └──────┬───────────┘ │
│ │ 校验通过 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 实际业务逻辑 │ │
│ └──────────────────┘ │
│ │
│ 不要在 LLM 侧做校验 —— LLM 不是安全边界。 │
│ 不要在客户端做校验 —— 客户端可以被绕过。 │
│ 校验必须在工具执行函数内部,在调用任何下游服务之前。 │
│ │
└─────────────────────────────────────────────────────────────┘一个完整的校验示例:
def execute_refund(order_id: str, amount: float, reason: str) -> dict:
"""执行退款。每次调用都做完整校验,不管谁传的参数。"""
# 1. 类型校验
if not isinstance(order_id, str) or not order_id.startswith("ord_"):
return {
"success": False,
"error": "订单号格式无效。期望格式:ord_ 开头,如 'ord_20260728_001'。"
}
# 2. 范围校验
if amount <= 0:
return {
"success": False,
"error": f"退款金额必须大于 0,收到:{amount}。"
}
if amount > 50000:
return {
"success": False,
"error": f"退款金额 {amount} 元超过单笔上限 50000 元。"
}
# 3. 权限校验 —— 最关键的一步
order = db.get_order(order_id)
if not order:
return {
"success": False,
"error": f"订单 {order_id} 不存在。请检查订单号是否正确。",
"retryable": False
}
if order["user_id"] != current_user_id():
return {
"success": False,
"error": "你无权操作此订单。请确认订单号是否正确。",
"retryable": False
}
# 4. 业务规则校验
if order["status"] == "refunded":
return {
"success": False,
"error": f"订单 {order_id} 已经退款过了,无法重复退款。"
}
# 5. 执行
# ... 实际退款逻辑 ...
return {"success": True, "refund_id": "ref_abc123", "amount": amount}7.4 什么操作绝对不该暴露为工具
有些操作,无论怎么加校验和确认,都不该暴露给 LLM:
# 绝对禁止暴露的操作类型:
# 1. 执行任意代码或命令
# ❌
{"name": "run_shell_command", ...} # 等价于给 LLM 一个 shell
{"name": "execute_sql", ...} # 等价于给 LLM 数据库管理员权限
# 2. 访问凭证或密钥
# ❌
{"name": "get_api_key", ...} # LLM 可能把密钥泄露给用户
{"name": "read_env_file", ...} # .env 文件里有所有密钥
# 3. 不受限的外部通信
# ❌
{"name": "send_http_request", ...} # SSRF 风险,LLM 可能探测内网
{"name": "call_any_api", ...} # 无法控制 LLM 会调什么 API
# 4. 修改系统配置或权限
# ❌
{"name": "grant_admin_access", ...} # 提权
{"name": "update_security_policy", ...} # 修改安全策略
# 5. 批量破坏性操作
# ❌
{"name": "delete_all_records", ...} # 不加限制的批量删除
{"name": "truncate_table", ...} # 清空表第8部分:工具设计的实战检查清单
8.1 新工具上线前的自检
┌─────────────────────────────────────────────────────────────┐
│ 工具设计检查清单(上线前逐项确认) │
├─────────────────────────────────────────────────────────────┤
│ │
│ □ 命名 │
│ ├── 动词_名词模式? │
│ ├── 没有缩写? │
│ ├── 没有技术术语? │
│ └── 与系统中其他工具风格一致? │
│ │
│ □ description │
│ ├── 说明了做什么、何时用、返回什么、有何限制? │
│ ├── 用业务语言而非技术语言? │
│ ├── 说明了什么时候不该用这个工具? │
│ └── 提到了与其他工具的关联(如有)? │
│ │
│ □ 参数 │
│ ├── 数量在 3-5 个之间(或已合理拆分)? │
│ ├── 每个参数有清晰的业务含义 description? │
│ ├── 每个参数包含格式/示例? │
│ ├── 使用了 enum 约束可选值? │
│ ├── required 列表准确反映了必填/可选? │
│ └── 参数名与相关工具中的同名参数保持一致? │
│ │
│ □ 粒度 │
│ ├── 一个工具对应一个"用户能用一句话描述的操作"? │
│ ├── 不存在"万能工具"(一个工具做所有事)? │
│ └── 常见操作不需要 3+ 个工具串联? │
│ │
│ □ 错误处理 │
│ ├── 返回包含 success: bool 的 dict? │
│ ├── 错误信息是给 LLM 看的业务语言? │
│ ├── 包含修正建议或下一步操作? │
│ └── 不泄露技术实现细节或敏感信息? │
│ │
│ □ 安全 │
│ ├── 参数校验在服务端(不在客户端,不靠 LLM)? │
│ ├── 敏感操作有确认机制? │
│ ├── 有权限校验(当前用户能否操作此资源)? │
│ ├── 有审计日志? │
│ └── 不在禁止暴露的类别中? │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:工具定义是 LLM 的 UI
工具定义 = name + description + parameters = LLM 的全部认知窗口
好的定义让 LLM 一次就选对工具、填对参数。
坏的定义让 LLM 反复试错、选错工具、填错参数。
设计的每一步都要想:LLM 看到这段文字,能理解吗?总结2:命名的黄金法则
动词_名词 模式 + 业务语言 + 全团队一致
✅ search_customers, create_order, send_email
❌ do_query, process_data, srch_usr总结3:参数的 3-5 法则
3-5 个参数是最佳区间。超过 5 个考虑拆工具或使用 options 对象。
enum 是性价比最高的约束——一次定义,同时指导 LLM + 防止非法输入。
参数的 description 要包含:业务含义 + 格式/示例 + 获取方式。总结4:粒度的判断标准
一个工具 = 一个用户能用一句话描述的业务动作
太粗:manage_order → LLM 不知道具体能做什么
太细:set_order_customer_id → LLM 要调 5 个工具完成一件事
恰好:create_order, cancel_order → 一个动词一个业务动作总结5:错误处理的核心原则
错误信息写给 LLM 看,不是写给日志看。
每条错误信息应该包含:
1. 发生了什么错(业务语言)
2. 为什么错了(可理解的原因)
3. 下一步可以做什么(修正建议或替代方案)
4. 是否可重试(让 LLM 知道是重试还是换思路)
返回 dict 而非抛异常——保持 LLM 收到的信息结构清晰。总结6:安全的底线
四层防线:
1. 定义层:不安全的操作根本不写进 tools 数组
2. 校验层:LLM 给的参数必须在服务端重新校验
3. 确认层:不可逆操作必须有用户确认环节
4. 审计层:所有调用可追溯、可回溯
LLM 的输出永远不可信——安全边界必须在你的代码里。章节测试
测试1:工具描述
以下工具定义存在哪些问题?请逐项指出并修正。
{
"name": "data",
"description": "处理数据",
"parameters": {
"type": "object",
"properties": {
"mode": {"type": "string", "description": "模式"},
"payload": {"type": "string", "description": "数据"}
},
"required": ["mode"]
}
}测试2:命名设计
将以下工具名改为符合最佳实践的命名:
usr_mgrprocessPaymentget-data-from-db
测试3:参数设计
一个 send_email 工具有以下参数:to, cc, bcc, subject, body, priority, template_id, scheduled_time, attachments, sender_name, reply_to。参数是否过多?如何优化?
测试4:工具粒度
用户说"帮我查一下张三最近的订单,如果有还没发货的,帮我催一下"。需要设计哪些工具?各自负责什么?
测试5:错误处理
LLM 调 create_order 时传了 "date": "明天" 而工具期望 "date": "2026-07-28"。写一个 LLM 能自我纠正的错误返回。
测试6:安全
以下哪些操作适合暴露为 Agent 工具? A. 查询用户订单列表(仅当前用户) B. 执行 SQL 语句 C. 向指定手机号发送验证码 D. 读取服务器配置文件 E. 取消订单(需确认)
测试7:综合
设计一套"客服工单系统"的工具集(至少 4 个工具),包含完整的 name、description、parameters。要求:命名一致、粒度合适、有安全的确认机制。
参考答案
测试1答案
问题:
name: "data"—— 完全无法表达工具的功能description: "处理数据"—— 无法判断何时使用,数据是什么mode参数无 enum 约束、无业务含义说明payload参数不知道应该填什么格式
修正:
{
"name": "search_customers",
"description": "在客户数据库中按姓名或手机号搜索客户。"
"适用场景:用户询问某位客户的信息时调用。"
"返回匹配的客户列表(ID、姓名、手机号)。",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词。客户姓名(如'张三')或手机号(如'13800138000')。"
},
"search_by": {
"type": "string",
"enum": ["name", "phone", "auto"],
"description": "搜索方式:'name'按姓名,'phone'按手机号,'auto'自动识别(默认)。"
}
},
"required": ["query"]
}
}测试2答案
usr_mgr → manage_users (但建议进一步拆分为 create_user, update_user 等)
processPayment → process_payment (或更明确:capture_payment)
get-data-from-db → query_customers (或 search_records,看具体场景)测试3答案
参数过多(11 个),建议:
- 必填保留:to, subject, body(核心 3 个参数)
- 可选合并:cc, bcc, priority, sender_name, reply_to 放入
options对象 - 独立工具:scheduled_time(定时发送 →
schedule_email)、attachments(添加附件 →add_attachment)、template_id(模板 → 路径参数或独立工具) - 最终
send_email保留 3 个必填 + 1 个 options 对象
测试4答案
需要 3 个工具:
- search_customers(query) —— 找到张三的 customer_id
- list_orders(customer_id, status?) —— 查张三的订单,可按状态过滤
- urge_order(order_id, message?) —— 催单,向仓库发送提醒
工作流:search_customers → list_orders → 过滤未发货 → urge_order
测试5答案
{
"success": False,
"error": "参数 'date' 格式错误。收到 '明天',"
"期望格式为 YYYY-MM-DD(如 '2026-07-28')。",
"corrected_value": "2026-07-29", # 服务端计算好的明天日期
"suggestion": "如需表示明天,请使用 '2026-07-29'。"
}重点是:告知期望格式、给出示例、如果可能的话直接给出修正后的值。
测试6答案
- A ✅:查询当前用户的订单是安全的,前提是服务端校验 user_id。
- B ❌:执行任意 SQL 是严重安全风险,应使用参数化查询的专用工具。
- C ✅:发送验证码是合理工具,但需要限流(同手机号 1/min)、确认机制。
- D ❌:配置文件可能含密钥,不应暴露给 LLM。
- E ✅:取消订单是合理工具,需要确认机制和服务端权限校验。
测试7答案(参考设计)
# 工具 1:创建工单
{
"name": "create_ticket",
"description": "在客服系统中创建新的工单。适用场景:用户反馈问题或提出需求。",
"parameters": {
"properties": {
"title": {"type": "string", "description": "工单标题,简短描述问题"},
"description": {"type": "string", "description": "问题详细描述"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "优先级:low(低)、medium(中)、high(高)、urgent(紧急)"
}
},
"required": ["title", "description"]
}
}
# 工具 2:查询工单
{
"name": "search_tickets",
"description": "查询用户的历史工单。适用场景:用户询问工单进度。",
"parameters": {
"properties": {
"query": {"type": "string", "description": "搜索关键词,匹配标题和描述"},
"status": {
"type": "string",
"enum": ["open", "in_progress", "resolved", "closed"],
"description": "工单状态过滤"
}
},
"required": []
}
}
# 工具 3:添加工单回复
{
"name": "add_ticket_reply",
"description": "向已有工单添加回复。适用场景:用户补充信息或客服跟进。",
"parameters": {
"properties": {
"ticket_id": {"type": "string", "description": "工单编号,可从 search_tickets 结果中获取"},
"content": {"type": "string", "description": "回复内容"}
},
"required": ["ticket_id", "content"]
}
}
# 工具 4:关闭工单(敏感操作,需要确认)
{
"name": "close_ticket",
"description": "关闭工单。不可逆操作。建议在用户确认问题已解决后调用。",
"parameters": {
"properties": {
"ticket_id": {"type": "string", "description": "工单编号"},
"resolution": {
"type": "string",
"description": "解决说明,记录问题是如何解决的"
},
"confirmation": {
"type": "boolean",
"description": "必须为 true 以确认关闭。此操作不可撤销。"
}
},
"required": ["ticket_id", "resolution", "confirmation"]
}
}相关笔记
- [[01-function-calling]] - Function Calling 基础原理与数据流
- [[03-rag-basics]] - RAG 工具的设计考量
- [[05-agent-workflow]] - 工具调用在工作流中的编排
- [[18-structured-output]] - 工具返回值的结构化设计
- [[09-安全沙箱]] - 工具安全的沙箱隔离
- [[10-权限与门卫]] - 工具调用的权限模型
下一步学习
- [ ] 阅读 03 - RAG 基础 —— 理解 RAG 类工具的特殊设计需求
- [ ] 阅读 09 - 结构化输出 —— 工具返回值的结构化设计
- [ ] 实践:将你项目中的一个现有 API 改造为符合本文规范的 Agent 工具
学习状态:🟡 开始学习