Higress ai-quota 插件实战:基于 Redis 的按 Consumer AI Token 配额管理与管控接口
【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress
本文围绕 Higress 官方 WASM 插件ai-quota展开,完整讲解其配置参数、部署示例,以及"配额校验—Token 扣减—查询/刷新/增减配额"的完整工作流。读完本文,你将掌握如何为每个 AI 调用方(consumer)分配固定 Token 配额、在流式响应结束时自动扣减配额,并内置一套基于管理 consumer 身份的配额管控 HTTP 接口,同时了解插件在 proxy-wasm 各阶段的底层实现逻辑。
功能定位与运行属性
ai-quota插件为特定 consumer 分配固定的 quota(配额),按配额策略对 AI 请求进行限流,同时提供配额管理能力,包括查询 quota、刷新 quota、增减 quota。其典型使用模式是:
- 在网关路由上为不同调用方(如不同租户、不同 API Key 持有者)预先在 Redis 中写入一个 Token 数值;
- 每次调用大模型完成接口时,插件校验该 consumer 是否还有剩余配额,无配额则直接拒绝;
- 大模型响应结束(流式或非流式)后,插件从响应中解析本次请求消耗的 input/output Token,并从 Redis 配额中扣减;
- 管理员可通过管理接口在线查询、刷新、增减某个 consumer 的配额,无需重启网关或手工操作 Redis。
根据文档说明,ai-quota需要配合认证插件(如key-auth、jwt-auth)获取认证身份的 consumer 名称,配合ai-statistics插件获取 AI Token 统计信息。
插件运行属性:
| 属性 | 值 |
|---|---|
| 插件执行阶段 | 默认阶段(UNSPECIFIED_PHASE) |
| 插件执行优先级 | 750(文档标称默认优先级) |
在 plugin.yaml 的部署示例中,ai-quota 的priority设置为280,介于 key-auth(300)与 ai-statistics(250)之间。从源码结构看,这一顺序保证了认证插件先于配额插件执行、先写入 consumer 标识,配额插件再读取该标识完成校验。
配置参数详解
插件顶层配置项如下(与 README.md 一致,默认值已结合 main.go 中parseConfig的实际解析逻辑核对):
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---|---|---|---|---|
redis_key_prefix | string | 选填 | chat_quota: | quota Redis key 前缀 |
admin_consumer | string | 必填 | - | 管理 quota 管理身份的 consumer 名称 |
admin_path | string | 选填 | /quota | 管理 quota 请求 path 前缀 |
enable_path_suffixes | []string | 选填 | ["/v1/chat/completions", "/v1/messages"] | 启用配额校验的请求路径后缀(仅用于 completion 请求,不影响管理接口路径) |
redis | object | 必填 | - | Redis 相关配置 |
parseConfig中的关键校验逻辑(见 main.go#L80-L151):
- 未配置
admin_consumer时直接返回错误missing admin_consumer in config,插件启动失败; enable_path_suffixes必须是数组且不能为空,元素中的空白串会被过滤,空数组会触发enable_path_suffixes must not be empty;admin_path缺省回退为/quota,redis_key_prefix缺省回退为chat_quota:;redis.service_name不能为空,且会基于它创建FQDNCluster类型的 Redis 客户端。
redis子对象各字段:
| 配置项 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
service_name | string | 必填 | - | Redis 服务名称,带服务类型的完整 FQDN 名称,例如my-redis.dns、redis.my-ns.svc.cluster.local |
service_port | int | 否 | 服务类型为固定地址(static service,即名称以.static结尾)时默认 80,其他为 6379 | Redis 服务端口 |
username | string | 否 | - | Redis 用户名 |
password | string | 否 | - | Redis 密码 |
timeout | int | 否 | 1000 | Redis 连接超时时间,单位毫秒 |
database | int | 否 | 0 | 使用的数据库 id,例如配置为 1,对应SELECT 1 |
源码中对service_port的默认值处理见 main.go#L123-L131:仅当服务名以.static结尾时才取 80,这与文档描述一致。
完整配置示例
以下示例来自文档,表示按请求头中的 API Key 识别不同 consumer 并进行区别限流:
redis_key_prefix: "chat_quota:" admin_consumer: consumer3 admin_path: /quota redis: service_name: redis-service.default.svc.cluster.local service_port: 6379 timeout: 2000结合仓库中 plugin.yaml 的完整部署形态,该插件通常与key-auth、ai-statistics一起以WasmPluginCR 声明式下发。key-auth 侧声明了三个 consumer:
# 摘自 plugins/wasm-go/extensions/ai-quota/plugin.yaml defaultConfig: consumers: - credential: "Bearer credential1" name: consumer1 - credential: "Bearer credential2" name: consumer2 - credential: "Bearer credential3" name: consumer3 global_auth: true keys: - authorization in_header: true priority: 300ai-quota 侧则配置admin_consumer: consumer3,即持有credential3的请求被视为配额管理员。matchRules 通过ingress: [qwen]将配额策略绑定到具体的 Ingress 路由上。
请求路径的模式识别:completion / admin / none
插件在请求头阶段会调用getOperationMode判定当前请求属于哪种模式(见 main.go#L289-L306):
| 模式 | 判定条件 | 行为 |
|---|---|---|
admin/refresh | 请求路径以/v1/chat/completions+admin_path+/refresh结尾 | 刷新配额 |
admin/delta | 请求路径以/v1/chat/completions+admin_path+/delta结尾 | 增减配额 |
admin/query | 请求路径以/v1/chat/completions+admin_path结尾 | 查询配额 |
completion | 请求路径以enable_path_suffixes中任一后缀结尾 | 执行配额校验与扣减 |
none | 以上均不匹配 | 放行,不做配额处理 |
需要注意两个源码级细节:
- 管理接口路径是固定拼接的:
fullAdminPath := "/v1/chat/completions" + adminPath,也就是说无论路由前缀是什么,管理接口始终挂在/v1/chat/completions之后(默认即/v1/chat/completions/quota系列路径)。单元测试TestGetOperationMode中专门验证了/v1/messages/quota不会被识别为管理接口(messages admin path not supported用例),而自定义后缀(如/llm/invoke)可以被识别为 completion 路径; - completion 判定与 admin 判定互不影响:
enable_path_suffixes只约束配额校验的完成接口路径,不改变管理接口的匹配逻辑。
该表驱动测试完整位于 main_test.go#L304-L394。
配额校验与 Token 扣减流程
请求头阶段:读取身份并校验配额
onHttpRequestHeaders(见 main.go#L153-L209)的处理链路:
- 读取请求头
x-mse-consumer(由 key-auth 等认证插件在认证成功后写入):- 头不存在:返回
401,状态详情ai-quota.no_key,正文 "Request denied by ai quota check. No Key Authentication information found."; - 头存在但为空:返回
403,状态详情ai-quota.unauthorized;
- 头不存在:返回
- 判定模式后:
none模式直接ActionContinue放行;admin模式交由后续 body 阶段处理(refresh/delta 会先缓冲请求体);completion模式跳过请求体读取,发起 RedisGET {redis_key_prefix}{consumer},以下任一情况判定为拒绝:Redis 调用出错、key 不存在(null)、配额值小于等于 0;拒绝时返回403,状态详情ai-quota.noquota,正文 "Request denied by ai quota check, No quota left"。
配额检查期间返回HeaderStopAllIterationAndWatermark,即挂起请求流水线直到 Redis 回调返回结果,这是 proxy-wasm 中典型的异步外部调用暂停/恢复模式。单元测试TestOnHttpRequestHeaders的 "chat completion mode" 用例验证了该行为:模拟 Redis 返回配额 1000 后,流恢复为ActionContinue(见 main_test.go#L111-L133)。
响应体阶段:解析 Token 并扣减配额
在流式响应体处理函数onHttpStreamingResponseBody(见 main.go#L239-L277)中:
- 插件逐段解析 SSE 流,通过 wasm-go 通用包
tokenusage.GetTokenUsage提取usage字段中的 input/output token 数; - 仅当
endOfStream为 true 且 input/output token 均已解析成功时,计算totalToken = inputToken + outputToken,并执行 RedisDECRBY {redis_key_prefix}{consumer} totalToken; - 中间分片数据原样透传,不改动响应内容。
TestOnHttpStreamingResponseBody用例模拟了流结束后的扣减调用,验证了 Redis 扣减回调被正确触发(见 main_test.go#L238-L302)。
配额管理接口实战
管理接口同样要求请求先通过认证并携带x-mse-consumer,且该 consumer 必须等于admin_consumer,否则返回403(ai-quota.unauthorized,"Unauthorized admin consumer.")。以下命令以插件生效于example.com/v1/chat/completions路由、admin_consumer为consumer3(对应Bearer credential3)为前提。
刷新 quota
将指定 consumer 的配额直接重置为新值:
curl https://example.com/v1/chat/completions/quota/refresh \ -H "Authorization: Bearer credential3" \ -d "consumer=consumer1"a=10000"执行后 Redis 中 keychat_quota:consumer1的值被刷新为 10000,成功时插件返回refresh quota successful。refreshQuota实现位于 main.go#L308-L341,请求体按application/x-www-form-urlencoded解析,consumer不能为空、quota必须是整数,否则返回 403。
查询 quota
查询指定 consumer 的剩余配额:
curl "https://example.com/v1/chat/completions/quota?consumer=consumer1" \ -H "Authorization: Bearer credential3"返回 JSON:
{"quota": 10000, "consumer": "consumer1"}若 key 不存在则返回quota: 0。queryQuota在请求头阶段即可处理(无需请求体),Redis 出错时返回503(ai-quota.error)。对应实现见 main.go#L343-L385,测试用例TestOnHttpRequestHeaders的 "admin query mode" 断言了返回值{"consumer":"consumer1","quota":500}。
增减 quota
对指定 consumer 的配额做增量调整:
curl https://example.com/v1/chat/completions/quota/delta \ -H "Authorization: Bearer credential3" \ -d "consumer=consumer1&value=100"Redis 中 keychat_quota:consumer1的值增加 100;value支持负数,传负值则减去对应值。deltaQuota(见 main.go#L387-L435)根据正负号分别走 RedisINCRBY/DECRBY,成功时返回delta quota successful。
错误响应一览
结合 util/http.go 的SendResponse与各处理函数,插件可能返回的响应如下:
| 状态码 | 状态详情(status detail) | 触发场景 |
|---|---|---|
| 401 | ai-quota.no_key | 请求头缺少x-mse-consumer(未通过认证插件) |
| 403 | ai-quota.unauthorized | consumer 为空;管理接口调用者不是admin_consumer;参数校验失败(consumer 为空、quota/value 非整数) |
| 403 | ai-quota.noquota | completion 请求无剩余配额(key 不存在、出错或值 ≤ 0) |
| 503 | ai-quota.error | Redis 调用失败 |
| 200 | ai-quota.refreshquota/ai-quota.queryquota/ai-quota.deltaquota | 管理操作成功 |
这些带语义的 status detail 便于在网关访问日志中直接定位限流/管理请求的处理结果。
配置解析的测试覆盖
插件配置解析由TestParseConfig覆盖(见 main_test.go#L69-L106):
- 基础配置解析后各字段符合预期(
AdminConsumer、RedisKeyPrefix、AdminPath、EnablePathSuffixes); - 缺少
admin_consumer时插件启动状态为OnPluginStartStatusFailed,即网关侧会拒绝加载该配置,属于启动期强校验; - 未配置
enable_path_suffixes时回退到默认值["/v1/chat/completions", "/v1/messages"],兼容 OpenAI 与 Anthropic 两种主流的 completion 路径。
落地建议小结
- 先认证后限流:务必确保 key-auth / jwt-auth 等认证插件在 ai-quota 之前执行(部署示例中通过 priority 300 > 280 体现),否则所有请求都会以 401
ai-quota.no_key被拒; - 预置配额:consumer 的配额 key(如
chat_quota:consumer1)需提前写入 Redis,未写入的 consumer 会被判定为无配额而拒绝,这一点在源码中由IsNull()判断直接体现; - 管理面收敛:
admin_consumer的凭据只分发给运营/管理侧账号,管理接口与普通 completion 接口共用同一路由,路径前缀区分(/quota、/quota/refresh、/quota/delta),无需额外暴露管理端口; - 注意管理路径的固定形态:管理接口始终基于
/v1/chat/completions拼接,若网关实际路由前缀不同,需要确保客户端 URL 与该形态一致,否则请求会落入none模式直接放行而不做配额处理。
核心源码均位于 plugins/wasm-go/extensions/ai-quota 目录:入口与全部业务逻辑在 main.go,本地响应构造工具在 util/http.go,行为验证在 main_test.go,可直接作为二次开发与排障的参考起点。
【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考