如何给 LLM 调用加自动重试与多模型 fallback:Portkey Gateway 配置解析
【免费下载链接】gatewayA blazing fast AI Gateway with integrated guardrails. Route to 1,600+ LLMs, 50+ AI Guardrails with 1 fast & friendly API.项目地址: https://gitcode.com/GitHub_Trending/ga/gateway
凌晨的监控里突然刷出一排 429 和 503,聊天接口的错误率开始爬坡。这篇文章带你把 Portkey Gateway 的网关配置从零跑通一遍:写一份重试配置、接到现有 SDK、再验证它真的生效。做完后,你能独立维护一份带重试、fallback 和缓存的生产配置,不需要改动业务代码。
一、它到底解决什么问题
读完这一节,你能说清楚网关层和“自己在代码里写重试”的分工边界。
原生 SDK 只负责把请求发给单一 provider。上游抖一下,错误就原样抛到你的业务层。Portkey Gateway 在中间做转发,行为由一份 JSON 配置驱动:哪些状态码重试、重试几次、失败后落到哪个 target、重复请求是否走缓存,全部声明在这份配置里。
| 能力 | 原生 SDK 的做法 | 通过 Portkey Gateway 的做法 |
|---|---|---|
| 失败重试 | 应用层自写循环加 sleep | 配置里声明retry.attempts,网关代为重试 |
| 多模型容灾 | 每个 provider 各写一套 try/catch | 一个targets数组声明 fallback 链 |
| 响应缓存 | 自建 Redis 层自己做 key | cache.mode声明 simple 或 semantic |
| 超时兜底 | 各 HTTP client 分散设置 | request_timeout统一毫秒级控制 |
| 观测 | 自己打日志再拼看板 | Logs 页统一看耗时、成本、重试次数 |
心智模型就一句话:行为从代码搬进配置,代码只发请求。
二、最小可用实现:配置从 0 到 1
读完这一节,你能创建一份重试配置,并用两种接法各发出一带重试的请求。
主线是三段:写配置、建配置、发请求。
第一步:写一份最小重试配置。网关不会猜你的意图,必须显式声明。下面的配置表示命中 429、500、503 时最多再试 2 次:
{ "retry": { "attempts": 2, "on_status_codes": [429, 500, 503] } }保存后,命中这三个状态码的请求会被网关自动重发,而不是把错误抛给你。
第二步:在控制台创建配置。进入 Configs 页,点 Create,起个名字,把上面的 JSON 粘进编辑器并保存,会得到一个形如pc-xxxxx的配置 ID,后面靠它引用:
这张图重点看两处:左侧的 JSON 编辑区和保存后列表里露出的配置 ID。
第三步:发起一次请求。有两种接法,按你现状选。
接法 A:项目 SDK 直连。适合新接入或愿意换 SDK 的场景,初始化时传配置 ID,此后所有请求自动带上重试:
import { Portkey } from 'portkey-ai'; const portkey = new Portkey({ apiKey: 'pk-xxxx', virtualKey: 'vk-xxxx', config: 'pc-xxxxx' // 配置 ID }); const response = await portkey.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'user', content: '你好' }] });效果:这行config让该客户端发出的每个请求都继承重试行为。
接法 B:兼容现有 OpenAI SDK。只改 baseURL 和默认头,业务代码一行不动:
import OpenAI from 'openai'; import { PORTKEY_GATEWAY_URL, createHeaders } from 'portkey-ai'; const openai = new OpenAI({ apiKey: 'sk-xxxx', baseURL: PORTKEY_GATEWAY_URL, // 指向网关而非 OpenAI defaultHeaders: createHeaders({ provider: 'openai', apiKey: 'pk-xxxx', config: 'pc-xxxxx' // 配置 ID }) }); // …其余调用代码不变效果:请求经网关转发,配置通过x-portkey-config请求头注入,SDK 侧无感知。
自托管部署时,整站行为也可以写在根目录的 conf.example.json 里,它演示了插件开关、provider 凭据和限流条目的结构。
三、配置项逐个讲:字段、默认值与适用场景
读完这一节,你能对着字段表决定每个开关开不开,并知道哪几个字段最容易写错。
核心字段一览(字段与默认值来自网关的配置校验逻辑,见 config.ts):
| 字段 | 默认值 | 作用 | 什么时候该开 |
|---|---|---|---|
retry.attempts | 0(不重试) | 最大重试次数,上限 5 | 上游偶发 429/5xx |
retry.on_status_codes | [429, 500, 502, 503, 504] | 触发重试的状态码列表 | 只想对特定错误码重试时 |
retry.use_retry_after_header | 关 | 429 时按供应商的 retry-after 头冷却再试 | 被限流且想尊重供应商建议 |
cache.mode | DISABLED | simple 精确匹配,semantic 语义匹配 | 重复提问多、想省 token |
cache.max_age | 可选,未设 | 缓存存活时长 | 需要控制数据新鲜度 |
strategy.mode | single | single / loadbalance / fallback / conditional | 多 provider 分流或容灾 |
targets[].weight | 可选 | loadbalance 下各 target 的流量占比 | 按额度分摊流量 |
request_timeout | 可选 | 单请求超时(毫秒),超时网关返回 408 | 防长尾请求拖垮线程池 |
三个容易踩坑的字段,各给一句最小示例。
attempts上限是 5(源码常量MAX_RETRIES),写更大不会按比例放大,别按“越大越稳”来加:
"retry": { "attempts": 5 } // 上限 5 次use_retry_after_header打开后,网关会读取retry-after-ms、x-ms-retry-after-ms、retry-after三个响应头决定等待时长:
"retry": { "attempts": 2, "use_retry_after_header": true }最后一条是硬约束:配置必须至少含provider+api_key、strategy+targets、cache、retry、request_timeout之一,否则整份配置被拒。写空对象“先占位”的做法会直接报错。
四、两个进阶场景:请求级覆盖与多级 fallback
读完这一节,你能处理两类真实需求:临时加大力度,以及单供应商挂掉不崩线。
场景一:请求级临时覆盖默认配置。业务动机:某批离线批处理请求允许更长等待,但不想动全局配置。把 config 作为第二个参数单独传即可:
const response = await portkey.chat.completions.create( { model: 'gpt-4o', messages }, { config: { retry: { attempts: 5 } } } // 仅本次请求生效 );效果:这次请求按 5 次重试执行,其他请求仍走客户端级的全局配置。
场景二:多级 fallback 叠加流量分摊。业务动机:OpenAI 被限流时不能让整个服务瘫痪。外层 loadbalance 把流量摊给 Anthropic 和 OpenAI 组,内层 fallback 让 OpenAI 失败后落到 Azure:
{ "strategy": { "mode": "loadbalance" }, "targets": [ { "virtual_key": "anthropic-key", "weight": 0.5 }, { "strategy": { "mode": "fallback" }, "weight": 0.5, "on_status_codes": [429, 503], "targets": [ { "virtual_key": "openai-key" }, { "virtual_key": "azure-key" } ] } ] }效果:流量五五开,OpenAI 命中 429/503 时自动切到 Azure,用户侧无感。
这张图看路由走向:同一层按 weight 分流,失败沿内层 targets 顺序下探。
五、怎么验证它真的生效了
读完这一节,你能用日志、响应头和 traceID 三件套确认配置不是摆设。
看日志。控制台 Logs 页列出每条请求的 provider、模型、token、成本和重试情况:
这张图重点看每行的状态标记:被缓存命中或触发重试的请求会有对应图标。
看响应头。网关会在响应里回写几个关键字段(定义见 src/globals.ts 的RESPONSE_HEADER_KEYS):
| 响应头 | 用途 |
|---|---|
x-portkey-retry-attempt-count | 实际重试次数,重试耗尽时为 -1 |
x-portkey-cache-status | 缓存状态,未命中为 MISS |
x-portkey-trace-id | 与日志互查的请求标识 |
发一条标记请求,便于事后在日志里精准过滤:
await portkey.chat.completions.create( { model: 'gpt-4o', messages }, { traceID: 'verify-retry-1' } // 日志页按它过滤 );效果:你在 Logs 页输入这个 traceID,就能看到这条请求的完整执行轨迹。
三个常见坑,按这个顺序排查:
- ⚠️ 完全没重试。先确认
attempts大于 0:网关把缺失的 attempts 归一成 0,等于没配(见 requestContext.ts 的normalizeRetryConfig);再确认错误码在on_status_codes列表里。 - 429 后直接放弃。开了
use_retry_after_header时,供应商给的冷却时间超过 60 秒总预算(MAX_RETRY_LIMIT_MS),网关会跳过本次重试,日志里表现为重试次数断档。 - 配置整体不生效。确认
x-portkey-config请求头真的到达了网关;自托管时直接看服务端访问日志最快。顺序固定:先看头、再看状态码、最后看 attempts。
重试的完整实现在 retryHandler.ts,排到第四类问题时可以进去读。
六、延伸导航:仓库内关键文件
按需取用,每个都只说一句话。
- 入门系列,从第一次调用讲到缓存与重试:cookbook/getting-started/
- 自托管示例配置,含插件开关与限流条目:conf.example.json
- 网关各接口的请求处理入口:src/handlers/
- 部署方式,Docker 与 K8s:docs/installation-deployments.md
重试、fallback 和缓存的本质,是把故障处理从代码挪进配置。下一步建议:挑你业务里最易抖的一条 LLM 调用接到这份配置上,用 traceID 标记一条请求,到 Logs 里确认重试次数和预期一致。
【免费下载链接】gatewayA blazing fast AI Gateway with integrated guardrails. Route to 1,600+ LLMs, 50+ AI Guardrails with 1 fast & friendly API.项目地址: https://gitcode.com/GitHub_Trending/ga/gateway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考