Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

智能体工程 / Agent Engineering

1. Agent 工程体系全景 / Agent Engineering System Overview

2. Function Calling - 让 LLM 具备行动能力 / Function Calling for Giving LLMs the Ability to Act

3. Agent 框架演进 - 从裸 SDK 到 LangGraph / The Evolution of Agent Frameworks from Raw SDKs to LangGraph

4. RAG 基础 - 让 Agent 拥有"知识" / Retrieval-Augmented Generation Fundamentals for Agent Knowledge

5. 记忆管理 - Agent 的大脑 / Memory Management as the Brain of an Agent

6. Agent 工作流 - 从单步到复杂的执行编排 / Agent Workflows from Single Steps to Complex Orchestration

7. 多 Agent 系统 - 多个 Agent 协作 / Multi-Agent Systems and Agent Collaboration

8. RAG 进阶 - 企业级知识库实战 / Advanced RAG for Enterprise Knowledge Bases

9. 真实 Agent 应用场景 / Real-World AI Agent Applications

10. Structured Output - 让 LLM 输出可控的结构化数据 / Structured Output for Controllable, Machine-Readable LLM Responses

11. Tools Design Best Practices - AI Agent 工具设计最佳实践 / Tools Design Best Practices for AI Agents

12. Agent 架构模式 - 从单 Agent 到多 Agent 的工程范式 / Agent Architecture Patterns

13. Agent Modes — 编程 Agent 的交互模式设计 / Designing Interaction Modes for Coding Agents

14. Agent Workflow 编排:从循环到持久化执行的演进

15. Context Engineering - 从 Prompt 设计到上下文编排 / Context Engineering: From Prompt Design to Context Orchestration

16. Agent 缓存工程:从 KV Cache、Prompt Cache 到语义缓存 / Agent Caching Engineering

17. Harness Engineering, Skills, and Loop Engineering — 从信任模型到验证系统 / From Trusting Models to Verifying Systems

18. MCP 协议 - AI 工具的"USB 接口" / Model Context Protocol for AI Tool Integration

19. Agent 评估与测试 — 如何衡量一个"不可预测"的系统 / Agent Evaluation and Testing — How to Measure an "Unpredictable" System

20. 安全沙箱 - Agent 的安全边界 / Secure Sandboxes as Agent Safety Boundaries

21. 权限与门卫 - Agent 的安全控制中枢 / Permissions and Policy Gates for Agent Control

22. API Key 管理与安全 - Agent 的密钥生命周期的管理 / API Key Lifecycle Management and Security for Agents

23. 提示词注入防护 - Agent 的防御前沿 / Prompt Injection Defense for AI Agents

24. 可观测性与调试 - Agent 运行的透明度保障 / Observability and Debugging for Transparent Agent Operations

25. 模型路由 - 让正确的模型做正确的事 / Model Routing for Matching Models to Tasks

26. OpenClaw 设计深度分析 - 为什么它让人觉得"活"了 / OpenClaw Design Analysis and the Illusion of Liveliness

27. Claude Code 泄露源码深度分析 - 512,000 行代码揭示的生产级 Agent 架构 / Claude Code Source Analysis and Production Agent Architecture

28. LobeChat 设计深度分析 - 全栈 Agent Chat 应用工程实践 / LobeChat Design Analysis and Full-Stack Agent Chat Engineering

29. 编程 Agent 全面对比:从 Claude Code 到 Pi 的设计哲学 / Coding Agents Comparison: Design Philosophies from Claude Code to Pi

30. 领域 Agent 的确定性工具编译与延迟执行——从自然语言规格到单次 CAE 提交

31. Agent 工程学习指南 / An AI Agent Engineering Learning Guide

本页目录

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 路径、没有文档、    │   │
│  │ 没有示例。它必须仅凭这些文字做出"要不要调"的判断。     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

0.2 坏的工具定义长什么样 ​

下面是一个真实的"反面教材"——你在很多项目中都能看到类似的工具定义:

