工具Schema设计

要点

  • 工具 Schema 是 LLM 和后端代码之间的「接口契约」,告诉模型这个工具叫什么、做什么、接受什么参数
  • 一个工具定义由三部分组成:name(工具名称)、description(功能描述)、inputSchema(输入参数结构)
  • inputSchema 遵循 JSON Schema 规范,用 typepropertiesrequireddescription 这几个关键字段描述参数
  • 参数描述的质量直接影响 LLM 能否正确填充参数——描述模糊,模型就会乱猜
  • 枚举值(enum)和嵌套对象是实战中用得最多的两个设计手段
  • Schema 设计得越精确,模型出错的概率越低;偷懒少写几行 description,后面就要花更多时间处理模型的错误调用

1. 什么是工具 Schema

上一节讲了工具调用的整体流程:LLM 决定调用哪个工具,后端执行工具,把结果返回给 LLM。这里有一个前置条件——LLM 怎么知道有哪些工具可用、每个工具需要什么参数?

答案是工具 Schema。Schema 是一份结构化的描述,用 JSON 格式把工具的信息写清楚。你可以把它理解为工具的「说明书」,这份说明书会在每次请求时随 prompt 一起发给 LLM。模型读到说明书之后,就知道当前有哪些工具、什么时候该用、怎么传参。

先看一个最简的工具定义:

// src/tools/weather.ts
const weatherTool = {
  name: 'get_weather',
  description: '获取指定城市的当前天气信息',
  inputSchema: {
    type: 'object',
    properties: {
      city: {
        type: 'string',
        description: '城市名称,例如:北京、上海'
      }
    },
    required: ['city']
  }
}

发给 Anthropic API 时,这个定义放在请求体的 tools 数组里:

// src/tools/api-call.ts
const response = await anthropic.messages.create({
  model: 'claude-sonnet-4-20250514',
  max_tokens: 1024,
  tools: [weatherTool],
  messages: [
    { role: 'user', content: '北京今天天气怎么样?' }
  ]
})

模型如果判断需要调用工具,会返回一个 tool_use 类型的 content block,里面包含 name: 'get_weather'input: { city: '北京' }。参数的结构和值,完全是模型根据 Schema 自己推断出来的。

模型推断的准确度,取决于 Schema 写得好不好。 这就是这一篇要解决的问题。

2. JSON Schema 基础

工具 Schema 的 inputSchema 字段遵循 JSON Schema 规范。工具场景下用到的子集很有限,先把核心关键字过一遍:

关键字作用示例
type指定数据类型'string''number''boolean''object''array'
properties定义对象的字段{ city: { type: 'string' } }
required列出必填字段['city', 'unit']
description字段说明'温度单位,可选值:celsius、fahrenheit'
enum限制取值范围['celsius', 'fahrenheit']
items定义数组元素类型{ type: 'string' }
default默认值'celsius'

这些关键字组合起来,可以描述绝大多数工具的输入结构。

3. 工具定义的三个部分

3.1 name:工具的唯一标识

name 是模型在 tool_use 响应里引用的标识符。命名建议:

  • 用小写字母加下划线,例如 get_weathersearch_users
  • 名字要能体现动作,不要写 tool1func_a
  • 同一组工具里名称不能重复

模型会根据名字来理解工具的大致用途。get_weather 一看就是查天气,tool_1 什么都看不出来。

3.2 description:决定模型要不要调用

description 帮模型判断「用户的问题是否需要调用这个工具」。好的 description 包含三个要素:

  1. 做什么:一句话说清楚功能
  2. 返回什么:调用后能拿到什么结果
  3. 什么时候该用:典型的使用场景

对比两种写法:

// 模糊
{ name: 'get_weather', description: '天气工具' }
 
// 清晰
{
  name: 'get_weather',
  description: '获取指定城市的实时天气数据,包括温度、湿度、风速。当用户询问某个地方的天气时使用。'
}

第一种只给了一个名字,模型不知道能返回什么、什么场景下该调用。第二种把功能、返回值、使用场景都交代了。

3.3 inputSchema:参数的结构定义

inputSchema 描述模型调用工具时需要传入的参数。根节点始终是 type: 'object',具体字段定义在 properties 里。后面几节详细展开。

4. 参数类型与必填规则

4.1 五种基本类型

