multi-backend-output-diff-after-model-switch · ZH · 2026-10-22

项目做到一半换后端:怎么在一把 key 上逮住那些悄悄变了的输出

给出一套可复用的回归 diff 装置:把同一组基准提示词在新旧后端各跑一遍,自动比对格式、结构与语气偏移,让模型静默变更在用户察觉之前先暴露出来。

为什么要做这件事

一个聚合站后面挂着多个模型供应商,换后端、切模型、上游静默更新,都可能在你毫不知情的时候改变输出。用户不会告诉你「今天语气变生硬了」,他们只会直接流失。

靠肉眼看几条回复是不够的:模型输出的差异往往是渐进的、局部的。你需要一套可重复的对比装置,把「变了」变成可观测的信号。

装置的核心:三个固定

一套能长期跑下去的回归对比,靠的是把变量固定住。

  • 固定提示词集:一组覆盖你真实业务形态的基准提示词,不要用「你好,介绍一下自己」这种无信息量的样本。
  • 固定参数:温度、最大长度、系统提示词、输出格式要求,全部写死在配置里。参数漂移会让你误判成模型漂移。
  • 固定判据:哪些算「回退」要事先定义,否则每次看结果都会重新吵一遍。

提示词集怎么选

基准集不需要大,但要有代表性。建议覆盖几类:

  1. 格式约束类:要求输出 JSON、固定字段、固定列表长度。这类最容易被静默变更打穿。
  2. 结构化推理类:多步推导、条件分支,看逻辑链是否还完整。
  3. 风格敏感类:你产品里对语气有要求的场景,比如客服话术、文案润色。
  4. 边界输入类:超长输入、空输入、异常格式,看错误处理是否还一致。
  5. 多语言类:如果你做中英混合,换后端时语言漂移很常见。

每条提示词配一份「期望特征描述」,而不是期望的完整文本——模型输出天然有随机性,逐字比对只会制造噪音。

跑两遍,怎么比

新旧后端各跑一遍,用同一把 key、同一套参数,把结果落到本地文件。对比分三层:

第一层:机械比对

能自动判定的先自动判定:

  • JSON 是否仍可解析,字段是否齐全,类型是否一致
  • 列表长度、段落数、是否出现不该出现的 markdown 标记
  • 输出长度分布是否明显偏移
  • 是否触发了截断(结尾不完整、被 max_tokens 切断)

这一层不需要任何模型参与,成本低、噪音小,先跑它。

第二层:结构比对

把输出拆成骨架再比:标题层级、列表嵌套、字段顺序、是否保留了要求的句式模板。格式回退往往先于内容回退出现。

第三层:语气与语义比对

这一层最难自动化,但可以用「裁判」思路降低人工量:把新旧两版输出和你的期望特征描述一起交给一个模型,让它只回答几个封闭问题,比如:

  • 新版是否仍满足语气要求(正式/简洁/不说教)
  • 新版是否引入了解释性前缀或多余客套
  • 新版是否丢失了旧版有的关键信息点

让裁判输出结构化判定而不是自由点评,结果才能汇总成表。注意裁判模型自身也会漂移,所以判据要尽量封闭、可复核。

建立基线

第一次跑出来的结果就是基线。之后每次换后端、切换模型、或上游有更新,都重跑一遍,与基线 diff。

几个实践要点:

  • 保留原始输出:连同参数、时间、后端标识一起存档,否则事后无法复现。
  • 记录判定结论:哪些是预期内变化(比如换了更强的模型),哪些是不可接受的回退。
  • 不要追求零差异:目标是抓住「回退」,不是消灭随机性。差异列表要人工过一遍再定级。

在聚合站场景下怎么用

一个 key 调多个模型,意味着你可以把同一组提示词在不同后端之间交叉跑,得到一张对比矩阵。这对两件事特别有用:

  • 切换前的验收:准备把某条业务线从 A 换到 B,先用基准集跑 B,看格式与语气是否还能接受。
  • 异常时的定位:用户反馈「输出变差了」,先跑基准集,判断是模型变了、参数变了,还是你的提示词和新后端不兼容。

如果你在多个供应商之间做冗余,这套装置也能帮你判断备用后端的输出是否还在可接受范围内。

落地成本与节奏

不需要一开始就做得很重。最小可用版本是:十几条提示词、一份参数配置、一个跑两遍的脚本、一张人工看的差异表。跑顺之后再考虑接裁判模型和自动定级。

节奏上,建议在这些时点必跑:切换后端前、上游宣布更新后、以及固定的周期性抽查。日常不用天天跑,但配置要随时能跑起来。

一句话总结

模型的静默变化不会提前通知你,但可以被测量。把提示词、参数、判据固定住,把新旧输出并排放在一起,格式和语气的回退就会在用户开口之前先出现在你的表里。