知识点思维导图
36 个知识节点
Tool 与 Function Calling(02) - Tool Schema 与参数校验:把模型输出变成可信输入
读完后,你应能完成以下任务:
- 给定一个业务函数签名,能写出包含类型、必填、枚举、长度和
additionalProperties的 Tool Schema,并用五组正常与异常参数的测试报告验证约束实际生效。- 给定两个功能重叠的工具,能重写名称和描述,输出“该用、不要用、参数含义”对照表,并通过固定问题集比较误选率。
- 给定模型生成的 JSON 参数,能实现“解析、结构校验、业务校验、权限校验”四层入口,并用分层错误日志证明失败发生在工具执行之前。
- 在文章沙盒运行订单查询示例,输出四条路径的执行结果,验证未知字段、非法枚举、越权订单和合法参数不会混成同一种错误。
一、为什么 Schema 不是接口装饰
Tool Schema 同时服务两个对象。
模型根据它决定:
- 有哪些工具。
- 哪个工具适合当前问题。
- 参数应该叫什么。
- 参数大概应该长什么样。
应用根据它决定:
- 模型返回值能不能解析。
- 字段是否完整。
- 类型和取值是否允许。
- 是否存在未声明字段。
如果只把 Schema 当成给模型看的提示,应用就会接受一份未经验证的外部输入。
如果只把 Schema 当成后端校验,描述写得含糊,模型又会频繁选错工具。
1.1 从业务函数直接抄参数为什么不够
假设后端已有函数:
query_orders(customer_id, status, limit)
只有字段名还不能形成可靠契约。
还需要回答:
customer_id是任意字符串,还是C开头的业务编号?status能传哪些枚举?limit的上下限是多少?- 多传
tenant_id时应该忽略还是拒绝? - 当前用户是否能访问这个客户?
前四项适合进入结构 Schema。
最后一项必须由服务端业务权限判断完成。
二、先写一份最小但完整的 Schema
{
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"pattern": "^C[0-9]{4}$",
"description": "客户编号,例如 C1001"
},
"status": {
"type": "string",
"enum": ["pending", "paid", "shipped", "cancelled"],
"description": "需要筛选的订单状态"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"description": "最多返回多少条订单"
}
},
"required": ["customer_id", "limit"],
"additionalProperties": false
}
这里每个约束都解决具体失败:
| 约束 | 拦截的问题 |
|---|---|
type |
把字符串 "10" 当整数使用 |
required |
漏掉执行所需字段 |
enum |
模型创造不存在的业务状态 |
minimum / maximum |
一次请求拉取过多数据 |
pattern |
把自然语言名字误当业务编号 |
additionalProperties: false |
拼错字段被静默忽略 |
2.1 不要为模型“自动纠错”所有参数
将 customerId 自动改成 customer_id 看起来友好,但会掩盖契约漂移。
更稳妥的处理是:
- 记录原始模型输出。
- 返回明确的参数错误。
- 把失败样本加入固定评测集。
- 修正 Schema 描述或模型适配层。
只有明确登记的版本迁移才适合做字段转换。
否则同一个拼写错误可能在不同服务中被转换成不同结果。
三、名称和描述决定模型怎样选
名称优先使用稳定的动词加对象:
get_order表示读取单张订单。list_orders表示按条件列出多张订单。create_refund_request表示创建退款申请,而不是直接退款。
避免名称:
process_data,不知道处理什么。order_tool,不知道是读还是写。do_refund,不知道是申请、审核还是付款。
3.1 描述要同时写正向和反向边界
差的描述:
查询订单。
更好的描述:
读取当前用户拥有的单张订单状态;仅在用户提供明确订单编号时使用。
不要用于列出全部订单,也不能修改、取消或退款。
这段描述告诉模型四件事:
- 动作是读取。
- 对象是单张订单。
- 前置条件是明确订单号。
- 修改类需求不能使用。
描述仍然不能替代权限校验,但能降低错误选择率。
四、参数进入执行器前要过四层门
flowchart LR
A[模型 arguments 字符串] --> B[JSON 解析]
B -->|失败| E1[invalid_json]
B --> C[Schema 校验]
C -->|失败| E2[schema_violation]
C --> D[业务规则校验]
D -->|失败| E3[business_rule_violation]
D --> E[身份与资源权限]
E -->|失败| E4[forbidden]
E --> F[工具执行]
四层不能合成一个 invalid_arguments。
错误分类越清楚,越容易判断应该修模型提示、Schema、业务规则还是授权逻辑。
4.1 JSON 解析层
这一层只回答:参数是不是合法 JSON 对象。
不要在这里读取数据库,也不要补业务默认值。
4.2 Schema 校验层
这一层回答:字段、类型和静态约束是否符合协议。
失败样本可以稳定进入契约测试。
4.3 业务规则层
这一层回答:参数组合在当前业务状态下是否合理。
例如已经取消的订单不能再筛选为待发货。
这类规则通常不能完整写进 JSON Schema。
4.4 权限层
这一层回答:当前服务端身份能否访问具体资源。
权限所需的 tenant_id、user_id 和角色应从会话或令牌中读取。
不要要求模型生成这些可信身份字段。
五、默认值应该放在哪里
默认值有三种来源:
| 默认值类型 | 例子 | 应放位置 |
|---|---|---|
| 展示偏好 | 默认返回 10 条 | 应用参数归一化层 |
| 安全上限 | 最多返回 20 条 | 服务端强约束 |
| 业务身份 | 当前租户 | 服务端上下文注入 |
Schema 中写 default 不代表所有模型供应商都会自动返回该字段。
应用必须明确决定:缺失时补默认值,还是要求模型重试。
安全上限即使存在默认值,也必须在执行前再次裁剪或拒绝。
六、可运行源码:让错误停在正确的层
示例只使用 Python 标准库,模拟必要的 Schema 约束。
main.py
预期判断:
- 合法参数进入
ready。 - 未知字段和非法枚举停在
schema。 - 越权客户停在
authorization。 - 所有失败都发生在真实工具执行之前。
七、怎样评测工具描述是否有效
不要只凭感觉修改描述。
准备一份固定问题集:
- 明确应该调用当前工具的问题。
- 明确应该调用其他工具的问题。
- 不需要任何工具的问题。
- 信息不足、应该先追问的问题。
- 带有恶意指令或越权意图的问题。
记录每条样本的:
- 期望工具。
- 实际工具。
- 参数是否符合 Schema。
- 是否应该追问。
- 最终是否执行。
每次只改变工具名称、描述或 Schema 中的一项,才能判断变化来自哪里。
八、常见错误怎么修
| 错误 | 为什么危险 | 修复方式 |
|---|---|---|
| 所有字段都可选 | 模型生成残缺请求后才在深层报错 | 真正必需字段写入 required |
| 允许额外字段 | 拼写错误被静默忽略 | 默认关闭 additionalProperties |
| 枚举只写在描述中 | 代码无法稳定校验 | 同时写入 Schema enum |
| 把当前用户作为模型参数 | 用户可诱导模型伪造身份 | 从服务端上下文注入 |
| 工具描述只写名词 | 模型不知道何时使用 | 写动作、条件和反向边界 |
| 所有错误返回同一句话 | 无法判断协议、业务还是权限问题 | 保存阶段和稳定错误码 |
九、总结
- Tool Schema 既是模型选择依据,也是应用执行前的参数契约。
- 名称和描述负责降低误选,结构约束负责让错误可检测。
- JSON 解析、Schema、业务规则和权限是不同校验层,不能混成一个模糊错误。
- 身份、租户和资源权限必须来自服务端上下文,不能由模型生成。
- 工具描述要通过固定问题集评测,而不是上线后靠偶发反馈猜测。
9.1 参考资料
学完自测
选择所有正确答案;提交后逐项核对判断依据。