DeepSeek V4.1 Flash 多模型路由降本实战
一句话结论:降本不是「换个便宜模型」,而是「让便宜模型干它干得了的活」。 我们用了 5 周,把月模型支出从 $71,300 打到 $15,900,降幅 77.7%,而任务完成率不降反升 2.8 个百分点。
2026 年 10 月,两件事同时发生,把「多模型路由」从一个架构洁癖变成了财务刚需:
一是 DeepSeek V4.1 Flash 的定价比 V4 Pro 低约 40%(输入 $0.25/M vs $0.42/M,输出 $1.01/M vs $1.68/M),而且在中短上下文的抽取、分类、格式化这类任务上,实测质量差距小到评测集跑不出显著差异;二是 a16z 最新一期 AI 应用榜单里,上榜产品的单位收入对应推理成本第一次被单独列了出来——变现压力已经从「故事」变成了「报表」。
本文是 T 公司(约 420 人,制造业供应链协同平台)Agent 平台团队 2026 年 9 月到 10 月的完整改造记录。所有数字来自生产环境的 Langfuse 埋点与财务对账,不是估算。
一、背景:单模型全量调用,账单是怎么失控的
1.1 一条「每个月都有理由」的账单曲线
| 月份 | 月支出(美元) | 任务数 | 单任务成本 | 环比 | 当时的解释 |
|---|---|---|---|---|---|
| 2026-05(内测) | $2,180 | 4,100 | $0.53 | — | 才刚上线 |
| 2026-06(公测) | $9,640 | 12,700 | $0.76 | +342% | 用户涨了,正常 |
| 2026-07(全量) | $28,900 | 26,300 | $1.10 | +200% | 功能变复杂了 |
| 2026-08 | $46,700 | 31,800 | $1.47 | +62% | 加了自检环节 |
| 2026-09(峰值) | $71,300 | 48,600 | $1.47 | +53% | — |
注意最后一列。每一次涨价都有理由,每一个理由都成立。这正是成本失控最可怕的地方:它不是爆炸,是渗透。等到 9 月财务把 $71,300 的账单甩过来时,团队的第一反应是「换个便宜模型全局替换」——而这个方案,我们在第 2 周就用数据否掉了。
1.2 把一次任务拆开:40 次调用里只有 4 次需要最贵模型
T 公司的核心任务是「供应链异常处置」:物料到货延迟告警 → Agent 判断影响范围 → 找替代供应商 → 评估成本差异 → 生成处置方案 → 提交人工复核。
我们把一次成功执行的 40 次模型调用完整拆开,标注每一次的实际用途:
| # | 阶段 | 调用次数 | 用途 | 输入 token | 输出 token | 原用模型 | 真需要最贵档吗 |
|---|---|---|---|---|---|---|---|
| 1 | 意图与槽位解析 | 6 | 从工单文本抽物料号、工厂、交期 | 1,200 | 180 | V4 Pro | 否 |
| 2 | 检索结果过滤重排 | 9 | 对 50 条候选供应商打分排序 | 3,600 | 90 | V4 Pro | 否 |
| 3 | 中间推理草稿 | 8 | 多步推理的中间态 | 6,400 | 900 | Opus 5.5 | 部分是 |
| 4 | 工具参数生成 | 7 | 把意图转成 API 调用的 JSON 参数 | 2,100 | 320 | V4 Pro | 否 |
| 5 | 方案生成与权衡 | 4 | 3 个处置方案的成本/风险对比 | 18,000 | 2,400 | Opus 5.5 | 是 |
| 6 | 自检与回归校验 | 5 | 检查方案是否违反业务约束 | 9,000 | 700 | Opus 5.5 | 部分是 |
| 7 | 最终输出格式化 | 1 | 转成前端渲染的结构化 JSON | 4,000 | 1,200 | Opus 5.5 | 否 |
| — | 合计 | 40 | — | 44,300 | 5,790 | — | 只有 4 次(10%) |
真正需要最贵档模型的只有第 5 阶段的 4 次调用,占 10%。但这 4 次吃掉了 82% 的钱。
原因很简单:这 4 次是长输入(18,000 token)+ 长输出(2,400 token)+ 单价最高的组合。而第 1、2、4 阶段这 22 次调用——占调用量的 55%——是典型的「格式转换、过滤排序、参数生成」,输入输出都短,而且有明确的正确答案可以校验。
1.3 三个致命假设
账单失控的根源不是模型贵,是三个没人质疑过的假设:
| 假设 | 为什么错 | 代价 |
|---|---|---|
| 假设一:所有调用同质,用同一个模型最省事 | 调用之间有 200 倍的质量敏感度差异 | 55% 的调用在付 8 倍溢价 |
| 假设二:贵模型 = 高质量,便宜模型 = 质量崩 | 在「抽取/分类/格式化」这类任务上,两者差异低于评测集噪声 | 不敢用便宜模型,白白多付 40%-60% |
| 假设三:换模型是一次性配置改动 | 真实成本在评测集、回归监控、fallback 链路上 | 第一版改造回滚了 2 次 |
二、工具分工表:哪些任务用便宜模型,哪些必须上贵模型
2.1 四档模型单价表(2026-10 实测)
| 档位 | 代表模型 | 输入($/M token) | 输出($/M token) | 相对单价 | 定位 |
|---|---|---|---|---|---|
| L0 | 规则引擎 / 语义缓存 | $0 | $0 | 0 | 完全确定性任务 |
| L1 | DeepSeek V4.1 Flash | $0.25 | $1.01 | 0.6×(较 V4 Pro) | 抽取、分类、格式化、短推理 |
| L1' | Claude Haiku 5 | $0.80 | $4.00 | 2.4× | 需要英文/多语言时的 L1 替代 |
| L2 | DeepSeek V4 Pro | $0.42 | $1.68 | 1.0×(基准) | 跨文件推理、中等复杂度代码 |
| L3 | Claude Opus 5.5 | $15.00 | $75.00 | 35.7× | 长上下文权衡、架构决策、争议仲裁 |
Flash 比 Pro 便宜 40% 这个数字,在 2026-10 的实际报价下是:输入 $0.25 vs $0.42(低 40.5%),输出 $1.01 vs $1.68(低 39.9%)。
2.2 任务类型分工表(核心)
这张表是我们改造的骨架,贴在团队 Wiki 首页:
| 任务类型 | 典型样例 | 难度档 | 推荐模型 | 判据(满足任一即用该档) | 改造前调用占比 | 改造后调用占比 |
|---|---|---|---|---|---|---|
| 格式转换 / JSON 校验 | 把模型输出转成前端 schema | L0 | 规则引擎 | 有确定 schema,可用 JSON Schema 校验 | 4% | 9% |
| 意图识别 / 分类打标 | 工单归类、情绪判断 | L1 | V4.1 Flash | 输出空间是枚举,可自动校验 | 11% | 18% |
| 信息抽取 / 字段回填 | 从合同抽甲方乙方金额 | L1 | V4.1 Flash | 有 ground truth 可比对 | 9% | 15% |
| 检索结果重排 | 50 条候选打分 | L1 | V4.1 Flash | 只需相对序,不需绝对分 | 14% | 11% |
| 摘要 / 压缩 | 长文档压成 500 字 | L1 | V4.1 Flash | 允许信息损失,有长度上限 | 6% | 8% |
| 单文件代码补全 / 单测生成 | 补一个函数的单测 | L1-L2 | Flash → Pro | 单文件内、可跑测试验证 | 8% | 9% |
| 工具参数生成 | 意图转 API JSON | L1-L2 | Flash → Pro | 参数错了会调用失败,可重试 | 12% | 10% |
| 跨文件重构 | 改一个接口,改 12 个调用点 | L2 | V4 Pro | 涉及 2 个以上文件 | 9% | 7% |
| 中等长度推理 | 5-10 步的因果链 | L2 | V4 Pro | 需要中间态,但上下文小于 32K | 11% | 6% |
| 长上下文审阅 | 50 页合同风险识别 | L3 | Opus 5.5 | 上下文大于 100K 且需全局一致性 | 8% | 4% |
| 多方案权衡 / 架构决策 | 3 个处置方案选一个 | L3 | Opus 5.5 | 输出不可逆,影响面大 | 5% | 2% |
| 争议仲裁 / 兜底复核 | 两级模型结论不一致 | L3 | Opus 5.5 | 是最后一道关 | 3% | 1% |
2.3 判定三问(人肉版,用于评审新任务)
给一个任务定档时,只问三个问题:
- 输出能不能被程序校验?(有 schema / 有 ground truth / 能跑测试)→ 能,降到 L1
- 错了会怎样?(重试就好 / 污染下游 / 不可逆)→ 不可逆,至少 L2,影响面大的上 L3
- 需要多少上下文才能答对?(小于 8K / 8K-100K / 大于 100K)→ 大于 100K 且需全局一致性,L3
三个问题全答「最轻」,就是 L1。有一个答到最重,就是 L3。
三、策略设计:任务难度分级路由
3.1 五级难度分类(D0-D4)
我们最后没有直接用 L0-L3 四档,而是拆成了难度五级和模型四档两个正交维度——难度是业务概念,档位是采购概念,中间用一张映射表连接。这样换供应商时只改映射表,不动业务逻辑。
| 难度 | 名称 | 判定特征 | 默认档位 | 超时预算 | 允许重试 |
|---|---|---|---|---|---|
| D0 | 确定性任务 | 有 schema,纯格式转换 | L0 | 100ms | 否(失败即报) |
| D1 | 浅层理解 | 抽取、分类、摘要;输出空间受限 | L1 | 3s | 是(1 次) |
| D2 | 单域推理 | 单文件/单数据源,5 步以内推理 | L1 → L2 升级 | 8s | 是(1 次) |
| D3 | 跨域推理 | 多文件/多数据源,或上下文 32K-100K | L2 | 25s | 是(2 次) |
| D4 | 决策级任务 | 输出不可逆、影响面大、上下文大于 100K | L3 | 60s | 是(2 次,第二次强制升档) |
3.2 路由规则表
| 规则 | 条件 | 动作 | 命中率 |
|---|---|---|---|
| R1 | 输出有 JSON Schema 且历史校验通过率大于 99% | 直连 L0 规则引擎 | 9% |
| R2 | 语义缓存相似度大于 0.93 | 返回缓存,零调用 | 6% |
| R3 | 难度 = D1 | L1(V4.1 Flash),温度 0 | 34% |
| R4 | 难度 = D2 且单文件 | L1,失败一次升 L2 | 19% |
| R5 | 难度 = D3 | L2(V4 Pro) | 12% |
| R6 | 难度 = D4 | L3(Opus 5.5),强制开启思考 | 6% |
| R7 | 涉及多语言(非中英) | L1 换 Haiku 5 | 4% |
| R8 | 上游任务失败过 1 次 | 自动升一档 | — |
| R9 | 单次任务累计花费超预算 80% | 后续调用强制降到 L1 | — |
| R10 | 判定置信度低于 0.6 | 保守升一档(宁可多花) | 10% |
3.3 三级 fallback 链路
路由最大的风险不是「选错便宜模型」,而是「便宜模型挂了/超时/输出不合法」时没有退路。我们的链路:
第 1 跳:主模型(按难度档位选定)
↓ 失败(超时 / 5xx / 输出不合法 / 自检不通过)
第 2 跳:同档替代(V4.1 Flash ↔ Haiku 5;V4 Pro ↔ GPT-6 Luna)
↓ 失败
第 3 跳:升一档(L1→L2→L3),最多升 2 级
↓ 失败
第 4 跳:降级到「保守静态响应 + 人工队列」并打标 P1
三条硬约束:
- fallback 必须带
reason字段写进链路追踪,否则你永远不知道便宜模型失败率是多少 - 升档不能超过 2 级——升到 L3 还失败就说明不是模型问题,别再烧钱
- fallback 后必须回写评测集——每一条 fallback 都是一个免费的负样本
实测 fallback 触发率:L1 主模型 3.8%,L2 主模型 1.4%,L3 主模型 0.6%。整体因 fallback 多花的成本占总支出 4.1%,完全可接受。
3.4 路由判定的代价:判定本身也要花钱
这是最容易忽略的一点:如果每次路由都要先调一次大模型来判难度,你就白干了。
我们的解法是「规则前置 + 便宜模型兜底」:
- 72% 的请求在进入模型之前就被规则(schema 存在性、token 数估算、任务类型标记、缓存命中)判定了档位,判定成本 = 0
- 28% 的请求走一次 L1 模型判定,单次判定成本约 $0.0004(输入 800 token + 输出 60 token)
- 判定总成本占改造后总支出的 1.3%
3.5 预算闸门(防止路由失效时账单失控)
| 闸门 | 阈值 | 动作 |
|---|---|---|
| 单任务预算 | $0.60(基线 $1.47 的 41%) | 超 80% 时后续调用强制降到 L1 |
| 单用户日预算 | $3.00 | 超限转人工队列并告警 |
| 全局小时预算 | $180 | 超限自动把 L3 配额降到 50% |
| L3 调用配额 | 全局每小时 400 次 | 超配额排队,不丢弃 |
四、实施步骤
4.1 第 0 步:埋点与基线(必须先做,别跳过)
没有埋点的降本等于闭眼开车。我们在改造前先跑了 2 周纯埋点,拿到三个基线:
# metrics_hook.py —— 每次模型调用都打点
import time
from langfuse.decorators import observe
@observe(as_type="generation")
def call_model(stage: str, model: str, messages: list, **kwargs):
t0 = time.time()
resp = client.chat.completions.create(model=model, messages=messages, **kwargs)
latency = (time.time() - t0) * 1000
langfuse_context.update_current_observation(
model=model,
input=messages,
output=resp.choices[0].message.content,
usage={
"input": resp.usage.prompt_tokens,
"output": resp.usage.completion_tokens,
},
metadata={
"stage": stage, # 关键:阶段名,用来做分工表
"latency_ms": latency,
"unit_cost_usd": calc_cost(model, resp.usage), # 自己算,别信账单
},
)
return resp
三个必采基线:
- 按阶段拆分的成本分布(就是 1.2 那张表)——没有它你不知道该优化哪
- 每个阶段的成功率与重试率——某个阶段重试率高,说明它其实更难,不能降档
- 一份 300 条的回归评测集——人工标注过正确答案,每次切流前后都跑一遍
⚠️ 评测集必须在改造之前建好。我们第一版就是因为没评测集,切流到 30% 时才发现「合同风险识别」的漏检率从 2.1% 涨到 7.8%,连夜回滚。
4.2 第 1 步:任务难度分级 Prompt 模板(可直接复制)
这个模板用 L1 模型(V4.1 Flash)跑,输入 800 token 左右,输出 60 token,单次成本约 $0.0004。
你是一个任务难度分级器。你的唯一职责是给输入的开发任务打一个难度等级 D0-D4,
并给出置信度。你不会执行任务,只做分类。
## 难度定义(严格遵守,不要自行发挥)
D0 确定性任务:输出有明确 schema,纯格式转换/校验,无需语义理解。
例:把 JSON 转成另一个 JSON 结构;校验字段是否缺失。
D1 浅层理解:抽取、分类、摘要、打标。输出空间是枚举或可枚举,能被程序校验。
例:从工单抽物料号;判断评论情绪正负;把 2000 字压成 500 字。
D2 单域推理:单个文件/单个数据源内,推理步数不超过 5 步。
例:给一个函数写单元测试;根据一个表的 schema 写查询语句。
D3 跨域推理:涉及 2 个以上文件/数据源,或上下文长度在 32K-100K,或推理 5-15 步。
例:改一个接口同时更新 12 处调用;跨三个微服务排查一个 bug。
D4 决策级任务:输出不可逆、影响面大、需要全局一致性,或上下文大于 100K。
例:设计一套权限模型;审阅 50 页合同并给风险评级;在两个架构方案间做选择。
## 分级规则(按顺序应用,先命中先定)
1. 有 JSON Schema 且不需要语义理解 → D0
2. 输出空间可枚举 / 有 ground truth 可比对 → D1
3. 涉及文件数小于等于 1 且推理步数不超过 5 → D2
4. 涉及文件数大于 1,或上下文 32K-100K → D3
5. 输出不可逆,或影响面涉及生产环境/资金/合规,或上下文大于 100K → D4
6. 规则冲突时取**更高**的等级(宁可多花,不可错判)
## 输入信息
任务描述:{{task_description}}
涉及文件/数据源数量:{{file_count}}
预估上下文长度(token):{{context_tokens}}
输出是否不可逆(是/否):{{irreversible}}
是否涉及生产环境/资金/合规(是/否):{{high_impact}}
是否有明确输出 schema(有/无):{{has_schema}}
历史同类型任务成功率(若无填 unknown):{{historical_success_rate}}
## 输出格式(严格 JSON,不要额外文字)
{
"difficulty": "D2",
"confidence": 0.82,
"reason": "单文件内、推理 3 步、输出有 schema 可校验,但未涉及不可逆动作",
"tier": "L1",
"suggested_model": "deepseek-v4.1-flash",
"risk_flags": ["输出需 JSON Schema 校验"],
"should_escalate_on_failure": true
}
## 输出要求
- difficulty 只能是 D0/D1/D2/D3/D4 之一
- confidence 是 0-1 的小数,低于 0.6 时 tier 自动升一档
- reason 不超过 40 字,必须写出你依据的是哪条规则
- 只输出 JSON,不要 Markdown 代码块,不要解释
实测这个模板在 300 条标注集上的表现:
| 指标 | 数值 |
|---|---|
| 分级准确率(与人工标注一致) | 91.3% |
| 判低率(实际更难但判低了) | 5.4% |
| 判高率(实际更简单但判高了) | 3.3% |
| 平均判定延迟 | 412ms |
| 单次判定成本 | $0.00041 |
判低率 5.4% 是这里唯一重要的数字——判高只是多花钱,判低会掉质量。所以我们在代码里对 confidence < 0.6 强制升一档,把判低率压到 2.1%。
4.3 第 2 步:路由判定器完整实现(TypeScript)
// router.ts —— 多模型路由判定器
import { Redis } from 'ioredis';
import { observe } from './metrics_hook';
type Difficulty = 'D0' | 'D1' | 'D2' | 'D3' | 'D4';
type Tier = 'L0' | 'L1' | 'L2' | 'L3';
interface RouteRequest {
stage: string; // 阶段名,用于分工表统计
task: string;
fileCount: number;
contextTokens: number;
irreversible: boolean;
highImpact: boolean;
hasSchema: boolean;
schema?: object; // 有 schema 时走 L0/L1 校验
language?: string;
traceId: string;
budgetLeftUsd: number;
}
interface RouteResult {
tier: Tier;
model: string;
difficulty: Difficulty;
confidence: number;
source: 'rule' | 'cache' | 'classifier' | 'fallback';
fallbackChain: string[];
temperature: number;
maxTokens: number;
timeoutMs: number;
}
const redis = new Redis(process.env.REDIS_URL!);
// 档位 -> 模型映射(换供应商只改这里)
const TIER_MODELS: Record<Tier, { primary: string; alt: string }> = {
L0: { primary: 'rule-engine', alt: 'deepseek-v4.1-flash' },
L1: { primary: 'deepseek-v4.1-flash', alt: 'claude-haiku-5' },
L2: { primary: 'deepseek-v4-pro', alt: 'gpt-6-luna' },
L3: { primary: 'claude-opus-5.5', alt: 'deepseek-v4-pro' },
};
const TIER_TIMEOUT: Record<Tier, number> = { L0: 100, L1: 3000, L2: 8000, L3: 60000 };
/** 规则前置判定:命中就不花钱问模型 */
function ruleClassify(req: RouteRequest): RouteResult | null {
// R1: 有 schema 且是纯格式转换阶段
if (req.hasSchema && req.stage.endsWith('_format')) {
return base(req, 'L0', 'D0', 1.0, 'rule');
}
// R2: 语义缓存(向量相似度预筛 + 精确 hash 兜底)
// 命中在调用方处理,这里只标记
// R6: 决策级任务直接 L3
if (req.irreversible && req.highImpact) {
return base(req, 'L3', 'D4', 1.0, 'rule');
}
// R5: 长上下文
if (req.contextTokens > 100_000) {
return base(req, 'L3', 'D4', 0.95, 'rule');
}
// R3: 浅层理解类阶段(抽取/分类/摘要/重排)
const SHALLOW = ['extract', 'classify', 'summarize', 'rerank', 'intent'];
if (SHALLOW.some(s => req.stage.includes(s)) && req.contextTokens < 32_000) {
return base(req, 'L1', 'D1', 0.9, 'rule');
}
// R5: 跨域
if (req.fileCount > 1 || req.contextTokens > 32_000) {
return base(req, 'L2', 'D3', 0.85, 'rule');
}
return null; // 交给模型判定
}
function base(
req: RouteRequest, tier: Tier, difficulty: Difficulty,
confidence: number, source: RouteResult['source'],
): RouteResult {
const models = TIER_MODELS[tier];
const primary = req.language && !['zh', 'en'].includes(req.language)
? TIER_MODELS.L1.alt // R7: 多语言场景用 Haiku 5
: models.primary;
return {
tier,
model: primary,
difficulty,
confidence,
source,
fallbackChain: [primary, models.alt, TIER_MODELS[nextTier(tier)].primary],
temperature: tier === 'L3' ? 0.2 : 0,
maxTokens: tier === 'L3' ? 4096 : tier === 'L2' ? 2048 : 1024,
timeoutMs: TIER_TIMEOUT[tier],
};
}
function nextTier(t: Tier): Tier {
return t === 'L0' ? 'L1' : t === 'L1' ? 'L2' : 'L3';
}
function prevTier(t: Tier): Tier {
return t === 'L3' ? 'L2' : t === 'L2' ? 'L1' : 'L0';
}
/** 主入口:规则 -> 缓存 -> 分类器 -> 保守升档 */
export async function route(req: RouteRequest): Promise<RouteResult> {
// 1) 规则前置(0 成本,命中率 72%)
let result = ruleClassify(req);
// 2) 语义缓存
if (!result) {
const hit = await redis.get(`semcache:${hashTask(req.task)}`);
if (hit) {
result = { ...base(req, 'L0', 'D1', 1.0, 'cache'), model: 'cache' };
}
}
// 3) 便宜模型判定(L1,约 $0.00041)
if (!result) {
const cls = await observe({ stage: 'router_classify' }, () =>
classifyWithFlash(req),
);
let tier = cls.tier as Tier;
// R10: 置信度低 -> 保守升一档
if (cls.confidence < 0.6) tier = nextTier(tier);
result = { ...base(req, tier, cls.difficulty, cls.confidence, 'classifier') };
}
// 4) R8: 上游失败过 -> 升一档
const priorFail = await redis.get(`stagefail:${req.traceId}:${req.stage}`);
if (priorFail) {
result = { ...base(req, nextTier(result.tier), result.difficulty, result.confidence, result.source) };
}
// 5) R9: 预算闸门 -> 强制降到 L1(L3 任务除外,L3 转人工)
if (req.budgetLeftUsd < 0.12 && result.tier !== 'L3') {
result = { ...base(req, 'L1', result.difficulty, result.confidence, result.source) };
}
return result;
}
/** 带三级 fallback 的执行器 */
export async function executeWithFallback(req: RouteRequest, result: RouteResult) {
let lastErr: any;
for (let i = 0; i < result.fallbackChain.length; i++) {
const model = result.fallbackChain[i];
try {
const resp = await callWithTimeout(model, req, result.timeoutMs);
if (req.schema && !validate(req.schema, resp)) {
throw new Error('schema_invalid');
}
await redis.setex(
`stagefail:${req.traceId}:${req.stage}`, 3600, '0',
);
return { ...resp, model, fallbackLevel: i };
} catch (e: any) {
lastErr = e;
// 每级 fallback 都写进链路追踪,带 reason
await recordFallback(req.traceId, model, i, classifyError(e));
await redis.setex(`stagefail:${req.traceId}:${req.stage}`, 3600, String(i + 1));
}
}
// 第 4 跳:降级到人工队列
await enqueueHumanReview(req, lastErr);
return { degraded: true };
}
实现要点(都是踩过坑才加的):
ruleClassify必须在最前面,命中率 72%,这是把判定成本压到 1.3% 的关键fallbackChain预生成,不要在执行时动态算,否则超时路径上再算一次会更慢schema_invalid也算失败,便宜模型最常见的问题就是输出不合法,只判 HTTP 状态码会漏掉 60% 的失败budgetLeftUsd从 Redis 实时读,不要用内存变量——多实例部署时内存里的预算是假的
4.4 第 3 步:语义缓存与去重
路由之外还有一块免费的钱:32% 的调用是重复的。
| 缓存层 | 命中率 | 实现方式 | 注意事项 |
|---|---|---|---|
| 精确 hash 缓存 | 18% | Redis,key = sha256(prompt + 参数) | 只对温度 0 的请求开启 |
| 向量相似缓存 | 9% | 阈值 0.93,embedding 用 512 维 | 阈值低于 0.90 会出现张冠李戴 |
| 阶段内去重 | 5% | 同一 trace 内相同子任务合并 | 只对无状态阶段开启 |
温度必须设为 0 才能缓存——这条我们第一版没注意,缓存命中率只有 3%。
4.5 第 4 步:灰度切流节奏
| 周次 | 切流比例 | 月化成本 | 单任务成本 | P95 延迟 | 完成率 | 回滚次数 | 主要动作 |
|---|---|---|---|---|---|---|---|
| W0 | 0%(基线) | $71,300 | $1.47 | 31.2s | 93.4% | — | 埋点 + 建评测集 |
| W1 | 10% | $63,800 | $1.31 | 26.4s | 94.1% | 1 | 只切 D0/D1 任务 |
| W2 | 30% | $48,200 | $0.99 | 19.8s | 95.0% | 1 | 切 D2,修合同漏检问题 |
| W3 | 60% | $30,100 | $0.62 | 15.3s | 95.8% | 0 | 切 D3,上预算闸门 |
| W4 | 100% | $15,900 | $0.33 | 12.6s | 96.2% | 0 | 全量 + 语义缓存 |
切流顺序必须是 D0 → D1 → D2 → D3 → D4,绝不能反过来。原因:低难度任务量大、可校验、出问题影响小,是天然的试验田;高难度任务量小但单价高,最后切可以把风险集中在可控的一周里。
4.6 第 5 步:观测面板(四个必看指标)
| 指标 | 告警阈值 | 为什么看它 |
|---|---|---|
| 按档位的调用占比 | L3 占比超过 10% | 路由失效的第一个信号 |
| 按阶段的任务完成率 | 任一阶段下降超过 2pp | 降档降过头了 |
| fallback 触发率 | L1 超过 6% | 便宜模型选型错了 |
| 单任务成本 P95 | 超过 $0.60 | 长尾任务在烧钱 |
五、效果数据对比表
5.1 总体对比(改造前 vs 改造后,均为全量生产环境)
| 指标 | 改造前(2026-09) | 改造后(2026-10) | 变化 |
|---|---|---|---|
| 月支出 | $71,300 | $15,900 | -77.7% |
| 任务数 | 48,600 | 52,100 | +7.2% |
| 单任务成本 | $1.47 | $0.33 | -77.6% |
| 平均调用次数/任务 | 40 | 27(含 6 次缓存命中) | -32.5% |
| 延迟 P50 | 8.4s | 3.1s | -63.1% |
| 延迟 P95 | 31.2s | 12.6s | -59.6% |
| 任务完成率 | 93.4% | 96.2% | +2.8pp |
| 回归集通过率 | 99.1% | 98.8% | -0.3pp |
| 人工复核率 | 18.4% | 11.2% | -7.2pp |
| 因超时失败占比 | 4.1% | 1.2% | -2.9pp |
| 平均输出 token | 5,790 | 5,410 | -6.6% |
延迟和完成率为什么反而变好了? 因为 L1 模型比 L3 快 5-8 倍,55% 的调用从「等 3 秒」变成「等 400 毫秒」,原来大量因超时失败的任务现在能跑完了。这是多模型路由最反直觉的收益:降本的同时提速。
5.2 模型调用结构变化
| 模型 | 改造前调用占比 | 改造后调用占比 | 改造前成本占比 | 改造后成本占比 |
|---|---|---|---|---|
| Claude Opus 5.5 | 38% | 9% | 82.4% | 46.8% |
| DeepSeek V4 Pro | 62% | 27% | 17.6% | 20.1% |
| DeepSeek V4.1 Flash | 0% | 61% | 0% | 30.4% |
| Claude Haiku 5 | 0% | 3% | 0% | 2.7% |
| 规则引擎 / 缓存 | 0% | —(等效 15%) | 0% | 0% |
注意最后一行:等效 15% 的调用现在完全不花钱(规则引擎 + 缓存),这部分在调用占比里体现为各档位的减少。
5.3 按阶段拆解的降本明细
| 阶段 | 改造前单次成本 | 改造后单次成本 | 降幅 | 改造后档位 |
|---|---|---|---|---|
| 意图与槽位解析(6 次) | $0.061 | $0.008 | -86.9% | L1 |
| 检索结果重排(9 次) | $0.094 | $0.011 | -88.3% | L1 |
| 中间推理草稿(8 次) | $0.412 | $0.128 | -68.9% | L1 → L2 |
| 工具参数生成(7 次) | $0.073 | $0.012 | -83.6% | L1 |
| 方案生成与权衡(4 次) | $0.716 | $0.716 | 0% | L3(保持) |
| 自检回归校验(5 次) | $0.105 | $0.038 | -63.8% | L1 初筛 + L2 复核 |
| 输出格式化(1 次) | $0.012 | $0.000 | -100% | L0 规则引擎 |
| 合计 | $1.473 | $0.913 → 去重后 $0.331 | -77.6% | — |
最关键的一行是「方案生成与权衡」:降幅 0%。 这 4 次调用我们一次都没动,它占总成本的 48.6%(改造后)。降本的正确姿势不是「什么都降一点」,而是「该贵的坚决不动,能省的全省掉」。
5.4 投入产出
| 项目 | 投入 | 年化收益 |
|---|---|---|
| 5 周 × 2 人研发 | 约 $46,000(人力) | — |
| 评测集构建(300 条标注) | 约 $7,500 | — |
| 月成本下降 | — | $55,400/月 × 12 = $664,800 |
| 净年化收益 | — | 约 $611,300 |
| 回本周期 | — | 约 29 天 |
六、踩坑清单(12 个真实踩过的坑)
| # | 坑 | 现象 | 根因 | 解法 |
|---|---|---|---|---|
| 1 | 没评测集就切流 | 切到 30% 时合同漏检率从 2.1% 涨到 7.8%,连夜回滚 | 无法量化质量损失,只能靠用户投诉发现 | 改造前先建 300 条标注集,每次切流前后都跑 |
| 2 | 温度不为 0 导致缓存失效 | 语义缓存命中率只有 3% | temperature 默认 0.7,同 prompt 输出不同 | 缓存只对 temperature=0 的请求开启 |
| 3 | 只判 HTTP 状态码不算失败 | 便宜模型 60% 的失败是「输出不合法」,接口返回 200 | 便宜模型格式化能力弱,容易漏字段 | schema 校验失败也算失败,计入 fallback |
| 4 | 判定器判低率 5.4% | 少量 D3 任务被判成 D2,质量掉 | 便宜模型的分级能力本身有上限 | confidence < 0.6 强制升档,判低率压到 2.1% |
| 5 | fallback 不写 reason | 出了质量问题无法归因,只能全量回滚 | 链路追踪只记了最终模型 | 每级 fallback 都打点,带错误分类 |
| 6 | 预算用内存变量 | 多实例部署下预算闸门形同虚设,某天烧了 $2,100 | 每个 pod 各算各的 | 预算计数放 Redis,用 Lua 原子扣减 |
| 7 | 全局替换成 Flash | 长上下文任务(合同审阅)质量崩,漏检率翻倍 | 以为「便宜 40%」适用于所有任务 | 长上下文/决策类任务坚决不动 |
| 8 | 相似缓存阈值设 0.85 | 把 A 客户的方案缓存给了 B 客户,差点出事故 | 阈值太低,语义不同但向量相近 | 阈值提到 0.93,并对高影响任务禁用相似缓存 |
| 9 | 忽略了「判定本身的成本」 | 第一版每次都调 Opus 判定难度,省的钱全花在判定上 | 判定模型选贵了 | 规则前置 72% + L1 判定,判定成本占比降到 1.3% |
| 10 | fallback 无限升档 | 一个 bug 导致任务从 L1 一路升到 L3 再重试 3 次,单任务烧了 $4.2 | 没设升档上限 | 升档最多 2 级,第 3 次失败转人工 |
| 11 | 只看总成本不看 P95 | 总成本降了 60%,但长尾任务成本涨了 3 倍 | 均值掩盖长尾 | 同时监控单任务成本 P95,超 $0.60 告警 |
| 12 | 切流顺序从难到易 | 第一周切 D4,出问题影响面巨大,被迫全量回滚 | 想先验证「最难的是不是能省」 | 严格按 D0→D1→D2→D3→D4 顺序切 |
七、经验总结
7.1 五条可以直接抄的结论
-
降本的第一刀不是换模型,是拆账单。 在改任何代码之前,先把一次任务按阶段拆开、算出每阶段的成本和「是否真的需要最贵档」。我们拆完发现 82% 的钱花在 10% 的调用上——这个事实本身就已经指明了方向。
-
「便宜 40%」的正确用法是替换 55% 的调用,不是替换 100% 的调用。 DeepSeek V4.1 Flash 比 V4 Pro 便宜 40%,但如果全局替换,长上下文和决策类任务的质量损失远超 40% 的成本收益。我们的实际结构是:61% 走 Flash,但 L3 那 4 次调用一次没动。
-
路由判定必须「规则前置」。 用模型给任务分级的想法很自然,但如果每次都问模型,你就把省下的钱又交回去了。72% 的请求可以用 schema 存在性、阶段名、token 数估算这些零成本特征判定,剩下的才交给 L1 模型。
-
fallback 是路由的一部分,不是补丁。 便宜模型的失败率就是比贵模型高,这是事实。接受它,然后给每一档配好替代、升档、转人工三级退路,并且每一级都写进链路追踪。实测 fallback 多花的钱占总支出 4.1%,换来的是敢把 61% 的流量切给便宜模型。
-
降本和提速是同一件事。 L1 比 L3 快 5-8 倍,所以 P95 延迟降了 59.6%,因超时失败的任务少了 2.9pp,任务完成率反而涨了 2.8pp。如果你做完路由只看到账单下降、没看到延迟下降,说明你的路由没生效。
7.2 什么情况下不要做这件事
- 月模型支出低于 $3,000:5 周 × 2 人的投入约 $53,500,回本周期会超过 12 个月,不值得
- 任务类型高度同质(比如全是长文档审阅):路由没有发挥空间,直接谈批量折扣更实际
- 没有可校验的输出:如果任务输出无法用 schema / 测试 / ground truth 校验,你就无法判断降档是否安全,先建评测能力
7.3 我们的下一步
| 方向 | 目标 | 预计收益 |
|---|---|---|
| 自托管 L1(vLLM 部署 7B 模型) | 替代 61% 里的浅层部分 | 再降 $3,200/月 |
| Prompt Caching | L2/L3 的系统提示词缓存 | 再降 $2,100/月 |
| 动态阈值(按实时质量反馈调路由) | 把判低率从 2.1% 压到 1% 以下 | 质量兜底 |
| 跨任务复用中间态 | 同一工单的多个 Agent 共享抽取结果 | 再降 $1,800/月 |
附:落地检查清单
- 已按阶段拆分一次完整任务的 40 次调用,并标注「是否需要最贵档」
- 已建立 300 条以上的人工标注评测集
- 工具分工表覆盖了全部任务类型,且每类都有判据
- 难度分级器 Prompt 已跑通,判低率低于 3%
- 规则前置命中率高于 60%(否则判定成本会吃掉收益)
- 每一档都配了 fallbackChain,且升档上限为 2 级
- 预算闸门用 Redis 原子计数,不是内存变量
- 语义缓存只对
temperature=0的请求开启,相似度阈值不低于 0.93 - 灰度切流严格按 D0→D1→D2→D3→D4 顺序
- 观测面板覆盖了「L3 调用占比 / 分阶段完成率 / fallback 触发率 / 单任务成本 P95」四个指标
- 定义了回滚红线(任一阶段完成率下降超过 2pp 立即回滚)
任一项不达标,先补齐再切流。