结构化输出与状态机:约束 Agent 的不确定性
用 JSON Schema、领域状态机和验证反馈把自然语言决策转成可执行、可测试的业务流程。
自然语言适合沟通,不适合直接驱动执行
模型回答“我建议先查询订单,然后创建退款申请”对人很好理解,但程序很难判断它到底选择了哪个动作、参数是否完整、下一状态是什么。若执行器靠正则或关键词解析这段文字,系统会在标点、措辞和模型版本变化时变得脆弱。
结构化输出的目标,是把模型的概率判断收敛到一个明确契约。模型仍然决定动作,但它必须在程序可以验证的形状中表达决定。
Schema 应描述领域,而不只是 JSON
仅要求“返回 JSON”不够。一个有效 Schema 要编码业务约束:必填字段、枚举、长度、数值范围、对象互斥关系和是否允许额外字段。
{
"type": "object",
"required": ["decision", "reason", "evidenceIds"],
"properties": {
"decision": {
"enum": ["request_more_info", "query_status", "propose_refund"]
},
"reason": { "type": "string", "maxLength": 300 },
"evidenceIds": {
"type": "array",
"items": { "type": "string" },
"maxItems": 10
},
"refund": {
"type": "object",
"required": ["orderId", "amountCents"],
"properties": {
"orderId": { "type": "string" },
"amountCents": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
Schema 还应表达条件关系:只有 decision 为 propose_refund 时才允许出现 refund。如果工具或模型接口不能直接表达复杂条件,可以在业务验证器中补充,不能把检查交给模型自己。
语法合法不等于业务合法
模型可能生成完全符合 JSON Schema 的退款金额,却超过订单实付金额;也可能引用格式正确但不存在的证据 ID。验证需要分层:
- 解析层检查是否为合法结构;
- Schema 层检查类型和基础约束;
- 领域层读取真实状态,检查业务不变量;
- 权限层确认当前用户可以执行该动作;
- 风险层判断是否需要人工审批。
每一层都返回稳定错误码。把“金额超过可退款余额”反馈给模型,让它修正一次,比返回整段堆栈更有效。修复次数必须有限;连续失败应降级到人工或安全终止。
用状态机约束动作顺序
结构化输出解决“动作长什么样”,状态机解决“现在能不能做”。例如退款流程可以定义:
collecting_information
-> eligible
-> awaiting_approval
-> executing
-> completed
任意可恢复失败 -> retryable_failure
任意业务拒绝 -> rejected
模型在 collecting_information 状态不能直接选择 execute_refund;它只能查询信息或提出资格判断。在 awaiting_approval 状态,唯一允许的系统动作是等待、取消或接收审批结果。
状态转换表应由代码拥有:
const transitions = {
collecting_information: ["query_order", "request_more_info"],
eligible: ["propose_refund", "reject"],
awaiting_approval: ["wait", "cancel"],
executing: ["check_execution_status"]
};
模型从当前允许集合中选择,无法通过文字说服执行器跳过审批。
状态需要版本和事件记录
长任务中,状态不能只是数据库里不断覆盖的一行。每次转换应记录前状态、动作、验证结果、后状态、时间和运行 ID。这样可以审计,也可以在逻辑升级后解释历史任务。
状态 Schema 发生变化时要有版本。运行中的旧任务可以继续使用旧版本,或经过明确迁移;不能在部署后突然用新代码解析不兼容检查点。
对并发操作使用版本号或条件更新。例如人工刚刚取消任务时,迟到的工具结果不能把状态从 cancelled 改成 completed。更新必须声明预期前状态,数据库不匹配时重新读取并判断。
什么时候不要让模型决定
固定业务规则、精确计算和简单路由不需要模型。例如退款余额、日期范围和角色权限应由代码计算。模型适合处理意图分类、非结构化信息提取、证据判断和开放式计划。
一个好的边界是:模型输出候选事实和动作,确定性系统验证并转换状态。不要要求模型既做判断又证明自己的判断合法。
测试结构化 Agent
测试集要覆盖缺失字段、未知枚举、额外字段、极端长度、非法状态转换、过期版本和并发更新。还要故意让模型得到冲突信息,确认它会选择“需要更多信息”而不是编造参数。
监控解析失败率、修复轮次、领域验证拒绝率和每个状态的停留时间。如果某个字段持续填错,优先改 Schema 名称、描述和枚举,而不是增加模糊的 Prompt 教训。
小结
结构化输出把模型决定变成可验证数据,状态机把动作限制在合法时序中。二者结合后,Agent 的不确定性仍然存在,但被关在清楚的业务边界内:错误可以分类、流程可以恢复、权限无法被自然语言绕过。