结构化输出与工具调用
JSON Mode、函数调用、工具 schema 与编排层校验:模型意图如何变成可执行接口。
Agent 系统里,模型最重要的输出不总是自然语言,而是可解析、可校验、可路由的结构化片段:JSON 对象、函数名、参数、工具调用 ID 和结束原因。
为什么需要结构化输出
自然语言适合解释,不适合直接驱动程序。编排层需要稳定字段来:
- 判断是继续对话、调用工具还是结束任务。
- 校验参数类型、必填项和枚举范围。
- 记录审计日志,并在失败后重试或降级。
《智能体设计模式》把工具使用拆成六步:定义工具、让 LLM 判断是否调用、生成结构化参数、由编排层执行、返回观察结果、再让 LLM 处理结果。模型只负责第 2、3 步的“意图生成”,执行和验证必须在系统层完成。来源:《智能体设计模式》,pp. 49-50。
三类常见接口
| 接口形态 | 模型做什么 | 编排层做什么 |
|---|---|---|
| JSON Mode / Response Schema | 按约定字段生成 JSON 文本 | 解析 JSON、做 schema 校验 |
| Function Calling / Tool Use | 返回工具名和参数对象 | 查注册表、鉴权、执行、回填结果 |
| 文本协议(ReAct、XML 标签) | 用固定格式包裹动作 | 正则或流式解析,容错和回退 |
优先使用 provider 原生支持的 function calling 或 JSON schema 能力;文本协议适合兼容旧系统,但解析成本和脆弱性更高。
工具 Schema 设计原则
一个可上线的工具至少要定义:
| 字段 | 作用 |
|---|---|
name | 稳定标识,供模型和注册表匹配 |
description | 告诉模型何时使用,避免和功能相近工具冲突 |
input_schema / parameters | 用 JSON Schema 约束参数形状 |
required | 明确必填项,减少幻觉参数 |
| 权限与副作用标记 | 只读、可写、是否需人工审批 |
Claude Code 的工具系统让所有工具都经过查找、输入验证、Hook、权限检查、执行、格式化,再返回给模型。来源:《Demystifying Claude Code v1.8》,pp. 83-87。
设计要点:
- 描述写给模型看,不是写给人类文档看:说明何时用、何时不用。
- 参数尽量小而稳:不要把整个业务流程塞进一个巨型参数对象。
- 输出也要结构化:工具返回统一 envelope,包含
ok、data、error_code、retryable等字段。 - 不要相信自然语言参数:即使模型返回了 JSON,也要做 schema 校验。
模型层与编排层的责任边界
模型层失败示例:选错工具、漏填参数、编造不存在字段。 编排层失败示例:未校验参数、未做权限检查、执行超时却没有错误语义。
调试时要先定位是哪一层出了问题,不要笼统说“模型不行”。
JSON 与函数调用的常见坑
- JSON 合法但语义错误:字段存在,值不符合业务规则。
- 多工具冲突:描述重叠,模型随机选择。
- 流式截断:tool arguments 还没完整到达就开始执行。
- 循环调用:模型反复请求同一工具,缺少停止条件。
- 大结果反灌:工具输出过长,挤爆上下文。
工具调用闭环
一个可维护的工具调用链路至少包含这些步骤:
- 注册工具:名称、描述、参数 schema、权限等级、是否有副作用。
- 装配上下文:只暴露本轮可用工具,避免工具列表过大。
- 模型选择工具:生成工具名和参数,而不是直接执行。
- 编排层校验:检查 schema、权限、速率限制、人工确认要求。
- 执行工具:记录输入、输出、耗时、错误码和副作用。
- 结果回填:把结构化 observation 放回上下文,让模型继续决策。
- 停止判断:达到目标、失败不可恢复或需要人工接管时结束。
模型只负责第 3 步的意图生成;其余步骤都应由系统代码负责。
工具 schema 示例
{
"name": "read_project_file",
"description": "读取当前项目内的文本文件。只在需要查看源码、配置或文档原文时使用。",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "相对项目根目录的文件路径,不允许绝对路径。"
},
"max_lines": {
"type": "integer",
"minimum": 1,
"maximum": 400
}
},
"required": ["path"],
"additionalProperties": false
},
"side_effect": "read-only"
}这个 schema 的重点不是字段多,而是边界清楚:什么时候用、参数怎么约束、是否只读、超出范围怎么办。
错误语义
工具返回错误时,不要只返回一段自然语言。建议使用统一 envelope:
{
"ok": false,
"error_code": "FILE_NOT_FOUND",
"message": "文件不存在或不在允许目录内。",
"retryable": false,
"data": null
}模型看到 retryable: false 时就不应继续用同样参数重试;编排层也可以用错误码做熔断、告警或人工接管。
安全边界
结构化输出不是安全机制本身。即使模型返回了合法 JSON,也要继续检查:
- 工具是否允许当前用户、当前任务和当前环境调用。
- 参数是否越权,例如绝对路径、外部 URL、生产环境标识。
- 副作用是否需要人工确认,例如删除文件、转账、发邮件、推送代码。
- 重试是否可能放大损失,例如重复扣费、重复写库、重复通知。
- 工具结果是否包含敏感信息,是否允许回灌给模型或展示给用户。
与 MCP 的关系
函数调用通常是应用和模型之间的集成;MCP 则是工具层的开放协议,让工具提供者实现一次 Server,多个 Agent 客户端复用。详见 智能体基础 中的 MCP 章节。
检查清单
- 是否优先使用 provider 原生的 structured output 或 tool calling 能力。
- 是否对模型返回做 JSON 解析、schema 校验和业务规则校验。
- 是否把工具权限、副作用和人工确认写进系统层,而不是只写进 prompt。
- 是否限制单轮最大工具调用次数,避免循环调用。
- 是否对工具结果做截断、摘要和敏感信息过滤。
延伸阅读
- 工具调用与记忆:工具接口、上下文和状态边界。
- 安全、权限与人类接管:高风险工具的审批和权限设计。
- Harness 工程构件:工具注册表与统一执行链路。
- OpenAI Structured Outputs 与 Function Calling:结构化输出和函数调用接口参考。