AI 供应商与 AI 路由
第 3 周开场就是 Console 最精彩的部分。今天看两条主线:LLM Provider(配置对接哪家大模型)如何变成 ai-proxy 插件配置,以及 AI 路由如何"一条变多个"展开成 Ingress + 多个插件 + EnvoyFilter。
AI 网关两条主线
ai-proxy 等 Wasm 插件干活。两条主线:Provider = 对接哪家(前台的通讯录),Route = 什么请求走哪家、怎么降级(前台的转接规则)。POST /v1/chat/completions {model:"gpt-4"}(OpenAI 格式)→ AI 路由按规则把它转给 qwen provider、并把模型名映射成 qwen-max → 通义那边正常返回。应用一行没改,就从 OpenAI 切到了通义。- LLM Provider(
/v1/ai/providers):定义"对接哪家大模型"——OpenAI/通义/Claude/DeepSeek… 含 API 密钥、端点。 - AI Route(
/v1/ai/routes):定义"什么请求走哪个 provider、按什么模型路由/映射、失败怎么降级"。
ai-proxy 等 Wasm 插件实现(上一站 Higress 讲过 ai-proxy 支持 39 家 provider)。LlmProvider 模型
sdk/model/ai/LlmProvider.java(字段 :33-46):
name / type / protocol / proxyName
tokens // List,各家的 API 密钥
tokenFailoverConfig
rawConfigs // Map,直接透传给 ai-proxy 插件的原始 KV
类型 LlmProviderType.java 有 30+ 常量(qwen/openai/moonshot/azure/deepseek/zhipuai/ollama/claude/gemini/doubao/bedrock/vertex/vllm…);协议 LlmProviderProtocol.java 枚举 OPENAI_V1(默认)与 ORIGINAL。
fromValue/fromPluginValue(:47-63)双向转换——因为前端 API 用一套命名,底层插件用另一套,中间要翻译。Handler 注册表(策略模式)
sdk/service/ai/LlmProviderServiceImpl.java 静态块(:59-90)建一个 Map<type, LlmProviderHandler>:
// 大多数厂商一行注册(域名固定):
map.put("moonshot", new DefaultLlmProviderHandler("moonshot", "api.moonshot.cn", 443, ...));
map.put("deepseek", new DefaultLlmProviderHandler("deepseek", "api.deepseek.com", 443, ...));
// 有特殊逻辑的用专用 Handler:
map.put("qwen", new QwenLlmProviderHandler()); // 注入 qwen 专属默认值
map.put("azure", new AzureLlmProviderHandler());
// Openai/ZhipuAI/Ollama/Claude/Bedrock/Vertex/Vllm ...
QwenLlmProviderHandler 就为通义注入了 qwenEnableCompatible=true(OpenAI 兼容模式)等专属默认值。addOrUpdate 全流程
LlmProviderServiceImpl.addOrUpdate(:104-246)是"Provider → 网关配置"的核心:
- 按 type 取 handler,没有就抛
ValidationException(:105-108)。 handler.normalizeConfigs归一化 +fillDefaultValues默认协议 openai/v1(:115-117)。- 取
ai-proxy插件的 GLOBAL 实例(没有就建空的,setInternal(true),:126-133)。 - 从全局配置取出
providers数组,handler.saveConfig把该 provider 序列化成 Map,按id替换或追加(:148-168)。 handler.buildServiceSource生成对应的服务来源并落库(:172-183)。handler.buildUpstreamService生成上游服务名,为该 service 再建一个 ai-proxy 实例绑定 provider(:185-222)。wasmPluginInstanceService.addOrUpdateAll落库;必要时syncRelatedAiRoutes联动更新引用它的 AI 路由(:234-236)。
getProviders(:387-414)则从这些资源里还原成 LlmProvider 列表。配置常量在 AiProxyConfig.java(providers/id/type/apiTokens/protocol/failover…)。AiRoute 模型与校验
sdk/model/ai/AiRoute.java(字段 :44-73):name/domains/pathPredicate/headerPredicates/upstreams/modelPredicates/authConfig/fallbackConfig/cors/...。validate(:75-112):
- upstreams 非空;
- upstream 权重之和必须 = 100(
:99-101); - headerPredicates 不能含模型路由头(
:90-93)。
AiUpstream.java:provider / weight / modelMapping(Map<原模型,目标模型>)。
modelMapping 很有意思——它能把请求里的模型名改写(比如把 gpt-4 映射到某 provider 的等效模型)。这让"应用写一个模型名,网关按路由映射到不同后端模型"成为可能。一对多编排(本课最精彩)
AiRouteServiceImpl.writeAiRouteResources(:242-250)——一条 AI 路由同时展开成多种资源:
buildRoute+saveRoute
模型名→请求头
每上游模型映射
统计(默认开)
writeModelRouterResources(:312-341):有 modelPredicates 时配全局 model-router 插件,把模型名映射到MODEL_ROUTING_HEADER请求头。writeModelMappingResources(:343-381):每个上游在 (ROUTE, SERVICE) 双维度配 model-mapper。writeAiStatisticsResources(:383-402):挂 ai-statistics 插件。
Fallback 与 EnvoyFilter
👨🏫:因为"请求失败后再改道重发"这种逻辑,Ingress 注解表达不了——注解只能描述静态转发规则。
👶:那怎么办?
👨🏫:只能下沉到更底层的 Envoy。Console 用 Velocity 模板渲染出一个
EnvoyFilter(直接改 Envoy 配置),配一条用 FALLBACK_FROM_HEADER 匹配的 fallback 路由。备用像"轮胎的备胎":RANDOM 等权重随机挑一个备用,SEQUENCE 按顺序试。writeAiRouteFallbackResources(:252-310):若启用降级,额外建一条"fallback 路由"(用 FALLBACK_FROM_HEADER 头匹配),并用 Velocity 模板渲染出一个 EnvoyFilter(模板 /templates/envoyfilter-route-fallback.yaml,:111-127 加载)实现失败重定向。
AiProxyController(/aiproxy)是控制台内置 AI 助手的反向代理(把界面上的"体验对话"转发到外部 AI),跟网关的 ai-proxy 插件不是一回事。今日小结 + 动手
🧠 今天你应该能回答
- AI 网关解决什么问题?两条主线(Provider/Route)?
- Handler 注册表为什么用策略模式?加新厂商怎么加?
- 加一个 Provider 落地成哪几种资源?
- 一条 AiRoute 展开成哪四件套?为什么要拆这么多插件?
- Fallback 为什么需要 EnvoyFilter?两个 "AiProxy" 怎么区分?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/backend/sdk/src/main/java/com/alibaba/higress/sdk
sed -n '59,90p' service/ai/LlmProviderServiceImpl.java
sed -n '104,246p' service/ai/LlmProviderServiceImpl.java
sed -n '242,402p' service/ai/AiRouteServiceImpl.java