Part 6 · Production 生产化

6.6 Generation & Output Contracts 采样、结构、流式提交与幂等重试

难度 ★★★ 阅读 ~16 min 面试出现频率 高(系统设计) 前置 4.1 / 6.1 / 6.5
Core Concept 核心概念

模型输出不是返回值,而是一段需要提交协议的候选数据

普通函数在相同输入下通常返回相同结果,并以一次明确的 return 结束。生成模型不同:sampling(采样)从一个概率分布中选择下一个 token;服务端实现、模型快照、并行计算与数值精度也都可能改变结果。即使把随机性参数调到最低,也不应把逐 token 相同当成生产契约。

因此测试目标不该是「同一个 prompt 永远返回同一串字符」,而应是允许多种表述,但每次都满足同一个可验证契约:结构合法、语义成立、权限允许、重复执行不产生重复副作用。把这组条件称为 output contract(输出契约)。

输出可提交 = 传输完整
     ∧ 语法 / schema 合法
     ∧ 领域语义合法
     ∧ 权限与业务不变量成立
     ∧ 当前操作尚未提交

任一条件不成立 → 拒绝提交、记录原因、进入受控修复或人工流程

① 采样:接受非复现,验证可接受集合

采样配置控制的是候选 token 分布的形状,不是正确性,也不是业务风险级别。把生成调得更保守可能减少表述差异,却不能证明事实正确;某些实现提供的复现实验辅助也不能跨模型版本、硬件或服务端升级提供永久保证。具体参数名与当前支持范围属于工具层,见本章侧栏。

生产测试应从字符串快照升级为三层:① 确定性断言——必需字段、枚举、范围、权限;② 语义断言——金额守恒、日期顺序、引用支持结论;③ 分布性回归——对同一测试集多次采样,监控通过率、拒答率与关键切片,而不是要求所有样本逐字相同。采样次数和容忍区间由错误代价决定。

② Structured syntax 不是 semantic validity

structured output(结构化输出)解决的是语法空间:字段是否存在、类型是否正确、枚举是否越界。它不能证明字段值符合现实或业务。一个完全通过 JSON Schema 的对象仍可能把退款金额写成负数、引用不存在的订单、安排结束时间早于开始时间。

验证层回答的问题失败示例负责人
传输事件是否完整结束?连接断开,只收到半个对象协议适配层
语法 / Schema形状与类型是否合法?amount 是字符串解析器 / schema validator
领域语义值是否满足业务规则?退款额超过原订单领域服务
授权当前主体能否执行?只读用户触发转账策略 / 权限层
提交这个操作是否已执行?超时重试生成第二张工单事务边界

③ Streaming 是 provisional events,不是多次小提交

流式输出改善感知延迟,却把一个逻辑结果拆成许多 provisional(暂定)事件。任何 delta 都可能是半个词、半段 JSON、随后被拒绝的工具参数,或属于最终状态为 failed / incomplete 的响应。UI 可以把它显示为「正在生成」,但业务系统不能据此提前写库、发邮件或调用有副作用的工具。

定义唯一的 commit point(提交点):收到明确完成状态,重组最终对象,通过全部验证,并成功抢占幂等键之后,才允许产生副作用。流中出现的文本只是观察值;完成事件也只是「可以开始验证」,不是「已经通过业务验证」。

每个阶段只缩小可接受集合;副作用只发生一次 stream deltas 暂定 · 仅展示 完成状态 完整 / 失败 / 中止 schema + 语义 + 权限 任一失败即拒绝 幂等提交 副作用一次 失败 / incomplete / validation error 记录原因 → 可重试则重试 → 否则人工处理 operation_id 贯穿 trace、重试与提交记录 同一业务意图可以生成多次,但只能提交一次
图 1 · 输出契约流水线。流式事件可以反复到达,生成也可以重试;只有最终对象通过完整性、schema、语义、权限与幂等检查后,副作用才越过提交点。

④ Retry 复制的是尝试,不应复制业务效果

超时意味着「调用方不知道结果」,不等于「服务端没有完成」。如果连接在服务端成功之后断开,盲目重试可能生成不同答案,更危险的是再次执行付款、建单或发送消息。每个业务意图都需要调用方生成的 idempotency key(幂等键),并贯穿模型调用、工具调用、审计日志与最终写入。

幂等不是「第二次请求返回同样文本」,而是同一业务意图重复到达时,系统最终只产生一次可见效果。常见实现是:以 operation_id 建唯一约束;提交前原子地写入「已处理」记录与业务结果;重复请求返回既有结果。若外部系统也支持幂等键,向下游透传;若不支持,就在自己控制的边界用 outbox、状态机或人工对账兜住不确定结果。

Case Study 失败案例

合法 JSON,重复创建了两张采购单

采购助理 · 从「输出格式问题」追到提交协议

事故:一个采购助理让模型流式生成采购单参数。前端看到 JSON 右花括号后立即调用建单工具;几百毫秒后连接被网关判为超时,任务队列自动重试。第二次生成的备注文字不同,但金额与供应商相同,于是系统创建了两张都符合 schema 的采购单。