string — 最常用的类型。如果有格式要求,在 description 里写清楚:

// src/tools/date-param.ts
{
  type: 'string',
  description: '查询的开始日期,格式为 YYYY-MM-DD,例如 2026-01-15'
}

不要只写「日期」两个字。模型可能返回 2026/1/15Jan 15, 2026 等各种格式,后端解析时就会出问题。

number 与 integernumber 是浮点数,integer 是整数。如果业务上一定是整数(页码、数量),用 integer

// src/tools/product-filter.ts
{
  price_min: { type: 'number', description: '最低价格,单位:元' },
  page: { type: 'integer', description: '页码,从 1 开始' }
}

boolean — 开关类参数。在 description 里注明默认值:

{
  include_archived: {
    type: 'boolean',
    description: '是否包含已归档的记录,默认 false'
  }
}

array — 用 items 定义元素类型:

{
  type: 'array',
  items: { type: 'string' },
  description: '需要批量删除的用户 ID 列表'
}

4.2 必填与可选

required 数组列出必填字段,不在里面的就是可选的:

// src/tools/search-params.ts
{
  type: 'object',
  properties: {
    keyword: { type: 'string', description: '搜索关键词' },
    category: {
      type: 'string',
      description: '分类筛选,可选值:tech、design、marketing'
    },
    page: { type: 'integer', description: '页码,默认 1' }
  },
  required: ['keyword']
}

划分原则:如果缺少这个参数工具就无法执行,就设为必填;如果缺了也能正常工作、有合理的默认行为,就设为可选。

  • 天气查询的 city:不知道查哪个城市就没法查,必填
  • 搜索的 page:不传就默认第一页,可选

可选参数在 description 里写清楚默认值,模型看到「默认 20」后即使不传也知道后端会用 20。

5. 参数描述、枚举与嵌套

5.1 参数描述怎么写

模型在决定参数值时,依赖的信息来源只有两个:用户的输入和 Schema 里的 description。用户通常不会把参数格式说得很精确,所以 description 是模型理解参数的主要线索。

几个原则:

说清楚业务含义,不要只写类型:

// 不够好
{ type: 'string', description: '字符串' }
 
// 更好
{ type: 'string', description: '收件人的手机号,11 位数字,例如 13800138000' }

给出取值范围和格式约束:

// src/tools/validation.ts
{
  order_id: {
    type: 'string',
    description: '订单编号,格式为 ORD-XXXXXXXX,例如 ORD-20260115'
  },
  limit: {
    type: 'integer',
    description: '返回条数上限,最小 1,最大 100,默认 20'
  }
}

一条 description 控制在 1-2 句话。 太长会让模型注意力分散,太短起不到约束作用。如果发现一个参数需要写一大段来解释,考虑拆成多个参数。

5.2 枚举值(enum)

当参数只有固定的几个选项时,用 enum 约束。这比在 description 里写「可选值有 A、B、C」更可靠,因为 enum 是 JSON Schema 的正式约束,模型会严格遵守:

// src/tools/unit-param.ts
{
  unit: {
    type: 'string',
    description: '温度单位',
    enum: ['celsius', 'fahrenheit']
  }
}

有了 enum,模型只可能返回 'celsius''fahrenheit',不会返回 'C''℃' 或拼写错误。

枚举值的命名建议用全小写英文单词,下划线连接多个词。如果枚举值需要附带说明,在 description 里补充:

{
  priority: {
    type: 'integer',
    description: '任务优先级,1 表示最低,5 表示最高',
    enum: [1, 2, 3, 4, 5]
  }
}

5.3 嵌套对象

工具的输入结构比较复杂时,参数本身也可以是对象。在 properties 下再嵌套 properties

// src/tools/nested-filter.ts
{
  type: 'object',
  properties: {
    keyword: { type: 'string', description: '搜索关键词' },
    filter: {
      type: 'object',
      description: '筛选条件',
      properties: {
        status: {
          type: 'string',
          description: '记录状态',
          enum: ['draft', 'published', 'archived']
        },
        date_range: {
          type: 'object',
          description: '发布日期范围',
          properties: {
            start: { type: 'string', description: '开始日期,格式 YYYY-MM-DD' },
            end: { type: 'string', description: '结束日期,格式 YYYY-MM-DD' }
          },
          required: ['start', 'end']
        }
      }
    }
  },
  required: ['keyword']
}

