跳到主要内容

路由与故障转移

本指南将介绍如何创建多目标模型,用一个面向调用方的模型别名承载多个目标模型。

多目标模型不会直接调用服务提供方。它使用 routing 配置块指向目标模型别名,并由 AISIX 为每次请求选择其中一个目标。

准备工作

请先准备以下内容:

  • 一个 Admin 和代理监听器都可用的自托管网关。
  • 网关 config.yaml 中的 Admin Key。
  • 两个可以承载流量的目标模型。如果还没有创建,请先配置服务提供方凭证模型别名
  • 一个用于验证的调用方 API Key。可以按照调用方 API Key 创建,也可以在本指南中创建。

选择路由策略

请根据面向调用方的别名需要承担的行为选择策略:

  • 使用 failover 保留一个主目标,并配置备用目标。
  • 使用 round_robin 在多个相似目标之间轮转请求。
  • 使用 weighted 将更多流量发送到部分目标,并配合 sticky 做稳定的 A/B 测试或金丝雀发布分配。
  • 使用 least_costleast_latencyleast_busy 根据运行时信号选择目标,例如价格、观测到的延迟或进行中的请求数,并优先尝试最合适的目标。

你也可以使用路由标签按请求缩小候选目标范围,或者使用通配符名称让一个别名匹配多个上游模型。

AISIX 会在可重试的上游失败上执行重试和故障转移,例如 5xx 响应、请求超时和传输错误。大多数上游 4xx 响应会被视为调用方侧问题,不会触发故障转移;但启用 retry_on_429 时,429 例外。

创建多目标模型

设置本指南中使用的 Admin Key 和模型别名:

export AISIX_ADMIN_KEY="admin-local-only-change-me"
export PRIMARY_MODEL="gpt-4o-primary"
export SECONDARY_MODEL="gpt-4o-secondary"
export ROUTING_MODEL="chat-prod"

创建一个故障转移模型:它会先从主目标开始,在失败时回退到备用目标。

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/models" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "'"${ROUTING_MODEL}"'",
"routing": {
"strategy": "failover",
"targets": [
{"model": "'"${PRIMARY_MODEL}"'"},
{"model": "'"${SECONDARY_MODEL}"'"}
],
"retries": 1,
"max_fallbacks": 1,
"retry_on_429": true
}
}'

你应该会看到类似下面的响应:

{
"id": "134c4b01-09e6-41b7-97c7-f4e9a608f4c2",
"value": {
"display_name": "chat-prod",
"routing": {
"strategy": "failover",
"targets": [
{
"model": "gpt-4o-primary"
},
{
"model": "gpt-4o-secondary"
}
],
"retries": 1,
"max_fallbacks": 1,
"retry_on_429": true
}
},
"revision": 1
}

如果后续需要更新、查看或删除该多目标模型,请复制高亮的 id

使用该配置时,AISIX 会从 gpt-4o-primary 开始。如果该目标发生可重试失败,AISIX 可以先重试一次,然后再故障转移到 gpt-4o-secondary

允许调用方访问

调用方 API Key 必须被允许使用多目标别名。如果已经有调用方 API Key 资源,请更新它的允许列表,使其包含 chat-prod

为了验证自托管配置,创建一个只能调用 chat-prod 的调用方 API Key:

export AISIX_API_KEY="sk-routing-demo"

AISIX_API_KEY_HASH=$(printf '%s' "${AISIX_API_KEY}" | shasum -a 256 | awk '{print $1}')

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/apikeys" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"key_hash": "'"${AISIX_API_KEY_HASH}"'",
"allowed_models": ["'"${ROUTING_MODEL}"'"]
}'

你应该会看到类似下面的响应:

{
"id": "9b7f01fd-5f26-4657-82ef-605cc2f0ce21",
"value": {
"key_hash": "dd08e1fdcc327a5f15dedfba33172b5412b887d9d12ffc1076f77683b1ddbe3e",
"allowed_models": [
"chat-prod"
]
},
"revision": 1
}

如果后续需要更新、轮换或删除该调用方 API Key,请复制高亮的 id

验证路由

向多目标别名发送请求:

curl -sSi -X POST "http://127.0.0.1:3000/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"model": "'"${ROUTING_MODEL}"'",
"messages": [
{"role": "user", "content": "Hello from AISIX routing."}
]
}'

成功请求会返回类似下面的响应:

HTTP/1.1 200 OK
content-type: application/json
x-aisix-call-id: ***
x-aisix-served-by: gpt-4o-primary
server: AISIX/0.1.0

{
"id": "chatcmpl-***",
"object": "chat.completion",
"created": **********,
"model": "chat-prod",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I assist you today with AISIX routing?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 13,
"completion_tokens": 13,
"total_tokens": 26
}
}

响应体会保留面向调用方的模型别名。x-aisix-served-by 响应头会显示具体由哪个目标模型处理了请求。

当需要确认某次路由请求由哪个目标处理时,可以使用该响应头。缓存命中、单目标模型响应、错误响应或部分端点族可能不会包含该响应头。

流式请求可以解析多目标别名,但流已经开始后不会再执行故障转移。

调整重试与运行时行为

本示例会对主目标执行一次重试,并向备用目标执行一次故障转移。

