第 09 期 / 共 10 期

AI 网关插件家族

解剖 plugins/wasm-go/extensions/ai-* 一整个家族,20 讲覆盖从协议归一化 (ai-proxy) 到治理 (rate-limit / cache / RAG / security) 的完整能力栈。

L01

AI 网关流水线

Client (OpenAI 协议)
  ↓
ai-security-guard  内容合规
  ↓
jwt-auth / key-auth
  ↓
ai-intent         意图识别 → 路由分流
  ↓
ai-token-ratelimit / ai-quota
  ↓
ai-cache          (命中即返回)
  ↓
ai-rag            注入上下文
  ↓
ai-history        历史会话
  ↓
ai-prompt-template / decorator
  ↓
ai-proxy          协议转换 → 转发到 LLM
  ↓
ai-statistics     记账

20 多个 AI 相关插件按 phase + priority 组成流水线。Higress 默认提供 ai-* phase 推荐顺序。

思考:缓存放在 RAG 之前还是之后?答:之前命中率高(无 RAG 注入差异),之后内容更安全(带上下文匹配)。
L02

ai-proxy 目录

extensions/ai-proxy/
  main.go             # 注册 SetCtx
  config.go           # ProviderConfig schema
  provider/
    provider.go       # ★ Provider 接口 + 工厂
    openai.go         # 直通
    azure.go
    qwen.go
    deepseek.go
    moonshot.go
    claude.go
    gemini.go
    cohere.go
    ollama.go
    ...20+ 个
思考:为什么把每家 LLM 独立成文件?答:协议差异大,分离便于维护。
L03

Provider 接口

type Provider interface {
    GetProviderType() string
    OnRequestHeaders(ctx, apiName, log) error
    OnRequestBody(ctx, apiName, body, log) (types.Action, error)
    OnResponseHeaders(ctx, apiName, log) (types.Action, error)
    OnStreamingResponseBody(ctx, apiName, chunk, isLast, log) ([]byte, error)
    OnResponseBody(ctx, apiName, body, log) (types.Action, error)
}

"4 个生命周期 + Streaming 钩子" 是适配 LLM 协议的统一抽象。

思考:apiName 用来干嘛?答:区分 /chat/completions vs /embeddings vs /moderations。
L04

OpenAI 协议归一化

所有 Provider 对外都用 OpenAI 协议入参。openai.go 是恒等转换(直接转发)。其它 Provider 在 OnRequestBody 阶段读取 OpenAI request → 改写为对应厂商 schema;响应阶段反向。

思考:为什么选 OpenAI 协议作 lingua franca?答:生态最大、客户端最多。
L05

qwen 通义

provider/qwen.go

  • 请求映射:messagesinput.messagesmodel → DashScope 模型名。
  • 响应映射:output.textchoices[].message.contentusage 字段名稍异。
  • 支持流式:每个 SSE chunk 改写后转发。
思考:rerank / 多模态接口如何兼容 OpenAI?提示:路径差异由 apiName 路由。
L06

claude 适配

provider/claude.go 处理 Anthropic 协议:

  • system 消息 → 顶层 system 字段(不在 messages 数组)。
  • stop_reason 映射回 OpenAI 的 finish_reason。
  • Tool use 协议差异较大,需要特别处理。
思考:Claude 流式事件类型更细(message_start / content_block_delta / ...),怎么合并成 OpenAI delta?
L07

gemini 适配

provider/gemini.go

  • 路径:/v1beta/models/{model}:generateContent
  • messages → contents 数组,role 名 "user/model"。
  • generationConfig 字段映射 temperature / topP。
思考:Gemini system instruction 字段叫什么?
L08

azure / openai

Azure OpenAI 与官方 OpenAI 协议几乎一致,差异:

  • URL 路径含 deployment:/openai/deployments/{deployment}/chat/completions
  • 认证用 api-key header 不是 Authorization
  • API version query 参数。
思考:同一插件配置同时多 Provider 时怎么路由?答:spec 里多个 provider entry + 客户端通过 model 字段触发。
L09

SSE 流式