模型调用时返回:

// model-response.json
{
  "keyword": "Hono",
  "filter": {
    "status": "published",
    "date_range": { "start": "2026-01-01", "end": "2026-06-20" }
  }
}

嵌套设计的两个建议:

  1. 不要超过三层嵌套。 到了四层五层,模型容易搞混层级关系,填错参数的概率明显上升。如果需要很深的嵌套,考虑把结构拍平。
  2. 每一层对象都要写 description。 不要只给叶子节点写描述,中间层的对象也需要一句话说明:
// 不要这样——模型不知道 filter 是干什么的
filter: { type: 'object', properties: { status: { ... } } }
 
// 要这样
filter: {
  type: 'object',
  description: '可选的筛选条件,不传则返回全部结果',
  properties: { status: { ... } }
}

5.4 数组里放对象

当工具需要接收一组结构化数据时,数组的元素类型设为 object

// src/tools/batch-create.ts
{
  items: {
    type: 'array',
    description: '要创建的商品列表',
    items: {
      type: 'object',
      properties: {
        name: { type: 'string', description: '商品名称' },
        price: { type: 'number', description: '价格,单位:元' },
        quantity: { type: 'integer', description: '库存数量' }
      },
      required: ['name', 'price']
    }
  }
}

数组内对象的 required 规则和顶层一样。这里 quantity 是可选的。

6. 一个完整的工具定义

把前面的内容组合起来,看一个贴近实战的完整示例:

// src/tools/create-order.ts
const createOrderTool = {
  name: 'create_order',
  description: '创建新的商品订单。当用户要求下单、购买商品时使用。需要指定商品列表和收货地址。',
  inputSchema: {
    type: 'object',
    properties: {
      items: {
        type: 'array',
        description: '订单中的商品列表,至少包含一个商品',
        items: {
          type: 'object',
          properties: {
            product_id: {
              type: 'string',
              description: '商品 ID,格式为 PRD-XXXXXX'
            },
            quantity: {
              type: 'integer',
              description: '购买数量,最小为 1',
              default: 1
            }
          },
          required: ['product_id']
        }
      },
      shipping_address: {
        type: 'object',
        description: '收货地址信息',
        properties: {
          receiver: { type: 'string', description: '收件人姓名' },
          phone: { type: 'string', description: '收件人手机号,11 位数字' },
          province: { type: 'string', description: '省份' },
          city: { type: 'string', description: '城市' },
          detail: { type: 'string', description: '详细地址,包括街道、门牌号等' }
        },
        required: ['receiver', 'phone', 'province', 'city', 'detail']
      },
      payment_method: {
        type: 'string',
        description: '支付方式',
        enum: ['alipay', 'wechat_pay', 'bank_card']
      },
      note: {
        type: 'string',
        description: '订单备注,例如「请在工作日配送」,最多 200 字'
      }
    },
    required: ['items', 'shipping_address']
  }
}

这个定义覆盖了前面讲到的要点:

  • name 用动词开头,语义清晰
  • description 说明了功能和典型使用场景
  • 枚举值用于限定支付方式
  • 嵌套对象处理收货地址,数组里放对象处理商品列表
  • required 只标记真正不可缺的字段,notepayment_method 可选
  • 每个参数都有 description,关键参数标注了格式、长度、默认值

总结

这篇把工具 Schema 的设计拆开讲了:

  • 工具定义由 namedescriptioninputSchema 三部分组成,分别承担标识、决策依据、参数约束的职责
  • JSON Schema 的子集足够覆盖工具场景,核心关键字是 typepropertiesrequireddescriptionenumitems
  • 五种基本类型各有适用场景:string 最通用,integernumber 区分整数和浮点数,boolean 用于开关,arrayobject 处理复杂结构
  • required 的划分标准是:缺了工具还能不能正常工作
  • 参数 description 是影响模型调用准确度的最关键因素,写清楚业务含义、格式约束、默认值
  • 枚举值用 enum 正式约束,比在 description 里写可选值更可靠
  • 嵌套对象不要超过三层,每一层都要写 description

Schema 设计的好坏,直接决定了模型调用工具的准确率。下一篇讲如何在 Hono 后端实现工具的注册和执行。