Agent 质量与生产化 · 生产化
学习舱 / Agent 质量与生产化 / 生产化

生产化:灰度、回滚、成本治理与 Runbook

整理中 深度 · 应用 更新 2026-09-22

前置 Harness 概览、Agent Eval

生产化:灰度、回滚、成本治理与 Runbook

一句话定位:把「评估通过的改动」安全地送上线——本文给出生产就绪的五项能力(可部署、版本管理、灰度发布、回滚与故障转移、成本治理与 Runbook),与一个 30 行可跑的灰度路由器。前置:Harness 总览——五部件是待送上线的那套骨架;Agent Eval——本篇接手它验收合格的改动。

先从「我们直接全量上了」说起

改动过了评估篇的回归门:新题涨了、旧题没塌,按流程可以上线。团队把改好的提示词直接覆盖了原文件,全量放开——毕竟评估都过了,还能出什么事?

半小时后三件事同时发生:某类任务的请求开始批量报错(新提示词跟一个冷门工具的参数合不来)、token 消耗翻了近一倍(新的思考指令让模型每次都想太久)、而当有人喊「先改回去」时,另一个问题浮出水面——提示词是直接改在原文件里的,旧版本已经不存在了。

从「评估通过」到「敢全量上线」,中间缺的是什么?

缺的是五项能力:①可部署、②版本管理、③灰度发布、④回滚与故障转移、⑤成本治理与 Runbook。评估体系(评估篇)告诉你「该不该上线」,本篇的五个能力解决「怎么上、出事怎么退」。逐个落地,末尾是一个 30 行的灰度路由器。

全景图:从能运行到可托付

生产就绪五能力:能运行 → 可托付 ① 可部署 本地 / 内网 / 混合 网关收口: 密钥 · 路由 · 审计 ② 版本管理 提示词 · 工具 知识库 · 模型 皆可回退 ③ 灰度发布 按用户 / 任务类型 小流量试错 同一用户同一版 ④ 回滚与转移 指针回拨,秒级 三级故障转移 不可逆先停手 ⑤ 治理 预算 熔断 Runbook 一次安全上线的走线 改动过回归门 → ②新旧版本并存 → ③放 5% 流量观测 → 指标平稳逐级放量,异常则④秒级回滚 → 全程⑤预算熔断与 Runbook 兜底 读法:①是地基,②③④是上线的三道保险,⑤是全程的账房与预案——回归门(评估篇)在流水线最上游

图 1:生产就绪五能力——①可部署是地基,②版本管理、③灰度、④回滚是上线三道保险,⑤成本治理与 Runbook 全程兜底;官方清单的「可监控」并入⑤、「可审计」并入①

五项能力与官方「可部署 / 可监控 / 可灰度 / 可回滚 / 可审计」的五可清单一一有归属:可监控并入⑤的告警线,可审计并入①的网关审计;可部署、可灰度、可回滚正对①③④——②版本管理不在五可清单上,却是清单里每一项的前提(没有版本库,回滚④无从谈起)。

渐进拆解:像上新菜一样上新版本

类比:餐厅推新菜

餐厅推新菜的流程就是这套能力:

  • 试吃是小范围灰度——先请熟客尝,不直接写进菜单。
  • 配方留底是版本管理——老配方的字条还压在抽屉里,差评随时撤。
  • 停售与换菜是回滚与故障转移——主料断供就换备选做法,都不是重来。
  • 成本核算是账房——新菜毛利率太低就下架。
  • 应急预案是 Runbook——客人过敏了第一句问什么、第二句做什么,墙上贴着。

五个能力没有一个是「跑起来」本身,全是「跑起来之后敢对结果负责」。

① 可部署:形态跟着数据走

三种常见形态:本地 Docker Compose(自用与小团队,一条编排文件起全套)、企业内网私有化(数据不出门,模型与网关都落在内网)、混合(模型 API 在云上、敏感数据与执行环境在内网,中间靠网关隔离)。判定线只有一条:数据出不出得了你的边界。