只有当流量计划需要不同行为时,才调整路由字段:

  • 如果只想选择目标而不跨目标故障转移,请设置 max_fallbacks: 0
  • 当上游限流响应也应参与重试和故障转移时,启用 retry_on_429
  • 当尝试某个目标比直接返回 503 all_candidates_unavailable 更合适时,配置 routing.on_all_filtered: "original_order"

对于流式请求,如果在第一个上游数据块返回前超时,可以故障转移到下一个目标;一旦流式字节已经开始返回,超时会结束当前流,而不会在响应中途故障转移。

使用 GET /admin/v1/models/status 查看目标模型的运行时状态。

完整的模型请求字段和响应结构,请参见 Admin API 参考

按成本、延迟或负载路由

least_costleast_latencyleast_busy 策略会根据运行时信号对每个目标排序。AISIX 会优先尝试最合适的目标,再依次向后尝试。顺序来自信号,而不是目标声明顺序。

  • least_cost 会根据目标模型每 1K Token 输入和输出总价排序。没有已知价格的目标排在最后。
  • least_latency 会根据近期上游延迟的移动平均值排序;对于流式响应,使用首 Token 时间。还没有样本的目标会排在最前,以便先被探测后再参与排序。
  • least_busy 会根据当前已分派到每个目标的进行中请求数排序。

routing 配置块中设置策略。例如,总是优先选择最便宜的健康目标:

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/models" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "chat-cheapest",
"routing": {
"strategy": "least_cost",
"targets": [
{"model": "'"${PRIMARY_MODEL}"'"},
{"model": "'"${SECONDARY_MODEL}"'"}
]
}
}'

价格来源取决于网关的管理方式:

  • 控制台托管网关 会自动解析每个目标的价格:控制台价格页面中配置的价格覆盖优先,其次使用内置模型价格目录。价格变更会重新排序现有路由组,无需修改模型。通配符别名没有目录价格,因此在 least_cost 下会排在最后,除非你为该别名名称添加价格覆盖。
  • 独立网关 会根据 Admin API 中每个目标模型直接设置的 cost 配置块排序。参见模型别名

为 A/B 测试和金丝雀发布拆分流量

使用 weighted 并设置 sticky: true,可以将固定比例的流量发送到金丝雀目标,同时让每个调用方稳定命中同一个变体。权重决定拆分比例,sticky 会按调用方确定性选择目标,因此同一个会话不会在多次请求间来回切换。

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/models" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "chat-canary",
"routing": {
"strategy": "weighted",
"sticky": true,
"targets": [
{"model": "gpt-4o-stable", "weight": 95},
{"model": "gpt-4o-canary", "weight": 5}
]
}
}'

默认情况下,sticky 分配以调用方 API Key 为键,因此每个 Key 会稳定落到同一个目标。如果希望改为按会话或最终用户作为分配键,请发送 x-aisix-routing-key 请求头:

curl -sS -X POST "http://127.0.0.1:3000/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "x-aisix-routing-key: session-1a2b" \
-H "Content-Type: application/json" \
-d '{"model": "chat-canary", "messages": [{"role": "user", "content": "Hello."}]}'

如果不设置 stickyweighted 会在每次请求时独立采样流量比例。

按请求标签路由

为目标添加标签,可以按每个请求携带的信号路由,例如团队、套餐或环境。给每个目标添加 tags 后,在请求中使用 x-aisix-routing-tags 请求头发送逗号分隔的标签。

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/models" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "chat-tiered",
"routing": {
"strategy": "failover",
"targets": [
{"model": "gpt-4o-premium", "tags": ["premium"]},
{"model": "gpt-4o-standard", "tags": ["default"]}
]
}
}'

携带 x-aisix-routing-tags: premium 的请求只会由标记为 premium 的目标处理。随后,配置的策略会对剩余目标排序。当没有目标匹配请求标签,或请求没有发送标签时,AISIX 会回退到标记为 default 的目标。如果没有匹配目标且没有目标标记为 default,请求会被拒绝。

curl -sS -X POST "http://127.0.0.1:3000/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "x-aisix-routing-tags: premium" \
-H "Content-Type: application/json" \
-d '{"model": "chat-tiered", "messages": [{"role": "user", "content": "Hello."}]}'

路由请求头只会从请求头中读取,不会转发给上游服务提供方。

使用通配符匹配模型名称

当模型别名的 display_name 包含单个 * 时,它会匹配所有符合该模式的请求模型名,因此一个别名可以承载多个上游模型,而不需要为每个模型分别创建别名。该别名是直接模型,不是多目标路由块。将 model_name 设置为 * 可以把匹配部分转发给上游;也可以设置为固定值,让所有匹配请求都固定到同一个上游模型。

curl -sS -X POST "http://127.0.0.1:3001/admin/v1/models" \
-H "Authorization: Bearer ${AISIX_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "openai/*",
"provider": "openai",
"model_name": "*",
"provider_key_id": "YOUR_PROVIDER_KEY_ID"
}'

使用该别名时,openai/gpt-4o 请求会由 openai 服务提供方的上游模型 gpt-4o 处理,openai/o3-mini 请求会使用 o3-mini。精确别名始终优先于通配符;多个通配符同时匹配时,最具体的通配符优先。

调用方 API Key 可以在 allowed_models 中授予相同模式的通配符,例如 ["openai/*"]。通配符别名不会出现在 GET /v1/models 中,因为它们是模式,而不是具体模型名。

下一步

你已经配置了多目标模型,并允许调用方 API Key 使用它。继续阅读多目标模型故障转移,端到端测试故障转移。