模型输出不稳定怎么兜底:从容错解析到降级链路的系统工程

agent高级
AI Engineer Roadmap2026年08月12日

把 LLM 当普通函数用,你会得到一个「偶尔返回 JSON、偶尔返回散文、偶尔什么都不返回」的诡异依赖。兜底不是补救,而是系统设计的一部分。


一、先认清现实:模型输出有哪些「不稳定」

在 Agent 系统中,模型输出的不稳定可以粗暴地分为四类,每一类的兜底手段完全不同:

不稳定类型表现典型原因
格式漂移该返回 JSON 返回了 Markdown;字段名大小写不一致指令理解偏差、模型版本切换
结构缺失少字段、字段类型错误、数组被截断输出长度上限、生成中断、多轮拼接丢失
内容漂移同一输入两次输出语义不同、数值抖动采样随机性(temperature)、模型更新
行为漂移该调工具没调、工具参数错误、循环不退出指令跟随不稳定、上下文过长

关键认知: 稳定不是「让模型变稳定」,而是「让系统在模型不稳定时仍然稳定」。工程上的兜底必须做到:即使模型返回了最差的结果,系统也不会崩溃、不会输出错误内容、不会卡死。


二、第一层兜底:结构化输出——把自由发挥关进笼子

2.1 强制工具调用格式

不要用「请以 JSON 返回」这种请求式约束,要使用平台级的结构化能力:

  • OpenAI / Anthropic 的 Function Calling / Tool Use:模型必须按 schema 生成参数;
  • Structured Output(JSON Schema 强制):OpenAI 的 response_format: { type: "json_schema" } 在解码层保证输出合法 JSON;
  • 开源推理服务(vLLM 等)的 guided decoding / outlines:采样时直接限制 token 范围。

这三者的共同点:非法输出在生成阶段就被物理性禁止,而不是生成后再靠提示词「希望它改」。

2.2 输出 Schema 设计守则

Schema 设计本身就能减少一半的不稳定:

  • 必填字段要少:能缺省就不要必填,用默认值兜底;
  • 类型收窄:用枚举而不是开放字符串("level": "low" | "medium" | "high"),枚举之外模型无从发挥;
  • 拒绝自由文本:需要自由文本的地方单独给字段,不让模型把非结构化内容混进结构化字段;
  • 版本化 schema:schema_version 字段随输出下发,解析侧按版本兼容处理。

三、第二层兜底:容错解析——模型乱写也能接住

即使强制了 schema,线上仍会出现解析失败(截断、非法字符、嵌套过深)。解析层必须宽容,而不是假设输入永远合法。

3.1 多级解析策略

原始输出
  → ① 严格解析(JSON.parse / schema 校验)      [成功则结束]
  → ② 修复解析(清洗 Markdown 围栏、截断 JSON、修正尾逗号)
  → ③ 模糊提取(正则提取关键字段,如首个 {...} 块)
  → ④ 全量兜底(返回默认结构 + 原文作为"raw_text"字段)
def safe_parse(raw: str, schema) -> Parsed:
    # ① 严格
    try:
        data = json.loads(raw)
        return schema_validate(data, schema)
    except Exception:
        pass
    # ② 清洗围栏与注释
    cleaned = strip_markdown_fence(raw)
    try:
        return schema_validate(json.loads(cleaned), schema)
    except Exception:
        pass
    # ③ 正则提取首个完整 JSON 对象
    m = re.search(r"\{.*\}", raw, re.S)
    if m:
        try:
            return schema_validate(json.loads(m.group()), schema)
        except Exception:
            pass
    # ④ 兜底:默认结构 + 原文
    return schema.defaults() | {"raw_text": raw, "parse_failed": True}

要点: 任何一层失败都不能抛异常中断用户请求,而是带着「解析失败」标记继续向下,由业务层决定是重试还是降级。

3.2 字段级修复

解析成功后,再对关键字段做类型与值域修复:

  • 字符串 → 数字:"价格是 100 元" 中提取 100;
  • 枚举漂移:映射表归一到合法枚举("high!" → "high");
  • 空值兜底:null/空字符串 替换为 schema 默认值或标记 unknown。

四、第三层兜底:校验与重试——失败是常态,重试是机制

4.1 分层的校验规则

LLM 输出必须经过「规则校验」,且规则优先于模型判断:

校验层校验内容手段
结构校验schema、必填字段、类型代码校验(确定性)
业务校验枚举合法、数值范围、日期格式规则引擎 / 正则(确定性)
语义校验内容与 query 相关、引用存在LLM 二次判定(概率性,成本高)

规则:能用确定性校验解决的,绝不动用第二次 LLM。

4.2 自动重试策略

校验失败后按以下顺序处理:

校验失败
  → 修复型重试:把失败原因(校验错误信息)反馈给模型,让其修正(1~2 次)
  → 兜底型重试:降低要求(放宽 schema / 换小模型)再试 1 次
  → 降级输出:返回默认值 / 缓存结果 / 明确告知"生成失败"

重试的纪律:

  • 必须携带失败原因重试,盲重试命中率极低;
  • 限制重试次数(≤2 次),否则成本与延迟失控;
  • 重试间可降 temperature,给模型换一条采样路径;
  • 写操作绝不静默重试(防止重复下单),重试只适合读操作与生成操作。

