背景
在 LLM API 网关中,当主模型服务不可用时,需要自动降级到备用模型。简单的重试逻辑只需切换 upstream 地址,但模型能力不同时往往需要改写请求体(例如替换 model 字段)。这个项目就是解决「降级 + 请求体重写」的问题。
架构
1 | 客户端请求 |
核心 Location 设计
1 | location / { |
三个 location 各自独立:/ 处理直接代理,@nexthop 处理带降级配置的请求,@retry 处理降级重试。使用命名 location @retry 而非普通路径,从设计上杜绝了外部直接访问的可能。
关键实现
请求体改写 (body_builder.lua)
1 | local cjson = require "cjson" |
每个降级目标可以有不同的 model 映射,在重试时动态替换。
流式响应支持
1 | -- 根据 Content-Length 判断是否使用流式传输 |
大于 64KB 或无 Content-Length 时自动切换流式传输,保证大响应不占满内存。
随机起点轮询
降级目标按随机起点轮询,避免所有请求同时打向同一个降级服务器:
1 | local start = math.random(#backups) |
技术要点
- ngx.ctx 丢失问题:
error_page内部跳转会清空ngx.ctx,改为用ngx.var传递数据 - proxy_intercept_errors:该指令不支持变量,通过拆分
location /和location @nexthop实现直连透传 vs 降级拦截的区别 - resty.http 库:本地 vendor 了完整 resty.http(1185 行),用于 @retry 阶段的 cosocket 请求
测试
1 | # 启动 |