> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/claude_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/claude_guide/di-er-bu-fen-gong-ju-pian/03_tools/3.2_schema.md).

# 3.2 定义工具 Schema

## 3.2.1 什么是工具 Schema？

在与 Claude 进行工具集成时，并非直接上传代码函数，而是提供一份 **Schema**（结构定义）。这份 Schema 就像是一份“说明书”，告诉 Claude：这里有一个工具，它的名字叫什么，用来做什么，以及使用时需要按照什么样的格式提供参数。

Claude 工具定义使用 **JSON Schema** 风格的结构来描述参数，但生产系统应按 Anthropic Structured Outputs / Strict Tool Use 文档核验当前支持的子集、复杂度限制和无效输出处理路径。换句话说，Schema 是强约束工具，不是“任意 JSON Schema 都完整执行”的保证。

## 3.2.2 工具定义的三大支柱

一个标准的工具定义（Tool Definition）由三个核心字段（名称、描述和参数结构）构成。每一个字段都直接影响模型的判断准确性和 Token 消耗。

### Name：名称

工具的唯一标识符。

* **格式限制**：必须匹配正则 `^[a-zA-Z0-9_-]{1,64}$`。通常推荐使用 `snake_case`（蛇形命名法），如 `get_stock_price`。
* **最佳实践**：名称本身应具有语义。Claude 会关注工具名称。例如 `search_database` 比 `tool_a` 更能引导模型正确使用。

### Description：描述 —— 提示工程的战场

这是工具定义中最重要的部分。许多开发者低估了 `description` 的作用。实际上，这里的文本会作为系统提示词 (System Prompt) 的一部分输入给模型。

**编写高质量描述的技巧**：

* **明确“何时”使用**：不要只写“获取天气”，而要写“当用户询问实时天气情况或气温时，使用此工具”。
* **提供相关上下文**：如果工具返回的数据有特定格式（如 CSV 或 JSON），可以在描述中提及，以便 Claude 做好解析准备。
* **示例增强**（Few-shot）：对于复杂工具，可以在描述中简要包含一个参数示例。

### Input Schema：参数结构

定义了工具接受的参数格式。这是一个标准的 JSON Schema 对象。

* **`type`**: 通常为 `object`。
* **`properties`**: 定义各个参数字段。
* **`required`**: 一个数组，列出必须提供的字段名。Claude 会努力确保生成的 JSON 包含这些字段。

## 3.2.3 完整示例解析

下面的示例展示了一个用于“查询酒店”的工具定义，包含了多种参数类型。

```json
{
  "name": "search_hotels",
  "description": "根据目的地、日期和价格范围搜索酒店。当用户计划旅行或寻找住宿时使用。",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "目的地城市或地标，如 'Paris' 或 'Eiffel Tower'"
      },
      "check_in_date": {
        "type": "string",
        "format": "date",
        "description": "入住日期，格式 YYYY-MM-DD"
      },
      "nights": {
        "type": "integer",
        "minimum": 1,
        "maximum": 30,
        "description": "入住晚数"
      },
      "amenities": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": ["wifi", "pool", "gym", "breakfast"]
        },
        "description": "所需的设施列表"
      },
      "price_range": {
        "type": "object",
        "properties": {
          "min": {"type": "number"},
          "max": {"type": "number"}
        },
        "description": "价格区间（每晚）"
      }
    },
    "required": ["location", "check_in_date", "nights"]
  }
}
```

### 结构可视化

将上述 Schema 结构化为一个类图，有助于理解 Claude 是如何看待这个工具的。

```mermaid
classDiagram
    class search_hotels {
        +String description "搜索酒店..."
        +Object input_schema
    }
    class InputParams {
        +String location
        +String check_in_date
        +Integer nights
        +Array amenities
        +Object price_range
    }
    search_hotels -- InputParams : requires

    note for InputParams "Required: location, check_in, nights"
```

**图 3-1：工具定义类图** 这个类图直观地展示了 `search_hotels` 工具的数据结构。Claude 将其视为一个主对象（Function），它依赖于一个参数对象（InputParams）。其中 `InputParams` 包含了所有必要的字段及其类型约束。这种可视化的理解有助于开发者在设计复杂参数时保持逻辑清晰。

## 3.2.4 高级参数技巧

让 Claude 更稳定地工作的关键，在于通过 Schema **限制生成空间**。

### 枚举 —— 抗幻觉神器

如果一个字段只有固定的几个有效值（如状态、类型、模式），**务必使用 `enum`**。