4.3 降级链路设计

一个生产级 Agent 的输出链路应该是「阶梯式」的:

L1 完整回答(大模型 + 全量上下文)      ┐
L2 简化回答(小模型 / 裁剪上下文)       ├─ 逐级降级,任一成功即返回
L3 缓存命中(历史相似问题结果)          │
L4 默认话术("暂时无法回答,请稍后重试") ┘

每一级的失败都会自动落到下一级,保证用户永远拿到响应,而不是错误页。


五、第四层兜底:确定性手段——让输出「可重复」

对稳定性要求极高的场景,可以牺牲部分质量换取确定性:

  • temperature = 0:绝大多数模型在 greedy 解码下对相同输入输出基本一致;
  • seed 固定:部分 API 支持 seed 参数,配合 temperature=0 进一步稳定采样(注意:非所有模型保证严格复现);
  • 确定性采样:开源模型可用 do_sample=False + beam search,输出完全确定;
  • 结果缓存:对相同输入直接返回历史结果,天然「零漂移」。

适用范围提示: 确定性手段适合「解析/分类/信息抽取」类任务;对创意生成类任务强行确定性会损失质量,应改用「校验 + 重试」而非「压随机」。


六、第五层兜底:版本管理与回滚——漂移的根本解药

输出不稳定的一个隐形来源是:你根本没有意识到模型或 Prompt 变了。 模型供应商的静默更新、Prompt 的随手修改,都可能让线上行为瞬间漂移。

6.1 三层版本控制

层版本化对象手段
Prompt 版本每个 Agent 的提示词Git + 语义化版本号,变更必须留档
Schema 版本输出结构schema_version 字段,解析按版本兼容
模型版本模型型号 / 快照显式锁定模型版本,禁止「跟随最新」

6.2 灰度与金丝雀

  • 新 Prompt / 新模型先走 10% 流量灰度,对比输出质量指标(见第七节);
  • 质量指标劣化(如解析失败率上升 1%、格式漂移率翻倍)时自动回滚到上一版本;
  • 建立「回滚开关」:线上出现大面积漂移时,一键切回稳定版本,而不是现场排障。

6.3 变更即回归测试

任何 Prompt / 模型 / Schema 变更,都要跑一遍固定回归集(几十~上百条典型请求),对比输出 diff。允许内容合理变化,但结构、字段、格式不允许回归。


七、可观测性:让「不稳定」可量化、可告警

兜底机制是否生效、漂移是否在恶化,必须靠指标说话:

结构层:解析失败率、schema 校验失败率、字段缺失率
重试层:重试率、重试成功率、降级触发率(按级别)
质量层:格式漂移率、内容重复率(同输入异输出)、用户投诉率
版本层:当前 Prompt/模型版本、灰度流量比例、回滚次数

两个核心告警:

  1. 解析失败率异常上升——通常是模型更新或 Prompt 改动导致,超过阈值立即告警并触发自动回滚;
  2. 同输入不同输出的高离散度——在测试集上周期性对比输出一致性,作为内容漂移的先行指标。

建议把「最终兜底层(L4 默认话术)触发率」当作系统健康的红线:它不应超过 1%,一旦超了就说明主链路已经大面积失效。


八、完整兜底架构图

用户请求
  └─> 生成(结构化输出约束)
        └─> 容错解析(严格 → 修复 → 模糊 → 默认)
              └─> 确定性校验(schema + 规则)
                    ├─ 通过 → 输出 ✅
                    ├─ 可修复 → 携带原因重试(≤2 次)→ 输出 ✅ / 降级
                    └─ 不可修复 → 降级链路(简化回答 → 缓存 → 默认话术)
上层保障:Prompt/Schema/模型三版本控制 + 灰度回滚 + 质量指标告警

设计原则总结:

  1. 生成阶段关笼子:结构化输出 + 约束解码,物理性禁止非法输出;
  2. 解析阶段多级兜底:任何解析失败都不抛异常,带着标记继续;
  3. 校验阶段确定性优先:规则校验永远走在 LLM 二次判定前面;
  4. 失败阶段阶梯降级:每一级失败自动落到下一级,用户永远有响应;
  5. 变更阶段版本管控:Prompt、Schema、模型三版本 + 灰度回滚,杜绝「静默漂移」;
  6. 运行阶段指标告警:把不稳定变成可量化指标,异常自动触发回滚。

九、关键结论

  • 稳定的系统不是「模型听话」,而是「模型不听话时系统也不出事」——兜底架构的完备性比 Prompt 的精妙程度更重要。
  • 结构约束 > 提示词请求:用 Function Calling / JSON Schema / 约束解码,把不稳定消灭在生成阶段。
  • 解析必须宽容:多级解析 + 字段修复,让「脏输出」也能被接住。
  • 失败要设计:校验 → 携带原因重试 → 阶梯降级,写操作绝不静默重试。
  • 变更要管控:Prompt / Schema / 模型三版本 + 灰度回滚,从源头掐断「漂移」。
  • 把「兜底层触发率」当作系统红线指标,主链路健康度一目了然。

兜底的终极形态是:无论模型今天「状态如何」,用户拿到的都是一个结构完整、内容合理、可追踪的响应。稳定性不是模型的属性,而是系统的设计。