为什么现有防线没挡住:JSON 合法只证明语法完整,不证明流已正常结束;schema 只检查字段类型,不知道订单是否重复;重试层用每次 HTTP 尝试的 request_id,而不是稳定的业务 operation_id;工具层没有唯一约束。每一层单独看都「工作正常」,组合起来却重复产生了真实副作用。

修复:
① delta 只更新 UI 草稿,不触发工具;仅在最终完成状态后解析。
② schema 之后增加供应商存在、金额上限、预算归属与用户权限校验。
③ 用户每次「确认采购」生成唯一 operation_id,所有重试复用它;数据库以该 id 建唯一约束。
④ 建单与「operation_id 已提交」在同一事务中写入;不确定结果进入 reconciliation 队列,不自动再建一单。

结果:生成仍然可能变化、网络仍然可能超时,但这两种不确定性不再越过业务提交点。团队也把测试从「是否返回合法 JSON」升级为「断流、重复事件、超时后成功、并发重试时是否仍只有一个订单」。

说明:本案例为综合多个真实项目的匿名化复盘,不含可识别的公司、产品或个人信息。稳定原则与边界参见 PyTorch Reproducibility 说明(跨版本与平台不保证完全复现)、WHATWG Server-sent events 标准(事件流传输语义)、RFC 9110 的幂等方法定义与 JSON Schema 规范;案例情节与结果为工程实践总结,非论文实验数据。
Tooling Sidebar 工具层
当前 API 的契约落点(会过期,正文不依赖本区块) 截至 2026-09

OpenAI Responses API:流式接口会发出带类型的事件。应用应区分 delta、response.completed、response.failed 与 response.incomplete,并检查最终 response 状态;事件名和字段以官方 Responses streaming events 参考为准。

Structured Outputs:Responses API 的 text.format 可使用 json_schema;严格模式只支持 JSON Schema 的一个子集。它保证受支持的结构约束,不承担事实、权限或业务语义校验。具体支持范围以官方 Structured Outputs 指南为准。

采样参数:不同供应商对 temperature、top-p、seed 与默认值的支持不同。生产 trace 要记录实际模型版本与采样配置;升级模型或 SDK 后重新跑分布性回归,不把 seed 当跨版本保证。

幂等边界:不要假设「SDK 自动重试」等于业务幂等。由应用生成稳定 operation_id,在自己的数据库与下游副作用处建立唯一约束;供应商若提供请求级幂等能力,也只是额外防线。

Quiz 自测

章末自测

架构评审一个客服系统开启 streaming,检测到 JSON 可以解析就立即执行退款;失败由 SDK 自动重试。指出至少四处契约缺口。

① 把可解析误当完成。中间 delta 恰好能组成合法 JSON,不代表流已完成;必须等待明确终态并拒绝 failed / incomplete。

② 只有语法验证。还需校验退款订单存在、金额不超过可退余额、币种一致、政策允许。

③ 缺少授权边界。模型提出退款不代表当前用户或 agent 有权限执行;执行层必须独立鉴权。

④ 重试不幂等。超时后服务端可能已退款。稳定 operation_id 必须跨 SDK 重试复用,并在退款写入处有唯一约束。

⑤ UI 与提交状态混淆。流式内容只能标为草稿;最终成功要来自事务提交结果,而不是模型文本。

场景判断启用严格 JSON Schema 后,是否可以删除业务校验?为什么?

不可以。Schema 能证明对象形状、类型、必填字段和部分枚举/范围约束,却不知道外部事实与动态业务状态。

例如 {"order_id":"A12","refund":500} 可以完全符合 schema,但 A12 可能不存在、属于另一个租户、已全额退款,或原订单只有 100 元。正确分层是:约束生成缩小语法空间,schema validator 再验证结构,领域服务读取权威状态验证语义,策略层验证权限,最后以幂等事务提交。

面试题改编「把采样调到最保守,能不能让 LLM 工作流可复现?你会怎么测试?」

不能把它当保证。保守采样可能降低输出差异,但模型快照、服务实现、并行与数值计算变化仍可能改变输出;即使文本稳定,也不代表答案正确。

测试契约而非字面。对固定版本记录 prompt、工具、采样参数与 trace;确定性检查 schema、枚举、金额守恒、权限和幂等;语义质量用标注集;对关键用例重复采样,比较契约通过率与切片分布。模型或供应商版本变化时重新跑回归,不要求逐字相同。

决策题一次工具调用超时后,你不知道下游是否成功。什么时候可以自动重试?

先看业务操作是否幂等。纯读取通常可安全重试;有副作用的操作只有在相同 operation_id 能被下游去重,或你能先查询其最终状态时才适合自动重试。

状态未知时不盲重放。先按 operation_id 查询提交记录;已成功则返回原结果,明确失败且错误可重试才重试,仍不确定则进入 reconciliation 或人工处理。指数退避解决的是负载冲击,不解决重复副作用。