无论哪种形态,出口都收口到网关——密钥托管、路由与审计集中(安全篇的网关收口);业界开源件已经把这套做成了现成品:LiteLLM 网关提供多供应商路由、重试与冷却、故障转移、预算与限流,指标直接进 Prometheus,自建前先看它。

路由还有一道省钱的前置闸:强弱分层——按任务难度把活分给不同体格的模型(格式抽取、改写走轻量档,多步推理、代码走旗舰档,规则与难度阈值写在路由配置里而非代码里);限流则落成按用户或按 key 的 TPM·RPM 上限,超限先排队再拒绝。

② 版本管理:能回退的东西才有资格上线

「版本」在 Agent 里远不止代码:提示词、工具配置、RAG 策略、知识库快照、模型版本,每一样都可能在你不知情时变化——甚至模型 id 没变,供应商也可能把它静默指向新的小版本。纪律有三条:

  • 一切上线物进版本库(提示词不写死在代码里)。
  • 版本与评估集绑定:某次上线用的是哪版提示词、跑的哪版题集、什么成绩,三样一起归档,改动才可复现、结果才可比。
  • 上线前核对真实模型:实际响应的模型与你以为在调的模型要一致。

回滚的全部速度优势,都来自这一节做扎实。

③ 灰度发布:拿一小撮真实流量先试

回归门的题集再全,也覆盖不了线上流量的长尾——灰度就是拿 5%、20% 的真实流量继续考试。切流按四个维度选:

  • 按用户:新用户先试。
  • 按部门。
  • 按任务类型:低风险任务先切。
  • 按风险等级:只读任务先切。

一条铁纪律:同一用户要稳定落在同一版本(哈希分桶:用用户 id 固定算出一个 0–99 的桶号,同一 id 每次算出同一个数——机制见文末代码),用户上午一个答案下午另一个答案,比全量出错更伤信任。放量节奏配合两道闸:回归门(旧题不许塌)与线上闸(可观测性篇的四类指标不越线),两道都绿才进下一档。

④ 回滚与故障转移:出事能退、坏了能扛

回滚是版本指针的回拨——一个指向「当前生效版本」的可改引用,改它即完成上线或退回,秒级;它快的唯一前提是②的版本都在。故障转移分三级,逐级升格(图 2):

  1. 重试与退避:瞬时抖动,指数退避后重试——模型调用与容错篇的容错机构。
  2. 换道:换备用模型、备用供应商,坏节点进入冷却期,冷却结束前不再往它身上打流量。
  3. 转人工:放弃自动恢复,按 Runbook 处置,含失败原因不明、连续转移无效的情形。

第三级的触发边界要刻在墙上:可重试的失败才配自动恢复;不可逆的动作(数据已外发、写操作已执行、删改已落盘)之前必须停手转人。

「永不掉线」的真实含义是「自动恢复有明确的放弃条件」,无条件的自动重试不是高可用,是把小事故放大成大事故的传送带。

三级故障转移:逐级升格,不可逆前停手 停手边界:数据已外发 · 写操作已执行 · 删改已落盘 —— 不可逆动作之前,停止自动恢复 第一级 · 重试与退避 瞬时抖动:指数退避后再试 第二级 · 换道 换备用模型或供应商 坏节点进冷却期 第三级 · 转人工 放弃自动恢复,按 Runbook 走 读法:从左到右逐级升格,代价逐级变贵;停手边界横在所有级之上——可重试才自动,不可逆先交人

图 2:三级故障转移阶梯——重试退避、换道冷却、转人工逐级升格;不可逆动作的停手边界压在所有层级之上

⑤ 成本治理与 Runbook:烧钱有度,出事有章法

成本治理把可观测性篇的告警线接上执行动作,三个动作按预算进度升格:

  • 预算线之内(按天、按用户):正常跑。
  • 逼近预算:自动降级——换便宜模型、缩上下文。
  • 越过预算:熔断——拒绝新任务。