* **Bad**: `"type": "string", "description": "排序方式"` (模型可能生成 'desc', 'descending', 'down' 等)
* **Good**: `"type": "string", "enum": ["asc", "desc"]`

### 验证信息：Format 与 Descriptions

虽然 JSON Schema 支持 `format: email` 或 `pattern`（正则），但 LLM 并不总是完美遵循这些隐式约束。 **建议**：将格式要求显式写在 `description` 中。

* `"description": "用户邮箱，必须是有效的 email 格式"`
* `"description": "日期，严格遵循 YYYY-MM-DD 格式"`

## 3.2.5 开发实战：使用 Pydantic 生成 Schema

在 Python 开发中，手动编写冗长的 JSON 往往容易出错。推荐使用 `Pydantic` 库来定义数据模型，并自动生成 Schema。

```python
from pydantic import BaseModel, Field
from typing import List, Optional

class HotelSearch(BaseModel):
    location: str = Field(..., description="目的地城市")
    nights: int = Field(1, ge=1, le=30, description="入住晚数")
    amenities: List[str] = Field(default_factory=list, description="设施列表")

#自动生成 Claude 兼容的 Schema
schema = HotelSearch.model_json_schema()

# 注意：生成的 schema 可能包含 'title' 等额外字段，Claude API 通常会忽略，但最好清理一下。
```

## 3.2.6 实战洞察：工具结构化频谱

工具设计不仅是写 JSON Schema，更是一门在“结构化”与“灵活性”之间寻找平衡的艺术。Claude Code 团队在实践中总结出一个核心概念——**工具结构化频谱**：

* **无结构（过于松散）**：让模型自由输出 Markdown 或自然语言，灵活但难以程序化解析，输出格式不稳定。
* **甜蜜点（恰到好处）**：设计专用工具，结构化输出参数，模型既能正确调用，程序也能可靠解析。
* **过度刚性（过于死板）**：在不相关的工具中硬塞额外参数，虽然结构化了，但混淆了模型的语义理解。

以下是 Claude Code 团队在设计“引导用户澄清”功能时的三次迭代——这个案例完美诠释了频谱上不同位置的效果差异：

### 尝试一：扩展现有工具参数（过度刚性）

团队最初在用于生成计划的 `ExitPlanTool` 中新增了一个 `questions` 参数，想让模型在输出计划的同时提出澄清问题。结果是**失败的**——模型在“给出计划”和“提问”两个矛盾状态之间左右为难，输出质量严重下降。

**教训**：不要让一个工具承担两种语义冲突的职责。

### 尝试二：自定义输出格式（无结构）

团队转而用提示词指示模型以特定的 Markdown 格式输出问题。结果**不稳定**——模型经常在问题后面追加多余文本，或者干脆忽略格式要求，导致程序解析困难。

**教训**：纯文本格式缺乏强制约束力，模型的遵从率不可靠。

### 尝试三：专用工具（甜蜜点）

最终，团队设计了一个独立的 `AskUserQuestion` 工具，模型可以在任何时候调用它来向用户提问。工具的 Schema 包含结构化的问题文本和可选的选项列表。结果是**成功的**——模型能准确调用，输出结构化且一致，程序端可以直接渲染为交互式 UI 组件。

**核心原则**：

* **一个工具只做一件事**。与其往现有工具里塞参数，不如新建一个语义清晰的专用工具。
* **独立工具优于参数扩展**。模型对“调用哪个工具”的判断，往往比“在一个工具里填哪些可选参数”更准确。

## 3.2.7 常见陷阱与调试

在定义工具时，开发者常犯以下错误：

1. **参数过多且非必填**：如果一个工具包含 20 个参数且大部分是可选的，Claude 往往会感到困惑，不知道该填哪些。**策略**：拆分为多个专注的小工具，或者将可选参数合并为一个 `options` 对象。
2. **不仅是 JSON 类型**：不要把所有东西都定义为 `string`。如果你需要数字，就用 `integer` 或 `number`；如果需要布尔开关，就用 `boolean`。这能减少后续代码转换的工作量。
3. **忽略错误处理**：即使定义了 Schema，未启用 strict 模式时 Claude 仍极小概率会生成不符合 Schema 的 JSON（尽管当前模型在这方面已非常强，且可用 `strict: true` 从 API 层面保证参数严格符合 Schema，见 3.7 节）。代码必须具备 `try-catch` 机制，在 JSON 解析失败时给 Claude 返回明确的错误信息（Tool Result），让它可以纠正并重试。

