跳到主要内容

LLM 应用的 CI/CD:为什么部署 Prompt 与部署代码完全不同

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的代码通过一个流程发布:特性分支 (feature branch) → 合并请求 (pull request) → 自动化测试 → 预发布 (staging) → 生产环境 (production)。每一步都有门槛。如果没有通过你定义的检查,任何东西都无法到达用户手中。这种“枯燥”正是它最好的地方。

现在想象你需要更新一个系统提示词 (system prompt)。你在仪表盘中编辑字符串,点击保存,更改立即生效 —— 没有测试,没有预发布,版本控制中没有 diff,除了手动改回去之外没有回滚的方法。这就是大多数团队的运作方式,也是提示词更改成为 LLM 应用非预期生产事故主要原因的原因。

挑战不在于团队粗心大意。而在于持续交付 (continuous delivery) 的规范是为确定性系统构建的,而 LLM 并非确定性的。整个思维模型需要从头重建。

核心问题:Prompt 是未经测试的代码

在传统软件中,Bug 通常是可以检测到的。错误的值会抛出异常,缺失的字段会返回 404,损坏的查询会返回零结果。系统会以“大声”的方式失败。

LLM 应用的失败是无声无息的。你的 API 返回 HTTP 200,延迟看起来正常,Token 使用量也正常 —— 但模型现在给出的答案却有细微的错误,幻觉出了上周还没有出现的细节,或者忽略了你认为已经明确指定的约束。用户看到的是质量下降的输出,而你的仪表盘却什么也看不出来。

这就是为什么 Prompt 更改如此危险。从系统提示词中删除一个词、重新排列一条指令、为了节省成本而修剪几个 Token —— 其中任何一项操作都可能改变模型的行为,而这些改变在初看时可能没什么问题,只有在整体汇总时才会显现。对 1,200 多个生产环境 LLM 部署的分析发现,Prompt 更新是导致非预期生产行为的首要原因,排在模型版本更改和基础设施故障之前。

显而易见的解决办法是将 Prompt 视为代码 —— 对其进行版本化、测试,并根据通过标准来设置部署门槛。但这立即引出了更深层的问题:你如何测试一个非确定性的东西?

对 LLM 而言,“通过 CI”意味着什么

在传统的 CI 流程中,测试要么通过,要么失败。函数的输出要么与预期值匹配,要么不匹配。

LLM 评估从根本上是不同的。你定义指标,根据这些指标对输出进行评分,并设置阈值。当评估分数超过你定义的最小值时,测试就算“通过” —— 而不是当输出完全匹配时。

一个 LLM CI 门槛的组成部分:

评估数据集 (Evaluation datasets) —— 一组经过策划的具有预期行为的输入。不是预期的精确输出(每次运行都会变化),而是预期的属性:“这应该包含免责声明”、“这不应推荐竞争对手”、“这应该在三句话内回答”。

评估器 (Evaluators) —— 对输出进行评分的函数。这些函数的范围从简单的字符串匹配(“输出是否包含‘我不知道’?”)到使用独立模型评估质量、相关性或政策合规性的 LLM-as-judge 评估器。

阈值和门槛 (Thresholds and gates) —— 定义“足够好”含义的数字目标。准确率指标可能要求 85% 的通过率。安全性指标可能要求 100% —— 不允许任何失败。

基准对比 (Baseline comparison) —— 使用与当前生产版本相同的数据集对新版本进行评分。如果新版本的评分明显变差,即使它通过了绝对阈值,也被视为回归 (regression)。

像 Braintrust 这样的工具会直接将评估结果发布到合并请求 (pull request) 中,如果分数下降则阻止合并。LangSmith 提供数据集管理和自动评估器运行。DeepEval 将类似 pytest 的评估引入到现有的 Python 测试套件中。这些平台已经从“线下实验的可选配置”转变为“安全部署的必需基础设施”。

关键的洞察在于门槛不是二元的 —— 它是概率性的。这改变了你对发布 (rollout) 的看法。

影子测试:部署新 Prompt 的正确方式

