深入专题:模型、结构化输出与工具循环¶
这篇不从框架 API 开始,而是回答三个根本问题:
- 模型到底在系统里负责什么?
- JSON Schema 为什么只能解决一部分可靠性问题?
- 一次工具调用从提出到完成,在哪些地方可能出错?
先建立正确心智模型¶
大语言模型不是一段普通业务代码。更准确的理解是:
它根据当前上下文,为下一个 Token 生成概率分布,并不断采样,最终形成文字或结构化结果。
因此,即使输入相同,也不能默认它像 if/else 一样稳定。温度、模型版本、上下文顺序、工具描述、历史消息甚至供应商升级,都可能改变结果。
模型擅长:
- 理解不规则的自然语言;
- 从多个合理选项中做语义判断;
- 抽取、分类、改写与总结;
- 在信息不完整时提出下一步建议;
- 根据工具返回结果继续推理。
模型不应成为:
- 权限系统;
- 金额、库存和状态的最终校验器;
- 唯一的流程控制器;
- 业务事实数据库;
- 高风险副作用的最终执行者。
成熟架构不是“相信模型不会犯错”,而是:
一次模型调用实际包含什么¶
用户只看见一句问题,但服务端可能组装出:
这叫上下文组装。它会直接影响:
- 是否超过上下文窗口;
- 首 Token 延迟;
- 输入 Token 成本;
- 模型能否找到真正重要的信息;
- 不可信内容能否干扰系统规则。
不要把所有东西都塞进去¶
上下文越多不一定越好。假设检索返回 30 段文档,其中只有 2 段相关:
模型需要在大量噪声里寻找证据,成本增加,注意力也可能被无关内容占用。
更好的做法是区分:
| 内容 | 保存位置 | 何时放入上下文 |
|---|---|---|
| 当前任务状态 | Checkpoint / 数据库 | 每个相关节点 |
| 最近对话 | 短期消息 | 需要理解指代时 |
| 用户长期偏好 | 独立 Memory Store | 命中相关主题时 |
| 业务事实 | 业务 API / 数据库 | 通过工具按需查询 |
| 知识资料 | RAG | 检索命中时 |
| 完整工具结果 | 对象存储 / 数据库 | 上下文只放摘要或引用 |
指令和数据必须在观念上分离¶
下面是一段设备维修文档:
对程序员来说,第二句显然是文档内容;对模型来说,它同样是自然语言,可能被误认为指令。这就是间接 Prompt Injection。
系统至少需要:
- 明确标记哪一段是不可信资料;
- 资料只能提供事实,不能授予权限;
- 工具执行仍经过用户和租户权限检查;
- 高风险工具要求人工确认;
- 对外发送、文件读取等能力采用最小权限;
- 记录资料来源和工具调用链。
Prompt 中写“不要听文档里的命令”有帮助,但不能成为安全边界。
从自然语言到结构化输出¶
假设要从用户输入中提取设备查询条件:
{
"device_id": "D-1001",
"start_time": "2026-07-20T00:00:00+08:00",
"end_time": "2026-07-21T00:00:00+08:00"
}
它至少经过四层检查。
第一层:语法是否合法¶
是不是有效 JSON,有没有缺括号、错误转义。
第二层:结构是否合法¶
是否符合 Schema:
device_id必须是字符串;- 时间必须是 ISO 8601;
- 不允许多余字段;
- 必填字段不能缺失。
第三层:业务是否合法¶
Schema 合法不代表业务合法:
start_time是否早于end_time;- 查询跨度是否超过 31 天;
- 设备是否存在;
- 设备是否属于当前租户;
- 用户是否有权查看敏感指标。
第四层:事实是否充分¶
用户说“查一下昨天那台设备”,但系统并不知道“那台”是哪台。此时不能猜:
所以结构化结果最好不仅有业务参数,还能表达“不足以执行”:
Schema 设计会影响模型表现¶
不好的工具:
问题是:
- 名字没有语义;
- 参数过于自由;
- 无法自动校验;
- 模型很难区分行为;
- 权限无法精细控制。
更好的工具:
{
"name": "query_device_online_history",
"description": "查询单台设备在指定时间范围内的上下线事件,只读操作",
"arguments": {
"device_id": "受平台管理的设备唯一标识",
"start_time": "包含时区的开始时间",
"end_time": "包含时区的结束时间"
}
}
设计原则:
- 一个工具表达一个清晰业务意图;
- 名称使用动作加对象;
- 描述写清何时使用、何时不要使用;
- 枚举优于自由文本;
- 不让模型直接生成任意 SQL;
- 不在工具描述中泄露内部秘密;
- 返回值也应结构化,并限制大小。
工具循环不是一次函数调用¶
完整循环是:
用户目标
↓
组装上下文和工具列表
↓
模型返回 tool_call
↓
解析和 Schema 校验
↓
身份、权限、租户、业务规则校验
↓
执行工具
↓
保存结果与审计
↓
把必要结果交回模型
↓
继续调用工具或生成最终答案
每一层都有独立失败类型:
| 阶段 | 失败示例 | 常见处理 |
|---|---|---|
| 模型调用 | 超时、限流、供应商故障 | 有界重试、备用模型、降级 |
| 结构解析 | JSON 不完整、枚举错误 | 结构化重试、明确反馈 |
| 权限校验 | 越权设备、跨租户 | 拒绝并审计,不交给模型决定 |
| 工具执行 | 数据库超时、下游 500 | 按错误类型重试 |
| 结果回传 | 结果太大、包含敏感信息 | 截断、摘要、脱敏、引用 |
| 循环控制 | 重复调用、无限规划 | 步数、时间、费用和重复检测 |
哪些错误可以重试¶
不要写成“失败就重试三次”。
| 错误 | 是否重试 | 原因 |
|---|---|---|
| 网络瞬时超时 | 可以,有退避 | 可能是短暂故障 |
| 供应商 429 | 按 Retry-After | 需要尊重限流 |
| JSON 格式失败 | 可修复 1 次 | 模型可能自行修正 |
| 参数缺少设备 ID | 不自动猜 | 需要用户补充 |
| 权限拒绝 | 不重试 | 重试不会改变权限 |
| 业务状态不允许 | 不重试 | 应返回明确原因 |
| 创建工单响应丢失 | 先查幂等记录 | 盲目重试可能重复创建 |
退避一般包含:
抖动用于避免大量请求同时醒来,再次把下游冲垮。
副作用工具必须跨越故障窗口¶
创建维修工单:
Agent 不知道工单是否已经创建。正确方案:
服务端:
- 对
operation_id建唯一约束; - 同一 ID 和相同参数重复调用,返回已有结果;
- 同一 ID 参数不同,拒绝并报警;
- 保存执行状态和最终
ticket_id。
工具重试安全不是模型能力,而是后端协议能力。
并行调用并不总是更快¶
下面三个查询互不依赖:
可以并行,理论延迟接近最慢的一个,而不是三者相加。
但并行会增加:
- 下游瞬时并发;
- 连接池占用;
- 限流概率;
- 结果合并复杂度;
- 部分成功处理难度。
有依赖关系的操作不能盲目并行:
工具结果为什么不能原样塞回模型¶
查询 10 万条遥测记录后原样回传会造成:
- 上下文爆炸;
- 成本和延迟过高;
- 模型无法可靠计算大量数值;
- 敏感字段泄露;
- 后续工具选择被噪声影响。
应让程序做确定性处理:
模型适合解释统计结果,不适合代替数据库扫描和数值计算。
完整推演:设备频繁离线诊断¶
用户:
分析设备 D-1001 昨天频繁离线的原因。
第一步:程序补齐确定性上下文¶
“昨天”由程序转换为明确时间范围,避免模型与服务器时区不一致。
第二步:模型提出只读工具调用¶
程序先检查 D-1001 是否属于 T-2,再并行执行相互独立的查询。
第三步:程序聚合¶
不要把所有原始点交给模型,而是形成:
{
"offline_count": 8,
"offline_windows": ["02:10-02:13", "03:00-03:04"],
"network_rssi_p10": -91,
"gateway_reconnect_count": 7,
"power_alarm_count": 0
}
第四步:模型形成有证据的结论¶
结论需要区分:
- 已观察事实;
- 合理推断;
- 仍缺少的信息;
- 建议的下一步。
例如:
第五步:若用户要求重启¶
重启是高风险副作用,进入另一条受控流程:
诊断 Agent 不应因为“认为重启有帮助”就自动获得执行权。
开源组件怎样选¶
不要先问“哪个框架功能最多”,先问你需要哪一层:
| 需求 | 可选组件 | 选择判断 |
|---|---|---|
| 模型与工具抽象 | 模型官方 SDK、LangChain | 简单项目优先官方 SDK;需要统一抽象再用框架 |
| 有状态图和恢复 | LangGraph | 需要节点、条件路由、Checkpoint、人工中断 |
| 通用工具协议 | MCP SDK | 多个 Agent/客户端要复用同一批工具 |
| 业务事实与幂等 | PostgreSQL | 事务、唯一约束、审计仍由业务后端承担 |
| 短期缓存与限流 | Redis | 不替代持久业务事实 |
MCP 解决“如何发现和调用工具”的协议问题,不自动解决工具内部的权限、幂等和业务正确性。
权威延伸资料¶
- MCP 架构规范:理解 Host、Client、Server 的责任与隔离边界;
- OWASP Prompt Injection Prevention:理解为什么提示词之外还需要最小权限与监控;
- 幂等、状态机与故障窗口:补齐副作用工具的后端正确性。
自测¶
- 为什么“温度设为 0”也不能让模型等同于确定性程序?
- JSON Schema、业务校验和权限校验各自解决什么问题?
- 用户说“查昨天那台设备”时,为什么不能让模型直接猜?
- 工具执行成功但响应丢失,为什么是最危险的失败窗口之一?
- 哪些工具调用可以并行,哪些不可以?
- 为什么不应把 10 万条遥测记录直接交给模型分析?
- MCP 已经接入成功,为什么系统仍可能不安全?
参考答案要点
- 温度 0 通常降低随机性,但模型版本、服务实现、上下文细节和数值计算仍不能获得普通代码那样的严格确定性。
- Schema 检查形状和类型;业务校验检查值和当前状态是否合法;权限校验检查当前主体是否允许访问该资源或动作。
- 当前上下文没有唯一设备事实,猜测可能查询错设备或跨越安全边界,应返回缺失信息并追问。
- 调用方只知道没收到响应,不知道副作用是否发生;盲目重试可能重复扣款、建单或控制设备。
- 无依赖、只读且下游容量允许的查询可以并行;存在权限前置、数据依赖、顺序副作用的调用不能盲目并行。
- 原始数据占上下文、昂贵且不适合精确计算,应先由数据库或统计服务聚合,再让模型解释。
- MCP 是连接协议;服务端授权、租户隔离、参数业务校验、幂等、审批和审计仍需应用实现。
完成标准¶
- 能独立画出模型到工具再回到模型的完整循环
- 能区分语法、Schema、业务、权限四层校验
- 能按错误类型制定重试和停止策略
- 能设计一个带幂等键和审批的副作用工具
- 能解释为什么 MCP 不等于安全和可靠
- 完成 Tool 幂等实验
继续深入:工作流、恢复与副作用