你负责一款 AI 编码助手:200 万 DAU,每天约 4000 万次模型调用,峰值约 2000 RPS,其中 75% 流量压在一家闭源前沿模型供应商上。传统依赖(数据库、云厂商)的风险是「宕机」;这条依赖的风险多三种:断供、改价、下场竞争。这三种都已经真实发生过:
需求:任一供应商在 72 小时内完全断供,核心功能可用、质量回退可度量且有上限;单任务成本有硬预算,毛利不为负;p95 首 token 延迟(TTFT)< 1.5s 不因切换而失守。
graph LR
C["客户端 / Agent"] --> GW["LLM Gateway
统一请求 schema"]
GW --> R{"Router
能力档 · 预算 · 健康度"}
R --> A1["Adapter: 供应商 A
主力闭源"]
R --> A2["Adapter: 供应商 B
热备闭源"]
R --> A3["Adapter: 开放权重
托管 / 自建 vLLM"]
R -.-> L[("成本账本
per-task budget")]
R -.-> H[("健康 / 熔断状态")]
S["影子流量采样"] --> EV["评测 Harness
golden set + 回归"]
A1 -.-> S
EV -->|"能力矩阵"| R
CT["合同与容量注册表
通知期 · 配额 · 价格"] --> R
Gateway 把业务请求翻译成内部统一 schema(消息、工具定义、缓存提示),业务代码不直接 import 任何厂商 SDK。Router 按「这个任务需要哪档能力、预算还剩多少、谁现在健康」选模型。Adapter 吸收各家差异(工具调用格式、prompt caching 语义、流式事件)。评测 Harness 持续用影子流量给每个候选模型打分,产出「能力矩阵」喂回 Router——没有这张矩阵,切换就是盲切。合同注册表把商务条款变成路由能读的数据。
一句话 trade-off:抽象越厚,切换越快;但会抹平各家独有的能力(缓存、长上下文、特定工具格式),你为可移植性付性能税。
抽象层的目标不是「所有模型都一样」,而是把差异显式化。每个模型声明一份能力契约:上下文长度、是否支持并行工具调用、结构化输出是否严格、prompt cache 的粒度与 TTL、价格。业务请求也声明它需要什么。路由时做的是约束匹配,而不是字符串替换模型名。最危险的反模式是半抽象:接口统一了,但 prompt 是对着某一家调出来的——切换时接口不报错,质量却悄悄掉。所以 prompt 也要按模型族版本化,和 adapter 一起存放。
| 方案 | 换来什么 | 牺牲什么 |
|---|---|---|
| 直连厂商 SDK | 第一时间用上新特性,延迟最低 | 切换成本按代码行数计;断供时要改业务代码 |
| 最小公分母接口 | 切换只改配置 | prompt caching、扩展思考等差异化能力用不上,成本和质量都吃亏 |
| 能力契约 + 可选扩展(推荐) | 默认可移植,需要时显式用独有能力并登记依赖 | 要维护能力矩阵与每家的 prompt 变体,评测成本随模型数线性增长 |
# 能力契约:请求声明需求,路由做约束匹配(pseudo-code)
@dataclass
class Capability:
max_context: int
parallel_tools: bool
strict_json: bool
cache_ttl_s: int # 0 = 不支持 prompt caching
usd_per_mtok_in: float
usd_per_mtok_out: float
def eligible(req, models):
out = []
for m in models:
cap = m.capability
if req.tokens_est > cap.max_context: continue
if req.needs_parallel_tools and not cap.parallel_tools: continue
if req.needs_strict_json and not cap.strict_json: continue
if m.quality[req.task_type] < req.min_quality: continue # 来自评测矩阵
out.append(m)
return out
def render_prompt(req, model):
return PROMPTS[req.task_type][model.family].format(**req.vars) # prompt 按模型族版本化
models 优先级列表、allow_fallbacks、按价格/吞吐/延迟排序),是「抽象层即服务」的样本;代价是再引入一层第三方依赖。一句话 trade-off:多家同时承载真实流量(active-active)贵且质量不一致;只在出事时切过去(active-passive)便宜,但备用路径会悄悄腐烂。
Day 23 讲过熔断、重试与舱壁;这里的新问题是备用路径的保鲜。一条从不走流量的 fallback,它的 prompt、配额、账号、限流参数都会过期——断供那天你才发现备用账号的 rate limit 只有主力的 1/20。所以要让备用供应商常年承载 5–15% 的真实流量,并保有书面确认的容量。第二个问题是相关故障:断供往往不是 5xx,而是 403、配额骤降、或某模型版本下线,健康检查必须把这些也当成故障信号。第三,降级要按能力档进行:主力不可用时,补全类任务可降到开放权重小模型,复杂 agent 任务宁可排队也不能降到会胡改代码的模型。
| 方案 | 换来什么 | 牺牲什么 |
|---|---|---|
| 单供应商 + 冷备 | 最省事,单一 prompt 调优 | 备用路径未经验证;容量无保证;切换当天质量未知 |
| 主力 + 热备(10% 常驻) | 备用路径持续被真实流量验证,容量与配额已证实 | 两套 prompt 与评测;用户体验有轻微不一致 |
| 按任务类型分供应商 | 每类任务用性价比最优模型,天然分散 | 任一家断供都会打掉一类功能;路由策略最复杂 |
# 带熔断与能力档降级的路由(pseudo-code)
FATAL = {401, 403, 404} # 鉴权失败 / 模型下线:不是抖动,是断供信号
def route(req):
for m in rank(eligible(req, MODELS), by=req.objective): # 成本 or 延迟
if breaker[m].open(): continue
try:
resp = m.adapter.call(req, timeout=req.ttft_budget)
breaker[m].success()
ledger.charge(req.task_id, m.cost(resp.usage))
return resp
except ProviderError as e:
if e.status in FATAL:
breaker[m].trip(hold_s=3600); alert("supplier-level", m, e)
else:
breaker[m].failure()
if req.tier == "agent":
return enqueue(req) # 宁可排队,不降到能力不够的模型
return degrade(req) # 补全类:降到开放权重小模型
一句话 trade-off:开放权重给你主权和成本下限,但要自己承担后训练、推理运维和能力差距;按 token 标价做预算会系统性低估推理模型的真实成本。
MIT FutureTech 的 Gundlach 等人(2025)估算:达到固定基准水平的价格每年下降约 5–10 倍,但跑前沿模型本身的价格每年上升约 3–18 倍——模型更大、推理 token 更多。也就是说,单价表在变便宜,你实际的账单却可能变贵:同一任务,推理模型可能多吐一个数量级的 token。预算单位因此必须从「每百万 token 多少钱」换成「每个完成任务多少钱」,并按任务实时扣账、超预算即降档或中止。开放权重是这套账本的地板:它的价格不会被单方面上调,也不会在你被收购时被收回。
| 方案 | 换来什么 | 牺牲什么 |
|---|---|---|
| 只用闭源前沿 | 能力最强、零运维 | 价格与供货由对方决定;毛利被上游锁死 |
| 托管开放权重(第三方推理商) | 无 GPU 运维,换供应商只换 endpoint | 仍有托管方依赖,但可在多家托管方之间迁移 |
| 自建开放权重 + 自有后训练 | 成本与路线自主;可针对自家任务做 RL | GPU 容量规划、推理优化、训练团队;能力差距要靠后训练补 |
# 每任务预算账本:按完成任务计费,而不是按 token(pseudo-code)
class TaskBudget:
def __init__(self, task_id, usd_cap, tier_ladder):
self.spent, self.cap, self.ladder = 0.0, usd_cap, tier_ladder
def before_step(self, est_tokens, model):
projected = self.spent + model.cost_estimate(est_tokens)
if projected <= self.cap:
return model
cheaper = self.ladder.next_below(model) # 降一档:前沿 → 主力 → 开放权重
if cheaper and self.spent + cheaper.cost_estimate(est_tokens) <= self.cap:
return cheaper
raise BudgetExceeded(self.task_id, self.spent) # 交给产品层:提示用户或转人工
def after_step(self, usage, model):
self.spent += model.cost(usage) # 推理 token 也计入:这是被单价表藏起来的部分
metrics.histogram("usd_per_task", self.spent, tags={"model": model.name})
一句话 trade-off:长约与预留容量换来价格和供货确定性,但锁定更深;短约灵活,但断供时没有任何书面保障。
技术冗余只能缩短断供后的恢复时间,合同条款决定你有多少预警时间。要谈的关键条款:终止通知期(Windsurf 只有不到五天)、控制权变更条款(你被收购时对方能否单方终止——Cursor 案中 OpenAI 的理由正是对新母公司守约的不信任)、模型下线通知期、预留容量与限流配额、价格锁定期、数据使用限制。另一面是评测门:切到新模型前,用 golden set 加影子流量比较任务通过率,回退必须可度量且有上限,否则切换就是把质量事故推给用户。
sequenceDiagram
participant V as 供应商 A
participant Ops as 平台 on-call
participant R as Router
participant E as 评测 Harness
V->>Ops: 终止通知 / 403 激增
Ops->>R: 冻结 A 的新流量份额
Ops->>E: 对候选 B、开放权重跑 golden set
E-->>Ops: 能力矩阵(各任务通过率差值)
Ops->>R: 按任务类型分批切流 10%→50%→100%
R->>E: 持续影子比对 + 用户侧指标
E-->>Ops: 回退超阈值则该类任务排队 / 降级
# 切换门:每类任务的质量回退必须在上限内(pseudo-code)
def promote(candidate, incumbent, task_types, max_drop=0.03):
plan = {}
for t in task_types:
base = eval_pass_rate(incumbent, GOLDEN[t]) if incumbent.alive else BASELINE[t]
cand = eval_pass_rate(candidate, GOLDEN[t])
shadow = shadow_agreement(candidate, t, sample=0.02) # 线上影子比对
if base - cand <= max_drop and shadow >= 0.9:
plan[t] = "ramp" # 10% → 50% → 100%
else:
plan[t] = "hold" # 继续用热备 / 排队 / 显式告诉用户能力受限
return plan
设月模型成本 100 万美元,热备单价比主力高 20%,10% 流量即多付约 2 万美元/月,外加一套 prompt 与评测的人力(约 0.5 人)。对比:若断供导致核心功能停摆 3 天,按月收入 300 万美元计,直接损失约 30 万美元,还没算流失。只要年断供概率高于约 10%,热备就划算——而过去 18 个月公开的断供与规则突变已有好几起。关键是把它算成保险费,而不是冗余浪费。
会。同一会话中途换模型,风格、工具调用习惯、甚至对代码库的理解都会断层。设计原则:会话粘性(会话内不换模型,除非熔断),按任务类型而非随机分流(让差异落在可解释的边界上),对显著降级显式告知。这与 Day 6 一致性里的「会话保证」同构:read-your-writes 保证的是用户看到自己的写,这里保证的是用户在一个会话里面对的是同一个「推理者」。
反推供应方动机:被竞争对手收购或即将被收购(Windsurf、Cursor 两例)、产品与供应商自营应用正面竞争、使用模式让单位成本失控(订阅额度被 agent 高频消耗)。启示是把战略风险也纳入依赖评估:融资与并购谈判期间提前加厚热备,与供应商自营产品重合度越高越要降低单一依赖,并避免把核心路径建在未入合同的使用权上。
它把风险从「被收回」变成「被停更」。权重已在你手里,旧版本永远能跑,所以不会被断供;但上游可能不再发布新版、许可证可能对新版本收紧,地缘政治也可能让某国模型在部分客户处不可用(企业采购审查)。所以开放权重兜底也要多源:至少两个不同来源的底座都在评测矩阵里,自有后训练流程能在两者之间迁移。风险没有消失,只是变成了慢变量,你有时间应对。
Day 55 押的是 S3 条件写这样有文档、有 SLA、长期稳定的原语,且多家对象存储已兼容同一语义,替换路径清晰。LLM 依赖押的是能力而非接口:接口可以统一,但「这个模型能把这类任务做对」没有规范可兼容,只能靠评测证明。所以前者的冗余是协议层的,后者必须是评测层的——评测矩阵才是 LLM 时代的「兼容性规范」。