可靠的 LLM API 路由:超时、重试和模型回退,无需语义回归
一种实用的架构,用于对 LLM API 故障进行分类、执行一个延迟预算、选择兼容的后备模型、保护副作用以及验证每个接受的响应。
回退请求不会仅仅因为另一个模型返回 HTTP 200 而成功。替换可能会超出原始延迟预算、省略所需的 JSON 字段、调用不同的工具或生成语义截然不同的答案。因此,可靠的 LLM API 路由需要的不仅仅是有序的模型列表:它还需要合同、失败分类器、有界尝试策略以及接受前的验证。
中心规则很简单:仅当失败似乎是暂时的时才重试,并且仅当下一个路由仍然可以满足原始请求契约时才回退。
在选择模型之前定义路由契约
首先描述成功的响应必须提供什么。该路由协定应该是机器可读的,并附加到每个工作负载或请求类。
<前><代码>{ "工作负载": "发票提取", “模式”:[“文本”,“图像”], “最大输入令牌”:50000, “require_tools”:假, “结构化输出”:{ “必需”:真实, "schema_id": "发票-v3", “严格”:正确 }, "allowed_model_classes": ["文档提取"], “最大成本美元”:0.08, “截止时间_毫秒”:8000 }合同应涵盖所需的模式、上下文容量、工具支持、结构化输出行为、可接受的模型类、最大成本和端到端截止日期。必要时添加特定于应用程序的约束,例如允许的区域、最小输出长度或所需的完成原因。
建议:为纯文本、架构约束的输出、工具使用、愿景和长上下文请求维护单独的、经过测试的路由组。作为可接受的文本后备的模型不会自动成为工具调用或图像输入可接受的后备。
在采取行动之前对故障进行分类
身份验证错误、格式错误的请求、速率限制和服务器故障需要不同的响应。将每个不成功的响应视为可重试会浪费容量并可能隐藏缺陷。
<表>事实:不成功的速率限制请求仍会计入提供商限制。因此,积极的立即重试可能会加深限制而不是解决问题。中断期间重试还会消耗额外的容量,并且多个应用程序层的重试策略可能会增加最终的负载。
建议:让一层拥有模型生成重试权。在典型的架构中,AI API 网关是正确的所有者,因为它可以查看路由运行状况、尝试历史记录、延迟和成本。尽可能在较低级别的客户端中禁用自动重试,或者将它们明确计入相同的尝试预算中。
花费一笔端到端延迟预算
每次尝试超时不足。在包含回退和验证之前,使用 5 秒超时进行三次尝试可以将预期的 5 秒操作转变为 15 秒响应。
记录请求进入网关时的绝对截止时间。每次尝试之前,计算剩余时间:
剩余 = 截止日期 - current_time
必需 = 连接_允许 + 生成_允许 + 验证_允许
如果剩余 < 需要:
stop_without_launching_another_attempt
对于 8 秒的截止时间,合理的初始分配可能会为网关工作和最终验证保留 300 毫秒,为主路由留出最多 4.5 秒的时间,并为一次回退保留大约 3.2 秒的时间。这些值只是示例,而不是基准。它们必须源自实际提供商、模型、区域和输出大小的测量延迟分布。
使用带抖动的上限指数退避进行瞬态重试:
延迟 = 随机(0, min(cap, base * 2^retry_index))
提供程序重试提示(例如重试后值)在剩余期限内时应优先考虑。少量尝试后停止。常见的策略是一次主要尝试加一次回退,并且仅针对无法生成计费输出的早期连接失败进行可选的同路由重试。
权衡:顺序回退提高了可用性,但增加了尾部延迟。并行或对冲请求可以减少减速期间的延迟,但它们会消耗更多容量,并且可能会产生多个成功生成的费用。对冲应仅限于延迟关键、无副作用的工作负载,并具有取消和成本控制功能。
按能力而不是排名选择后备
后备表应该编码兼容性而不是全局优先顺序。在考虑运行状况、延迟或价格之前,根据合约过滤候选路由。
候选人=路线
.filter(支持所需模式)
.filter(context_limit>=estimated_input_size)
.filter(支持所需工具)
.filter(supports_requested_schema_mode)
.filter(允许的模型类中的模型类)
.filter(估计成本 <= 剩余成本预算)
.filter(not_temporarily_suppressed)
所选 = 排名(候选者,健康状况,延迟,成本)
结构化输出支持值得明确测试。即使两条路由通告架构约束生成,它们也可能支持不同的 JSON 架构子集或以不同的方式解释边缘情况。支持工具的模型在工具选择、参数构造和并行调用行为方面也可能有所不同。
事实:切换模型系列可以在改变风格、推理质量、安全行为、标记化和工具选择的同时保持传输可用性。 HTTP 成功并不是语义等效的证据。
预测:随着模型目录的扩展,生产路由策略将越来越多地使用版本化的功能配置文件和特定于工作负载的验收测试,而不是静态模型列表。将此视为设计方向,而不是对提供者行为的保证。
在接受响应之前验证响应
通过相同的验收管道运行每个响应,包括主要响应。验证应该在结果被缓存、内部计费为成功或传递给工具执行器之前进行。
- 确认传输已完成并且可以解析响应信封。
- 检查完成原因,并在需要完整输出时拒绝截断。
- 根据原始架构验证结构化输出。
- 验证必填字段、枚举值和应用程序不变量。
- 仅允许注册的工具名称并根据每个工具架构验证参数。
- 应用特定于工作负载的语义检查,否则错误接受的代价会很高。
对于发票提取,语义检查可能需要非负总计、支持的货币代码以及明确定义的容差内的行项目总计。对于分类,需要来自允许的集合的标签。对于代码生成,解析或编译可能是合适的。这些检查并不能证明质量,但可以防止可预见的合同违规行为被视为成功。
不要默默地修复每个错误的响应。确定性标准化,例如删除无害的周围空白,是可以接受的。猜测缺失的金融字段或重写工具参数会改变模型的含义,并应触发拒绝或人工审核。
将生成重试与副作用分开
LLM 请求通常使用 HTTP POST,它本质上不是幂等的。更重要的是,模型响应可以发起外部操作,例如对支付方式收费、发送消息、创建票证或修改基础设施。重试生成和重放该操作是单独的决定。
在应用程序边界分配一个操作 ID,并为每个模型调用分配一个尝试 ID。根据确定性密钥保留工具执行状态,例如:
execution_key = operation_id + tool_name + canonical_arguments_hash
在执行工具之前,检查该密钥是否处于待处理、已完成或失败状态。返回已完成执行的存储结果,而不是再次运行它。对于参数可能合法更改的操作,需要应用程序级批准或新的操作 ID。
不明确的超时需要特殊处理。如果发送请求后连接失败,网关可能不知道是否发生了生成。提供者支持的幂等密钥在可用时可以提供帮助。否则,将结果记录为未知并应用特定于工作负载的重播策略,而不是假设什么也没发生。
抑制不健康的路由并公开每一次尝试
断路器或临时运行状况抑制可防止每个新请求重新发现相同的失败路由。在定义的错误率或连续故障阈值后打开电路,然后允许有限的探头处于半开状态。按路由和故障类别调整阈值,以便格式错误的客户端请求不会使健康的模型显得不可用。
记录一个请求级事件和每次尝试一个事件。有用的字段包括操作 ID、尝试 ID、选定的提供者和模型、故障类别、状态代码、延迟、令牌计数、估计成本、回退原因、验证结果、电路状态和最终结果。根据敏感度和保留要求对提示、输出和工具参数进行编辑或哈希处理。
有用的操作指标包括回退率、每个已完成请求的尝试次数、截止日期耗尽率、验证拒绝率、不明确的结果、每个接受响应的成本以及最终路由的延迟。 HTTP 成功率上升以及验证拒绝率上升是一个警告,表明传输可用性正在掩盖合约失败。
生产部署清单
- 为每个工作负载类别定义版本化路由协定。
- 将提供商错误分为永久、暂时、不兼容、无效响应和不明确的类别。
- 选择一名重试所有者并限制总尝试次数。
- 通过网关、提供商客户端、验证和工具执行传达绝对期限。
- 建立经过能力测试的后备组,而不是一个全球模型链。
- 验证架构、工具调用、完成原因和域不变量。
- 使用操作和执行键消除重复的副作用。
- 使用有界半开探测添加路由抑制。
- 记录尝试级延迟、令牌、成本、失败和接受结果。
- 注入超时、429 秒、选定的 5xx 错误、格式错误的 JSON、上下文溢出以及暂存成功缓慢。
从针对单个低风险工作负载的主要路线和一个兼容的后备路线开始。在扩展策略之前比较接受的响应质量、延迟和成本。目标不是尽可能高的回退率。它是一个有界系统,要么返回满足原始契约的响应,要么在导致重复工作或语义损坏之前明显失败。