## 3.2.8 工具设计哲学：为 Agent 而非为用户设计

在讨论 Token 成本之前，必须理解一个根本性的转变：**当我们为 Agent 设计工具时，原则与为传统软件设计 API 完全不同**。

**关键认知：工具是人与非人之间的契约**

传统 API 设计面向程序员——我们知道开发者会仔细阅读文档，理解边界情况，并相应地构建代码。

但 Agent（LLM）面对大量工具时的能力与限制完全不同：

* Agent 的“上下文”有限——过多工具会导致混淆
* Agent 没有逻辑编程的严格性——它会根据模糊的启发式做出决策
* Agent 的决策基于概率，而非确定性规则

**三个工具设计原则**

1. **优先完整性而非最小化**

许多开发者觉得工具应该像 REST API 一样最小化，但对 Agent 来说，**一个工具应该完成一个完整的业务流程**，而非暴露各个原始操作。

示例对比：

* ❌ **过度细粒度（不适合 Agent）**：
  * `list_users()`
  * `list_events()`
  * `create_event()`
* ✅ **适合 Agent 的完整工具**：
  * `schedule_event(user_name, description, availability)`：内部找到用户、检查可用性、创建事件，一次完成

2. **让工具处理“多步”任务**

Agent 调用工具时，工具可以（也应该）在幕后执行多个步骤。这样做有两个好处：

* 减少 Agent 的工具调用次数，降低 Token 消耗
* 减少 Agent 的认知负担——它不需要记得前一个调用的结果，传入下一个调用

示例：

* 不要让 Agent 先调用 `search_logs(id)`，再调用 `analyze_logs(log_data)`
* 而是让 Agent 调用一次 `investigate_customer_issue(customer_id)`，工具内部处理搜索和分析

3. **工具应该是“人类做什么，工具就做什么”**

最好的工具设计思路是问自己：**如果一个人类专家接收同样的要求，会怎么做？** 然后让工具镜像这个人类工作流。

例如：

* 人类处理“客户取消请求”时：会找到用户、查看订购历史、发送保留优惠
* 所以工具也应该叫 `handle_cancellation_request(customer_id)` 而非 `cancel_subscription()` + `send_offer()` 两个分开的工具

**根据任务特征选择工具还是参数**

一个常见的设计问题：什么时候应该添加一个新工具，什么时候应该添加一个参数？

| 情景              | 选择        | 原因                            |
| --------------- | --------- | ----------------------------- |
| 两个操作代表完全不同的意图   | 新工具       | Agent 在“调用哪个工具”上的准确度高于“填哪个参数” |
| 一个参数会使工具的名义目标模糊 | 新工具       | 语义清晰的工具名比复杂的参数组合更能引导 Agent    |
| 两个操作很少一起使用      | 新工具       | 减少不必要的参数加载到上下文                |
| 两个操作是同一任务的前后步骤  | 同一工具的多个参数 | 合并能减少 Agent 的协调开销             |

***

*融入自 Anthropic 的《Writing effective tools for agents — with agents》中关于工具设计哲学的最佳实践*

## 3.2.9 Token 消耗警示

需注意，**整个 Schema 定义都会被计入 Input Tokens**。 如果定义了 50 个工具，每个都有详细的描述和复杂的参数，这可能消耗数千 Token，不仅增加成本，还可能挤占模型对用户问题的注意力（Attention）。

* **动态工具加载**：如果在某些对话分支中只需要特定工具，可以动态修改 API 请求中的 `tools` 列表，只发送当下相关的工具。

***

完成工具定义后，接下来将探讨 Claude 在调用工具后，该如何处理返回的结果。

➡️ [处理工具调用结果](/claude_guide/di-er-bu-fen-gong-ju-pian/03_tools/3.3_results.md)

***

> **🔥 踩坑实录**
>
> 团队在定义自定义工具时，参数类型中使用了 `"type": "integer"` 而非 `"type": "number"`。本来问题不大，但在某些场景下，Claude 的数值推理会生成浮点数（比如计算平均值），这些浮点数值会被 JSON Schema 校验拒绝。表面现象是“工具调用随机失败，有时成功有时不成功”，非常难以定位。最后发现根本原因是参数类型约束不够宽松。改为 `"type": "number"` 后，问题彻底消失。教训：理解 `integer` 和 `number` 的区别很关键——整数更严格，浮点数更宽容。如果业务允许，优先选择 `number`。