影子模式 (Shadow mode) 是 LLM 变更中置信度最高的部署模式。其核心理念非常直接:当新的 Prompt 版本准备好投入生产时,你将其与在线系统并行运行,为其提供相同的输入,但在输出到达用户之前将其丢弃。

每一个真实的生产请求都在加深你对新版本行为方式的理解。你不再依赖于可能无法捕捉到真实用户查询分布的合成评估数据集。你是在针对真实流量运行候选版本,收集真实输出,并进行离线评分。

运作机制:

  • 在请求到达 LLM 之前,在应用层复制传入的请求
  • 将一份副本路由到当前的生产环境 Prompt,另一份路由到候选版本
  • 仅将生产环境的响应返回给用户
  • 记录两个响应以进行异步评估
  • 在夜间运行你的评估器;次日早晨查看汇总结果

与 A/B 测试的关键区别在于,影子模式完全不会让用户接触到候选版本。A/B 测试会分流,这意味着在测试期间,一半的用户可能会得到质量下降的输出。影子测试则完全推迟了这种风险,直到你对新版本有了统计学上的信心。

局限性在于成本 —— 你正在运行两倍的 LLM 调用。对于高流量应用来说,这笔开销可能非常显著。一些团队通过对采样百分比的流量进行影子测试,而不是全部流量,来缓解这一问题。

金丝雀发布与何时停止

一旦影子测试让你有了信心,下一步就是受控流量曝光。LLM 的金丝雀部署遵循与微服务相同的模式——将一小部分真实流量路由到新版本——但推进或停止的标准完全不同。

对于微服务,你会观察错误率和延迟。对于 LLM,你需要持续运行在线评估:近乎实时地对输出进行评分的轻量级自动化检查。这是一个更难的基础设施问题,因为 LLM 评估本身既昂贵又缓慢。

实际做法:

  • 针对每个金丝雀响应运行快速、廉价的评估器(基于规则或基于嵌入的相似度检查)
  • 在抽样后的子集上运行较慢的 LLM 作为裁判(LLM-as-judge)评估器
  • 每 15 分钟聚合一次分数,并与生产基准进行对比
  • 提前定义停止标准:如果安全评分下降超过 X%,则自动停止

“推进”决策——从 2% 移动到 25% 再到 100%——也应该是自动化且由标准驱动的,而不是基于工程师手动查看仪表盘并判定它“看起来不错”。

蓝绿部署实现即时回滚

金丝雀发布擅长及早发现问题。蓝绿部署则擅长让回滚变得迅速。

在蓝绿部署中,你维护两个完整的生产环境:当前的稳定版本(蓝)和你正在推广的新版本(绿)。当满足推广标准时,流量会原子化地切换——一次性全部切换——从蓝色转向绿色。

如果切换后出现问题,回滚是即时的:将流量切回蓝色。无需重新部署,无需等待金丝雀流量排空,也没有部分回滚的复杂性。

对于 LLM,“环境”不仅包括应用程序代码,还包括提示词版本、模型版本、任何 RAG 索引快照以及评估基准。所有这些都需要锁定在一起。在不将新提示词锁定到与之测试过的模型的情况下推广它,会导致细微的不兼容性,而这些不兼容性往往在后期才会显现。

这意味着 LLM 应用的部署产物不仅仅是一个 Docker 镜像。它是一个清单(manifest):prompt_version: v4.2 | model: claude-sonnet-4-6 | rag_index: 2026-04-08

行为漂移:无声的回归

最隐蔽的失败模式不是糟糕的部署,而是在没有任何部署事件触发的情况下积累的漂移。

模型提供商会持续更新他们的模型。今天的 claude-sonnet-4-6 API 端点表现可能与三个月前不同。这些更新可能是有益的(更好的推理能力、更少的幻觉),也可能悄悄地破坏你的应用程序所依赖的假设(改变的 JSON 格式化行为、不同的拒绝阈值、偏移的指令遵循倾向)。