降级与熔断的决策在网关做(第①项的收口位置),业务代码无感。降级之前先省着花,缓存的层次要记全六档——命中即不出网,这是成本与延迟的第一道闸:

缓存档命中条件与所在层
精确匹配缓存同一请求原样命中——网关的第一档
语义缓存相似问题经向量库命中
Embedding 缓存同一段文本的向量算一次反复用,语料没变就不必重算嵌入
提示词缓存稳定前缀复用,模型侧与网关侧都认它
RAG 缓存检索结果按查询指纹复用,语料没变就不重检
工具结果缓存查过的不再重查

Runbook 则是告警到行动的翻译表:每条告警对应四栏——现象(什么指标、超了多少)、诊断(先看哪条 Trace、跑哪条命令)、处置(回滚?扩容?换道?)、升级(搞不定找谁)。可观测性篇要求「告警带 Trace 链接」,Runbook 补上「点开之后做什么」——两片拼上,值班才算有章法。

动手:30 行的灰度路由器

灰度的核心机制三样:哈希分桶(稳定同版)、指标账、越线自动回滚。保存为 rollout.py:

import hashlib

# 灰度切流:同一用户永远落同一版本(按 id 哈希分桶,不是随机)
def pick_version(user_id, percent):
    bucket = int(hashlib.md5(user_id.encode()).hexdigest(), 16) % 100
    return "v2" if bucket < percent else "v1"

# 灰度配置与指标账:放量比例、v2 的错误账、回滚阈值
flag = {"percent": 30, "v2_errors": 0, "v2_total": 0, "rollback_at": 0.25}

def serve(version, n):                    # 模拟线上表现:从第 5 个请求起,落到 v2 的开始出错
    return not (version == "v2" and n >= 5)

log = []
for i in range(1, 11):                    # 10 个用户先后进来
    user = f"user-{i:02d}"
    version = pick_version(user, flag["percent"])
    ok = serve(version, i)
    note = ""
    if version == "v2":
        flag["v2_total"] += 1
        flag["v2_errors"] += (not ok)
        rate = flag["v2_errors"] / flag["v2_total"]
        if rate >= flag["rollback_at"]:
            flag["percent"] = 0           # 自动回滚:错误率越线,全量切回 v1
            note = f" ← v2 错误率 {rate:.0%} 越线,自动回滚"
    log.append(f"{user} → {version}{note}{'(失败)' if not ok else ''}")

print("\n".join(log))
print(f"收官配置:灰度比例 {flag['percent']}%(v2 共 {flag['v2_total']} 次,错误 {flag['v2_errors']} 次)")

实跑输出(已验证,python3 rollout.py):

user-01 → v1
user-02 → v1
user-03 → v1
user-04 → v2
user-05 → v2 ← v2 错误率 50% 越线,自动回滚(失败)
user-06 → v1
user-07 → v1
user-08 → v1
user-09 → v1
user-10 → v1
收官配置:灰度比例 0%(v2 共 2 次,错误 1 次)

对着输出读:30% 桶位本会落三人(user-04/05/09),但 user-05 的请求失败后 v2 错误率立刻到 50%、越过 25% 的回滚线,系统把放量比例直接归零——user-09 也停在了 v1,最终只有两人真正吃到 v2,小流量试错、越线秒退,爆炸半径被锁在两人身上。哈希分桶保证了「同一用户同一版」,回滚后各用户回到自己熟悉的旧版,而不是再洗一次牌。

真实系统里把 serve 换成真调用、错误账接可观测性篇的四类指标、回滚动作换成把流量指针拨回旧版本(版本在②的库里躺着),这就是一套最小可用的发布护栏。顺带说一句:代码里那个可以在线改的 flag 字典,就是最小的 Feature Flag——配置与代码分离,改它不用重新发版。

