AI 功能依赖图:当多个服务共用同一个模型时的韧性工程
你的 embedding 模型在周二下午 3 点宕机了。不到 30 秒,你的支持聊天机器人停止回答问题,个性化推荐引擎开始返回空结果,文档搜索一无所获,入职助手也停止工作了。你的值班工程师打开事件频道,看到来自 15 个彼此看不出联系的功能同时发出的告警。没有堆栈跟踪指向根本原因。这看起来像是分布式系统故障 —— 但其实不是。这是一个单一的共享依赖项失效了,而你之前并不知道有 15 个功能共享它。
这就是 AI 功能依赖问题:你的产品功能底层的基础设施层是深度互连的,但你的架构图却将每个功能显示为孤立的方框。当耦合是不可见的,故障传播也是不可见的 —— 直到问题爆发。
隐藏的耦合问题
传统的分布式系统有着易于理解的耦合模式。微服务通过 HTTP 或消息队列相互调用,服务映射图使这些关系变得明 确。当服务 A 依赖于服务 B 时,这种依赖关系存在于代码和配置中。
AI 功能通过基础设施耦合,而不是通过代码。你的搜索功能、聊天助手和内容推荐可能没有共享任何代码 —— 它们有独立的仓库、独立的团队、独立的部署。但如果它们都调用同一个 embedding 模型端点来将查询向量化,它们就是耦合的。当 embedding 提供商的服务降级时,这三个功能会同时降级。
AI 功能的构建方式加剧了这种耦合。一个典型的 AI 功能栈如下所示:用户输入 → embedding 模型 → 向量数据库 → 重排序器 (reranker) → LLM → 响应。15 个产品功能共享这个技术栈,意味着 15 个故障爆炸半径都汇聚在相同的点上:一个 embedding 端点、一个向量数据库集群、一个 LLM 提供商。其中任何一个环节发生事故,都会同时传播到所有 15 个功能。
更糟糕的是经济成本。LLM 请求既昂贵又缓慢。当提供商服务降级时,重试逻辑会自动启动 —— 且每次重试都会重新发送数千个 token 的请求。在传统系统中,重试会增加挣扎中服务的负载。在 AI 系统中,重试不仅增加负载,还会烧掉你的每月 token 预算,且不产生任何结果。AI 系统的级联故障损失的是真金白银,而不仅仅是可用性。
梳理你拥有的资源
管理 AI 依赖关系的第一步是让它们可见。这比听起来要难,因为依赖关系存在于配置和运行状态中,而不是调用图中。
首先清查产品使用的每个 AI 组件:embedding 模型提供商和版本、向量数据库集群、LLM 提供商、特定模型的 API 密钥以及重排序服务。然后映 射哪些产品功能接触了哪些组件。
结果就是一个 AI 功能依赖图。中心是 AI 组件;边缘是产品功能。一张绘制良好的图表会立即揭示你的高风险组件 —— 即那些连接功能最多的节点。它们就是你的单点故障。
这张图可能会让你感到惊讶。由不同团队拥有的、从未被认为相关的两个功能可能会共享基础设施组件。一个帮助台摘要功能和一个搜索排名功能可能在代码上没有任何共同之处,但可能都依赖于同一个 embedding 模型端点。
保持图表的更新。团队最常犯的错误是将依赖图视为一次性的架构练习。随着功能的增加和供应商的更换,图表会过时,它提供的可见性也会随之消失。应将其视为活的文档,每当添加新功能或更换供应商时都要进行更新。
在正确的层级设置熔断器
熔断器是防止分布式系统级联故障的标准模式。其核心机制很简单:跟踪对下游依赖项调用的失败率;当失败超过阈值时,停止向该依赖项发送流量,并立即返回失败,直到依赖项恢复。
将熔断器应用于 AI 基础设施需要为每个熔断器识别正确的层级。一个典型的 AI 功能栈有三个不同的层级,它们需要独立的熔断器。
推理层 (The inference layer) 处理对 LLM 的调用。当错误率超过 5% 或 P95 延迟超过你的 SLA(对于大多数应用通常为 30 秒)时,开启熔断。与传统熔断器的一个关键区别是:要跟踪失败请求中的 token 消耗。当推理熔断器开启时,你希望快速失败,特别为了避免 token 浪费。按模型设置熔断 器优于按提供商设置;同一提供商的不同模型可能具有独立的故障特征。
检索层 (The retrieval layer) 处理对向量数据库的调用。这一层有不同的故障模式:超时、索引过时错误以及质量下降(返回低相关性结果)。检索层熔断器应独立于推理层。检索超时不应阻止推理调用 —— 当检索失败时,LLM 通常仍能凭借基础知识产生有用的响应。应将检索和推理视为可独立熔断的环节。
Embedding 层 (The embedding layer) 处理对 embedding 模型的调用。Embedding 故障特别危险,因为它们会同时影响摄入(添加新内容)和查询时的检索。当 embedding 熔断器开启时,来自近期查询的缓存 embedding 可以继续支撑大部分检索流量。该熔断器应快速开启,因为 embedding 失败是导致检索以及任何依赖检索的下游推理失效的根本原因。
对于 AI 系统,半开状态 (half-open state) 比传统服务更重要。当熔断器在超时后重新打开时,在完全关闭之前,只允许通过一两个测试请求。处于部分降级状态的 LLM 提供商可能会断断续续地返回成功响应;在部分故障的情况下过早关闭熔断器会导致其反复跳闸。
优雅降级:失败的是功能,而不是输出
区分设计良好的 AI 系统与脆弱系统的原则是:当依赖项失效时,要显式失败,而不是隐性损坏。
一个不返回结果的搜索功能是“功能失败”。而一个自信地返回错误结果的搜索功能则是“输出损坏”。用户可以 通过稍后重试或切换到手动流程来从功能失败中恢复。而损坏的输出会导致他们基于错误信息做出决策,且他们无法意识到需要对其进行纠正。
为每个 AI 功能设计一个降级链。该链条包含多个层级,每一层都在保留输出完整性的同时降低功能能力。
对于一个基于 RAG 的聊天助手,降级链可能如下所示:最顶层是完整的检索加带有完整上下文的推理。下一层是跳过检索,仅使用基础模型知识进行推理——这适用于向量存储不可用的情况。再下一层是返回一个静态的回退响应,告知用户功能暂时受限。最底层则是彻底禁用该功能,并显示清晰的用户可见消息。
对于搜索功能:最顶层是完整的语义搜索,当嵌入服务宕机时回退到关键词搜索,进一步回退到静态的热门结果,最后是禁用该功能。
核心规则:当无法保证质量时,绝不允许回退层返回看似确信的输出。如果因为检索失败而导致推理在不完整的语料下运行,要么向用户揭示这种不确定性,要么禁用该功能。一旦你返回了一个看起来完整但实际上缺失信息的答案,你就已经将一次“失败”转化为了“损坏”。
将降级路径视为头等大事意味着要对其进行监控(Instrumenting)。统计每个回退层级激活的频率。当回退使用率升高时发出告警。回退使用率高是依赖层出现问题的早期信号——通常发生在主电路熔断之前。
通过隔离避免隐藏耦合
应对 AI 功能依赖故障最持久的解决方案是首先减少共享耦合。虽然某些耦合是不可避免的——你无法消除共享的供应商——但你可以缩小其影响范围。
按功能的供应商配额可以防止某个功能的流量激增耗尽共享供应商的速率限制。在不同功能之间分配明确的配额:40% 给搜索,40% 给聊天,剩下的 20% 给其他功能。当某个功能超出配额时,应用背压(Queueing 或丢弃该功能的请求),而不是允许它消耗其他功能的容量。
供应商抽象层允许在主要供应商失效时路由到备用供应商。像 LiteLLM 这样的工具在多个 LLM 供应商之间暴露了统一的 API。备用供应商的回退方案需要提前设置——包括模型选择、能力映射、成本建模——但在任何供应商发生事故时,它都能立即发挥作用。回退方案已配置完毕,当电路熔断时,网关会自动切换。
关键功能的专用端点以成本换取隔离。大多数托管推理供应商都提供专用端点,提供预留容量和 SLA 保证,与共享容量池隔离开来。对于降级代价极其昂贵的功能——如面向客户的聊天、支付流程、任何对时间敏感的业务——专用端点提供了共享端点无法提供的硬件级隔离。
嵌入模型版本固定 (Pinning) 可以防止一种微妙的失败模式:如果你允许嵌入模型在不受控的情况下更新,新的嵌入将与现有索引的嵌入不兼容,从而导致隐性的检索质量下降。明确固定模型版本。升级时,重新对你的语料库进行嵌入,使用测试查询验证检索质量,然后进行原子化切换。将嵌入视为可以从源数据生成的派生数据——将其作为原始数据存储会使模型升级永远充满风险。
