当 LLM 成为单点故障
Mitchellh(Terraform 作者)在一条引发广泛讨论的推文中指出:大量企业正在经历「AI psychosis」—— 不顾后果地将 LLM 嵌入核心业务流程,却缺乏与之配套的工程保障体系。这种模式的结果不是 AI 赋能,而是制造了大量脆弱的单点故障。当 LLM API 响应超时或返回异常输出时,整个业务流程随之崩溃,而工程团队甚至没有可回退的传统代码路径。
这不是技术能力问题,而是架构决策问题。传统软件工程中任何关键路径都必须有降级方案:数据库连接池有熔断、API 网关有超时回退、微服务调用有重试与幂等设计。然而当开发者首次接触 LLM 时,这套工程纪律似乎被遗忘了。本文从断路器设计、回退链架构、版本隔离与可观测性四个维度,拆解 LLM 生产部署中的反模式,并给出可直接落地的参数配置。
断路器模式:从 HTTP 熔断到 LLM 调用
断路器(Circuit Breaker)是分布式系统的基础模式,其核心逻辑是:当下游服务持续失败时,主动中断调用以避免资源耗尽和级联故障。然而在 LLM 集成场景中,这个模式几乎从未被正确实现。常见的错误做法是:在一个 try-catch 块中调用 LLM,若超时则无限重试,或者直接让异常向上抛出导致整个请求失败。
断路器状态机与参数配置
一个健壮的 LLM 断路器应实现三态状态机:Closed(正常)、Open(熔断)、Half-Open(探测恢复)。推荐参数如下:
class LLMCircuitBreaker:
def __init__(
self,
failure_threshold: int = 5, # 连续失败次数达到阈值后打开断路器
recovery_timeout: int = 30, # 30 秒后尝试半开状态探测恢复
half_open_requests: int = 3, # 半开状态允许 3 个探测请求
success_threshold: int = 2, # 半开状态需 2 次成功才完全恢复
latency_threshold_ms: int = 10000, # 单次调用超过 10s 视为失败
error_rate_threshold: float = 0.3 # 30 秒窗口内错误率超过 30% 打开断路器
):
self.state = "closed"
self.failure_count = 0
self.success_count = 0
self.last_failure_time = None
self.request_timestamps = []
failure_threshold=5 意味着连续 5 次失败(或 30 秒内错误率超过 30%)后,断路器将进入 Open 状态。在此状态下,所有 LLM 调用将立即返回预设的降级响应,而非实际发起网络请求。这避免了持续向一个不可靠的 API 端点发送请求造成的资源浪费和延迟累积。
分层超时设计
不同类型的 LLM 操作对延迟的容忍度截然不同。一个实用的分层超时策略:
| 操作类型 | 推荐超时 | 超时处理 |
|---|---|---|
| Embedding 生成 | 3 秒 | 回退到本地模型或缓存 |
| 单轮 Completion | 30 秒 | 回退到规则引擎 |
| 多轮对话生成 | 60 秒 | 返回「服务繁忙,请稍后重试」 |
| 流式响应(首 token) | 10 秒 | 终止连接,返回部分结果 |
超时设置不应依赖 LLM API 的服务端限制,而应在调用侧实现。Python 中可以使用 asyncio.wait_for 配合自定义异常:
async def call_llm_with_timeout(prompt: str, timeout: float = 30.0):
try:
result = await asyncio.wait_for(
llm_client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
),
timeout=timeout
)
return result
except asyncio.TimeoutError:
# 触发断路器计数
circuit_breaker.record_failure()
raise LLMTimeoutError(f"LLM call exceeded {timeout}s")
回退链设计:四层降级架构
缺乏回退链是「AI psychosis」最典型的症状。当 LLM 不可用时,系统要么返回错误,要么在用户无感知的情况下返回低质量结果。一个合格的架构应实现四层回退链,从高智能到高确定性逐层降级。
第一层:模型版本回退
在配置层面维护可用模型列表,按优先级排序。当 primary 模型不可用时,自动切换到备用模型:
llm_config:
primary_model: "gpt-4o-2024-05-13"
fallback_models:
- "gpt-4o-mini-2024-07-18"
- "claude-3-5-sonnet-20241022"
- "gemini-2.0-flash-exp"
version_policy: "pinned" # pinned | latest | hybrid
version_policy 为 pinned 时强制使用指定版本,避免「latest」标签导致的静默行为变更。许多团队经历过凌晨两点收到告警,原因是 OpenAI 悄悄升级了模型,导致输出格式与生产代码的解析逻辑不兼容。版本固定不等于永不升级,而是通过 feature flag 控制新模型的灰度放量。
第二层:API 供应商回退
多云部署不仅是成本优化手段,更是可靠性保障。在 LLM 调用层面,应实现供应商级别的故障隔离:
class LLMFallbackChain:
def __init__(self):
self.providers = [
ProviderConfig("openai", priority=1, timeout=30),
ProviderConfig("anthropic", priority=2, timeout=35),
ProviderConfig("google", priority=3, timeout=25),
ProviderConfig("local", priority=4, timeout=10) # 本地部署模型
]
async def execute(self, prompt: str) -> str:
for provider in self.providers:
try:
result = await self.call_provider(provider, prompt)
return result
except (TimeoutError, RateLimitError, ServiceUnavailableError):
continue
# 所有提供商失败,回退到规则引擎
return self.rule_based_fallback(prompt)
第三层:规则引擎回退
当所有 LLM 提供商均不可用时,规则引擎作为最后的高确定性路径。规则引擎不产生创意性输出,但能保证基本功能的可用性。例如,一个客服系统的规则引擎可以维护一套 if-then 映射表,覆盖最常见的 20% 问题:
class RuleBasedFallback:
def __init__(self):
self.rules = [
(r"如何重置密码", "请访问 https://example.com/reset 获取帮助"),
(r"退款政策", "我们的退款政策是收到商品后 7 天内可申请..."),
(r"订单状态", "请提供订单号,我来帮您查询")
]
def match(self, query: str) -> Optional[str]:
for pattern, response in self.rules:
if re.search(pattern, query):
return response
return None # 无匹配规则,返回通用响应
第四层:人工介入通道
对于关键业务场景(如金融交易、医疗建议),自动化回退应包含人工介入路径。这不是技术实现,而是流程设计:建立一个优先级队列,让人工客服在降级模式下接管对话,并在恢复后进行记录和跟进。
版本隔离:避免静默行为变更
「AI psychosis」的另一个常见症状是忽视模型版本的不稳定性。传统软件的语义化版本控制(SemVer)让开发者清楚知道升级的影响范围,但 LLM 没有这种保证。一个「minor version」更新可能让输出格式完全改变,或者让拒绝回答的阈值大幅降低。
镜像隔离策略
在容器化环境中,应将模型版本固化在镜像层,而非依赖运行时拉取:
# Dockerfile
FROM python:3.11-slim
ENV MODEL_VERSION=gpt-4o-2024-05-13
ENV ANTHROPIC_VERSION=claude-3-5-sonnet-20241022
RUN pip install openai==1.12.0 anthropic==0.21.0
这种做法的好处是:镜像哈希即代表确定的模型版本,任何基础设施重放都能得到完全一致的推理结果。在 Kubernetes 环境中,可以通过镜像 tag + digest 双重锁定:
image: openai/gpt-4o@sha256:a1b2c3d4e5f6... # digest 锁定实际层
Feature Flag 渐进放量
即便使用版本锁定,也不应一次性全量切换。实现 feature flag 控制下的渐进放量:
async def call_llm_with_flags(prompt: str, user_context: dict):
flag = await feature_flag_client.evaluate("llm_model_version", {
"user_id": user_context.get("id"),
"region": user_context.get("region"),
"timestamp": datetime.utcnow().isoformat()
})
model = MODEL_CONFIG.get(flag, MODEL_CONFIG["default"])
if flag == "new_model_canary":
# 金丝雀流量:5% 用户使用新模型
if random.random() > 0.05:
model = MODEL_CONFIG["stable"]
这允许团队在 5% 流量上验证新模型行为,再通过 A/B 测试扩大覆盖范围,并设置自动化监控检测输出质量的漂移。
可观测性:LLM 调用的监控盲区
传统 API 调用的可观测性已非常成熟:请求率、错误率、P99 延迟、分布直方图都是标配指标。但 LLM 调用的可观测性往往被忽视,原因是开发者将 LLM 视为「智能服务」而非普通 HTTP 端点。
核心指标体系
一个完整的 LLM 监控体系应包含以下指标:
延迟指标:不仅关注总耗时,还应拆分 token 生成速度。流式输出的场景下,「首 token 延迟」(Time to First Token,TTFT)往往比「总生成时间」更重要,因为用户感知的是第一个字符出现的时间。
async def monitored_llm_call(prompt: str) -> dict:
start = time.time()
ttft = None
async for event in llm_client.chat.stream(messages=[...]):
if ttft is None:
ttft = time.time() - start # 记录首 token 延迟
metrics.increment("llm.tokens.generated")
accumulated_output += event.content
total_latency = time.time() - start
metrics.histogram("llm.latency.total", total_latency)
metrics.histogram("llm.latency.ttft", ttft)
metrics.histogram("llm.tokens.per.second", tokens_generated / total_latency)
return {"output": accumulated_output, "metrics": {...}}
质量指标:LLM 输出的质量无法用 HTTP 状态码衡量,需要自定义评分。实用做法是实现「输出验证器」,对关键字段的结构化输出进行 schema 校验:
class OutputValidator:
def __init__(self):
self.schema = {
"type": "object",
"required": ["answer", "confidence", "sources"],
"properties": {
"answer": {"type": "string", "minLength": 10},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"sources": {"type": "array", "items": {"type": "string"}}
}
}
def validate(self, output: str) -> tuple[bool, Optional[str]]:
try:
parsed = json.loads(output)
jsonschema.validate(parsed, self.schema)
return True, None
except json.JSONDecodeError:
return False, "Invalid JSON format"
except jsonschema.ValidationError as e:
return False, f"Schema validation failed: {e.message}"
任何验证失败都应记录到 metrics 中,并触发告警。当 validation_error_rate 超过 5% 时,应自动触发模型版本回退。
行为漂移检测:使用统计方法检测输出分布的变化。例如,追踪输出的平均长度、特殊字符出现频率、句子平均复杂度等指标。当某项指标在滑动窗口内超出历史均值 2 个标准差时,触发调查告警。
def detect_drift(metrics_window: list[float], window_size: int = 100) -> bool:
if len(metrics_window) < window_size:
return False
recent = metrics_window[-window_size:]
historical = metrics_window[:-window_size]
recent_mean = mean(recent)
historical_std = stdev(historical)
historical_mean = mean(historical)
z_score = (recent_mean - historical_mean) / historical_std
return abs(z_score) > 2 # 超出 2 个标准差视为漂移
工程 checklist:上线前的必检项
在将 LLM 集成推入生产环境之前,工程团队应完成以下检查:
断路器配置:failure_threshold 设置为 5,recovery_timeout 不低于 30 秒,在 OpenAI/Anthropic API 调用前必须实现超时包装。
回退链完整性:至少实现 2 层降级(备用模型 + 规则引擎),关键业务场景需包含人工介入路径,回退切换应在 500ms 内完成。
版本控制:镜像 digest 锁定实际模型层,feature flag 控制灰度放量,新模型上线前必须在 shadow mode 下运行至少 48 小时。
可观测性:TTFT 和总延迟直方图必录,输出 schema 校验覆盖率超过 90%,validation error rate 超过 5% 时自动触发回退。
成本控制:实现每分钟请求数上限防止突发流量,单次请求最大 token 数限制,月末成本异常告警阈值设置为月均的 150%。
结语
「AI psychosis」的本质是工程纪律的缺失。LLM 不是魔法,而是一个高延迟、不确定、有配额限制的外部服务。将其纳入生产路径时,必须应用与传统微服务调用相同的可靠性工程原则:断路器防止级联故障,回退链保证基本可用性,版本隔离避免静默变更,可观测性提供故障根因的可见性。
将这些工程实践纳入 LLM 集成的标准流程,不是在削弱 AI 的能力,而是在将其从「可能随时崩溃的黑箱」转变为「可预测、可控制的生产级组件」。
参考来源:本文基于 Mitchellh 关于企业 AI 架构的技术观察,结合生产环境 LLM 集成的最佳工程实践整理。断路器参数参考了 Netflix Hystrix 的成熟模式并针对 LLM 特性进行了调整。
内容声明:本文无广告投放、无付费植入。
如有事实性问题,欢迎发送勘误至 i@hotdrydog.com。