主题
生产治理与降级
一句话:生产治理不是给 Agent 多挂几个告警,而是把“一次任务最多花多少、失败时退到哪、钱花在谁身上”变成可计算、可执行、可回滚的约束;换来的是成本和故障可控,代价是每个场景都要用自己的任务分布校准,不能抄一套默认值。
为什么需要
演示阶段只有一条链路:一个模型、一把密钥、几个用例。接进生产后,同一条链路会同时遇到三件事:一次“调研并写报告”不是一次调用,而是十几次到几十次;工具结果含糊、格式不合法、超时无反馈都会触发重试,而重试带着更长的上下文;模型无状态,每一轮重发历史,累计发送量接近平方增长。
这三件事叠在一起,账单和故障都不是线性增长。治理要回答的是:钱花在哪、什么时候必须停、某个组件挂了以后还能提供什么。它是贯穿接入、编排、运行态、模型网关的横切约束,不是“第八层”;平台分层见 01-项目总览与定位,运行态状态机见 04-运行态Pod池与任务编排。
核心机制
一、Token 经济学的三大放大器
路径爆炸:把任务拆成“读文件 → 分析 → 写稿 → 校对 → 格式整理 → 最终输出”六段,每段平均 2–5 次调用(规划、选工具、总结、返工),一次任务就是 12–30 次调用;按本书口径记为 10–30 次。假设每天 2,000 个任务、每任务 20 次调用,就是 4 万次/天;测试时 20 次/天没感觉,上线后是 2,000 倍。第一个放大器是调用次数,不是单价。
重试放大:设任务基础消耗 25K token、30% 的任务至少重试一次。若重试次数不设上限,期望倍数是 1/(1-0.3)≈1.43;最多重试两次是 1+0.3+0.3²=1.39。重试还要重发更长上下文,设每次为原来的 1.5 倍,成本倍数约为 1+0.3×1.5+0.09×2.25≈1.65。算例:2,000 任务/天 × 30% × 25K = 1,500 万 token/天,30 天 4.5 亿;按输入价 $15/百万 token 粗算,仅重试增量约 $6,750/月。若把 2,000×30×25K 全量都按 $15/百万计,才得到 $22,500/月——那是“全部流量都按该价计”的口径,不是重试增量,差异来自假设。
上下文膨胀:设每条用户消息和每条回答各约 1K token,第 i 轮输入约为 (2i-1)K,单轮线性增长;但 N 轮累计发送量是 Σ(2i-1)K = N²K,所以累计成本是 O(n²),不是单轮平方。10 轮累计约 100K token,最后一轮 19K;20 轮累计约 400K,最后一轮 39K。这里算的是发送量;前缀缓存命中会按缓存价打折,但发送与延迟仍在。三者是相乘关系,预算要按“单任务总成本”设,不能只盯单次价格。
二、五重预算(示例配置,不是默认值)
| 维度 | 防什么 | 客服 | 分析 | 超限动作 |
|---|---|---|---|---|
| 时间 | 用户等待、长尾拖死连接 | 30s | 180s | 先切短路径/降级,仍超时则终止 |
| 步数 | 无进展空转、反复规划 | 8 | 25 | 强制压缩上下文并收尾 |
| 模型调用 | 成本与 API 配额 | 8 | 25 | 剩余步骤改小模型或跳过非关键推理 |
| 工具调用 | 外部资源、写操作风险 | 10 | 50 | 只允许只读/幂等工具 |
| Token | 输入输出总费用 | 3,000 | 15,000 | 压缩历史、限制输出、降级 |
步数是循环轮次;模型调用通常与步数同阶(每次 THINK 一次,规划/重试另计),工具调用可多于步数(一步并行多个工具),所以不要用一个上限同时管两种资源。校准方法:每场景取 200–500 条真实任务,统计各维度的 P50/P95,预算取 max(P95×1.1, 业务 SLA),目标是把正常任务的误杀率压到 1% 以下;上线后按月复核,业务变化就重算。预警阈值 80/85/90/95 不是四个告警等级,而是四个动作触发器:时间到 80% 切短路径、关可选检索;步数到 85% 强制压缩并停止探索;模型调用到 90% 把剩余推理降级到小模型;工具调用到 95% 只放行只读操作;Token 到 80% 压缩上下文并限制输出。时间与 Token 已经花出去、不可回收,所以最早预警;工具调用单次便宜且可替换,允许更接近上限。到 100% 才硬终止,且先降级,降级仍不行才终止——降级是软失败,终止是硬失败。
三、L0–L6 降级阶梯
| 级别 | 保留能力 | 典型触发(示意起点) |
|---|---|---|
| L0 | 大模型 + RAG + 工具 | 全部健康 |
| L1 | 检索 + 工具 + 模板回答 | LLM 延迟 > 5s 或成功率 < 95% |
| L2 | 大模型 + 工具 | RAG 延迟 > 3s 或准确率 < 80% |
| L3 | 小模型 + RAG | 工具成功率 < 90% 或 CPU > 85% |
| L4 | 知识库查询 | 模型全不可用或内存 > 90% |
| L5 | 人工 | 错误率 > 5% |
| L6 | 稍后重试 | 依赖维护或大面积网络故障 |
关键是 L1–L3 按“哪个组件先挂”跳级,不是按严重程度逐级下滑:LLM 挂进 L1,RAG 挂进 L2,工具挂或资源紧进 L3;L4–L6 才是资源、错误率、兜底链。恢复要逐级回升:每 30 秒探测,连续两次成功才升一级,恢复条件比触发条件更严,避免在阈值附近抖动。旧资料里的“5 级优雅降级”(主模型 → 备用模型 → 缓存/知识库 → 静态话术 → 排队/人工)是同一套链路的粗粒度说法,和 L0–L6 不是两套机制;本书统一用 L0–L6,不再并列“5 级”与“7 级”两套口径。
四、五层流量漏斗
| 层 | 作用 | 命中率(场景假设) | 延迟(场景假设) |
|---|---|---|---|
| FAQ | 高频标准问题直接答 | 入口流量的 90% | ~5ms |
| 语义缓存 | 相似问题复用答案 | 入口流量的 4% | ~20ms |
| 小模型 | 简单分类/抽取 | 入口流量的 0.6% | ~150ms |
| RAG | 检索后拼装回答 | 入口流量的 0.4% | ~800ms |
| 大模型 | 复杂推理与生成 | 入口流量的 5% | ~2s |
口径要算清楚:100 万 CCU → 10% 活跃 → 10 万 QPS → 5,000 RPS 到大模型。CCU 是同时在线用户,不是 QPS;10 万活跃用户按每人每秒 1 次请求,才是 10 万 QPS。写成“100 万并发”会把单位从用户偷换成请求:100 万 QPS 相对 10 万 QPS 差 10 倍,相对 5,000 RPS 差 200 倍。上表命中率是“占入口流量”的份额,不是每层输入的命中率;若说“缓存命中率 85%”,分母通常是“到达该层的请求”,两者不能相加。另一组案例常见的说法是 FAQ 80%、缓存命中 85%、小模型过滤 60%,但三个分母分别是入口、到达缓存、到达小模型,不能直接推出“最后 5% 到大模型”;引用时要连分母一起写。延迟与成本同样要标场景。
五、容错六件套
- Supervisor Tree:父监控子,one-for-one 重启,失败上报;防止一个子任务拖垮整条链路,代价是多一层监督与状态同步。
- 三态熔断:正常 → 熔断(快速失败)→ 半开探测一条;防止下游持续失败引发重试风暴,代价是窗口和阈值要调。
- 幂等键:以租户+会话+任务+步骤+工具生成键,下游去重;防止重试重复扣款/建单,依赖下游支持。
- Watchdog:最大轮次、单步超时、心跳,超时落检查点;防止卡死和无限循环,可能误杀慢任务。
- 上下文压缩:到窗口 75% 摘要旧轮次,保留近期与钉住事实;防止撑爆窗口和成本上升,压缩本身要花一次调用。
- 优雅降级:按 L0–L6 退到仍可用的能力;防止组件故障直接报错,代价是能力与质量下降。
层级化为什么比线性管道稳:线性 10 步、每步可靠率 0.85,端到端是 0.85^10≈0.197,约 20%。这个推导假设各步独立同分布;真实故障往往相关(同一网络、同一模型、同一依赖),所以不能把 20% 当成保证值。层级化的价值不是把概率乘回去,而是缩小故障域:一个子 Agent 挂了,父 Agent 可以重启、隔离或换路,兄弟节点不受影响;线性管道一个节点挂,整条断。
六、成本归因:Agent / 用户 / 任务
- Agent:哪个 Agent 设计最烧钱;看单成功任务成本、循环次数、降级率。
- 用户/租户:谁在用、是否滥用、如何分摊;看人均成本 P95、配额使用率、拒绝率。
- 任务:哪类任务不划算、预算怎么调;看单任务成本分布、成功率、人工接管率。
Trace 最小字段:trace_id、session_id、task_id、tenant_id、user_id、agent_id、step_no、span_type(THINK/TOOL/RAG/COMPRESS)、model、model_version、prompt_tokens、completion_tokens、cached_tokens、unit_price_version、tool_name、latency_ms、decision、outcome/error_class。没有 task_id 就只能看模型总账单,回答不了“哪个任务贵”;没有 unit_price_version,降价或换供应商后历史成本不可比。单位任务成本要用“总成本 ÷ 成功任务数”,否则一个便宜但大量失败的路由会显得很划算。
读图顺序是请求先过预算和漏斗,再进编排;Trace 不是终点,而是把实际成本、质量和降级路径回写给预算与漏斗,形成闭环。治理横切在每一段,不单独成层——所以它只有虚线,没有自己的位置。
图的第一层分叉就是全文的判据:局部故障按组件跳级(L1–L3),整体故障按严重程度下沉(L4–L6)。把这两类混在一个列表里排队,就会出现“LLM 挂了却先降小模型”这种既没治好病、又降低了质量的组合。回升侧只放一条边:每次只升一级,并且要求连续 2 次成功,目的是防止阈值附近来回抖动;回升不走跳级,因为降级可以激进、恢复必须保守。
实战场景
场景一:月底账单超支三倍,五个长会话把预算烧穿
现象:接入生产第 4 周对账,平台月度支出 6.9 万美元,是预算 2.1 万的 3.3 倍,增量的 64% 来自分析场景。财务限期两周给出任务粒度的解释,否则冻结额度。金额与倍数是经验值,需按自己的任务分布校准。
排查:按供给、流量、任务结构、预算配置四层分开定位。供给——unit_price_version 与上月一致,单价没变,不是换供应商引起的口径漂移;流量——入口 QPS 持平,10 万 QPS 压到 5,000 RPS 的比例没有恶化,不是用户涨了;任务结构——按 task_id 聚合,Top 5 个任务花掉 3.1 万美元,单个任务跑到 640 个循环轮次,而分析场景的步数预算是 25,按累计发送量 N²K = 640²×1K ≈ 4.1 亿 token,一个任务顶 32 个正常任务(按每任务 20 次调用);预算配置——计数器挂在单次请求和子任务上,主管编排下每个子 Agent 各领一份 25 步,检查点恢复后 step_no 归零、时间维度同步重置。五重里只有模型调用和工具调用真正生效,时间、步数、Token 三重的任务级总量从未落地。
sql
-- 分母用成功任务,否则便宜但大量失败的路由会显得很划算
SELECT task_id, tenant_id, agent_id, MAX(step_no) AS steps,
SUM(prompt_tokens + completion_tokens - cached_tokens) AS billed,
COUNT(*) FILTER (WHERE outcome = 'success') AS ok_tasks
FROM trace_span GROUP BY 1,2,3 ORDER BY billed DESC LIMIT 20;对症做法:把预算从单次调用改成任务级总量,并与状态一起持久化;五重补齐,80/85/90/95 仍是动作触发器不是告警等级。为什么必须是五重而不是把 Token 上限调大:模型无状态、每轮重发历史,第 i 轮输入约 (2i-1)K,N 轮累计 N²K,累计成本随轮次近似二次增长,单靠每步上限拦不住长任务;时间与 Token 花出去不可回收,所以到 80% 就切短路径、压缩上下文,步数到 85% 强制收尾、模型调用到 90% 转小模型、工具到 95% 只放行只读,到 100% 先降级、降级仍不行才终止。
yaml
budget_scope:
unit: task_id # 不按子任务、不按单次请求
persist_with: checkpoint # 恢复不清零
subtask_share: 0.4 # 子任务最多消耗父任务预算四成(经验值,需校准)
session_hard_cap_tokens: 120000 # 经验值,需校准
budgets:
analysis: {time_s: 180, steps: 25, model_calls: 25, tool_calls: 50, tokens: 15000}复盘要点:单一维度等于没有预算,计数器不与 task_id 一起落库,每恢复一次就重置一次;只看月总额是事后看账单,过账要看 Top-N task_id 集中度,没有 task_id 就只能回答“模型总账单花了多少”。
场景二:供应商限流 45 分钟,用户看到的是报错页
现象:某周三 14:20–15:05,供应商对分析场景的调用批量返回 429;客服与分析共用一把供应商密钥,配额被分析侧吃满,客服入口整段报错,工单 217 条,人工接管率从 6% 拉到 100%,投诉升级到 CTO。触发时长与工单量为经验值,需校准。
排查:分四类定位。容量——运行态 Pod 池利用率不足 40%,不是 GPU 不够;模型——内网备用模型健康探测全绿,不是模型坏了;供给——网关侧 429 带 Retry-After,配额被上游掐住,是供给问题;流程——入口 QPS 无突增,不是活动带来。配置层只剩一条:降级触发源只挂在“LLM 延迟 > 5s 或成功率 < 95%”这类指标上,熔断器只做快速失败、抛错到底;限流风暴落在没有触发源的空档里,链路以硬失败收场——容错六件套里“优雅降级防止组件故障直接报错”这一条等于没接。
对症做法:给限流补显式触发源,并把逐档的用户可见后果写进 runbook,避免值班现场猜档位。
| 档位 | 保留能力 | 触发条件 | 用户可见后果 |
|---|---|---|---|
| L0 | 大模型 + RAG + 工具 | 全部健康 | 完整回答 |
| L1 | 检索 + 工具 + 模板回答 | LLM 延迟 > 5s 或成功率 < 95%;限流后部分 200 | “当前为模板回答”,答案变短 |
| L3 | 小模型 + RAG | 工具成功率 < 90% 或 CPU > 85% | 只给结论,不给处置建议 |
| L4 | 知识库查询 | 模型全不可用(含 429 耗尽配额)或内存 > 90% | 静态条目 + “未做推理”,无工单写操作 |
| L5 | 人工 | 错误率 > 5% | 排队转人工,给预计等待时间 |
| L6 | 稍后重试 | 依赖维护或大面积网络故障 | 保留输入草稿,提示稍后自动重跑 |
这次实际链路:14:20 LLM 成功率跌破 95% 进 L1;14:26 配额耗尽、模型调用维度吃到 100%,按 degrade_then_terminate 先进 L4 再判要不要终止;14:41 错误率超过 5% 转 L5;14:58 配额恢复后每 30 秒探测、连续两次成功逐级回升到 L0,用户侧影响从 45 分钟压到 18 分钟。恢复条件比触发条件更严、回升一次只升一级;不这样做会:跳级和回升抖动同时发生,值班只能手改配置。
复盘要点:降级阈值补齐之后,供给侧还得按租户和场景分池配密钥,否则一个场景的限流会打穿另一个场景的入口。另外 L1、L4 返回的都是 200,但答案不完整,降级会掩盖质量问题——每档配一条质量门禁加一条用户提示,把“没报错”和“治理成功”分开。缺的 L2 不是笔误:这次 RAG 健康,按“哪个组件先挂跳哪级”本来就不经过它。
场景三:降级阈值设错,正常抖动天天触发 L1
现象:上线第二周 L1 每天触发 40 多次,最长一次持续 22 分钟;同期 6% 的正常分析任务被误杀,口径要求是 1% 以下。触发次数是真实埋点计数,阈值来自另一套默认值,需按本场景分布校准。
排查:拉 7 天窗口看 LLM 延迟分布,P50 约 1.2s、P95 约 3.4s、最坏值 5.2s。三个问题叠在一起:触发窗口只有 10 秒、样本 20 条,一个慢尾请求就能翻档;阈值把最坏值当常态上界,P95 与 5s 之间没有缓冲;Token 预算按上月演示样本设的 15,000,没按新的任务分布重算,所以命中量集中在正常任务而不是异常任务。
对症做法:定阈用三档统计量——P50 定正常带,P50 到 P95 之间不翻档;P95 定触发线,取 max(P95×1.1, 业务 SLA);最坏值只定硬终止和 Watchdog,不做降级触发。触发和回升都带持续窗口与最小样本,恢复侧比触发侧更严;5s 这条线保留,但要求 120 秒窗口内 200 个样本同时越线才翻档:
yaml
triggers:
llm: {latency_ms: 5000, success_rate: 0.95, level: L1, window_s: 120, min_samples: 200}
recover: {probe_interval_s: 30, recover_after_successes: 2}阈值改动走版本、灰度、回滚,与模型灰度同一条链路;上线前把五类故障各演练一次:模型不可用、RAG 超时、工具 500、网络抖动、预算耗尽。
复盘要点:每周只看降级触发次数会把治理做反,次数下降可能是阈值设松;误杀率、单位成功任务成本、检查点恢复时长要和触发次数同一张表,每行有 owner,只发邮件等于没有治理。
最小上手
yaml
# 示例,不是通用默认值
budgets:
customer_service: {time_s: 30, steps: 8, model_calls: 8, tool_calls: 10, tokens: 3000}
analysis: {time_s: 180, steps: 25, model_calls: 25, tool_calls: 50, tokens: 15000}
warnings: {time: 0.80, steps: 0.85, model_calls: 0.90, tool_calls: 0.95, tokens: 0.80}
on_exceed: degrade_then_terminate
degradation:
probe_interval_s: 30
recover_after_successes: 2
triggers:
llm: {latency_ms: 5000, success_rate: 0.95, level: L1}
rag: {latency_ms: 3000, accuracy: 0.80, level: L2}
tool: {success_rate: 0.90, cpu: 0.85, level: L3}
memory: {usage: 0.90, level: L4}
error_rate: {value: 0.05, level: L5}
network: {outage: true, level: L6}
trace:
required: [trace_id, session_id, task_id, tenant_id, user_id, agent_id, step_no, span_type, model, prompt_tokens, completion_tokens, cached_tokens, unit_price_version, tool_name, latency_ms, outcome]生产动作
- 用真实分布校准:每场景采样 200–500 条任务,按 P95 设预算,误杀率超过 1% 就查预算或任务拆分;上线后每周看单位成功任务成本、降级率、误杀率、检查点恢复时长。
- 阈值配动作和 owner:80/85/90/95 各对应一个自动动作与一个负责人,只有邮件告警等于没有治理。
- 先定 Trace schema:
task_id、模型版本、缓存命中、降级路径、计费单价版本必须落库。 - 演练与灰度:模型不可用、RAG 超时、工具 500、网络抖动、预算耗尽各跑一次;预算与阈值改动走版本、灰度和回滚。
边界
- 小规模不必上全套,预算也治不了需求不清:并发低、任务单一时,固定步数上限加人工看账单更便宜;合法任务确实需要 50 步,8 步预算只会稳定误杀,先拆任务或改 SLA。
- 降级会掩盖质量问题:L1/L2 仍返回 200,但答案可能不完整;要有质量门禁和用户提示,不能把“没报错”当成治理成功。
0.85^10≈20%依赖独立同分布,真实故障相关时不能这么算;层级化的收益主要是隔离故障域和可重调度。跨模型 token 计数可能差 30–50%(经验值),归因要用供应商返回的 usage 和当期单价。
讲清楚
生产治理是把 Agent 的成本和故障从“事后看账单”变成“事前设约束、事中可降级、事后可归因”。先看三个放大器:一次任务十到三十次调用、三成重试放大 25K token 的算例、历史重发让累计发送量到 O(n²);再用时间/步数/模型/工具/Token 五重预算,在 80/85/90/95 触发不同动作,到顶才终止。组件挂了按 L0–L6 跳级降级,恢复逐级回升;入口用 FAQ、缓存、小模型、RAG、大模型五层漏斗把 10 万 QPS 压到 5,000 RPS。最后用 Supervisor Tree、熔断、幂等、Watchdog、压缩和降级兜住链路,用 Trace 把成本归到 Agent、用户和任务。它不适合任务定义不清或规模很小的团队,也不该被当成“多挂几个告警”。
适用:2026 年企业级 Agent 生产治理实践;预算与阈值为示例,需按真实任务分布校准 | 初稿 2026-10