Skip to content

Commit aa4472c

Browse files
authored
feat(ai-proxy-multi): let configured HTTP statuses trigger a fallback (#13852)
1 parent d069213 commit aa4472c

6 files changed

Lines changed: 247 additions & 8 deletions

File tree

apisix/plugins/ai-proxy-multi.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,8 @@ local function retry_on_error(ctx, conf, code, body)
934934
ctx.server_picker.after_balance(ctx, true)
935935
if (code == 429 and fallback_strategy_has(conf.fallback_strategy, "http_429")) or
936936
(code >= 500 and code < 600 and
937-
fallback_strategy_has(conf.fallback_strategy, "http_5xx")) then
937+
fallback_strategy_has(conf.fallback_strategy, "http_5xx")) or
938+
base.is_fallback_http_status(conf, code) then
938939
-- Slow-failure guard: only retry when the failed attempt finished within
939940
-- retry_on_failure_within_ms. A slow failure (e.g. a 5xx returned after
940941
-- minutes) is given back to the client directly, so fallback never doubles

apisix/plugins/ai-proxy/base.lua

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ local core = require("apisix.core")
2121
local require = require
2222
local pcall = pcall
2323
local pairs = pairs
24+
local ipairs = ipairs
2425
local type = type
2526
local table = table
2627
local math_floor = math.floor
@@ -48,6 +49,26 @@ local function count_request_tools(body)
4849
end
4950

5051

52+
-- Statuses the user opted into retrying through fallback_http_statuses.
53+
-- Both the error path below and ai-proxy-multi's retry decision consult this,
54+
-- and they have to agree: a status that is not diverted to the error path has
55+
-- its response streamed to the client and never reaches the retry callback.
56+
function _M.is_fallback_http_status(conf, status)
57+
local statuses = conf.fallback_http_statuses
58+
if not statuses then
59+
return false
60+
end
61+
62+
for _, s in ipairs(statuses) do
63+
if s == status then
64+
return true
65+
end
66+
end
67+
68+
return false
69+
end
70+
71+
5172
local function resolve_cap(cap_entry, key, conf, ctx)
5273
local val = cap_entry and cap_entry[key]
5374
if type(val) == "function" then
@@ -387,7 +408,8 @@ function _M.before_proxy(conf, ctx, on_error)
387408
-- Upstream responded — mark source before any early returns
388409
core.response.set_response_source(ctx, "upstream")
389410

390-
if res.status == 429 or (res.status >= 500 and res.status < 600) then
411+
if res.status == 429 or (res.status >= 500 and res.status < 600)
412+
or _M.is_fallback_http_status(conf, res.status) then
391413
-- Read the upstream error body before closing so the provider's
392414
-- error details survive: logged on fallback (see retry_on_error)
393415
-- and returned to the client when no retry happens.