python
# 反面教材:这样的工具 LLM 几乎必然用错
{
    "name": "do_query",                         # 名字模糊
    "description": "执行查询",                    # 什么查询?查什么?
    "parameters": {
        "type": "object",
        "properties": {
            "q": {                               # 参数名是缩写
                "type": "string",
                "description": "查询参数"          # 什么参数?格式是什么?
            },
            "t": {                               # 又是缩写
                "type": "string",
                "description": "类型"
            }
        },
        "required": ["q"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

LLM 拿到这个工具后会怎样?它不知道 q 到底该填 SQL 语句、Elasticsearch DSL、还是自然语言关键词。它不知道 t 是 "user" 还是 "USER" 还是 "users"。最终它会猜——而 LLM 的猜测,十次有八次是错的。

0.3 好的工具定义长什么样 ​

python
# 正面的工具定义
{
    "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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1.2 description 的四要素法则 ​

一条好的 description 应该回答四个问题:

┌─────────────────────────────────────────────────────────────┐
│                description 四要素法则                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 做什么(What)                                           │
│     "在客户数据库中按姓名或手机号搜索客户"                    │
│                                                             │
│  2. 什么时候用(When)                                       │
│     "适用场景:用户询问某位客户的信息时调用"                  │
│     "不要用于批量导出或报表生成(用 export_customers)"       │
│                                                             │
│  3. 返回什么(Returns)                                      │
│     "返回匹配的客户列表,含 ID、姓名、手机号、注册时间"       │
│     "若无匹配结果返回空数组 []"                              │
│                                                             │
│  4. 有什么限制/副作用(Constraints)                          │
│     "单次最多返回 20 条结果"                                 │
│     "调用后会记录审计日志"                                   │
│     "不会发送任何通知给客户"                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

对比:

python
# 坏:只回答了一个问题(做什么),而且含糊
"description": "搜索客户"

# 好:回答了全部四个问题
"description": "在客户数据库中按姓名或手机号搜索客户。"
               "适用场景:用户询问某位客户的信息时调用。"
               "返回匹配的客户列表(ID、姓名、手机号、注册时间),最多 20 条。"
               "无匹配结果时返回空数组。不会修改任何数据。"
1
2
3
4
5
6
7
8

1.3 常见陷阱:description 里的"程序员语言" ​

description 是写给 LLM 看的,LLM 的理解方式更接近普通用户而非程序员:

python
# 陷阱 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1.4 description 的"排除法"价值 ​

description 除了告诉 LLM"什么时候该用",更重要的是告诉它"什么时候不该用":

python
{
    "name": "search_knowledge_base",
    "description": "搜索公司内部知识库中的技术文档和 FAQ。"
                   "注意:此工具只能搜索已发布的知识库文章,"
                   "无法搜索实时数据、客户订单或财务报表。"
                   "如果用户问的是订单相关问题,请使用 search_orders。"
                   "如果用户问的是财务数据,请使用 query_finance_db。"
}
1
2
3
4
5
6
7
8

这种"排除法"能显著减少 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)     列举,按条件过滤,通常分页        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

2.2 不要做的事 ​

python
# 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 里说明支持手机号查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

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 可预测"。            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

第3部分:参数设计——让 LLM 填对每一个"槽位" ​

3.1 参数数量:3-5 个为宜 ​

这是经过大量实践验证的经验数字:

┌─────────────────────────────────────────────────────────────┐
│              参数数量对准确率的影响(经验观察)                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  0-2 个参数:                                                │
│  ├── 优点:LLM 几乎不出错                                    │
│  └── 不足:工具能力有限,可能需要多次调用                      │
│                                                             │
│  3-5 个参数:★ 最佳区间                                     │
│  ├── 优点:能力和易用性的平衡点                               │
│  └── 准确率:大部分主流模型在此区间表现稳定                    │
│                                                             │
│  6-8 个参数:                                                │
│  ├── 优点:一个工具做更多事                                   │
│  └── 风险:LLM 开始漏填参数、填错类型、混淆字段               │
│                                                             │
│  9+ 个参数:                                                 │
│  ├── 实测:即使 GPT-4o 也开始频繁出错                         │
│  └── 解决:拆成多个工具,或把高级参数移到"options"对象       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

超过 5 个参数时的拆分策略:

python
# 坏:参数太多
{
    "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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

3.2 必填 vs 可选:给 LLM 明确的"自由度"信号 ​

┌─────────────────────────────────────────────────────────────┐
│              required 字段的"行为引导"作用                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  required 不仅是给程序看的校验规则——它直接影响 LLM 的行为:   │
│                                                             │
│  required 中的参数:                                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM 知道"这个必须填",它会:                          │   │
│  │ • 如果用户没提供 → 主动追问用户                        │   │
│  │ • 如果可以从上下文推断 → 推断后填入                    │   │
│  │ • 如果找不到也推断不出 → 返回澄清问题给用户            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  不在 required 中的参数:                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ LLM 知道"这个可以不填",它会:                        │   │
│  │ • 如果用户明确提了 → 填入                             │   │
│  │ • 如果用户没提 → 省略(用服务端默认值)               │   │
│  │ • 不会主动追问(设计预期如此)                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

3.3 enum 约束:给你能给的,锁住该锁的 ​

enum 是参数设计中最被低估的约束。它同时做了三件事:指导 LLM 选值、防止非法输入、减少服务端校验负担。

python
# 没有 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" ← 干净、可预测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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'。" │
│                   "通常出现在用户消息中'手机号:'后面。"     │
│  }                                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

第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 什么格式?              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

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 步才能完成一个常见任务 → 考虑封装        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  经验法则:                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 一个工具 = 一个"业务动作"                             │   │
│  │ 一个业务动作 = 用户能用一句话描述的操作                │   │
│  │                                                       │   │
│  │ ✅ "创建订单"  → 一个工具                              │   │
│  │ ✅ "取消订单"  → 一个工具                              │   │
│  │ ❌ "在订单表插入一行并更新库存并发送通知" → 太细       │   │
│  │ ❌ "管理订单"   → 太粗                                 │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

4.3 具体示例:create_user 怎么拆 ​

python
# 太粗 —— 一个工具做所有用户相关的事
{
    "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, status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

4.4 何时"故意做粗":便利工具 ​

有时候故意做一个"粗"工具是合理的——当它对应一个常见的复合操作时:

python
# 便利工具:封装常见的两步操作
{
    "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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

关键在于 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 看到这个后可以直接修正参数并重试。                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

5.2 错误返回的四种模式 ​

python
# 模式 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 换一个工具或告诉用户提供更多信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

5.4 错误信息的四条设计原则 ​

python
# 原则 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": "数据库暂时不可用,请稍后重试。"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

第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(...)              │   │
│  │                                                       │   │
│  │ 特征:后一步用哪个工具取决于前一步的结果                 │   │
│  │ 用户:"帮我下单一个商品,没货就通知我补货时购买"         │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

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 修改信息"  │   │
│  │   ]                                                   │   │
│  │ }                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

6.3 工具组合的常见模式 ​

python
# 模式:资源查询 → 资源操作
# 这是最常���的组合模式
# 步骤 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"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

第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,耗时)                  │   │
│  │ • 什么上下文(触发此工具的对话轮次)                  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

7.2 敏感操作的确认机制 ​

python
# 方案 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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

7.3 参数校验:防线必须在自己手里 ​

┌─────────────────────────────────────────────────────────────┐
│           参数校验的正确位置                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  LLM 输出参数                                                │
│       │                                                     │
│       ▼                                                     │
│  ┌──────────────────┐    校验失败 → 返回错误让 LLM 修正      │
│  │ 你的校验层        │                                       │
│  │ (必须在这里)      │                                       │
│  │                  │                                       │
│  │ 1. 类型检查       │                                       │
│  │ 2. 格式检查       │                                       │
│  │ 3. 范围检查       │                                       │
│  │ 4. 权限检查       │                                       │
│  │ 5. 业务规则检查   │                                       │
│  └──────┬───────────┘                                       │
│         │ 校验通过                                           │
│         ▼                                                   │
│  ┌──────────────────┐                                       │
│  │ 实际业务逻辑      │                                       │
│  └──────────────────┘                                       │
│                                                             │
│  不要在 LLM 侧做校验 —— LLM 不是安全边界。                    │
│  不要在客户端做校验 —— 客户端可以被绕过。                    │
│  校验必须在工具执行函数内部,在调用任何下游服务之前。         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

一个完整的校验示例:

python
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}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

7.4 什么操作绝对不该暴露为工具 ​

有些操作,无论怎么加校验和确认,都不该暴露给 LLM:

python
# 绝对禁止暴露的操作类型:

# 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", ...}          # 清空表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

第8部分:工具设计的实战检查清单 ​

8.1 新工具上线前的自检 ​

┌─────────────────────────────────────────────────────────────┐
│            工具设计检查清单(上线前逐项确认)                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  □ 命名                                                      │
│  ├── 动词_名词模式?                                         │
│  ├── 没有缩写?                                              │
│  ├── 没有技术术语?                                          │
│  └── 与系统中其他工具风格一致?                              │
│                                                             │
│  □ description                                              │
│  ├── 说明了做什么、何时用、返回什么、有何限制?              │
│  ├── 用业务语言而非技术语言?                                │
│  ├── 说明了什么时候不该用这个工具?                          │
│  └── 提到了与其他工具的关联(如有)?                        │
│                                                             │
│  □ 参数                                                      │
│  ├── 数量在 3-5 个之间(或已合理拆分)?                     │
│  ├── 每个参数有清晰的业务含义 description?                  │
│  ├── 每个参数包含格式/示例?                                  │
│  ├── 使用了 enum 约束可选值?                                │
│  ├── required 列表准确反映了必填/可选?                      │
│  └── 参数名与相关工具中的同名参数保持一致?                  │
│                                                             │
│  □ 粒度                                                      │
│  ├── 一个工具对应一个"用户能用一句话描述的操作"?            │
│  ├── 不存在"万能工具"(一个工具做所有事)?                  │
│  └── 常见操作不需要 3+ 个工具串联?                          │
│                                                             │
│  □ 错误处理                                                  │
│  ├── 返回包含 success: bool 的 dict?                       │
│  ├── 错误信息是给 LLM 看的业务语言?                         │
│  ├── 包含修正建议或下一步操作?                              │
│  └── 不泄露技术实现细节或敏感信息?                          │
│                                                             │
│  □ 安全                                                      │
│  ├── 参数校验在服务端(不在客户端,不靠 LLM)?              │
│  ├── 敏感操作有确认机制?                                    │
│  ├── 有权限校验(当前用户能否操作此资源)?                  │
│  ├── 有审计日志?                                            │
│  └── 不在禁止暴露的类别中?                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

核心总结 ​

总结1:工具定义是 LLM 的 UI ​

工具定义 = name + description + parameters = LLM 的全部认知窗口

好的定义让 LLM 一次就选对工具、填对参数。
坏的定义让 LLM 反复试错、选错工具、填错参数。

设计的每一步都要想:LLM 看到这段文字,能理解吗?
1
2
3
4
5
6

总结2:命名的黄金法则 ​

动词_名词 模式 + 业务语言 + 全团队一致

✅ search_customers, create_order, send_email
❌ do_query, process_data, srch_usr
1
2
3
4

总结3:参数的 3-5 法则 ​

3-5 个参数是最佳区间。超过 5 个考虑拆工具或使用 options 对象。
enum 是性价比最高的约束——一次定义,同时指导 LLM + 防止非法输入。
参数的 description 要包含:业务含义 + 格式/示例 + 获取方式。
1
2
3

总结4:粒度的判断标准 ​

一个工具 = 一个用户能用一句话描述的业务动作

太粗:manage_order → LLM 不知道具体能做什么
太细:set_order_customer_id → LLM 要调 5 个工具完成一件事
恰好:create_order, cancel_order → 一个动词一个业务动作
1
2
3
4
5

总结5:错误处理的核心原则 ​

错误信息写给 LLM 看,不是写给日志看。

每条错误信息应该包含:
1. 发生了什么错(业务语言)
2. 为什么错了(可理解的原因)
3. 下一步可以做什么(修正建议或替代方案)
4. 是否可重试(让 LLM 知道是重试还是换思路)

返回 dict 而非抛异常——保持 LLM 收到的信息结构清晰。
1
2
3
4
5
6
7
8
9

总结6:安全的底线 ​

四层防线:
1. 定义层:不安全的操作根本不写进 tools 数组
2. 校验层:LLM 给的参数必须在服务端重新校验
3. 确认层:不可逆操作必须有用户确认环节
4. 审计层:所有调用可追溯、可回溯

LLM 的输出永远不可信——安全边界必须在你的代码里。
1
2
3
4
5
6
7

章节测试 ​

测试1:工具描述 ​

以下工具定义存在哪些问题?请逐项指出并修正。

python
{
    "name": "data",
    "description": "处理数据",
    "parameters": {
        "type": "object",
        "properties": {
            "mode": {"type": "string", "description": "模式"},
            "payload": {"type": "string", "description": "数据"}
        },
        "required": ["mode"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

测试2:命名设计 ​

将以下工具名改为符合最佳实践的命名:

  • usr_mgr
  • processPayment
  • get-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答案 ​

问题:

  1. name: "data" —— 完全无法表达工具的功能
  2. description: "处理数据" —— 无法判断何时使用,数据是什么
  3. mode 参数无 enum 约束、无业务含义说明
  4. payload 参数不知道应该填什么格式

修正:

python
{
    "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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

测试2答案 ​

usr_mgr        → manage_users     (但建议进一步拆分为 create_user, update_user 等)
processPayment → process_payment  (或更明确:capture_payment)
get-data-from-db → query_customers (或 search_records,看具体场景)
1
2
3

测试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 个工具:

  1. search_customers(query) —— 找到张三的 customer_id
  2. list_orders(customer_id, status?) —— 查张三的订单,可按状态过滤
  3. urge_order(order_id, message?) —— 催单,向仓库发送提醒

工作流:search_customers → list_orders → 过滤未发货 → urge_order


测试5答案 ​

python
{
    "success": False,
    "error": "参数 'date' 格式错误。收到 '明天',"
             "期望格式为 YYYY-MM-DD(如 '2026-07-28')。",
    "corrected_value": "2026-07-29",     # 服务端计算好的明天日期
    "suggestion": "如需表示明天,请使用 '2026-07-29'。"
}
1
2
3
4
5
6
7

重点是:告知期望格式、给出示例、如果可能的话直接给出修正后的值。


测试6答案 ​

  • A ✅:查询当前用户的订单是安全的,前提是服务端校验 user_id。
  • B ❌:执行任意 SQL 是严重安全风险,应使用参数化查询的专用工具。
  • C ✅:发送验证码是合理工具,但需要限流(同手机号 1/min)、确认机制。
  • D ❌:配置文件可能含密钥,不应暴露给 LLM。
  • E ✅:取消订单是合理工具,需要确认机制和服务端权限校验。

测试7答案(参考设计) ​

python
# 工具 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"]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

相关笔记 ​

  • [[01-function-calling]] - Function Calling 基础原理与数据流
  • [[03-rag-basics]] - RAG 工具的设计考量
  • [[05-agent-workflow]] - 工具调用在工作流中的编排
  • [[18-structured-output]] - 工具返回值的结构化设计
  • [[09-安全沙箱]] - 工具安全的沙箱隔离
  • [[10-权限与门卫]] - 工具调用的权限模型

下一步学习 ​

  • [ ] 阅读 03 - RAG 基础 —— 理解 RAG 类工具的特殊设计需求
  • [ ] 阅读 09 - 结构化输出 —— 工具返回值的结构化设计
  • [ ] 实践:将你项目中的一个现有 API 改造为符合本文规范的 Agent 工具

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇10. Structured Output - 让 LLM 输出可控的结构化数据 / Structured Output for Controllable, Machine-Readable LLM Responses
下一篇12. Agent 架构模式 - 从单 Agent 到多 Agent 的工程范式 / Agent Architecture Patterns

持续记录,持续成长

Copyright © Tidenflow