AI 网关能力(ai-proxy)
第 4 周开始,进入 Higress 最"招牌"的部分。第 3 周你会写通用插件了(Day 12-14 的套路);今天看用同一套 Wasm 机制做出来的王牌 AI 插件 ai-proxy——把 39 家 LLM 厂商统一成 OpenAI 协议、模型路由、协议自动转换。这就是"AI 原生网关"名号的由来。
ai-proxy 是前台里的"万能翻译官 + 电源适配器":你的应用只会说一种"普通话"(OpenAI 协议),后端却站着几十家说不同方言的大模型(通义、文心、Claude、Gemini…)。ai-proxy 负责把你的普通话现场翻译成对方的方言、插上对方的插座——换一家大模型,你一个字代码都不用改,只改前台配置。ai-proxy 是什么
extensions/ai-proxy/(775 行 main + 60+ provider 文件)——把各家 LLM 统一成 OpenAI/Anthropic 协议的核心插件。
gpt-4……换厂商就得改一大片代码、重新测试、重新上线。要是想同时接好几家做灰度对比,那更是灾难。Provider 架构
// ai-proxy/provider/provider.go:265 —— 基接口极简
type Provider interface { GetProviderType() string }
// providerInitializer 接口(:211):ValidateConfig + CreateProvider
// providerInitializers 注册表(:224):39 家厂商的 map
// CreateProvider(:920):按 typ 从注册表取 initializer 创建
providerInitializers 是"厂商名 → 构造器"的 map,CreateProvider 按配置的 type 取对应厂商实现。基接口 Provider 极简(只有 GetProviderType),能力靠可选接口组合(L04)。加一家新厂商 = 实现接口 + 注册进 map。39 家厂商
providerInitializers(provider.go:224)注册 39 家:openai/azure/qwen(通义千问)/claude/gemini/deepseek/moonshot(月之暗面)/doubao(豆包)/hunyuan(混元)/spark(讯飞星火)/baidu(文心)/minimax/bedrock(AWS)/vertex(Google)/ollama/vllm…
provider/<厂商>.go 文件,实现"把统一 OpenAI 请求翻译成本厂商格式"的接口。新增一家厂商,不用改核心代码,只写一个新文件 + 注册。所以社区能快速贡献新厂商支持——从最初几家到现在 39 家。国产大模型(通义/文心/星火/混元/豆包/月之暗面/DeepSeek)全覆盖——这是 Higress 在国内 AI 场景的巨大优势。"开闭原则"(对扩展开放)让 ai-proxy 能跟上 LLM 厂商爆发的速度。可选接口组合
// provider.go 一组可选能力接口(Go 鸭子类型/类型断言):
RequestHeadersHandler(:269)、RequestBodyHandler(:273)
StreamingResponseBodyHandler(:277)
TransformRequestHeadersHandler(:289)、TransformRequestBodyHandler(:293)
TransformResponseBodyHandler(:307)
// 各 provider 按需实现哪几个(不用全实现)
GetProviderType,其余能力拆成一组可选接口——厂商需要哪个就实现哪个。运行时用 Go 的类型断言判断"这个 provider 实现了 TransformRequestBody 吗",实现了才调。(回想 Envoy 课 Day 13 的组合式 adapter——同一个思想!)这让简单厂商代码极少、复杂厂商能全定制——优雅的可扩展设计。👶 小白:把所有方法都写进一个大接口,谁不需要就留空实现,不也行吗?为什么非要拆成一堆可选接口?
👨🏫 老师:能跑,但很脏。① 39 家厂商,每家都被迫写一堆空方法,代码噪音大、容易写错;② 加一个新能力,所有厂商都得改(哪怕用不上)。拆成可选接口后:厂商只实现自己需要的那几个,运行时用 Go 类型断言 if p, ok := provider.(TransformRequestBodyHandler); ok 判断"它到底会不会这招",会才调。简单厂商可能就实现 1 个接口、几十行;复杂厂商实现全套。各取所需,互不拖累。
foobar:① 新建 provider/foobar.go,实现 GetProviderType() + 需要的转换接口;② 在 providerInitializers map 里加一行 "foobar": &foobarInit{}。核心代码一行不改——这就是"开闭原则"(对扩展开放、对修改关闭)为什么能让社区把厂商从几家堆到 39 家。统一 OpenAI 协议
// ai-proxy/main.go 路径→ApiName 映射(:51):
// /v1/chat/completions → ApiNameChatCompletion(覆盖 OpenAI/Anthropic/Qwen 各风格)
// onHttpRequestHeader(:217)取 activeProvider → 解析 apiName
// Claude↔OpenAI 协议自动转换(:251,needClaudeResponseConversion)
// onHttpRequestBody(:314)→ provider.OnRequestBody 做模型映射 + 请求体改写
ai-proxy 识别请求路径(/v1/chat/completions 等)→ 找到目标 provider → 把统一请求翻译成厂商真实格式。甚至能自动转协议——客户端用 Claude 协议,后端是 OpenAI,自动转换(needClaudeResponseConversion)。这就是"万能适配器"的翻译现场。模型映射
// provider.go mapModel(:998)支持 前缀*/~正则/*
// defaultTransformRequestBody(:1396)用 sjson 只替换 model 名
// 避免整体反序列化以提性能
gpt-4)映射到实际后端模型(如通义的 qwen-max),支持前缀/正则/通配。于是应用无感切换底层模型。性能细节:改请求体里的 model 名,用 sjson(只改 JSON 里那一个字段)而非"反序列化整个 body → 改 → 再序列化"——因为 LLM 请求体可能很大(长 prompt),整体反序列化慢。只动一个字段的 sjson 快得多。这是"热路径性能优化"的实战——大 body 场景避免不必要的全量解析。model-router
extensions/model-router(main.go:204):从 body 取 model,higress/auto 时按最后一条 user 消息正则命中目标模型,否则把 provider/model 拆进 header(x-higress-llm-model),交给 Envoy 按 header 选上游集群。
gpt-* 走 OpenAI 集群、qwen-* 走通义集群。它把模型信息拆进 header,Envoy 按 header 匹配路由(回想 Istio 课 Day 08 的 header 路由)。甚至支持 higress/auto——按用户问题内容智能选模型(简单问题用便宜模型、复杂问题用强模型)。ai-proxy + model-router 配合:先路由选后端、再协议转换。今日小结 + 动手
🧠 今天你应该能回答
- ai-proxy 解决什么?为什么叫"万能适配器"?
- Provider 架构用什么模式?基接口为什么极简?
- 为什么能支持 39 家厂商?(开闭原则)
- 可选接口组合怎么工作?和 Envoy adapter 什么关系?
- 模型映射是什么?sjson 为什么是性能优化?model-router 干什么?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress/plugins/wasm-go/extensions/ai-proxy
grep -n 'providerInitializers\|type Provider interface\|func CreateProvider' provider/provider.go
ls provider/ | head -40 # 看有多少厂商文件