对生产环境中 LLM 响应的分析显示,即使没有提示词更改,行为漂移也是可衡量的:相同运行下的响应长度方差可能超过 20%,静默模型更新后指令遵循度可能会偏移 30%,而 JSON 序列化行为——特别是在转义和嵌套方面——在模型版本之间的变化会破坏下游解析。

实际的防御手段是运行独立于部署流水线的持续行为监控:

  • 保留一组固定的“金丝雀提示词”——具有已知预期行为的输入——并按计划(每小时或每天)针对你的生产端点运行它们
  • 跟踪输出属性随时间的变化:长度、结构、必需元素的出现、与参考输出的语义相似度
  • 当指标偏移超过阈值时发出警报,即使没有发生任何部署
  • 保持在有特定模型快照可用时锁定到该快照的能力

这使不可见的问题变得可观察。GetOnStack 事件——由于行为逐渐漂移到无限对话循环,一个多智能体系统的成本在四个星期内从 127 美元/周飙升至 47,000 美元——正是持续行为监控可以及早发现的那种故障。

JSON 序列化:一个被低估的故障类别

一个没有得到足够关注的故障模式:模型版本之间的 JSON 处理不一致。

LLM 应用程序如果使用结构化输出或函数调用,就必须依赖模型生成有效的、可解析的 JSON。不同的模型版本和提供商处理边缘情况的方式不同——他们如何转义特殊字符,如何格式化嵌套对象,以及当 Schema 演进时如何处理工具调用参数。

在生产环境中观察到的常见故障:

  • 双重序列化——当工具结果返回预序列化的 JSON 字符串,而应用程序再次对其调用 json.dumps() 时,会产生后续模型调用无法解析的转义乱码
  • Schema 演进破坏——在对话进行中升级工具的输入 Schema,导致模型尝试针对旧的 Schema 进行调用
  • 提供商特定的特性——同样的提示词在 Claude、GPT 和 Gemini 上产生微妙不同的 JSON 格式,而你的解析器只正确处理其中一种变体

这些故障特别危险,因为它们会产生连锁反应。10 步智能体工作流中第 3 步的 JSON 解析失败可能直到第 8 步才会显现,到那时诊断根本原因需要详细的分布式链路追踪。

解决办法是结构性的:在序列化和反序列化边界验证所有 JSON,明确地对你的工具 Schema 进行版本控制,并在你的评估套件中包含 JSON 往返测试——不仅仅是“模型是否响应”,而是“响应是否能被实际消费它的代码解析”。

总结:一个最小化的 LLM CI/CD 流水线

一个生产级别的 LLM 部署流水线至少需要:

  1. 提示词版本化 (Prompt versioning) —— 将每一个提示词都视为带有唯一 ID 的不可变产物。禁止对已部署的提示词进行就地编辑。
  2. 评估数据集纳入版本控制 —— 你的测试用例与代码存放在同一个仓库中,共同演进,并在每次 PR 时运行。
  3. 自动化评估门禁 —— 导致评估分数下降的 PR 将被禁止合并,就像单元测试失败会阻止合并一样。
  4. 针对高风险变更的阴影测试 (Shadow testing) —— 在接触任何用户之前,新的模型版本和重大的提示词结构调整都需经过阴影模式验证。
  5. 带在线评估的分阶段发布 —— 结合持续评分和自动化停止标准的金丝雀部署。
  6. 独立于部署的行为监控 —— 定时运行金丝雀提示词,以捕获两次发布之间模型提供商发生的漂移。
  7. 部署清单 (Deployment manifests) —— 将提示词 + 模型 + RAG 快照捆绑为版本锁定的单元,以便统一进行晋级或回滚。

那些成功交付了大规模可靠 LLM 应用的团队都趋向于采用这种模式。而那些将提示词视为配置而非代码的团队,则在累积无形的技术债,这些债务最终会演变成生产事故。

现在的工具链已经足够成熟,上述任何一项都不需要从零开始构建。真正的差距在于纪律 —— 即以过去几十年软件部署所积累的严谨态度来对待 LLM 部署。如果不这样做,代价将是那些你无法解释的事故和无法复现的性能退化。

References:Let's stay in touch and Follow me for more thoughts and updates