主题
原文:https://www.mstx.cn/harness/422.html · 创建:2026-09-05 · 更新:2026-09-05 · 阅读:1103
Schema是LLM的"使用说明书"
想象你给一个从未见过咖啡机的人一台咖啡机,没有说明书。他可能会:
- 把咖啡豆倒进水箱
- 按错按钮导致机器损坏
- 完全不知道如何启动
但如果给他一份清晰的说明书——"水箱加水,豆仓加豆,按'浓缩'按钮"——他就能正确操作。
工具Schema就是LLM的"使用说明书"。LLM从未见过你的工具函数,它对工具的全部理解来自于你提供的Schema。Schema写得不好,LLM就会:
- 选错工具
- 填错参数
- 该调用时不调用,不该调用时乱调用
本节将深入讨论如何设计让LLM正确理解参数的工具Schema。这是Agent工程中最容易被低估、却对最终表现影响最大的环节之一。
一个对比实验
让我们先看一个具体的对比。假设你有一个"查询订单"的工具:
Schema A(糟糕的设计):
json
{
"name": "check_order",
"description": "查询订单",
"parameters": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "订单ID"
}
},
"required": ["id"]
}
}Schema B(良好的设计):
json
{
"name": "check_order_status",
"description": "查询用户订单的当前状态。当用户询问'我的订单到哪了'、'什么时候发货'、'订单状态'等问题时使用此工具。",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "订单编号。通常以'ORD'开头,后跟8位数字,如'ORD12345678'。从用户对话中提取。如果用户未提供订单号,可以先询问用户。"
}
},
"required": ["order_id"]
}
}现在测试这两个Schema。用户输入:"帮我查一下ORD87654321到哪了。"
使用Schema A的LLM行为:
- 不确定id是指什么ID(用户ID?订单ID?产品ID?)
- 可能把id填成"ORD87654321到哪了"(因为不知道如何提取)
- 可能不调用工具,直接回答"请提供订单ID"
使用Schema B的LLM行为:
- 明确知道这是查询订单状态的工具
- 知道order_id的格式是"ORD"开头加8位数字
- 能从用户输入中正确提取ORD87654321
- 如果用户没提供订单号,知道应该先询问
同样的工具函数,不同的Schema,表现天差地别。
Schema设计的五个核心要素
一个好的工具Schema需要回答五个问题:
要素一:这个工具是做什么的?
这是description字段的职责。好的描述不是一句话带过,而是精确界定工具的用途和适用场景。
糟糕的描述:"查询订单"
良好的描述:"查询用户在系统中的订单状态。当用户询问订单进度、发货状态、物流信息时使用此工具。"关键技巧:在描述中列举触发场景。LLM是模式匹配器,你给它具体的场景描述,它就能更准确地将用户请求与工具匹配。
要素二:什么时候不应该用这个工具?
大多数工具描述只说了"什么时候用",但没说"什么时候不用"。这会导致LLM在不适用的场景中错误地调用工具。
改进后的描述:
"查询用户在系统中的订单状态。当用户询问订单进度、发货状态、物流信息时使用此工具。
注意:此工具只能查询已有订单的状态,不能创建订单、取消订单或修改订单。如果用户要求这些操作,请告知用户当前不支持。"关键技巧:明确排除不适用场景。这比列举适用场景更能防止误用。
要素三:每个参数的确切含义是什么?
参数描述是Schema设计中最常被忽视的部分。很多开发者只写了参数名和类型,描述留空或写一个模糊的占位文本。
糟糕的参数描述:
"order_id": {"type": "string", "description": "订单ID"}
良好的参数描述:
"order_id": {
"type": "string",
"description": "订单编号。格式为'ORD'开头后跟8位数字,如'ORD12345678'。从用户对话中提取。如果用户提供的格式不完整或不正确,可以先尝试补全或询问用户确认。"
}关键技巧:参数描述应该包含格式说明、示例值、提取来源和缺失时的处理方式。
要素四:参数之间有依赖关系吗?
有些工具的参数存在条件依赖。比如一个"发送通知"工具:
参数:
- notify_type: "email" | "sms" | "push"
- email_address: 仅当notify_type为"email"时需要
- phone_number: 仅当notify_type为"sms"时需要如果Schema不说明这种依赖关系,LLM可能在notify_type="email"时填了phone_number而没填email_address。
在JSON Schema中表达条件依赖:
json
{
"type": "object",
"properties": {
"notify_type": {
"type": "string",
"enum": ["email", "sms", "push"],
"description": "通知方式:email=邮件,sms=短信,push=推送"
},
"email_address": {
"type": "string",
"description": "收件人邮箱地址。仅当notify_type为'email'时需要填写。"
},
"phone_number": {
"type": "string",
"description": "收件人手机号。仅当notify_type为'sms'时需要填写。"
}
},
"required": ["notify_type"],
"allOf": [
{
"if": {"properties": {"notify_type": {"const": "email"}}},
"then": {"required": ["email_address"]}
},
{
"if": {"properties": {"notify_type": {"const": "sms"}}},
"then": {"required": ["phone_number"]}
}
]
}注意:不是所有Provider都支持allOf/if/then这类高级JSON Schema特性。如果你的Provider不支持,退而求其次:在参数描述中用自然语言说明条件依赖。
要素五:参数的边界在哪里?
参数的值域应该有明确的边界约束。
没有约束的参数:
"age": {"type": "integer", "description": "年龄"}
有约束的参数:
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150,
"description": "用户年龄。范围为0-150。如果用户提供的年龄超出此范围,可能是输入错误,请询问确认。"
}常见的边界约束:
- 数值范围:minimum、maximum
- 字符串长度:minLength、maxLength
- 枚举值:enum
- 字符串格式:pattern(正则表达式)
参数命名的重要性
参数的名称同样重要。LLM是根据名称+描述来理解参数的。
模糊的参数名:
- id——什么ID?用户ID?订单ID?产品ID?
- data——什么数据?
- input——什么的输入?
- value——什么值?
- type——什么类型?
清晰的参数名:
- order_id——明确是订单ID
- user_email——明确是用户邮箱
- start_date——明确是开始日期
- product_sku——明确是产品SKU
命名原则:
- 具体优于泛化:order_id > id
- 使用领域术语:product_sku > product_code(如果SKU是你的业务术语)
- 避免歧义:date可以改为delivery_date或order_date
- 保持一致:如果所有工具都用user_id,不要某个工具突然用uid
枚举值的艺术
当一个参数只能取有限的值时,使用enum。但enum的使用有讲究:
糟糕的枚举:
"status": {
"type": "string",
"enum": ["0", "1", "2", "3"],
"description": "订单状态"
}LLM不知道"0"是什么意思。它需要在别处找到映射关系(如果有的话),否则只能猜测。
良好的枚举:
"status": {
"type": "string",
"enum": ["pending", "shipped", "delivered", "cancelled"],
"description": "订单状态。可选值:pending=待发货,shipped=已发货,delivered=已送达,cancelled=已取消"
}更好的枚举(使用有语义的值,并在描述中解释):
"status": {
"type": "string",
"enum": ["pending_payment", "paid", "shipped", "delivered", "refunded", "cancelled"],
"description": "订单状态过滤条件。可选值:pending_payment=待支付,paid=已支付,shipped=已发货,delivered=已送达,refunded=已退款,cancelled=已取消。如果不确定用户意图,默认使用空值表示不过滤。"
}枚举设计的原则:
- 使用有语义的值,而非数字代码
- 在描述中解释每个枚举值的含义
- 枚举值不要过多(超过7个时LLM选择准确率下降)
- 考虑是否需要"不确定"选项
Schema设计的常见错误
错误一:描述信息堆砌但无结构
description="此工具用于查询用户的订单信息,包括订单状态、物流信息、支付信息、商品信息、收货地址、发票信息、优惠券使用情况、历史订单记录、退换货状态、评价信息、投诉记录、客服沟通记录、配送时间、签收状态、订单备注、订单金额、支付方式、优惠金额、实付金额、运费、税费、订单来源、订单渠道、订单标签、订单优先级、订单关联的营销活动、订单使用的积分、订单使用的礼品卡、订单使用的余额、订单使用的代金券、订单使用的折扣码、订单的创建时间、订单的更新时间、订单的完成时间、订单的取消时间、订单的退款时间"这个描述包含了太多信息,LLM反而抓不住重点。好的描述应该聚焦核心功能,细节可以在参数描述中展开。
错误二:参数描述和函数实现不一致
python
# 函数签名:期望user_id
def get_user_profile(user_id: str) -> str:
...
# Schema中写的是uid
"uid": {"type": "string", "description": "用户ID"}这会导致LLM填了uid,但函数期望user_id,调用失败。你的Harness中应该有验证机制(4.1节中已实现)来捕获这类不一致。
错误三:所有参数都是必需的
json
{
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"category": {"type": "string", "description": "分类"},
"sort_by": {"type": "string", "description": "排序方式"},
"page": {"type": "integer", "description": "页码"},
"page_size": {"type": "integer", "description": "每页数量"}
},
"required": ["query", "category", "sort_by", "page", "page_size"]
}实际上用户可能只想用关键词搜索,其他参数应该是可选的。把不应强制要求的参数标记为必需,会导致LLM强迫性地为每个参数编造值,或者因为无法确定某个参数而放弃整个工具调用。
原则:required列表应该只包含真正不可或缺的参数。 通常只有1-2个核心参数是必需的,其余应该是可选的。
错误四:Schema太复杂
一个工具有20个参数,其中15个是嵌套对象。LLM在处理这种复杂Schema时准确率会显著下降。
原则:如果一个工具需要超过7个参数,考虑拆分为多个工具。 如果一个参数是深度嵌套的复杂对象,考虑展平或拆分为独立工具。
一个实用的Schema设计流程
综合以上内容,这里给出一个实用的Schema设计流程:
第一步:明确工具的核心功能。
用一句话说清楚:这个工具是做什么的?如果你无法用一句话说清楚,这个工具可能承担了太多职责,需要拆分。
第二步:确定核心参数。
哪些参数是完成核心功能不可或缺的?通常只有1-3个。这些参数标记为required。
第三步:确定可选参数。
哪些参数是增强功能但不影响基本使用?这些参数作为可选,给LLM灵活性。
第四步:为每个参数写详细描述。
每个参数的描述应该包含:
- 参数的含义(用领域术语)
- 值的格式或范围
- 从用户输入的哪里提取这个值
- 如果用户没提供,应该怎么办
第五步:添加使用场景和排除场景到工具描述。
在工具的description中说明:
- 什么场景下使用此工具(列举触发关键词)
- 什么场景下不使用此工具
第六步:测试Schema。
用一个测试LLM,给它各种用户输入,检查它是否:
- 正确判断何时调用工具
- 正确提取参数
- 不调用工具时行为合理
第七步:迭代优化。
根据测试结果,优化描述和参数定义。Schema设计是一个迭代过程,不是一次性的。
用LLM来辅助设计Schema
一个有意思的技巧:让LLM帮你设计Schema。
你可以这样提示:
python
我有一个Python函数:
def check_order_status(order_id: str) -> dict:
"""查询订单状态"""
# 实现...
请为这个函数设计一个工具Schema(JSON格式),要求:
1. 名称清晰,使用领域术语
2. 描述包含适用场景和排除场景
3. 参数描述包含格式说明、示例值、提取来源
4. 只将真正必需的参数标记为requiredLLM生成的Schema通常质量不错,可以作为起点,然后由你人工审查和优化。LLM善于生成结构化的文本,但不了解你的具体业务场景——所以它生成的Schema需要你根据实际业务知识进行修正。
本节小结
工具Schema是LLM理解工具的唯一途径。设计良好的Schema应该回答五个问题:
- 这个工具是做什么的?(描述)
- 什么时候不应该用?(排除场景)
- 每个参数的确切含义是什么?(参数描述)
- 参数之间有依赖关系吗?(条件约束)
- 参数的边界在哪里?(值域约束)
核心原则:LLM对工具的理解完全来自于你提供的Schema。Schema写得越精确,LLM使用工具的能力就越强。投入在Schema设计上的时间,会在Agent表现上得到数倍的回报。
在下一节中,我们将讨论工具的安全沙箱——如何确保工具在执行时不会造成意外的伤害。