OnStreamingResponseBody 每来一段 chunk 调用一次。注意:

  • isLastChunk 决定是否要 flush 内部 buffer。
  • 多个 chunk 可能跨 SSE 事件边界,要重新拼装。
  • Higress 提供 EventStreamParser 工具帮助按事件划分。
思考:流式途中报错怎么返给客户端?答:发一条 data: {"error": ...} 后关流。
L10

ai-token-ratelimit

extensions/ai-token-ratelimit:按 token 数量限流(不是按 QPS)。请求阶段先用 tokenizer 估算 prompt token,响应阶段把 usage.total_tokens 扣账,超阈值即下次拒绝。后端依赖 Redis。

思考:tokenizer 怎么塞进 Wasm?答:要么内置 BPE 表,要么外呼 token 计算服务。
L11

ai-quota

面向多租户:按用户 / 团队 / API key 设置月度配额。与 ai-token-ratelimit 的区别:粒度(用户)+ 时间窗(月)+ 持久化(Redis hash)。

思考:超额时优雅降级 vs 直接拒绝怎么选?
L12

ai-cache 精确与语义

支持两种缓存:

  • 精确缓存:以 messages 字面做 key,最简单。
  • 语义缓存:调 embedding API → 向量化 → 相似度查询(向量库)。

命中时直接 SendHttpResponse 返回缓存,跳过 ai-proxy。

思考:流式响应能缓存吗?答:可,缓存完整聚合后的内容。
L13

ai-rag

把请求的 query 发给向量库(Aliyun DashVector / Milvus / 阿里云搜索等),把召回的 chunks 注入到 messages 的 system 字段或 user 之前。

思考:RAG 注入多少条 chunks?怎么权衡 token 与召回率?
L14

ai-history 上下文

把历史会话从 Redis / 持久化层拉出来,自动拼到 messages 前面,让无状态客户端拥有有状态会话能力。session_id 通常通过 header / cookie 传入。

思考:上下文太长怎么截断?提示:保留 system + 最近 N 轮 + 摘要。
L15

ai-intent 意图

用一个小模型分类用户意图(购票 / 投诉 / 闲聊),根据结果改写 model 字段或 route 名,分流到不同 Provider。这是"AI 路由"。

思考:分类失败时兜底策略?
L16

ai-security-guard

把请求 / 响应内容送到内容合规服务(阿里云绿网 / 自研敏感词),命中即拦截。两阶段:请求拦敏感问题,响应拦敏感答案。

思考:流式响应的合规怎么做?答:边截边合规,按句校验。
L17

ai-statistics

埋点:记录每次调用的 model / tokens / 耗时 / cache hit 等。Higress 把它落到 access log 字段,可由 Promtail / Loki / SLS 消费。

思考:埋点放最后一个 phase 的好处?答:能看到所有插件影响后的最终结果。
L18

ai-load-balancer

对多 Provider / 多 API key 做智能负载:

  • 按健康度(失败率)熔断。
  • 按延迟选择最快。
  • 按 quota 剩余分配。
思考:跨 Provider LB 与 Envoy Cluster LB 关系?答:插件层做模型/key 选择,Cluster LB 做 upstream IP 选择。
L19

ai-search / ai-agent

更高层能力:

  • ai-search:让 LLM 调用搜索引擎补全实时信息。
  • ai-agent:把 function calling 与外部工具串成 Agent flow,可在网关层完成简单 Agent。
思考:把 Agent 放网关层而不是应用层的好处?答:复用治理(限流/缓存),多团队共享。
L20

新增一个 LLM Provider

步骤:

  1. provider/ 新建 my.go,实现 Provider 接口。
  2. provider.go 的工厂 switch 加 case "my"
  3. config.go 加该 provider 的字段。
  4. 写单测:构造 OpenAI request → 调 OnRequestBody → 校验改写后的 body。
  5. build → 替换 ai-proxy 镜像。

整个过程不需要触碰控制面任何代码。

本期收尾:你已经能扩展任何新 LLM。下一期我们收尾——MCP / 证书 / hgctl / 学习路线回顾。