一、痛点
1.1 技能管理分散
-
没有统一的技能注册、发布、审核机制
-
技能上线无门槛,质量和安全性无法保障
-
扣费标准混乱,同样类型的 API 不同部门定价差异大
-
Skill 与 API 的对应关系不清晰,调用方无法感知背后的能力边界
1.2 API 管理混乱
-
内部 API 服务散落在各业务线,没有统一注册和元数据管理
-
各环境(dev/stage/prod)域名不统一,调用方需维护多套地址配置
-
现存接口能力无法被系统化导入和复用,重复建设严重
-
API 变更无版本追踪,上下游兼容性难以保障
1.3 认证授权体系缺失
-
各服务各自实现认证逻辑,标准不统一
-
AKSK 管理分散,存在密钥泄露风险
-
无法追溯调用来源,出了问题难以定责
1.4 商业化能力薄弱
-
缺乏统一的额度管理和扣费追溯机制
-
扣费逻辑散落在各服务中,难以审计
-
无法提供完整的账单和成本分析
-
API 本身无商业化语义,只有在与 Skill 绑定时才具备扣费策略,现有设计未体现这一分层
1.5 可观测性不足
-
缺少全链路调用追踪
-
配额使用情况不可见
-
异常告警缺失,问题发现滞后
二、设计目标
2.1 功能目标
| 目标 | 说明 |
|---|---|
| 统一内网域名 | 每个环境一个域名入口,基于阿里云 PrivateZone 解析,所有内部 API 能力均通过 AI 网关访问 |
| 统一认证 | 所有 AI 请求通过 AKSK 签名验签,身份可追溯 |
| 统一路由 | Agent 服务统一注册,流量统一分发 |
| 统一计费 | 所有 Skill 调用经过权益中心扣费,标准透明 |
| 统一管理 | API 全生命周期可管可控;Skill 创建→审核→发布→下线,严格版本化 |
| API 导入与解析 | 支持从现存服务导入接口定义(OpenAPI/Swagger),留存为系统 API 元数据 |
| Agent 组装 Skill | Agent 可自由选择已发布的 Skill 版本进行组装,生成固定版本下载链接 |
2.2 性能目标
| 指标 | 目标值 |
|---|---|
| 网关层延迟(不含后端) | P99 < 50ms |
| 验签延迟 | P99 < 10ms |
| 系统可用性 | 99.9% |
| 单机吞吐 | 10 万 QPS |
2.3 架构原则
-
控制面与数据面一体:同一套服务,逻辑分离
-
控制面:高性能流量转发,根据规则对指定 API 能力做前后置处理
-
数据面:面向内部人员,提供管理配置和观测能力
-
-
插件化管道:验签、路由、计费以中间件形式存在,可独立迭代
-
无状态设计:网关节点无状态,支持 K8s HPA 弹性扩缩容
-
故障隔离:单点故障不影响整体可用性,降级有预案
-
API 与商业化解耦:API 本身无商业化属性,只有在与 Skill 绑定后,才具备扣费策略;Skill 未绑定的 API 调用视为无商业化状态
三、整体架构
3.1 架构分层

3.2 统一内网域名体系
AI 网关为每个环境提供唯一的内网域名入口,基于阿里云 PrivateZone(pvz) 进行 DNS 解析,所有内部 API 能力均通过该域名访问,调用方无需关心后端服务地址。
| 环境 | 域名 | 说明 |
|---|---|---|
| 开发环境 | ai-gateway.dev.platform.com |
对应 dev 集群,pvz 解析到网关 dev 节点 |
| 预发环境 | ai-gateway.stage.platform.com |
对应 stage 集群,pvz 解析到网关 stage 节点 |
| 生产环境 | ai-gateway.prod.platform.com |
对应 prod 集群,pvz 解析到网关 prod 节点 |
调用规范:
plaintext
POST https://ai-gateway.{env}.platform.com/api/v1/agent/{agent_id}/invoke
POST https://ai-gateway.{env}.platform.com/api/v1/skill/{skill_id}/v{version}/{api_path}
所有 Agent 对 Skill 包含的 API 的调用,必须通过内网域名走 AI 网关,禁止绕过网关直接访问后端服务。网关负责完成路由、验签、扣费等前置逻辑后,再将请求转发到对应后端。
3.3 控制面 vs 数据面
| 维度 | 控制面(流量平面) | 数据面(管理平面) |
|---|---|---|
| 定位 | 高性能流量转发 | 内部管理和观测 |
| 处理内容 | 验签、限流、路由、商业化扣费 | 配置管理、规则下发、指标查询、日志检索 |
| 延迟要求 | 微秒级,P99 < 50ms | 毫秒级即可 |
| 变更频率 | 每次请求 | 配置变更时 |
| 实现方式 | Tower 中间件管道 | HTTP API + 后台页面 |
3.4 组件职责
| 组件 | 职责 | 技术栈 |
|---|---|---|
| AI 网关 | 统一入口、验签、路由、商业化、可观测 | Rust + Axum/Tower |
| Agent 服务 | 业务逻辑处理,Skill 编排 | 各业务线自研 |
| 权益中心 | 额度管理、扣费执行、账单生成 | 独立微服务 |
| 后端 API | 具体 AI 能力提供 | 各部门服务,cli 化 |
| Redis | 规则缓存、Nonce 防重放、限流计数 | |
| Kafka | 扣费事件、审计日志、指标上报 |
四、核心概念:API、Skill 与 Agent 的关系
4.1 三层概念模型
理解 AI 网关的核心,需要先厘清三个概念的边界和组合关系:

关键设计原则:
-
API 本身无商业化属性:API 只是一个接口定义,描述了路径、方法、参数和后端地址。它不携带任何扣费策略。
-
Skill 赋予 API 商业化语义:当 API 被纳入某个 Skill 时,在 Skill 层面为该 API 配置扣费策略(按次/按 Token/按秒)。同一个 API 在不同 Skill 中可以有不同的扣费策略。
-
Agent 通过 Skill 使用 API:Agent 不直接引用 API,而是选择 Skill 版本进行组装。调用时,网关根据 Skill 中的 API 绑定关系,完成路由和扣费。
4.2 请求透传上下文
所有经过 AI 网关的请求,网关会在转发给后端时,统一注入以下上下文信息:
| Header | 说明 |
|---|---|
X-User-ID |
调用方用户 ID |
X-Agent-ID |
发起调用的 Agent ID |
X-Skill-ID |
当前调用关联的 Skill ID |
X-Skill-Version |
Skill 版本号 |
X-API-ID |
当前调用的 API ID |
X-Request-ID |
全链路请求唯一标识 |
X-Trace-ID |
链路追踪 TraceID |
后端服务通过这些 Header 可以感知完整的调用上下文,无需自行实现认证逻辑。
五、核心模块设计
5.1 网关管道架构
AI 网关基于 Rust Tower 生态构建,请求处理管道如下:

5.2 AKSK 认证模块
5.2.1 数据结构
rust
pub struct AccessKey {
pub ak: String, // Access Key,公开
pub sk_encrypted: String, // Secret Key,AES256 加密存储
pub user_id: String,
pub enabled: bool,
pub expired_at: i64, // 0 表示永不过期
pub rate_limit: Option<i32>, // 每分钟调用次数限制
pub allowed_agents: Vec<String>, // 允许访问的 Agent 列表
}
#[derive(Debug, Clone)]
pub struct AuthContext {
pub user_id: String,
pub ak: String,
pub agent_id: Option<String>,
pub skill_id: Option<String>,
pub skill_version: Option<String>,
pub api_id: Option<String>,
pub is_internal: bool, // 是否内部服务调用(免验签)
}
5.2.2 签名协议
请求头
http
POST /api/v1/skill/{skill_id}/v{version}/{api_path} HTTP/1.1
Host: ai-gateway.prod.platform.com
X-AK: ak_xxxxxxxxxxxxxxxx
X-Signature: <hmac-sha256-base64>
X-Timestamp: 1743408000
X-Nonce: 550e8400-e29b-41d4-a716-446655440000
X-Agent-ID: agent_video_001
Content-Type: application/json
{
"user_id": "user_123",
"query": "生成一个视频",
"parameters": {}
}
签名计算
plaintext
body_hash = SHA256(body)
message = method + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + body_hash
signature = HMAC-SHA256(message, SK)
5.2.3 验签中间件实现
rust
// src/middleware/auth.rs
use axum::{
http::{Request, StatusCode},
middleware::Next,
response::Response,
Json,
};
use serde::Serialize;
#[derive(Serialize)]
pub struct ErrorResponse {
pub code: &'static str,
pub http_status: u16,
pub message: String,
}
/// 验签中间件
pub async fn auth_middleware<B>(
mut req: Request<B>,
next: Next<B>,
) -> Result<Response, (StatusCode, Json<ErrorResponse>)> {
let ak = req.headers()
.get("X-AK")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| auth_error("AUTH_FAILED", "Missing X-AK header"))?;
let signature = req.headers()
.get("X-Signature")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| auth_error("AUTH_FAILED", "Missing X-Signature header"))?;
let timestamp = req.headers()
.get("X-Timestamp")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<i64>().ok())
.ok_or_else(|| auth_error("TIMESTAMP_EXPIRED", "Invalid X-Timestamp"))?;
let nonce = req.headers()
.get("X-Nonce")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| auth_error("AUTH_FAILED", "Missing X-Nonce header"))?;
// 验证时间戳窗口 (±5 分钟)
let now = current_timestamp();
if (now - timestamp).abs() > 300 {
return Err(auth_error("TIMESTAMP_EXPIRED", "Request timestamp expired"));
}
// 检查 Nonce 防重放(Redis)
if is_nonce_duplicate(nonce).await {
return Err(auth_error("NONCE_DUPLICATE", "Duplicate request detected"));
}
// 获取 SK
let sk = get_sk_from_cache(ak).await
.ok_or_else(|| auth_error("AK_INVALID", "Access key not found"))?;
// 计算签名并比对
let body = extract_body(&req).await;
let method = req.method().to_string();
let path = req.uri().path().to_string();
if !verify_signature(&sk, &method, &path, timestamp, nonce, &body, signature) {
return Err(auth_error("SIGNATURE_INVALID", "Signature mismatch"));
}
// 记录 Nonce(Redis TTL 5 分钟)
record_nonce(nonce, ak).await;
let user_id = get_user_id_by_ak(ak).await
.ok_or_else(|| auth_error("AK_INVALID", "User not found"))?;
// 从路径解析 skill_id、version、api_id
let (skill_id, skill_version, api_id) = resolve_skill_context(&path).await?;
req.extensions_mut().insert(AuthContext {
user_id: user_id.clone(),
ak: ak.to_string(),
agent_id: extract_agent_id(&req),
skill_id,
skill_version,
api_id,
is_internal: false,
});
req.headers_mut().insert("X-User-ID", user_id.parse().unwrap());
Ok(next.run(req).await)
}
5.3 API 管理模块
5.3.1 API 定义与导入
系统支持两种方式录入 API:
-
手动创建:在数据面管理页面填写 API 元数据
-
批量导入:上传 OpenAPI/Swagger JSON 文件,系统自动解析并生成 API 记录
导入后的 API 以 api_id 为唯一标识留存在系统中,可被多个 Skill 引用。API 本身不携带任何商业化策略,只有在与 Skill 绑定时才具备扣费语义。
5.3.2 数据结构
rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiDefinition {
pub api_id: String, // 全局唯一,如 api_video_generate
pub name: String,
pub description: String,
pub department: String, // 所属部门
pub owner: String,
pub method: String, // GET / POST / PUT / DELETE
pub path_pattern: String, // /api/v1/video/generate
pub backend_service: String, // 后端服务名,如 video-service
pub backend_url: String, // 后端实际地址
pub timeout_ms: i32,
pub request_schema: Option<serde_json::Value>, // OpenAPI 请求参数 schema
pub response_schema: Option<serde_json::Value>, // OpenAPI 响应 schema
pub status: ApiStatus,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ApiStatus {
Active, // 可用
Disabled, // 禁用
Deprecated, // 已废弃
}
5.4 Skill 管理模块
5.4.1 Skill 的本质
Skill = 一份文本描述说明书 + 一组 API 绑定关系
-
描述说明书:面向 Agent 开发者,说明该 Skill 的功能、使用场景、输入输出规范、注意事项等
-
API 绑定关系:该 Skill 包含哪些 API,以及每个 API 对应的商业化扣费策略
5.4.2 Skill 生命周期
plantuml
@startuml
skinparam state {
BackgroundColor White
BorderColor Black
}
[*] --> Draft
note right of Draft
包含以下操作:
- 填写 Skill 描述说明书
- 绑定 API 列表
- 为每个 API 配置扣费策略
- 设置版本号
end note
state "Draft (草稿)" as Draft
state "Reviewing (审核中)" as Reviewing
state "Approved (已发布)" as Approved
state "Rejected (已驳回)" as Rejected
state "Disabled (已禁用)" as Disabled
Draft --> Reviewing : 提交审核
Reviewing --> Approved : 审核通过(生成固定版本)
Reviewing --> Rejected : 审核驳回
Rejected --> Draft : 修改后重新提交
Approved --> Disabled : 下线
Disabled --> Draft : 重新启用(新版本)
note right of Approved
审核通过后:
1. 锁定版本号,内容不可修改
2. 生成 Skill 包下载链接
3. 同步路由规则到网关节点
4. Agent 可选择该版本进行组装
end note
@enduml
5.4.3 版本机制
Skill 采用语义化版本号(SemVer)管理:
-
每次提交审核时必须指定版本号,格式为
v{major}.{minor}.{patch} -
审核通过后版本号锁定,内容不可修改
-
如需更新,必须创建新版本草稿重新走审核流程
-
Agent 组装时选择具体版本,组装结果固定,不受后续版本发布影响
5.4.4 数据结构
rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Skill {
pub skill_id: String,
pub name: String,
pub description_doc: String, // Skill 文本描述说明书(Markdown)
pub department: String,
pub owner: String,
pub status: SkillStatus,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillVersion {
pub skill_version_id: String, // 全局唯一
pub skill_id: String,
pub version: String, // v1.2.0
pub changelog: String, // 本版本变更说明
pub api_bindings: Vec<SkillApiBinding>,
pub status: SkillVersionStatus,
pub download_url: Option<String>, // 审核通过后生成
pub approved_at: Option<i64>,
pub approved_by: Option<String>,
pub created_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillApiBinding {
pub api_id: String,
pub billing_policy: BillingPolicy, // 该 API 在此 Skill 中的扣费策略
pub required: bool, // 是否为必选 API
pub description: String, // 该 API 在此 Skill 中的用途说明
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillingPolicy {
pub billing_type: BillingType,
pub points_per_call: Option<i64>,
pub points_per_1k_tokens: Option<i64>,
pub points_per_second: Option<i64>,
pub currency: String, // quota_point / cash_point
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BillingType {
PerCall, // 按次
PerToken, // 按 Token
PerSecond, // 按秒(流式)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkillVersionStatus {
Draft,
Reviewing,
Approved,
Rejected,
Disabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkillStatus {
Active,
Disabled,
}
5.5 Agent 管理模块
5.5.1 Agent 组装 Skill
Agent 可以从已发布的 Skill 版本列表中,选择一个或多个 Skill 版本进行组装。组装完成后:
-
系统生成一个固定版本的 Skill 包(JSON 格式),包含所有选中 Skill 的描述文档和 API 路由信息
-
生成唯一的下载链接,Agent 可以通过该链接下载并安装到本地
-
组装结果固定,后续 Skill 版本更新不影响已组装的版本,Agent 可主动选择升级
rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
pub agent_id: String,
pub name: String,
pub description: String,
pub backend_url: String,
pub health_check_url: Option<String>,
pub load_balancer: LoadBalancerConfig,
pub status: AgentStatus,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSkillAssembly {
pub assembly_id: String, // 组装包唯一 ID
pub agent_id: String,
pub skill_versions: Vec<AgentSkillSelection>, // 选中的 Skill 版本列表
pub download_url: String, // 固定版本下载链接
pub created_at: i64,
pub created_by: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSkillSelection {
pub skill_id: String,
pub skill_version_id: String,
pub version: String, // v1.2.0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AgentStatus {
Active,
Disabled,
Draining,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadBalancerConfig {
pub lb_type: String, // round_robin / least_conn / ip_hash
pub instances: Vec<BackendInstance>,
pub health_check_interval: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendInstance {
pub addr: String,
pub weight: i32,
pub healthy: bool,
pub last_health_check: i64,
}
5.5.2 路由中间件实现
rust
// src/middleware/routing.rs
use std::sync::Arc;
use parking_lot::RwLock;
pub struct RouterState {
routes: RwLock<HashMap<String, AgentRoute>>,
}
pub struct AgentRoute {
pub backend_url: String,
pub instances: RwLock<Vec<BackendInstance>>,
pub lb_strategy: LbStrategy,
pub current_index: AtomicUsize,
}
enum LbStrategy {
RoundRobin,
LeastConn,
IpHash,
}
impl RouterState {
pub fn select_instance(&self, agent_id: &str) -> Option<BackendInstance> {
let route = self.routes.read().get(agent_id)?;
match route.lb_strategy {
LbStrategy::RoundRobin => {
let idx = route.current_index.fetch_add(1, Ordering::Relaxed);
let instances = route.instances.read();
if instances.is_empty() {
return None;
}
Some(instances[idx % instances.len()].clone())
}
_ => todo!(),
}
}
}
5.6 额度与限流分层设计
5.6.1 分层模型
AI 网关的额度和限流体系分为四个层次,由管理员统一配置:
plaintext
┌────────────────────────────────────────────────────────────┐
│ Layer 1:系统级 Token 总额度 │
│ - 管理员为每个 Agent 设置系统级 Token 总量上限 │
│ - 控制 Agent 整体的资源消耗天花板 │
├────────────────────────────────────────────────────────────┤
│ Layer 2:用户级额度 │
│ - 管理员为每个 Agent 下的用户设置独立额度 │
│ - 单用户消耗不影响其他用户 │
├────────────────────────────────────────────────────────────┤
│ Layer 3:Agent 对特定 Skill API 的限流阈值 │
│ - 管理员为 Agent + Skill + API 三元组设置 QPS 上限 │
│ - 控制某 Agent 对某个具体 API 能力的调用频率 │
├────────────────────────────────────────────────────────────┤
│ Layer 4:API 本身的全局限流总阈值 │
│ - 管理员为每个 API 设置全局 QPS 上限 │
│ - 无论哪个 Agent 调用,总量不超过该阈值(保护后端服务) │
└────────────────────────────────────────────────────────────┘
5.6.2 数据结构
rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentQuotaConfig {
pub agent_id: String,
pub system_token_quota: i64, // Agent 系统级 Token 总额度
pub user_default_token_quota: i64, // 该 Agent 下用户的默认额度
pub user_quota_overrides: HashMap<String, i64>, // 特定用户的额度覆盖
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentApiRateLimit {
pub agent_id: String,
pub skill_id: String,
pub api_id: String,
pub qps_limit: i32, // Agent 对该 Skill API 的 QPS 上限
pub burst_limit: i32, // 突发流量上限
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiGlobalRateLimit {
pub api_id: String,
pub global_qps_limit: i32, // API 全局 QPS 上限(保护后端)
pub global_burst_limit: i32,
}
5.6.3 限流检查顺序
请求到达网关后,限流检查按以下顺序执行,任一层拦截即返回 429:
plaintext
1. API 全局限流检查(Layer 4)
↓ 通过
2. Agent 对该 Skill API 的限流检查(Layer 3)
↓ 通过
3. 用户级额度检查(Layer 2)
↓ 通过
4. Agent 系统级 Token 额度检查(Layer 1)
↓ 通过
5. 扣费 → 转发请求
5.7 商业化扣费模块
5.7.1 扣费模式
| 模式 | 适用场景 | 算法 |
|---|---|---|
| 按次扣费 | 图片生成、视频生成 | 一次 50 点 |
| 按 Token 扣费 | 大模型调用 | 每 1K Token 10 点 |
| 按秒扣费 | 流式输出、实时处理 | 一秒 1 点 |
5.7.2 扣费流程
plantuml
@startuml
autonumber
skinparam sequenceMessageAlign center
participant "Agent" as Agent
participant "AI 网关" as Gateway
participant "权益中心" as Rights
participant "后端 API" as Backend
Agent -> Gateway: Skill API 调用\nPOST /api/v1/skill/{skill_id}/v{version}/{api_path}
activate Gateway
Gateway -> Gateway: 路径匹配 → 识别 Skill + API
Gateway -> Rights: 扣费请求\nPOST /api/v1/billing/deduct
activate Rights
alt 扣费成功
Rights -> Rights: 扣减额度\n记录流水
Rights --> Gateway: 返回成功 + transaction_id
deactivate Rights
Gateway -> Backend: 代理转发请求\n注入 X-User-ID / X-Agent-ID / X-Skill-ID / X-API-ID 等
activate Backend
Backend --> Gateway: 返回业务结果
deactivate Backend
Gateway --> Agent: 返回最终结果
deactivate Gateway
else 扣费失败
Rights --> Gateway: QUOTA_EXCEEDED
deactivate Rights
Gateway --> Agent: 返回 402 + 标准错误体
deactivate Gateway
end
@enduml
5.8 限流中间件
rust
// src/middleware/rate_limit.rs
use governor::{Quota, RateLimiter, clock::QuantaInstant};
use std::num::NonZeroU32;
pub struct RateLimitState {
// 用户级限流
user_limiters: DashMap<String, RateLimiter<QuantaInstant>>,
// AK 级限流
ak_limiters: DashMap<String, RateLimiter<QuantaInstant>>,
// Agent + Skill + API 三元组限流
agent_api_limiters: DashMap<String, RateLimiter<QuantaInstant>>,
// API 全局限流
api_global_limiters: DashMap<String, RateLimiter<QuantaInstant>>,
// 全局配置
default_quota: Quota,
}
impl RateLimitState {
pub fn check_api_global(&self, api_id: &str) -> Result<(), RateLimitError> {
let limiter = self.api_global_limiters
.entry(api_id.to_string())
.or_insert_with(|| {
RateLimiter::direct(Quota::per_second(NonZeroU32::new(10000).unwrap()))
});
limiter.check().map_err(|_| RateLimitError::ApiGlobalExceeded)
}
pub fn check_agent_api(&self, agent_id: &str, skill_id: &str, api_id: &str) -> Result<(), RateLimitError> {
let key = format!("{}:{}:{}", agent_id, skill_id, api_id);
let limiter = self.agent_api_limiters
.entry(key)
.or_insert_with(|| {
RateLimiter::direct(Quota::per_second(NonZeroU32::new(1000).unwrap()))
});
limiter.check().map_err(|_| RateLimitError::AgentApiExceeded)
}
pub fn check_user(&self, user_id: &str) -> Result<(), RateLimitError> {
let limiter = self.user_limiters
.entry(user_id.to_string())
.or_insert_with(|| {
RateLimiter::direct(Quota::per_minute(NonZeroU32::new(1000).unwrap()))
});
limiter.check().map_err(|_| RateLimitError::Exceeded)
}
}
5.9 可观测性中间件
rust
// src/middleware/observability.rs
use tracing::{info, Span};
use tracing_opentelemetry::OpenTelemetrySpanExt;
pub async fn observability_middleware<B>(
mut req: Request<B>,
next: Next<B>,
) -> Response {
let trace_id = req.headers()
.get("X-Trace-ID")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| &generate_trace_id());
Span::current().set_attribute("trace_id", trace_id);
Span::current().set_attribute("path", req.uri().path());
Span::current().set_attribute("method", req.method().to_string());
let start = Instant::now();
let response = next.run(req).await;
let latency_ms = start.elapsed().as_millis();
let auth_ctx = req.extensions().get::<AuthContext>();
let user_id = auth_ctx.map(|c| c.user_id.as_str()).unwrap_or("anonymous");
let agent_id = auth_ctx.and_then(|c| c.agent_id.as_deref()).unwrap_or("-");
let skill_id = auth_ctx.and_then(|c| c.skill_id.as_deref()).unwrap_or("-");
let api_id = auth_ctx.and_then(|c| c.api_id.as_deref()).unwrap_or("-");
info!(
target = "gateway.access",
trace_id,
user_id,
agent_id,
skill_id,
api_id,
method = %req.method(),
path = %req.uri().path(),
status = response.status().as_u16(),
latency_ms,
"Request processed"
);
metrics::histogram!("gateway_request_duration_ms", latency_ms as f64);
metrics::increment_counter!("gateway_requests_total",
"status" => response.status().as_u16().to_string(),
"agent_id" => agent_id,
"skill_id" => skill_id,
);
response
}
六、数据流向
6.1 入口请求数据流

6.2 Skill API 调用数据流

七、时序图
7.1 完整请求处理时序
plantuml
@startuml
title 完整请求处理时序图
autonumber
participant "调用方" as Client
participant "AI 网关" as Gateway
participant "Agent 服务" as Agent
participant "后端 API" as Backend
participant "权益中心" as Rights
participant "Redis" as Redis
== 入口请求阶段 ==
Client -> Gateway: 1. POST /api/v1/agent/{id}/invoke\nHeaders: X-AK, X-Signature, X-Timestamp, X-Nonce
Gateway -> Redis: 2. 查询 AK 对应 SK
Redis --> Gateway: 返回 sk_encrypted
Gateway -> Gateway: 3. 验证时间戳 (±5 分钟)
Gateway -> Gateway: 4. 计算签名并比对
Gateway -> Redis: 5. 检查 Nonce 是否重复
Redis --> Gateway: 返回检查结果
Gateway -> Redis: 6. 记录 Nonce(TTL 5 分钟)
Gateway -> Gateway: 7. 限流检查(API全局 → Agent+Skill+API → 用户 → Agent系统额度)
Gateway -> Gateway: 8. 查找 Agent 路由配置
Gateway -> Agent: 9. 透传请求\nHeaders: X-User-ID, X-AK, X-Agent-ID
== Agent 业务处理阶段 ==
Agent -> Agent: 10. 解析请求,编排 Workflow
Agent -> Agent: 11. 调用 Skill API
== Skill API 扣费阶段 ==
Agent -> Gateway: 12. POST /api/v1/skill/{skill_id}/v{version}/{api_path}
activate Gateway
Gateway -> Gateway: 13. 路径匹配 → 识别 Skill + API + 扣费策略
Gateway -> Rights: 14. POST /billing/deduct\n{user_id, agent_id, skill_id, api_id, version, points}
activate Rights
alt 扣费成功
Rights -> Rights: 扣减额度 + 记录流水
Rights --> Gateway: 返回成功 + transaction_id
Gateway -> Backend: 15. 转发请求\n注入完整上下文 Headers
Backend --> Gateway: 16. 返回结果
Gateway --> Agent: 17. 返回 API 响应
Agent --> Gateway: 18. 返回 Agent 处理结果
Gateway --> Client: 19. 返回最终响应
else 扣费失败
Rights --> Gateway: 返回 QUOTA_EXCEEDED
Gateway --> Client: 返回 402 + 标准错误体
end
destroy Rights
destroy Gateway
destroy Agent
destroy Backend
@enduml
八、数据库设计
8.1 用户表(users)
sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL UNIQUE,
username VARCHAR(128) NOT NULL,
email VARCHAR(256),
department VARCHAR(128),
status SMALLINT NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
8.2 AKSK 表(aksk)
sql
CREATE TABLE aksk (
ak VARCHAR(64) PRIMARY KEY,
sk_encrypted TEXT NOT NULL,
user_id VARCHAR(64) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
created_at BIGINT NOT NULL,
expired_at BIGINT NOT NULL DEFAULT 0,
rate_limit INTEGER,
allowed_agents JSONB NOT NULL DEFAULT '[ ]'::jsonb,
last_used_at BIGINT,
CONSTRAINT fk_aksk_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);
8.3 API 定义表(api_definitions)
sql
-- 系统中所有 API 能力的元数据,本身无商业化属性
CREATE TABLE api_definitions (
id BIGSERIAL PRIMARY KEY,
api_id VARCHAR(64) NOT NULL UNIQUE, -- 全局唯一,如 api_video_generate
name VARCHAR(256) NOT NULL,
description TEXT,
department VARCHAR(128) NOT NULL,
owner VARCHAR(128) NOT NULL,
method VARCHAR(16) NOT NULL, -- GET / POST / PUT / DELETE
path_pattern VARCHAR(512) NOT NULL, -- /api/v1/video/generate
backend_service VARCHAR(256) NOT NULL, -- 后端服务名
backend_url VARCHAR(512) NOT NULL, -- 后端实际地址
timeout_ms INTEGER NOT NULL DEFAULT 30000,
request_schema JSONB, -- OpenAPI 请求参数 schema
response_schema JSONB, -- OpenAPI 响应 schema
global_qps_limit INTEGER, -- API 全局 QPS 上限(Layer 4)
global_burst_limit INTEGER,
status SMALLINT NOT NULL DEFAULT 1, -- 1:active 2:disabled 3:deprecated
source VARCHAR(32) NOT NULL DEFAULT 'manual', -- manual / imported
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
CREATE INDEX idx_api_department ON api_definitions(department);
CREATE INDEX idx_api_status ON api_definitions(status);
8.4 Agent 表(agents)
sql
CREATE TABLE agents (
id BIGSERIAL PRIMARY KEY,
agent_id VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(256) NOT NULL,
description TEXT,
backend_url VARCHAR(512) NOT NULL,
health_check_url VARCHAR(512),
load_balancer JSONB NOT NULL,
system_token_quota BIGINT NOT NULL DEFAULT 0, -- Agent 系统级 Token 总额度(Layer 1)
user_default_quota BIGINT NOT NULL DEFAULT 0, -- 该 Agent 下用户默认额度(Layer 2)
status SMALLINT NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
8.5 Agent 用户额度覆盖表(agent_user_quotas)
sql
-- 管理员为特定 Agent 下的特定用户设置独立额度(Layer 2 覆盖)
CREATE TABLE agent_user_quotas (
id BIGSERIAL PRIMARY KEY,
agent_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
token_quota BIGINT NOT NULL,
used_tokens BIGINT NOT NULL DEFAULT 0,
reset_at BIGINT,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE (agent_id, user_id)
);
8.6 Agent 对 Skill API 限流配置表(agent_api_rate_limits)
sql
-- 管理员为 Agent + Skill + API 三元组设置限流阈值(Layer 3)
CREATE TABLE agent_api_rate_limits (
id BIGSERIAL PRIMARY KEY,
agent_id VARCHAR(64) NOT NULL,
skill_id VARCHAR(64) NOT NULL,
api_id VARCHAR(64) NOT NULL,
qps_limit INTEGER NOT NULL,
burst_limit INTEGER NOT NULL DEFAULT 0,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE (agent_id, skill_id, api_id)
);
8.7 Skill 表(skills)
sql
-- Skill 主表,描述 Skill 的基本信息和说明文档
CREATE TABLE skills (
id BIGSERIAL PRIMARY KEY,
skill_id VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(256) NOT NULL,
description_doc TEXT NOT NULL, -- Skill 文本描述说明书(Markdown)
department VARCHAR(128) NOT NULL,
owner VARCHAR(128) NOT NULL,
status SMALLINT NOT NULL DEFAULT 1, -- 1:active 2:disabled
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
8.8 Skill 版本表(skill_versions)
sql
-- Skill 版本表,每个版本审核通过后锁定,内容不可修改
CREATE TABLE skill_versions (
id BIGSERIAL PRIMARY KEY,
skill_version_id VARCHAR(64) NOT NULL UNIQUE,
skill_id VARCHAR(64) NOT NULL,
version VARCHAR(32) NOT NULL, -- v1.2.0
changelog TEXT, -- 本版本变更说明
status SMALLINT NOT NULL DEFAULT 1, -- 1:draft 2:reviewing 3:approved 4:rejected 5:disabled
download_url VARCHAR(512), -- 审核通过后生成
approved_at BIGINT,
approved_by VARCHAR(128),
rejected_reason TEXT,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
UNIQUE (skill_id, version),
CONSTRAINT fk_sv_skill FOREIGN KEY (skill_id) REFERENCES skills(skill_id)
);
CREATE INDEX idx_sv_skill_id ON skill_versions(skill_id);
CREATE INDEX idx_sv_status ON skill_versions(status);
8.9 Skill 版本 API 绑定表(skill_version_api_bindings)
sql
-- Skill 版本与 API 的绑定关系,以及该 API 在此 Skill 中的商业化策略
-- API 只有在此处与 Skill 绑定后,才具备扣费语义
CREATE TABLE skill_version_api_bindings (
id BIGSERIAL PRIMARY KEY,
skill_version_id VARCHAR(64) NOT NULL,
api_id VARCHAR(64) NOT NULL,
billing_type VARCHAR(32) NOT NULL, -- per_call / per_token / per_second
points_per_call BIGINT,
points_per_1k_tokens BIGINT,
points_per_second BIGINT,
currency VARCHAR(32) NOT NULL DEFAULT 'quota_point',
required BOOLEAN NOT NULL DEFAULT true,
description TEXT, -- 该 API 在此 Skill 中的用途说明
UNIQUE (skill_version_id, api_id),
CONSTRAINT fk_svab_version FOREIGN KEY (skill_version_id) REFERENCES skill_versions(skill_version_id),
CONSTRAINT fk_svab_api FOREIGN KEY (api_id) REFERENCES api_definitions(api_id)
);
8.10 Agent Skill 组装表(agent_skill_assemblies)
sql
-- Agent 选择 Skill 版本进行组装的记录,生成固定版本下载链接
CREATE TABLE agent_skill_assemblies (
id BIGSERIAL PRIMARY KEY,
assembly_id VARCHAR(64) NOT NULL UNIQUE,
agent_id VARCHAR(64) NOT NULL,
skill_versions JSONB NOT NULL, -- [{skill_id, skill_version_id, version}, ...]
download_url VARCHAR(512) NOT NULL,
created_by VARCHAR(128) NOT NULL,
created_at BIGINT NOT NULL,
CONSTRAINT fk_asa_agent FOREIGN KEY (agent_id) REFERENCES agents(agent_id)
);
CREATE INDEX idx_asa_agent_id ON agent_skill_assemblies(agent_id);
8.11 用户配额表(user_quotas)
sql
CREATE TABLE user_quotas (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL UNIQUE,
total_points BIGINT NOT NULL DEFAULT 0,
used_points BIGINT NOT NULL DEFAULT 0,
remaining_points BIGINT NOT NULL DEFAULT 0,
reset_at BIGINT,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
CONSTRAINT fk_uq_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);
8.12 扣费流水表(billing_transactions)
sql
CREATE TABLE billing_transactions (
id BIGSERIAL PRIMARY KEY,
transaction_id VARCHAR(64) NOT NULL UNIQUE,
request_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
agent_id VARCHAR(64) NOT NULL,
skill_id VARCHAR(64) NOT NULL,
skill_version VARCHAR(32) NOT NULL,
api_id VARCHAR(64) NOT NULL,
points BIGINT NOT NULL,
billing_type VARCHAR(32) NOT NULL,
status SMALLINT NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL,
metadata JSONB
);
CREATE INDEX idx_bt_user_id ON billing_transactions(user_id);
CREATE INDEX idx_bt_agent_id ON billing_transactions(agent_id);
CREATE INDEX idx_bt_created_at ON billing_transactions(created_at);
九、错误码
| 错误码 | HTTP 状态码 | 说明 |
|---|---|---|
| AUTH_FAILED | 401 | 验签失败 |
| AK_INVALID | 401 | AK 不存在 |
| SIGNATURE_INVALID | 401 | 签名不匹配 |
| TIMESTAMP_EXPIRED | 401 | 时间戳超出窗口 (±5 分钟) |
| NONCE_DUPLICATE | 401 | Nonce 重复 (重放攻击) |
| AGENT_NOT_FOUND | 404 | Agent 不存在或无权访问 |
| SKILL_NOT_FOUND | 404 | Skill 不存在 |
| SKILL_VERSION_NOT_FOUND | 404 | Skill 版本不存在 |
| SKILL_DISABLED | 403 | Skill 未启用或已下架 |
| API_NOT_FOUND | 404 | API 不存在 |
| API_DISABLED | 403 | API 已禁用或废弃 |
| QUOTA_EXCEEDED | 402 | 额度不足 |
| BILLING_SYSTEM_ERROR | 502 | 权益中心系统错误 |
| BACKEND_UNAVAILABLE | 503 | 后端服务不可用 |
| BACKEND_TIMEOUT | 504 | 后端服务超时 |
| RATE_LIMITED | 429 | 调用频率超限 |
| RATE_LIMITED_API_GLOBAL | 429 | API 全局限流触发 |
| RATE_LIMITED_AGENT_API | 429 | Agent 对该 Skill API 限流触发 |
标准错误响应格式
json
{
"code": "QUOTA_EXCEEDED",
"http_status": 402,
"message": "额度不足,需要 50 点,剩余额度 20 点",
"details": {
"skill_id": "skill_video_gen",
"skill_version": "v1.2.0",
"api_id": "api_video_generate",
"agent_id": "agent_video_001",
"user_id": "user_123",
"required_points": 50,
"remaining_points": 20
},
"request_id": "req_xxxxxxxxxxxxxxxx",
"timestamp": 1743408000
}
十、部署架构
10.1 部署拓扑

10.2 弹性扩缩容
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ai-gateway-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ai-gateway
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "10000"
十一、监控与告警
11.1 核心指标
| 指标 | 类型 | 告警阈值 |
|---|---|---|
| gateway_request_duration_ms | Histogram | P99 > 50ms |
| gateway_requests_total | Counter | 错误率 > 5% |
| auth_failures_total | Counter | 失败率 > 10% |
| billing_deduct_total | Counter | 失败率 > 3% |
| user_remaining_points | Gauge | < 100 点预警 |
| api_global_rate_limit_total | Counter | 触发次数突增告警 |
| agent_api_rate_limit_total | Counter | 触发次数突增告警 |
11.2 日志规范
json
{
"level": "INFO",
"timestamp": "2026-03-30T12:00:00Z",
"trace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"event": "request_processed",
"data": {
"user_id": "user_123",
"ak": "ak_xxx",
"agent_id": "agent_video_001",
"skill_id": "skill_video_gen",
"skill_version": "v1.2.0",
"api_id": "api_video_generate",
"status": 200,
"latency_ms": 1250,
"billing_points": 50
}
}
原型图链接:https://modao.cc/proto/qiYhRGBAtcq2j6Oh6gXcHF/sharing?view_mode=read_only&screen=VFN75VpB7egrQW #AI网关数据面原型设计-分享