apisix/plugins/ai-proxy/schema.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,27 @@ _M.ai_proxy_multi_schema = {
453453
}
454454
}
455455
},
456+
fallback_http_statuses = {
457+
type = "array",
458+
minItems = 1,
459+
uniqueItems = true,
460+
items = {
461+
type = "integer",
462+
minimum = 400,
463+
maximum = 599,
464+
},
465+
description = "Additional upstream HTTP status codes that make the "
466+
.. "request fall back to another instance, on top of the "
467+
.. "http_429 / http_5xx entries of fallback_strategy. Use it "
468+
.. "for statuses that mean the instance's credential is "
469+
.. "unusable rather than the request being wrong, e.g. [401, "
470+
.. "402] when an API key is expired or out of quota. It is "
471+
.. "opt-in per status because most 4xx responses are caused by "
472+
.. "the request itself and retrying them on another instance "
473+
.. "would only burn quota. max_retries and "
474+
.. "retry_on_failure_within_ms bound these retries the same "
475+
.. "way they bound the fallback_strategy ones.",
476+
},
456477
max_retries = {
457478
type = "integer",
458479
minimum = 0,

docs/en/latest/plugins/ai-proxy-multi.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ When an instance's `provider` is set to `bedrock`, the Plugin expects requests i
6868
| Name | Type | Required | Default | Valid values | Description |
6969
|------------------------------------|----------------|----------|-----------------------------------|--------------|-------------|
7070
| fallback_strategy | string or array | False | | string: "instance_health_and_rate_limiting", "http_429", "http_5xx"<br />array: ["rate_limiting", "http_429", "http_5xx"] | Fallback strategy. When set, the Plugin will check whether the specified instance's token has been exhausted when a request is forwarded. If so, forward the request to the next instance regardless of the instance priority. When not set, the Plugin will not forward the request to low priority instances when token of the high priority instance is exhausted. |
71-
| max_retries | integer | False | | greater or equal to 0 | Maximum number of fallback retries after the initial request fails. Bounds how many additional instances a single request tries, so it does not exhaust every configured instance. Only takes effect together with `fallback_strategy`. When unset, the Plugin retries until an instance succeeds or all are tried. |
72-
| retry_on_failure_within_ms | integer | False | | greater or equal to 1 | Only fall back to another instance when the upstream fails within this many milliseconds. Fast failures (such as connection errors or quick `429`/`5xx`) are retried, while a slow failure that takes longer than this is returned to the client directly to avoid doubling the wait time. Only takes effect together with `fallback_strategy`. When unset, the Plugin retries regardless of how long the failed attempt took. |
71+
| fallback_http_statuses | array[integer] | False | | between 400 and 599 | Additional upstream HTTP status codes that make the request fall back to another instance, on top of the `http_429` and `http_5xx` entries of `fallback_strategy`. Use it for statuses that mean the instance's credential is unusable rather than the request being wrong, such as `[401, 402]` when an API key is expired or out of quota. It is opt-in per status because most `4xx` responses are caused by the request itself and retrying them on another instance would only burn quota. `max_retries` and `retry_on_failure_within_ms` bound these retries the same way they bound the `fallback_strategy` ones. |
72+
| max_retries | integer | False | | greater or equal to 0 | Maximum number of fallback retries after the initial request fails. Bounds how many additional instances a single request tries, so it does not exhaust every configured instance. Only takes effect together with `fallback_strategy` or `fallback_http_statuses`. When unset, the Plugin retries until an instance succeeds or all are tried. |
73+
| retry_on_failure_within_ms | integer | False | | greater or equal to 1 | Only fall back to another instance when the upstream fails within this many milliseconds. Fast failures (such as connection errors or quick `429`/`5xx`) are retried, while a slow failure that takes longer than this is returned to the client directly to avoid doubling the wait time. Only takes effect together with `fallback_strategy` or `fallback_http_statuses`. When unset, the Plugin retries regardless of how long the failed attempt took. |
7374
| balancer | object | False | | | Load balancing configurations. |
7475
| balancer.algorithm | string | False | roundrobin | [roundrobin, chash, semantic] | Load balancing algorithm. When set to `roundrobin`, weighted round robin algorithm is used. When set to `chash`, consistent hashing algorithm is used. When set to `semantic`, the Plugin picks an instance by the semantic similarity between the request prompt and each instance's `examples`, and its options are configured under `semantic_opts`. Note that `semantic` does not participate in health checks or `fallback_strategy` / retry — an upstream failure on the chosen instance is returned to the client; it only falls back (to the `semantic_opts.fallback` instance, else the first instance) when no instance clears its threshold or embedding fails. |
7576
| balancer.hash_on | string | False | | [vars, headers, cookie, consumer, vars_combinations] | Used when `type` is `chash`. Support hashing on [NGINX variables](https://nginx.org/en/docs/varindex.html), headers, cookie, consumer, or a combination of [NGINX variables](https://nginx.org/en/docs/varindex.html). |
@@ -173,8 +174,8 @@ The setting covers `ai-proxy`, `ai-proxy-multi`, and `ai-request-rewrite`, which
173174

174175
When the selected LLM upstream returns a `429` or `5xx` status, `ai-proxy-multi` reads the upstream error body before deciding whether to fall back:
175176

176-
- If the request is retried on another instance (per `fallback_strategy`, `max_retries`, and `retry_on_failure_within_ms`), the failed instance's error body is recorded in the error log for diagnostics, since a later attempt's response is sent to the client instead.
177-
- If the request is not retried (no matching `fallback_strategy`, retries exhausted, or the failure took longer than `retry_on_failure_within_ms`), the upstream status code and error body are returned to the client, preserving the upstream `Content-Type`.
177+
- If the request is retried on another instance (per `fallback_strategy`, `fallback_http_statuses`, `max_retries`, and `retry_on_failure_within_ms`), the failed instance's error body is recorded in the error log for diagnostics, since a later attempt's response is sent to the client instead.
178+
- If the request is not retried (no matching `fallback_strategy` or `fallback_http_statuses`, retries exhausted, or the failure took longer than `retry_on_failure_within_ms`), the upstream status code and error body are returned to the client, preserving the upstream `Content-Type`.
178179

179180
## Examples
180181

docs/zh/latest/plugins/ai-proxy-multi.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ import TabItem from '@theme/TabItem';
6868
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
6969
|------------------------------------|----------------|----------|-----------------------------------|--------------|-------------|
7070
| fallback_strategy | string 或 array || | string: "instance_health_and_rate_limiting", "http_429", "http_5xx"<br />array: ["rate_limiting", "http_429", "http_5xx"] | 故障转移策略。设置后,插件将在转发请求时检查指定实例的令牌是否已耗尽。如果是,则无论实例优先级如何,都将请求转发到下一个实例。未设置时,当高优先级实例的令牌耗尽时,插件不会将请求转发到低优先级实例。 |
71-
| max_retries | integer || | 大于或等于 0 | 初始请求失败后允许的最大故障转移重试次数。用于限制单个请求最多尝试多少个额外实例,避免穷举所有已配置的实例。仅在配置 `fallback_strategy` 时生效。未设置时,插件会持续重试直到某个实例成功或所有实例都已尝试。 |
72-
| retry_on_failure_within_ms | integer || | 大于或等于 1 | 仅当上游在指定毫秒数内失败时才故障转移到其他实例。快速失败(如连接错误、快速返回的 `429`/`5xx`)会触发重试,而耗时超过该值的慢失败会直接将错误返回给客户端,避免客户端等待时间翻倍。仅在配置 `fallback_strategy` 时生效。未设置时,插件无论失败请求耗时多久都会重试。 |
71+
| fallback_http_statuses | array[integer] || | 400 到 599 之间 |`fallback_strategy` 中的 `http_429``http_5xx` 之外,额外触发故障转移到其他实例的上游 HTTP 状态码。适用于表示该实例凭证不可用(而非请求本身有问题)的状态码,例如 API Key 过期或额度耗尽时的 `[401, 402]`。该能力按状态码显式启用,因为大多数 `4xx` 响应由请求本身导致,在其他实例上重试只会白白消耗额度。`max_retries``retry_on_failure_within_ms` 对这些重试的约束方式与对 `fallback_strategy` 的重试一致。 |
72+
| max_retries | integer || | 大于或等于 0 | 初始请求失败后允许的最大故障转移重试次数。用于限制单个请求最多尝试多少个额外实例,避免穷举所有已配置的实例。仅在配置 `fallback_strategy``fallback_http_statuses` 时生效。未设置时,插件会持续重试直到某个实例成功或所有实例都已尝试。 |
73+
| retry_on_failure_within_ms | integer || | 大于或等于 1 | 仅当上游在指定毫秒数内失败时才故障转移到其他实例。快速失败(如连接错误、快速返回的 `429`/`5xx`)会触发重试,而耗时超过该值的慢失败会直接将错误返回给客户端,避免客户端等待时间翻倍。仅在配置 `fallback_strategy``fallback_http_statuses` 时生效。未设置时,插件无论失败请求耗时多久都会重试。 |
7374
| balancer | object || | | 负载均衡配置。 |
7475
| balancer.algorithm | string || roundrobin | [roundrobin, chash, semantic] | 负载均衡算法。设置为 `roundrobin` 时,使用加权轮询算法。设置为 `chash` 时,使用一致性哈希算法。设置为 `semantic` 时,插件根据请求提示词与各实例 `examples` 之间的语义相似度选择实例,其相关选项配置在 `semantic_opts` 下。注意:`semantic` 不参与健康检查,也不参与 `fallback_strategy` / 重试——被选中实例的上游失败会直接返回给客户端;只有当没有实例达到其阈值或嵌入请求失败时,才会回退(回退到 `semantic_opts.fallback` 实例,若未配置则回退到第一个实例)。 |
7576
| balancer.hash_on | string || | [vars, headers, cookie, consumer, vars_combinations] |`type``chash` 时使用。支持基于 [NGINX 变量](https://nginx.org/en/docs/varindex.html)、标头、cookie、消费者或 [NGINX 变量](https://nginx.org/en/docs/varindex.html)组合进行哈希。 |

0 commit comments

Comments
 (0)