上线 Runbook 的最小模板(每条告警一行,四栏):

  • 现象:哪个指标、超阈多少(来自监控告警,带 Trace 链接);
  • 诊断:先看什么(失败步 Trace → 错误率按版本拆分 → 最近一次变更);
  • 处置:回滚指针 / 切备用模型 / 扩容,各自的前提条件写死;
  • 升级:多久没恢复找谁、带着什么材料。

边界与取舍

生产化按规模分寸,不全上。 个人自用工具,灰度就是「自己先试三天」,Runbook 就是手机备忘录里三行字——五能力全上是一人项目的负资产。判定线沿用安全篇那条:出事的损失可逆吗、影响的人有多少。三人以上用、能写数据、能花钱的 Agent,五能力至少各要一个最小版;顺序也讲究:②版本管理最优先(没有它其他四个都是空谈),⑤成本治理其次(烧钱最先失控),③④再上,①的形态常在第一天就定了。

最后的坏味道预警:如果上线靠胆量、回滚靠重写、烧钱看账单——三个洞各缺一样:②的版本库、④的指针化、⑤的预算线。到这里,五部件的骨架、质量与生产化的护栏就都齐了——回到全景图,把整张地图串成你自己的路线。

自测

Prompt 变更的灰度按用户还是按任务类型切?各自的观察指标是什么?

两种都常用,观察指标不同。按用户切:同一用户稳定同一版(哈希分桶),观察的是体验一致性相关指标——该用户群的任务成功率、人工接管率、投诉与差评,适合面向「人的感受」的改动(语气、格式、交互)。按任务类型切:低风险任务类型先切(只读查询先于写操作),观察的是该类型的专项指标——该类任务成功率、工具失败率、成本偏差,适合风险分层明显的改动。共同纪律:两道闸同开(回归门的题集 + 线上闸的四类指标),任一越线即冻结放量或回滚。

「Agent 永不掉线」的三级故障转移,边界条件是什么——什么时候该放弃自动恢复转人工?

三级逐级升格:重试与退避(瞬时抖动)→ 换道(备用模型或供应商,坏节点进冷却期)→ 转人工。放弃自动恢复的边界刻在不可逆上:数据已外发、写操作已执行、删改已落盘——这些动作一旦发生就无法靠重试撤销,之前必须停手转人;失败原因未知且连续多级转移无效时,继续自动切只是在放大损失。所以「永不掉线」的准确含义是「可重试的失败自动恢复,不可逆的动作前明确放弃自动、交给人」——外加一份把每条告警翻译成行动的 Runbook。

灰度切流为什么用「同一用户稳定同一版」的哈希分桶,而不是随机分流?

因为用户对一致性的信任比对单次最优更敏感:同一个问题上午一个答案、下午另一个答案,即使两个版本各自都对,用户也会认定系统「不稳定」。哈希分桶让 user-03 今天、明天、后天都落在同一版——灰度期间观察到的是「版本的差异」而不是「随机的噪声」,指标才可归因;这也让回滚的影响可预期(回滚后各用户回到自己熟悉的旧版,而不是再洗一次牌)。

参考与延伸

本节点大纲与自测题(8 个子点 · 3 问)

大纲

  • 生产就绪差距:能运行 → 可部署/可监控/可灰度/可回滚/可审计
  • 部署形态:本地 Docker Compose / 企业内网 / 混合
  • 缓存体系:Prompt / Semantic / Embedding / RAG / Tool Result Cache
  • 模型路由与降级:强弱分层、Fallback、限流熔断
  • 版本管理:Prompt / 工具 / RAG 策略 / 知识库版本
  • 灰度发布与 Feature Flag:按用户/部门/任务类型/风险等级
  • 三级故障转移与冷却退避
  • 成本治理与上线 Runbook

自测——答不上来就是缺口

  • Prompt 变更的灰度按用户还是按任务类型切?各自的观察指标是什么?
  • 「Agent 永不掉线」的三级故障转移,边界条件是什么——什么时候该放弃自动恢复转人工?
  • 灰度切流为什么用「同一用户稳定同一版」的哈希分桶,而不是随机分流?