2133 lines
109 KiB
Markdown
2133 lines
109 KiB
Markdown
# EasyAI AI 网关中台设计文档
|
||
|
||
## 1. 建设目标
|
||
|
||
将现有 `easyai-server-main` / `easyai-integration-api` 中与 `integration-platform` 强相关的能力拆分为一个独立项目:
|
||
|
||
- 独立运行:后端 Go、复用 Agent memory 的 PostgreSQL 18(`easyai-pgvector`)、前端 React + TSX。
|
||
- 前端 UI:使用 `shadcn-ui` + Tailwind CSS + Radix UI + `lucide-react`,保持控制台形态与 EasyAI 运维后台一致。
|
||
- 独立部署:HTTP API、任务执行器、任务事件持久化与进度回调均由本服务承载;接入 EasyAI 主站时,业务前端实时推送继续复用 `server-main` 现有 WebSocket 网关。
|
||
- 身份双模式:支持 Gateway 本地账号注册登录,也支持沿用 `easyai-server-main` 的 JWT claim、角色权限和 OpenAPI `sk-*` 校验方式。
|
||
- 能力对接:作为 Chat、生图、生视频等模型能力网关,供 `easyai-server-main` 和前端直接调用。
|
||
- 渐进迁移:新服务稳定后,再让 `server-main` 的 `OpenaiService` / `IntegrationPlatformService` 切为远程调用门面。
|
||
|
||
## 2. 迁移边界
|
||
|
||
### 2.1 迁入 AI Gateway
|
||
|
||
| 能力 | 新服务职责 |
|
||
| --- | --- |
|
||
| 平台管理 | 平台 CRUD、启停、优先级、凭证配置、可用性检测、默认折扣系数 |
|
||
| 基准模型库 | 维护全量 provider、基准模型、模型能力、图像/视频/文本能力标签、基准定价和默认限流模板 |
|
||
| 模型管理 | 平台模型、模型类型、能力标签、上下文窗口、计费配置;支持从基准模型继承、按折扣继承或自由覆盖 |
|
||
| 模型路由 | 复用现有 `assignClientsByModelName` 的平台过滤、并发负载、优先级、队列等待语义 |
|
||
| 生成任务 | Chat、图片生成、图片编辑、视频生成、语音、Embedding 等任务入口 |
|
||
| 队列执行 | 按平台/模型/方法维度做并发控制、等待队列、超时、重试;队列状态必须持久化,服务异常重启后可恢复 |
|
||
| 测试模式 | 支持 simulation / dry-run,不向真实平台提交任务,仍完整走路由、队列、限流、重试、进度、结果归一流程 |
|
||
| 计费预估 | 在任务创建前或前端配置面提供 estimated billing,且必须使用与真实路由一致的有效模型价格 |
|
||
| 任务结果 | 保存任务请求、状态、结果摘要、billings,向主服务发送结算事件 |
|
||
| 推送 | 承接任务级进度事件,提供类似原 RxJS Observable 的中间过程返回能力;业务实时进度通过配置回调地址回调 `server-main` 内部任务进度接口,再由主服务复用原 WebSocket 网关推给业务前端 |
|
||
|
||
### 2.2 接入 server-main 时仍保留在 server-main
|
||
|
||
| 能力 | 原因 |
|
||
| --- | --- |
|
||
| 用户、组织、租户、角色 | EasyAI 主站模式下仍是主账号体系;Gateway 保存同步副本和策略快照 |
|
||
| 余额、资源包、扣费流水 | 需要复用现有账单锁、组织扣费、消费记录 |
|
||
| API Key 创建与撤销 | 接入模式下 `sk-*` 生命周期属于主服务用户体系;独立模式由 Gateway 本地 API Key 模块承接 |
|
||
| 文件上传 | 复用 `server-main` 已开放的文件上传接口,OSS/COS/S3 密钥和上传实现继续只落在主服务 |
|
||
| 业务 WebSocket 网关 | 现有前端已经订阅主服务 WebSocket 通道,Gateway 通过任务进度回调接入,不直接替换业务推送链路 |
|
||
| 对话、绘图历史、工作流历史 | 与产品域模型绑定,Gateway 只返回任务结果和结算载荷 |
|
||
|
||
## 3. 总体架构
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph client [调用方]
|
||
FE[React / easyai-main-web]
|
||
MAIN[easyai-server-main]
|
||
OPENAPI[OpenAPI Client]
|
||
end
|
||
|
||
subgraph gw [API Gateway / Ingress]
|
||
HTTP[HTTP Routes]
|
||
PUSH[SSE / WebSocket Routes]
|
||
end
|
||
|
||
subgraph aigw [EasyAI AI Gateway]
|
||
API[Go HTTP API]
|
||
AUTH[Auth Middleware]
|
||
ROUTER[Model Router]
|
||
QUEUE[Queue + Runtime]
|
||
CALLBACK[Task Progress Callback]
|
||
VENDOR[Vendor Clients]
|
||
ADMIN[React Admin Console]
|
||
end
|
||
|
||
subgraph data [Data]
|
||
PG[(Agent memory PostgreSQL<br/>easyai-pgvector)]
|
||
REDIS[(Redis later)]
|
||
end
|
||
|
||
subgraph servermain [server-main]
|
||
USER[User / Org]
|
||
APIKEY[API Key Verify]
|
||
WSGW[WebSocket Gateway]
|
||
BILL[Billing + Ledger]
|
||
FILES[Open File Upload]
|
||
end
|
||
|
||
FE --> HTTP --> API
|
||
FE <-->|business progress ws| WSGW
|
||
ADMIN --> PUSH
|
||
OPENAPI --> HTTP --> API
|
||
MAIN -->|internal HTTP| API
|
||
ADMIN --> API
|
||
API --> AUTH
|
||
AUTH -->|JWT local verify| API
|
||
AUTH -->|sk-* delegate| APIKEY
|
||
API --> ROUTER --> QUEUE --> VENDOR
|
||
API --> PG
|
||
QUEUE --> PG
|
||
QUEUE --> CALLBACK -->|POST task progress callback to server-main| WSGW
|
||
QUEUE -->|settlement event| BILL
|
||
API -->|POST /v1/files/upload| FILES
|
||
```
|
||
|
||
## 4. Monorepo 方案
|
||
|
||
本脚手架采用 `Nx + pnpm + go.work`:
|
||
|
||
- `Nx` 负责任务编排、缓存、前后端统一命令:`dev`、`build`、`test`、`migrate`。
|
||
- `go.work` 管理 Go 模块,避免把 Go 项目硬塞进 Node 包管理。
|
||
- `pnpm-workspace.yaml` 管理 React 前端与 TypeScript contracts。
|
||
- 前端控制台使用 `shadcn-ui` 作为组件体系,配套 Tailwind CSS、Radix UI primitive、`lucide-react` 图标和 `class-variance-authority` 管理组件变体。
|
||
|
||
选择这个组合的原因:
|
||
|
||
- 与 EasyAI 现有 Nx 使用习惯接近,便于团队统一命令。
|
||
- Go 后端保持 Go 原生模块边界,后续可以拆 `apps/worker`、`libs/go/*`。
|
||
- 前端仍可复用 React 生态和 TSX 风格,contracts 可给前端、SDK、BFF 共用。
|
||
- `shadcn-ui` 代码是可拷贝进仓库的 TSX 组件,不绑定运行时 UI 框架,适合后续沉淀平台管理、队列监控、模型计费配置等复杂表单。
|
||
|
||
### 4.1 前端 UI 约束
|
||
|
||
- 基础组件统一从 `apps/web/src/components/ui/*` 引入,不直接散落自定义 button/input/table 样式。
|
||
- 首期需要引入并固化:`Button`、`Input`、`Textarea`、`Select`、`Switch`、`Checkbox`、`Tabs`、`Dialog`、`Sheet`、`Table`、`Badge`、`Alert`、`Toast/Sonner`、`Tooltip`、`Progress`、`DropdownMenu`、`Form`。
|
||
- 图标统一使用 `lucide-react`,按钮类动作优先用语义图标加 tooltip。
|
||
- 控制台不是营销页,布局以高密度管理视图为主:平台列表、模型配置、队列监控、任务详情、客户端运行状态、限流配置、重试策略。
|
||
- 主题 token 以 CSS variables 管理,后续可与 EasyAI 全局后台色板对齐。
|
||
|
||
## 5. 授权设计
|
||
|
||
### 5.1 身份模式与 JWT 用户授权
|
||
|
||
Gateway 支持三种身份模式:
|
||
|
||
- `standalone`:Gateway 本地账号注册登录,签发 Gateway JWT。
|
||
- `server-main`:只接受 `server-main` JWT / OpenAPI `sk-*` 校验结果,用户与租户从主服务同步。
|
||
- `hybrid`:同时接受 Gateway 本地账号和 `server-main` 身份,按 `source` 区分。
|
||
|
||
接入 `server-main` 时兼容主服务 JWT:
|
||
|
||
- secret:读取 `CONFIG_JWT_SECRET`,默认值与现有 `jwtConstants.secret` 保持一致。
|
||
- token 有效期:由 `server-main` 签发控制,当前 access token 为 `600s`,refresh token 为 `7d`。
|
||
- claim 兼容:
|
||
- `sub`:用户 ID
|
||
- `username`
|
||
- `role`
|
||
- `tenantId`
|
||
- `gatewayTenantId`
|
||
- `tenantKey`
|
||
- `source`
|
||
- `gatewayUserId`
|
||
- `userGroupId` / `userGroupKey` / `userGroupKeys`
|
||
- `sso_id`
|
||
- API Key 场景扩展:`apiKeyId`、`apiKeySecret`、`apiKeyName`
|
||
|
||
Gateway 本地登录首期只签发短期 access token,不做 refresh token;接入 `server-main` 的刷新仍走主服务。
|
||
|
||
### 5.1.1 本地账号注册登录
|
||
|
||
首期普通账号只做用户名/邮箱 + 密码:
|
||
|
||
| Method | Path | Permission | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `POST` | `/api/v1/auth/register` | `public` | 注册 Gateway 本地账号,创建或加入租户,返回 access token |
|
||
| `POST` | `/api/v1/auth/login` | `public` | 本地账号登录,返回 access token |
|
||
|
||
注册字段:
|
||
|
||
- `username` / `email`:至少一个必填。
|
||
- `password`:至少 8 位,落库为 bcrypt hash。
|
||
- `tenantKey` / `tenantName`:可选;未传时创建个人租户。
|
||
|
||
安全边界:
|
||
|
||
- 普通注册首期只给 `user` 角色。
|
||
- 允许注册时填写已有 `tenantKey` 只是脚手架行为;正式上线前需要租户邀请、域名校验或管理员审批。
|
||
- `server-main` 模式可关闭本地注册登录,只保留外部 token 入口。
|
||
|
||
### 5.2 权限等级
|
||
|
||
权限枚举与 `server-main` 保持一致:
|
||
|
||
| Permission | 用途 |
|
||
| --- | --- |
|
||
| `public` | 健康检查、公开模型列表等 |
|
||
| `basic` | 普通用户任务创建、任务查询 |
|
||
| `creat` | 创作者级能力 |
|
||
| `power` | 平台配置、模型配置、计费配置 |
|
||
| `manager` | 超级管理与危险操作 |
|
||
|
||
角色映射与 `server-main` 一致:`user`、`creator`、`operator`、`manager`、`admin`。
|
||
|
||
### 5.3 OpenAPI API Key
|
||
|
||
对于 `Authorization: Bearer sk-*` 或 `x-comfy-api-key`:
|
||
|
||
1. Gateway 不直接持有 API Key hash。
|
||
2. Gateway 调用 `server-main` 的内部校验接口。
|
||
3. `server-main` 返回标准用户 claim。
|
||
4. Gateway 将 claim 放入请求上下文,后续任务、结算事件都带 `apiKeyId` / `apiKeyName`。
|
||
|
||
建议在 `server-main` 增加内部接口:
|
||
|
||
```http
|
||
POST /internal/platform/auth/verify-api-key
|
||
Authorization: Bearer ${SERVER_MAIN_INTERNAL_TOKEN}
|
||
Content-Type: application/json
|
||
|
||
{ "apiKey": "sk-..." }
|
||
```
|
||
|
||
### 5.4 内部服务授权
|
||
|
||
`server-main` 调 Gateway 内部接口时使用服务令牌:
|
||
|
||
```http
|
||
Authorization: Bearer ${SERVER_MAIN_INTERNAL_TOKEN}
|
||
X-EasyAI-Actor: easyai-server-main
|
||
```
|
||
|
||
内部令牌只用于服务间调用,不替代用户上下文。用户发起的任务仍应透传用户 JWT 或 API Key claim。
|
||
|
||
## 6. 接口规范与兼容路由
|
||
|
||
### 6.1 兼容优先原则
|
||
|
||
AI Gateway 的外部接口必须以“兼容现有 EasyAI 路由、请求 DTO、响应结构、错误码、流式语义”为第一目标,不能要求前端或 `server-main` 因拆分服务而大面积改调用路径。
|
||
|
||
- **管理面兼容**:沿用原 `integration-platform`、`integration/platform-api`、`integration-platform/snapshots` 路由。
|
||
- **站内生成接口兼容**:沿用原 `OpenaiController` 根路径接口,例如 `/chat/completions`、`/images/generations`。
|
||
- **OpenAPI 兼容**:沿用 `/v1/*` 路由,例如 `/v1/chat/completions`、`/v1/images/generations`,API Key 语义与原来一致。
|
||
- **新增接口隔离**:`/api/v1/*` 只用于 Gateway 自身控制台、调试、内部 SDK 或新能力,不作为迁移期替代原路由。
|
||
- **响应包装**:对原来返回 `ResOp` 的管理接口继续返回同形态;对 OpenAI-compatible 接口保持原 JSON / stream chunk 结构;对任务类接口保持原 `taskId`、`status`、`url`、`billings` 等字段语义。
|
||
- **路径匹配顺序**:迁移 `integration-platform` 时必须保留静态路径优先于 `:id` / `:action/:id` 的规则,避免 `models/list/enabled`、`dynamic-priority/:id`、`scene-script/:id/partial` 被动态路由误吞。
|
||
|
||
### 6.2 平台管理兼容路由
|
||
|
||
| Method | Path | Permission | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `POST` | `/integration-platform` | `power` | 创建平台 |
|
||
| `POST` | `/integration-platform/preset` | `power` | 创建预置平台 |
|
||
| `GET` | `/integration-platform` | `power` | 平台列表 |
|
||
| `GET` | `/integration-platform/list/ops-compact` | `power` | 运维精简列表 |
|
||
| `GET` | `/integration-platform/queue-stats` | `power` | 队列统计 |
|
||
| `POST` | `/integration-platform/clear-queue/:platformId` | `power` | 清理平台队列 |
|
||
| `POST` | `/integration-platform/find-by-ids` | `power` | 按 ID 批量查平台 |
|
||
| `POST` | `/integration-platform/find-by-model` | `basic` | 按模型查候选平台 |
|
||
| `GET` | `/integration-platform/models/list/enabled` | `basic` | 启用模型列表 |
|
||
| `GET` | `/integration-platform/models/list/all` | `power` | 全量模型列表 |
|
||
| `PATCH` | `/integration-platform/dynamic-priority/:id` | `power` | 动态优先级 |
|
||
| `DELETE` | `/integration-platform/dynamic-priority/:id` | `power` | 删除动态优先级 |
|
||
| `POST` | `/integration-platform/disable/:id` | `power` | 禁用平台 |
|
||
| `POST` | `/integration-platform/enable-model/:id` | `power` | 启用平台模型 |
|
||
| `PATCH` | `/integration-platform/scene-script/:id/partial` | `power` | 局部更新场景脚本 |
|
||
| `PATCH` | `/integration-platform/:action/:id` | `power` | 兼容启停等 action |
|
||
| `PATCH` | `/integration-platform/:id` | `power` | 更新平台 |
|
||
| `DELETE` | `/integration-platform/:id` | `manager` | 删除平台 |
|
||
| `GET` | `/integration-platform/models/:type` | `basic` | 按类型查模型 |
|
||
| `GET` | `/integration-platform/models/strict/:type` | `basic` | 按类型严格查模型 |
|
||
| `POST` | `/integration-platform/models/estimatedBilling` | `basic` | 预估扣费 |
|
||
| `POST` | `/integration-platform/models/estimatedBilling/:workflowId` | `basic` | 工作流预估扣费 |
|
||
| `POST` | `/integration-platform/resetException/:id` | `power` | 重置异常状态 |
|
||
| `POST` | `/integration-platform/:id/copy` | `power` | 复制平台 |
|
||
| `GET` | `/integration-platform/:id` | `power` | 平台详情 |
|
||
|
||
兼容表中的旧权限用于保持原接口语义;在新 Gateway 第一阶段,所有写入 provider / platform 凭证、启停平台、删除平台、复制平台的接口都要额外要求全局管理员,即 `manager/admin` 角色。
|
||
|
||
### 6.3 平台 API 配置兼容路由
|
||
|
||
| Method | Path | Permission | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `POST` | `/integration/platform-api` | `power` | 创建 API 配置 |
|
||
| `GET` | `/integration/platform-api` | `power` | API 配置列表 |
|
||
| `GET` | `/integration/platform-api/:id` | `power` | API 配置详情 |
|
||
| `PATCH` | `/integration/platform-api/:id` | `power` | 更新 API 配置 |
|
||
| `DELETE` | `/integration/platform-api/:id` | `manager` | 删除 API 配置 |
|
||
| `POST` | `/integration/platform-api/execute` | `power` | 调试执行 |
|
||
| `GET` | `/integration-platform/snapshots/platform/:platformId` | `power` | 平台快照 |
|
||
|
||
### 6.4 站内生成兼容路由
|
||
|
||
| Method | Path | 说明 |
|
||
| --- | --- | --- |
|
||
| `GET` | `/models/list` | 站内模型列表 |
|
||
| `POST` | `/chat/completions` | 站内 Chat |
|
||
| `POST` | `/chat/completions/cancel/:requestId` | 取消 Chat |
|
||
| `POST` | `/chat/structured-output` | 结构化输出 |
|
||
| `POST` | `/images/generations` | 生图 |
|
||
| `POST` | `/images/edits` | 图片编辑 |
|
||
| `POST` | `/video/generations` | 生视频 |
|
||
| `POST` | `/embeddings` | Embeddings |
|
||
| `GET` | `/embeddings/models` | Embedding 模型 |
|
||
| `GET` | `/ai/result/:taskId` | 任务结果 |
|
||
|
||
### 6.5 OpenAPI 兼容路由
|
||
|
||
| Method | Path | 说明 |
|
||
| --- | --- | --- |
|
||
| `POST` | `/v1/chat/completions` | OpenAI-compatible Chat |
|
||
| `POST` | `/v1/chat/completions/cancel/:requestId` | 取消 Chat |
|
||
| `POST` | `/v1/images/generations` | 生图 |
|
||
| `POST` | `/v1/images/edits` | 图片编辑 |
|
||
| `POST` | `/v1/video/generations` | 生视频 |
|
||
| `GET` | `/v1/ai/result/:taskId` | 任务结果 |
|
||
| `GET` | `/v1/ai/cancel/:taskId` | 取消任务 |
|
||
| `POST` | `/v1/embeddings` | Embeddings |
|
||
| `GET` | `/v1/embeddings/models` | Embedding 模型 |
|
||
| `GET` | `/v1/models` | OpenAI-compatible 模型列表 |
|
||
| `GET` | `/v1/models/list` | EasyAI 模型列表 |
|
||
|
||
`/v1/balance`、`/v1/files/upload`、`/v1/creatToken`、`/v1/removeToken` 与用户余额、文件、API Key 生命周期强绑定,默认仍由 `server-main` 保留;若网关暴露同路径,只能做兼容代理或调用 `server-main` 内部接口。
|
||
|
||
### 6.6 Gateway 新增接口
|
||
|
||
| Method | Path | Permission | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `POST` | `/api/v1/auth/register` | `public` | Gateway 本地账号注册,`invitationCode` 可选 |
|
||
| `POST` | `/api/v1/auth/login` | `public` | Gateway 本地账号登录 |
|
||
| `GET` | `/api/v1/me` | `basic` | 网关当前身份调试 |
|
||
| `GET` | `/api/v1/catalog/providers` | `power` | 基准 provider 列表 |
|
||
| `GET` | `/api/v1/catalog/base-models` | `power` | 基准模型库列表 |
|
||
| `GET` | `/api/v1/catalog/base-models/:id` | `power` | 基准模型详情 |
|
||
| `GET` | `/api/v1/tenants` | `power` | 网关租户列表,支持本地租户和 server-main 同步租户 |
|
||
| `GET` | `/api/v1/tenants/:id` | `power` | 租户详情、来源、策略和同步状态 |
|
||
| `POST` | `/api/v1/tenants/sync` | `power` | 从 `server-main` 拉取或接收租户同步 |
|
||
| `GET` | `/api/v1/tenant-invitations` | `power` | 本地租户邀请码列表 |
|
||
| `POST` | `/api/v1/tenant-invitations` | `power` | 创建本地注册邀请码 |
|
||
| `GET` | `/api/v1/users` | `power` | 网关用户列表,支持本地用户和 server-main 同步用户 |
|
||
| `GET` | `/api/v1/users/:id` | `power` | 用户详情、来源、角色、用户组和同步状态 |
|
||
| `POST` | `/api/v1/users/sync` | `power` | 从 `server-main` 拉取或接收用户同步 |
|
||
| `GET` | `/api/v1/user-groups` | `power` | 用户组策略列表 |
|
||
| `GET` | `/api/v1/user-groups/:id` | `power` | 用户组详情、成员与策略 |
|
||
| `POST` | `/api/v1/user-groups/:id/sync` | `power` | 同步用户组策略到 `server-main` |
|
||
| `GET` | `/api/v1/wallet/accounts` | `basic` | 独立模式本地余额账户 |
|
||
| `GET` | `/api/v1/wallet/transactions` | `basic` | 独立模式本地余额流水 |
|
||
| `GET` | `/api/v1/recharge/orders` | `basic` | 独立模式充值订单 |
|
||
| `POST` | `/api/v1/recharge/orders` | `basic` | 独立模式创建充值订单 |
|
||
| `GET` | `/api/v1/api-keys` | `basic` | 独立模式 API Key 列表 |
|
||
| `POST` | `/api/v1/api-keys` | `basic` | 独立模式创建 API Key |
|
||
| `PATCH` | `/api/v1/api-keys/:id/disable` | `basic` | 禁用本地 API Key |
|
||
| `POST` | `/api/v1/pricing/estimate` | `basic` | 使用 effective pricing resolver 做价格预估 |
|
||
| `GET` | `/api/v1/pricing/rules` | `power` | 定价规则列表 |
|
||
| `GET` | `/api/v1/tasks/:taskId` | `basic` | Gateway 任务详情 |
|
||
| `GET` | `/api/v1/tasks/:taskId/events` | `basic` | SSE 任务进度 |
|
||
| `GET` | `/api/v1/runtime/clients` | `power` | 客户端运行状态 |
|
||
| `GET` | `/api/v1/runtime/queues` | `power` | 队列与限流状态 |
|
||
| `GET` | `/api/v1/runtime/rate-limit-windows` | `power` | TPM/RPM 当前窗口与并发 lease 状态 |
|
||
| `POST` | `/api/v1/runtime/tasks/:taskId/replay` | `power` | 重放任务事件 |
|
||
| `POST` | `/api/v1/runtime/tasks/:taskId/callbacks/replay` | `power` | 重放任务进度回调 outbox |
|
||
|
||
### 6.7 内部接口
|
||
|
||
| Method | Path | 调用方 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `POST` | `/internal/v1/settlements` | Gateway worker | 回调主服务结算失败时补偿 |
|
||
| `POST` | `${TASK_PROGRESS_CALLBACK_URL}` | Gateway worker | 任务进度回调到 `server-main` 内部任务进度接口,实际路径由配置决定 |
|
||
| `POST` | `/internal/platform/tenants/sync` | server-main | 同步租户增量、禁用状态和租户策略到 Gateway |
|
||
| `POST` | `/internal/platform/users/sync` | server-main | 同步用户增量、禁用状态、角色和用户组关系到 Gateway |
|
||
| `POST` | `/internal/platform/user-groups/sync` | server-main | 同步用户组、折扣和限流策略到 Gateway |
|
||
| `POST` | `/internal/v1/task-callbacks` | server-main | 迁移期主服务回写历史或任务绑定 |
|
||
|
||
## 7. 数据模型
|
||
|
||
数据模型按“基准模型库 -> 平台实例 -> 平台模型覆盖 -> 任务运行态”分层:
|
||
|
||
- 基准模型库保存 provider、canonical model、能力 schema、默认能力、基准价格、默认限流模板,是所有平台模型的 fallback。
|
||
- 创建平台时可以设置 `default_discount_factor`,平台模型默认按“基准价格 x 折扣系数”计算;没有任何平台侧配置时,能力和价格都 follow 基准模型。
|
||
- 平台模型可以覆盖能力、定价和限流。覆盖只作用于该平台模型,不能反向污染基准模型库。
|
||
- 租户是隔离域:独立模式下 Gateway 管理 `gateway_tenants`;接入模式下同步 `server-main` 租户/组织,任务、用户、平台可见性、限流与计费策略都需要带租户上下文。
|
||
- 用户是身份域:独立部署时 Gateway 自己维护 `gateway_users`、登录、API Key、余额、充值订单和钱包流水;接入 `server-main` 时 Gateway 保存用户同步副本和策略快照,认证、余额、充值、订单仍可由 `server-main` 作为事实源。
|
||
- 用户组是策略作用域:不同用户组可以绑定充值折扣、模型计费折扣、TPM/RPM/并发、队列优先级、API Key 配额等策略。独立模式由 Gateway 完整执行;`server-main` 模式下充值和余额执行归主服务,Gateway 执行模型调用侧策略。
|
||
- estimated billing、真实任务 billings、控制台价格预览必须走同一个 effective pricing resolver。
|
||
|
||
### 7.0 身份运行模式
|
||
|
||
Gateway 需要支持三种身份运行模式,默认配置为 `IDENTITY_MODE=hybrid`:
|
||
|
||
| 模式 | 配置 | 用户事实源 | API Key / 登录 | 用户组策略 | 适用场景 |
|
||
| --- | --- | --- | --- | --- | --- |
|
||
| Standalone | `IDENTITY_MODE=standalone` | Gateway `gateway_users` | Gateway 本地用户、密码/SSO、API Key | Gateway 维护并执行充值折扣、调用折扣、限流和并发 | 独立商业化或单独部署 |
|
||
| Server-main gateway | `IDENTITY_MODE=server-main` | `server-main` | JWT 复用主服务 secret,`sk-*` 调用主服务校验 | `server-main` 与 Gateway 同步;主服务执行充值/余额,Gateway 执行调用折扣和限流 | EasyAI 主站拆分后的 AI 网关 |
|
||
| Hybrid | `IDENTITY_MODE=hybrid` | Gateway + `server-main` | 两种方式并存,按 `source` 区分 | 按 `source + group_key` 合并,冲突以更高优先级策略为准 | 迁移灰度或私有化集成 |
|
||
|
||
本地注册规则:
|
||
|
||
- 普通注册默认开放,`invitationCode` 可选;不填邀请码时创建或复用 `tenantKey` 对应的 Gateway 本地租户,未填 `tenantKey` 时生成个人租户。
|
||
- 填写邀请码时必须命中 `gateway_tenant_invitations` 的有效记录,注册用户加入邀请码指定租户,并可绑定邀请码指定用户组。
|
||
- 平台凭证、provider 凭证和全局模型配置暂时只允许全局管理员维护;租户侧先只做模型使用、用量查看和策略展示。
|
||
|
||
身份解析流程:
|
||
|
||
1. 从 JWT / API Key / 内部调用 token 解析出外部身份。
|
||
2. 根据 `source + external_user_id` 在 `gateway_users` 找到或创建同步副本。
|
||
3. 按用户、租户、API Key、组织命中 `gateway_user_group_memberships`。
|
||
4. 根据用户组优先级和策略合并规则得到 effective policy。
|
||
5. 创建任务时把 `gateway_user_id`、`user_source`、`user_group_id`、`user_group_key`、`user_group_policy_snapshot` 写入 `gateway_tasks`,后续重试和结算不受同步变更影响。
|
||
|
||
### 7.0.1 多租户模型
|
||
|
||
多租户支持不能只停留在 claim 的 `tenantId` 字符串,Gateway 需要有自己的租户表和执行上下文:
|
||
|
||
- `gateway_tenants` 保存租户事实或同步副本,使用 `source + external_tenant_id` 幂等同步。
|
||
- `gateway_users.gateway_tenant_id` 关联 Gateway 租户,`tenant_id` / `tenant_key` 保留与 `server-main` 兼容的外部标识。
|
||
- `gateway_tasks` 固化 `gateway_tenant_id`、`tenant_id`、`tenant_key`,确保任务恢复、重试、结算和审计不受后续租户变更影响。
|
||
- `integration_platforms.visibility_scope` 区分 `global`、`tenant`、`private`,租户专属平台只能被对应租户路由到。
|
||
- 限流、定价、用户组和 quota 都可以以租户作为 scope;同一请求会同时命中 global、tenant、user_group、user、api_key 等策略。
|
||
- 控制台数据查询默认按当前用户租户过滤;`power/manager` 可跨租户查看和管理。
|
||
|
||
### 7.1 `integration_platforms`
|
||
|
||
保存平台实例与凭证:
|
||
|
||
- `provider`:平台类型,如 `openai`、`runninghub`、`jimeng`。
|
||
- `name`:运营可识别名称。
|
||
- `base_url`
|
||
- `auth_type`
|
||
- `credentials`:加密后的凭证 JSON,后续应接入 KMS。
|
||
- `config`:限流、超时、重试、平台私有配置。
|
||
- `visibility_scope`:`global`、`tenant`、`private`,用于多租户平台可见性。
|
||
- `tenant_id` / `tenant_key`:租户专属平台的归属。
|
||
- `default_discount_factor`:平台默认折扣系数,基于基准模型价计算平台有效价。
|
||
- `priority`
|
||
- `status`
|
||
|
||
### 7.2 `platform_models`
|
||
|
||
保存平台模型配置:
|
||
|
||
- `platform_id`
|
||
- `base_model_id`:关联基准模型库,未配置能力/价格时 follow 基准模型。
|
||
- `model_name`
|
||
- `model_type`:`chat`、`image`、`video`、`audio` 等。
|
||
- `capabilities`:平台模型有效能力,来自基准模型能力与平台覆盖合并。
|
||
- `pricing_mode`:`inherit`、`inherit_discount`、`custom`。
|
||
- `discount_factor`:模型级折扣,未设置时使用平台默认折扣。
|
||
- `billing_config`:平台模型有效计费配置,来自基准价、折扣和自定义覆盖。
|
||
- `enabled`
|
||
|
||
### 7.3 `gateway_tasks`
|
||
|
||
保存任务请求与状态:
|
||
|
||
- `kind`:`chat.completions`、`images.generations`、`videos.generations`
|
||
- `user_id`:请求 claim 中的用户 ID,独立模式为 Gateway 用户 ID,接入模式为 `server-main` 用户 ID。
|
||
- `gateway_user_id`:Gateway 本地用户表 ID,用于关联同步副本和审计。
|
||
- `user_source`:`gateway`、`server-main`、`sync`。
|
||
- `tenant_id`
|
||
- `user_group_id` / `user_group_key`
|
||
- `user_group_policy_snapshot`:任务创建时解析出的用户组策略快照,用于审计和重试稳定性。
|
||
- `model`
|
||
- `request`
|
||
- `status`:`queued`、`running`、`succeeded`、`failed`、`cancelled`
|
||
- `queue_key`:限流队列 key,例如 `${platformKey}-${model}` 或 `${provider}-${methodName}`
|
||
- `priority`
|
||
- `idempotency_key`
|
||
- `remote_task_id`
|
||
- `remote_task_payload`
|
||
- `run_mode`:`production`、`simulation`
|
||
- `simulation_profile`
|
||
- `simulation_seed`
|
||
- `locked_by`
|
||
- `locked_at`
|
||
- `heartbeat_at`
|
||
- `next_run_at`
|
||
- `result`
|
||
- `billings`
|
||
- `error`
|
||
|
||
### 7.4 `gateway_task_attempts`
|
||
|
||
保存每一次客户端尝试,支持“上一个客户端失败,下一个客户端重试”的完整审计:
|
||
|
||
- `task_id`
|
||
- `attempt_no`
|
||
- `platform_id`
|
||
- `client_id`
|
||
- `model_id`
|
||
- `status`:`submitted`、`polling`、`succeeded`、`failed`、`skipped`
|
||
- `retryable`
|
||
- `error_code`
|
||
- `error_message`
|
||
- `remote_task_id`
|
||
- `simulated`
|
||
- `request_snapshot`
|
||
- `response_snapshot`
|
||
- `started_at`
|
||
- `finished_at`
|
||
|
||
### 7.5 `gateway_task_events`
|
||
|
||
保存任务执行过程中的事件,用于控制台 SSE / WebSocket 重放、进度回调 outbox 投递与服务重启后的进度恢复:
|
||
|
||
- `task_id`
|
||
- `seq`:单任务内递增序号
|
||
- `event_type`:`queue_status`、`node_status`、`progress`、`partial_result`、`completed`、`failed`
|
||
- `status`
|
||
- `progress`
|
||
- `message`
|
||
- `payload`
|
||
- `created_at`
|
||
|
||
客户端断线重连时可通过 `Last-Event-ID` 或 `afterSeq` 回放事件。
|
||
|
||
### 7.5.1 `gateway_task_callback_outbox`
|
||
|
||
保存投递给 `server-main` 内部任务进度接口的回调。Gateway 产生事件后先写 `gateway_task_events`,再写 callback outbox,由独立 worker 按配置的 `TASK_PROGRESS_CALLBACK_URL` 投递,失败后可重试。
|
||
|
||
- `task_id`
|
||
- `event_id` / `seq`:与 `gateway_task_events` 对应,用于幂等与顺序控制。
|
||
- `callback_url`:当前使用的回调地址快照,避免配置变化影响已排队事件。
|
||
- `payload`:回调给 server-main 的标准事件载荷。
|
||
- `status`:`pending`、`delivering`、`delivered`、`failed`、`skipped`
|
||
- `attempts`
|
||
- `next_attempt_at`
|
||
- `last_error`
|
||
- `delivered_at`
|
||
|
||
### 7.6 `runtime_client_states`
|
||
|
||
保存平台客户端的运行时状态:
|
||
|
||
- `client_id`
|
||
- `platform_id`
|
||
- `provider`
|
||
- `method_name`
|
||
- `queue_key`
|
||
- `running_count`
|
||
- `waiting_count`
|
||
- `limiter_ratio`
|
||
- `cooldown_until`
|
||
- `last_assigned_at`
|
||
- `last_error`
|
||
- `updated_at`
|
||
|
||
该表用于恢复与观测;实时并发计数仍可通过事务锁、`SKIP LOCKED`、Redis token bucket 或内存计数加 PG 校验组合实现,但 PG 是最终事实源。
|
||
|
||
### 7.7 `gateway_upload_assets`
|
||
|
||
保存网关任务引用的上传资产:
|
||
|
||
- `task_id`
|
||
- `source`:`server-main-open-upload`、`remote-url`、`multipart`、`simulation`
|
||
- `server_main_file_id`:主服务返回的文件 ID。
|
||
- `object_key`
|
||
- `url`
|
||
- `content_type`
|
||
- `size`
|
||
- `checksum`
|
||
- `metadata`
|
||
|
||
### 7.8 `settlement_outbox`
|
||
|
||
保存结算事件 outbox:
|
||
|
||
- `task_id`
|
||
- `event_type`
|
||
- `payload`
|
||
- `status`
|
||
- `attempts`
|
||
- `next_attempt_at`
|
||
|
||
结算事件必须按 `eventId` / `taskId` 幂等,不能因为 Gateway 重启、HTTP 回调失败或 MQ 抖动重复扣费。
|
||
|
||
### 7.9 表结构草案
|
||
|
||
以下为首期 PostgreSQL 表结构草案。字段类型以后续 migration 为准,但语义和索引边界应保持稳定。
|
||
|
||
#### 7.9.1 `model_catalog_providers`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 基准 provider ID |
|
||
| `provider_key` | `text` | unique, not null | 稳定 provider key,如 `openai`、`runninghub`、`jimeng` |
|
||
| `display_name` | `text` | not null | 展示名称 |
|
||
| `provider_type` | `text` | not null | `openai_compatible`、`workflow_app`、`video_vendor`、`audio_vendor` |
|
||
| `capability_schema` | `jsonb` | not null, default `{}` | provider 支持的能力字段定义 |
|
||
| `default_rate_limit_policy` | `jsonb` | not null, default `{}` | provider 默认 TPM/RPM/并发模板 |
|
||
| `status` | `text` | not null | `active`、`deprecated`、`hidden` |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 文档链接、logo、排序等 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_model_catalog_provider_key(provider_key)`
|
||
- `idx_model_catalog_provider_status(status)`
|
||
|
||
#### 7.9.2 `base_model_catalog`
|
||
|
||
保存全量基准模型。这个表不代表某个平台已经开通该模型,而是标准模型与价格来源。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 基准模型 ID |
|
||
| `provider_id` | `uuid` | FK | 所属基准 provider |
|
||
| `provider_key` | `text` | not null | 冗余 provider key,方便查询 |
|
||
| `canonical_model_key` | `text` | unique, not null | Gateway 内部标准模型 key |
|
||
| `provider_model_name` | `text` | not null | 供应商原始模型名 |
|
||
| `model_type` | `text` | not null | `chat`、`image`、`video`、`audio`、`embedding`、`music`、`digital_human`、`model_3d` |
|
||
| `display_name` | `text` | not null | 展示名称 |
|
||
| `capabilities` | `jsonb` | not null, default `{}` | 上下文、多模态、参考图/视频/音频、尺寸、时长、质量等基准能力 |
|
||
| `base_billing_config` | `jsonb` | not null, default `{}` | 基准计费配置 |
|
||
| `default_rate_limit_policy` | `jsonb` | not null, default `{}` | 默认 TPM/RPM/并发限制模板 |
|
||
| `pricing_version` | `int` | not null, default `1` | 基准价格版本 |
|
||
| `status` | `text` | not null | `active`、`deprecated`、`hidden` |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 供应商文档、排序、标签 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_base_model_catalog_key(canonical_model_key)`
|
||
- `idx_base_model_catalog_provider(provider_key, model_type, status)`
|
||
- `idx_base_model_catalog_capabilities` 使用 `GIN(capabilities)`
|
||
|
||
#### 7.9.3 `model_pricing_rules`
|
||
|
||
保存基准模型和平台模型的可版本化价格规则。平台模型没有自定义规则时,使用 `base_model_catalog.base_billing_config` 并应用折扣。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 价格规则 ID |
|
||
| `scope_type` | `text` | not null | `base_model`、`platform`、`platform_model` |
|
||
| `scope_id` | `uuid` | nullable | 对应基准模型、平台或平台模型 ID |
|
||
| `resource_type` | `text` | not null | `text_input`、`text_output`、`image`、`video`、`audio`、`music`、`digital_human`、`model` |
|
||
| `unit` | `text` | not null | `1k_tokens`、`image`、`5s`、`second`、`character_1k`、`item` |
|
||
| `base_price` | `numeric` | not null | 基准单价,单位使用 EasyAI resource / credit |
|
||
| `currency` | `text` | not null, default `resource` | `resource`、`credit`、`cny`、`usd` |
|
||
| `base_weight` | `jsonb` | not null, default `{}` | 固定权重,如默认倍率 |
|
||
| `dynamic_weight` | `jsonb` | not null, default `{}` | 分辨率、质量、时长、有无音频等动态权重 |
|
||
| `effective_from` | `timestamptz` | nullable | 生效开始 |
|
||
| `effective_to` | `timestamptz` | nullable | 生效结束 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `idx_model_pricing_scope(scope_type, scope_id, resource_type)`
|
||
- `idx_model_pricing_effective(effective_from, effective_to)`
|
||
|
||
#### 7.9.4 `gateway_tenants`
|
||
|
||
保存 Gateway 可识别的租户。独立模式下它是租户事实表;接入 `server-main` 时它是租户/组织同步副本。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | Gateway 租户 ID |
|
||
| `tenant_key` | `text` | unique, not null | Gateway 内稳定租户 key |
|
||
| `source` | `text` | not null | `gateway`、`server-main`、`sync` |
|
||
| `external_tenant_id` | `text` | nullable | 外部租户/组织 ID |
|
||
| `name` | `text` | not null | 租户名称 |
|
||
| `description` | `text` | nullable | 说明 |
|
||
| `default_user_group_id` | `uuid` | FK nullable | 默认用户组 |
|
||
| `plan_key` | `text` | nullable | 套餐或商业计划 key |
|
||
| `billing_profile` | `jsonb` | not null, default `{}` | 独立模式账务资料或接入模式同步摘要 |
|
||
| `rate_limit_policy` | `jsonb` | not null, default `{}` | 租户级 TPM/RPM/并发/队列策略 |
|
||
| `auth_policy` | `jsonb` | not null, default `{}` | 注册、邀请、SSO、域名限制等认证策略 |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 同步、运营、展示扩展信息 |
|
||
| `status` | `text` | not null | `active`、`disabled`、`locked`、`deleted` |
|
||
| `synced_at` | `timestamptz` | nullable | 最近同步时间 |
|
||
| `source_updated_at` | `timestamptz` | nullable | 外部源更新时间 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
| `deleted_at` | `timestamptz` | nullable | 软删除时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_gateway_tenants_tenant_key(tenant_key)`
|
||
- `uniq_gateway_tenants_source_external(source, external_tenant_id)`
|
||
- `idx_gateway_tenants_status(status, created_at)`
|
||
|
||
#### 7.9.5 `gateway_users`
|
||
|
||
保存 Gateway 可识别的用户。独立模式下它是用户事实表,并与本地 API Key、钱包、充值订单形成闭环;接入 `server-main` 时它是用户同步副本和策略执行缓存,不迁入主服务余额、订单、充值流水。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | Gateway 用户 ID |
|
||
| `user_key` | `text` | unique, not null | Gateway 内稳定用户 key |
|
||
| `source` | `text` | not null | `gateway`、`server-main`、`sync` |
|
||
| `external_user_id` | `text` | nullable | 外部用户 ID,`server-main` 模式为主服务用户 ID |
|
||
| `username` | `text` | not null | 登录名或同步用户名 |
|
||
| `display_name` | `text` | nullable | 展示名 |
|
||
| `email` | `text` | nullable | 邮箱 |
|
||
| `phone` | `text` | nullable | 手机号 |
|
||
| `avatar_url` | `text` | nullable | 头像 |
|
||
| `password_hash` | `text` | nullable | 仅独立模式使用,接入主服务时为空 |
|
||
| `gateway_tenant_id` | `uuid` | FK nullable | Gateway 租户 ID |
|
||
| `tenant_id` | `text` | nullable | 外部租户 ID 或兼容 claim |
|
||
| `tenant_key` | `text` | nullable | Gateway 租户 key |
|
||
| `default_user_group_id` | `uuid` | FK nullable | 默认用户组 |
|
||
| `roles` | `jsonb` | not null, default `[]` | `user`、`creator`、`operator`、`admin` 等角色 |
|
||
| `auth_profile` | `jsonb` | not null, default `{}` | SSO、MFA、API Key 策略等认证资料 |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 同步、运营、展示扩展信息 |
|
||
| `status` | `text` | not null | `active`、`disabled`、`locked`、`deleted` |
|
||
| `last_login_at` | `timestamptz` | nullable | 最近登录时间 |
|
||
| `synced_at` | `timestamptz` | nullable | 最近同步时间 |
|
||
| `source_updated_at` | `timestamptz` | nullable | 外部源更新时间 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
| `deleted_at` | `timestamptz` | nullable | 软删除时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_gateway_users_user_key(user_key)`
|
||
- `uniq_gateway_users_source_external(source, external_user_id)`
|
||
- `idx_gateway_users_status(status, created_at)`
|
||
- `idx_gateway_users_tenant(tenant_id, tenant_key, status)`
|
||
|
||
同步规则:
|
||
|
||
- `source='gateway'`:用户由 Gateway 创建和禁用,可独立使用;充值、余额、API Key 由 Gateway 本地模块承接。
|
||
- `source='server-main'`:用户由主服务同步,Gateway 只保存执行模型调用所需字段;禁用、角色、用户组变更以主服务事件或同步任务为准。
|
||
- 同一个外部用户以 `source + external_user_id` 幂等 upsert,避免用户名变更造成重复用户。
|
||
- 用户被禁用后,新任务拒绝进入队列;已运行任务按任务策略快照继续或按管理员策略取消。
|
||
|
||
#### 7.9.6 `gateway_user_groups`
|
||
|
||
保存用户组及其策略。用户组可以由 Gateway 管理,也可以从 `server-main` 同步;独立模式下 Gateway 执行全部策略,接入模式下充值和余额仍以 `server-main` 为事实源。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 用户组 ID |
|
||
| `group_key` | `text` | unique, not null | 稳定用户组 key,如 `free`、`pro`、`enterprise` |
|
||
| `name` | `text` | not null | 展示名称 |
|
||
| `description` | `text` | nullable | 说明 |
|
||
| `source` | `text` | not null | `gateway`、`server-main`、`sync` |
|
||
| `priority` | `int` | not null, default `100` | 多组命中时优先级,越小越优先 |
|
||
| `recharge_discount_policy` | `jsonb` | not null, default `{}` | 充值折扣/赠送资源策略;独立模式由 Gateway 执行,接入模式由 `server-main` 执行 |
|
||
| `billing_discount_policy` | `jsonb` | not null, default `{}` | 模型调用计费折扣策略,用于 estimated billing 和 billings |
|
||
| `rate_limit_policy` | `jsonb` | not null, default `{}` | 用户组 TPM/RPM/并发/队列策略 |
|
||
| `quota_policy` | `jsonb` | not null, default `{}` | 日/月额度、API Key 额度、最大任务数 |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 展示、运营、同步元数据 |
|
||
| `status` | `text` | not null | `active`、`disabled` |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"recharge_discount_policy": {
|
||
"type": "tiered_bonus",
|
||
"tiers": [
|
||
{ "minAmount": 100, "bonusRatio": 0.05 },
|
||
{ "minAmount": 1000, "bonusRatio": 0.12 }
|
||
]
|
||
},
|
||
"billing_discount_policy": {
|
||
"defaultDiscountFactor": 0.9,
|
||
"modelTypeDiscounts": { "chat": 0.85, "image": 0.95 }
|
||
},
|
||
"rate_limit_policy": {
|
||
"rules": [
|
||
{ "metric": "rpm", "limit": 1200, "windowSeconds": 60 },
|
||
{ "metric": "tpm_total", "limit": 300000, "windowSeconds": 60 },
|
||
{ "metric": "concurrent", "limit": 50, "leaseTtlSeconds": 900 }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 7.9.7 `gateway_user_group_memberships`
|
||
|
||
保存用户组成员关系。一个用户、租户或 API Key 可命中多个组,运行时按组优先级和策略合并规则解析有效策略。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 成员关系 ID |
|
||
| `group_id` | `uuid` | FK | 用户组 ID |
|
||
| `principal_type` | `text` | not null | `user`、`tenant`、`api_key`、`organization` |
|
||
| `principal_id` | `text` | not null | 对应 Gateway 用户 ID、`server-main` 外部用户 ID、租户 ID、API Key ID 或组织 ID |
|
||
| `source` | `text` | not null | `gateway`、`server-main`、`sync` |
|
||
| `priority` | `int` | not null, default `100` | 关系级优先级 |
|
||
| `effective_from` | `timestamptz` | nullable | 生效开始 |
|
||
| `effective_to` | `timestamptz` | nullable | 生效结束 |
|
||
| `status` | `text` | not null | `active`、`disabled` |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 同步和运营元数据 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_user_group_membership(group_id, principal_type, principal_id)`
|
||
- `idx_user_group_membership_principal(principal_type, principal_id, status)`
|
||
- `idx_user_group_membership_effective(effective_from, effective_to)`
|
||
|
||
成员解析规则:
|
||
|
||
- 独立模式优先使用 `principal_type='user' + gateway_users.id`。
|
||
- 接入 `server-main` 时可使用 `principal_type='user' + external_user_id`,并通过 `gateway_users.source='server-main'` 反查同步副本。
|
||
- 多个组同时命中时,先按 `gateway_user_group_memberships.priority`,再按 `gateway_user_groups.priority` 合并策略。
|
||
|
||
#### 7.9.8 `gateway_tenant_invitations`
|
||
|
||
本地注册邀请码。邀请码不是普通注册的必填项,但填写后必须可校验,并决定用户加入哪个租户和用户组。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 邀请 ID |
|
||
| `tenant_id` | `uuid` | FK | 加入的 Gateway 租户 |
|
||
| `invite_code` | `text` | unique, not null | 注册邀请码 |
|
||
| `role` | `text` | not null, default `user` | 注册后角色,默认普通用户 |
|
||
| `user_group_id` | `uuid` | FK nullable | 注册后默认用户组 |
|
||
| `max_uses` | `int` | nullable | 最大使用次数 |
|
||
| `used_count` | `int` | not null | 已使用次数 |
|
||
| `expires_at` | `timestamptz` | nullable | 过期时间 |
|
||
| `status` | `text` | not null | `active`、`disabled` |
|
||
| `metadata` | `jsonb` | not null | 渠道、备注、审批信息 |
|
||
|
||
#### 7.9.9 `gateway_api_keys`
|
||
|
||
独立模式本地 API Key 表。`server-main` 接入模式下,`sk-*` 仍调用主服务校验;Gateway 只记录执行快照和策略。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | API Key ID |
|
||
| `gateway_tenant_id` / `gateway_user_id` | `uuid` | FK | 所属租户与用户 |
|
||
| `key_prefix` | `text` | not null | 展示和快速定位前缀 |
|
||
| `key_hash` | `text` | unique, not null | secret hash,不保存明文 |
|
||
| `name` | `text` | not null | 用户命名 |
|
||
| `scopes` | `jsonb` | not null | 可调用能力范围 |
|
||
| `user_group_id` | `uuid` | nullable | API Key 级策略组 |
|
||
| `rate_limit_policy` / `quota_policy` | `jsonb` | not null | Key 级限流和额度覆盖 |
|
||
| `status` | `text` | not null | `active`、`disabled`、`revoked` |
|
||
| `expires_at` / `last_used_at` | `timestamptz` | nullable | 过期与最近使用 |
|
||
|
||
#### 7.9.10 `gateway_wallet_accounts`
|
||
|
||
独立模式本地钱包账户。Phase 1 先支持资源余额闭环,后续可扩展人民币、美元或企业额度。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 钱包账户 ID |
|
||
| `gateway_tenant_id` / `gateway_user_id` | `uuid` | FK | 所属租户与用户 |
|
||
| `currency` | `text` | not null | `resource`、`credit`、`cny`、`usd` |
|
||
| `balance` | `numeric` | not null | 可用余额 |
|
||
| `frozen_balance` | `numeric` | not null | 冻结余额 |
|
||
| `total_recharged` | `numeric` | not null | 累计充值 |
|
||
| `total_spent` | `numeric` | not null | 累计消费 |
|
||
| `status` | `text` | not null | `active`、`frozen`、`disabled` |
|
||
|
||
#### 7.9.11 `gateway_wallet_transactions`
|
||
|
||
独立模式钱包流水。任务扣费、充值到账、退款、人工调整都必须写流水,并使用 `idempotency_key` 防重。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 流水 ID |
|
||
| `account_id` | `uuid` | FK | 钱包账户 |
|
||
| `direction` | `text` | not null | `credit`、`debit`、`freeze`、`unfreeze` |
|
||
| `transaction_type` | `text` | not null | `recharge`、`billing`、`refund`、`adjustment` |
|
||
| `amount` | `numeric` | not null | 金额 |
|
||
| `balance_before` / `balance_after` | `numeric` | not null | 变动前后余额 |
|
||
| `idempotency_key` | `text` | nullable | 幂等 key |
|
||
| `reference_type` / `reference_id` | `text` | nullable | 关联任务、订单或人工工单 |
|
||
|
||
#### 7.9.12 `gateway_recharge_orders`
|
||
|
||
独立模式充值订单。第一阶段先闭合订单、折扣、入账和流水;支付渠道可以从手工确认开始,再接真实支付。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 充值订单 ID |
|
||
| `gateway_tenant_id` / `gateway_user_id` | `uuid` | FK | 所属租户与用户 |
|
||
| `amount` | `numeric` | not null | 入账基础额度 |
|
||
| `bonus_amount` | `numeric` | not null | 用户组充值折扣或赠送额度 |
|
||
| `payable_amount` | `numeric` | not null | 实际应付 |
|
||
| `currency` | `text` | not null | 币种或资源类型 |
|
||
| `channel` | `text` | not null | `manual`、`wechat`、`alipay`、`stripe` |
|
||
| `status` | `text` | not null | `created`、`pending`、`paid`、`closed`、`failed` |
|
||
| `external_order_id` | `text` | nullable | 第三方支付订单 |
|
||
| `idempotency_key` | `text` | nullable | 创建订单幂等 key |
|
||
| `paid_at` | `timestamptz` | nullable | 支付完成时间 |
|
||
|
||
#### 7.9.13 `integration_platforms`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 平台实例 ID |
|
||
| `provider` | `text` | not null | 平台类型,如 `openai`、`runninghub`、`jimeng` |
|
||
| `platform_key` | `text` | unique, not null | 稳定平台 key,用于队列和客户端注册 |
|
||
| `name` | `text` | not null | 展示名称 |
|
||
| `base_url` | `text` | nullable | 平台 API 地址 |
|
||
| `auth_type` | `text` | not null | `bearer`、`api_key`、`basic`、`custom` |
|
||
| `credentials` | `jsonb` | not null, default `{}` | 加密后的凭证 JSON |
|
||
| `config` | `jsonb` | not null, default `{}` | 超时、重试、限流、平台私有配置 |
|
||
| `visibility_scope` | `text` | not null, default `global` | `global`、`tenant`、`private` |
|
||
| `tenant_id` | `text` | nullable | 租户外部 ID 或兼容 claim |
|
||
| `tenant_key` | `text` | nullable | Gateway 租户 key |
|
||
| `default_pricing_mode` | `text` | not null, default `inherit_discount` | 平台默认价格模式:`inherit`、`inherit_discount`、`custom` |
|
||
| `default_discount_factor` | `numeric` | not null, default `1` | 平台默认折扣系数,创建平台时可统一基于基准模型打折 |
|
||
| `retry_policy` | `jsonb` | not null, default `{}` | 平台级重试策略 |
|
||
| `rate_limit_policy` | `jsonb` | not null, default `{}` | 平台级限流策略 |
|
||
| `priority` | `int` | not null, default `100` | 静态优先级,越小越优先 |
|
||
| `dynamic_priority` | `int` | nullable | 动态优先级覆盖 |
|
||
| `status` | `text` | not null | `enabled`、`disabled`、`exception` |
|
||
| `disabled_reason` | `text` | nullable | 禁用原因 |
|
||
| `cooldown_until` | `timestamptz` | nullable | 熔断/冷却到期时间 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
| `deleted_at` | `timestamptz` | nullable | 软删除 |
|
||
|
||
索引:
|
||
|
||
- `idx_integration_platforms_provider_status(provider, status)`
|
||
- `idx_integration_platforms_status_priority(status, priority, dynamic_priority)`
|
||
- `idx_integration_platforms_cooldown(cooldown_until)`
|
||
- `idx_integration_platforms_tenant_scope(visibility_scope, tenant_id, tenant_key, status)`
|
||
|
||
#### 7.9.14 `platform_models`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 模型配置 ID |
|
||
| `platform_id` | `uuid` | FK | 所属平台 |
|
||
| `base_model_id` | `uuid` | FK nullable | 关联基准模型;为空时表示纯自定义模型 |
|
||
| `model_name` | `text` | not null | 请求使用的模型名 |
|
||
| `model_alias` | `text` | nullable | 对外展示/兼容别名 |
|
||
| `model_type` | `text` | not null | `chat`、`image`、`video`、`audio`、`embedding` |
|
||
| `display_name` | `text` | not null | 展示名称 |
|
||
| `capability_override` | `jsonb` | not null, default `{}` | 相对基准模型的能力覆盖 |
|
||
| `capabilities` | `jsonb` | not null, default `{}` | 解析后的有效能力,用于路由和前端展示 |
|
||
| `pricing_mode` | `text` | not null, default `inherit_discount` | `inherit`、`inherit_discount`、`custom` |
|
||
| `discount_factor` | `numeric` | nullable | 模型级折扣,空值时继承平台默认折扣 |
|
||
| `billing_config_override` | `jsonb` | not null, default `{}` | 自定义计费覆盖 |
|
||
| `billing_config` | `jsonb` | not null, default `{}` | 解析后的有效计费配置,用于 estimated billing 和真实 billings |
|
||
| `permission_config` | `jsonb` | not null, default `{}` | 平台维度权限过滤配置 |
|
||
| `retry_policy` | `jsonb` | not null, default `{}` | 模型级重试策略 |
|
||
| `rate_limit_policy` | `jsonb` | not null, default `{}` | 模型级限流策略 |
|
||
| `enabled` | `boolean` | not null, default `true` | 是否启用 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引与约束:
|
||
|
||
- `uniq_platform_model_type(platform_id, model_name, model_type)`
|
||
- `idx_platform_models_base(base_model_id)`
|
||
- `idx_platform_models_lookup(model_type, model_name, enabled)`
|
||
- `idx_platform_models_alias(model_alias)`
|
||
- `idx_platform_models_capabilities` 使用 `GIN(capabilities)`
|
||
|
||
#### 7.9.15 `gateway_tasks`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | Gateway 任务 ID |
|
||
| `external_task_id` | `text` | nullable | 与 `server-main` / 前端兼容的外部任务 ID |
|
||
| `kind` | `text` | not null | `chat.completions`、`images.generations` 等 |
|
||
| `run_mode` | `text` | not null | `production`、`simulation` |
|
||
| `user_id` | `text` | not null | 请求 claim 中的用户 ID |
|
||
| `gateway_user_id` | `uuid` | nullable | Gateway 用户表 ID |
|
||
| `user_source` | `text` | not null, default `gateway` | `gateway`、`server-main`、`sync` |
|
||
| `gateway_tenant_id` | `uuid` | nullable | Gateway 租户 ID |
|
||
| `tenant_id` | `text` | nullable | 外部租户 ID 或兼容 claim |
|
||
| `tenant_key` | `text` | nullable | Gateway 租户 key |
|
||
| `api_key_id` | `text` | nullable | OpenAPI Key ID |
|
||
| `user_group_id` | `uuid` | nullable | 命中的用户组 ID |
|
||
| `user_group_key` | `text` | nullable | 命中的用户组 key |
|
||
| `user_group_policy_snapshot` | `jsonb` | not null, default `{}` | 用户组策略快照 |
|
||
| `model` | `text` | not null | 请求模型 |
|
||
| `model_type` | `text` | nullable | 模型类型 |
|
||
| `request` | `jsonb` | not null | 原始请求快照 |
|
||
| `normalized_request` | `jsonb` | not null, default `{}` | 参数处理后的请求 |
|
||
| `status` | `text` | not null | `queued`、`leased`、`running`、`polling`、`succeeded`、`failed_retryable`、`failed_final`、`cancelled` |
|
||
| `queue_key` | `text` | not null | 队列与限流 key |
|
||
| `priority` | `int` | not null, default `100` | 任务优先级 |
|
||
| `idempotency_key` | `text` | nullable | 幂等 key |
|
||
| `remote_task_id` | `text` | nullable | 供应商任务 ID |
|
||
| `remote_task_payload` | `jsonb` | nullable | 供应商任务载荷 |
|
||
| `simulation_profile` | `jsonb` | nullable | 模拟配置 |
|
||
| `simulation_seed` | `text` | nullable | 模拟种子 |
|
||
| `locked_by` | `text` | nullable | worker ID |
|
||
| `locked_at` | `timestamptz` | nullable | 租约时间 |
|
||
| `heartbeat_at` | `timestamptz` | nullable | worker 心跳 |
|
||
| `next_run_at` | `timestamptz` | not null | 下次可运行时间 |
|
||
| `attempt_count` | `int` | not null, default `0` | 已尝试次数 |
|
||
| `max_attempts` | `int` | not null, default `1` | 最大尝试次数 |
|
||
| `result` | `jsonb` | nullable | 归一化结果 |
|
||
| `billings` | `jsonb` | nullable | 计费结果 |
|
||
| `error_code` | `text` | nullable | 错误码 |
|
||
| `error_message` | `text` | nullable | 错误信息 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
| `finished_at` | `timestamptz` | nullable | 完成时间 |
|
||
|
||
索引:
|
||
|
||
- `idx_gateway_tasks_queue(status, next_run_at, priority, created_at)`
|
||
- `idx_gateway_tasks_lease(status, heartbeat_at)`
|
||
- `idx_gateway_tasks_user_created(user_id, created_at desc)`
|
||
- `idx_gateway_tasks_external(external_task_id)`
|
||
- `uniq_gateway_tasks_idempotency(user_id, idempotency_key)` where `idempotency_key is not null`
|
||
|
||
#### 7.9.16 `gateway_task_attempts`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | attempt ID |
|
||
| `task_id` | `uuid` | FK | 所属任务 |
|
||
| `attempt_no` | `int` | not null | 第几次尝试 |
|
||
| `platform_id` | `uuid` | nullable | 尝试平台 |
|
||
| `platform_model_id` | `uuid` | nullable | 尝试模型配置 |
|
||
| `client_id` | `text` | nullable | 客户端实例 ID |
|
||
| `queue_key` | `text` | not null | 限流 key |
|
||
| `status` | `text` | not null | `submitted`、`polling`、`succeeded`、`failed`、`skipped` |
|
||
| `retryable` | `boolean` | not null, default `false` | 是否可重试 |
|
||
| `simulated` | `boolean` | not null, default `false` | 是否模拟 |
|
||
| `remote_task_id` | `text` | nullable | 供应商任务 ID |
|
||
| `request_snapshot` | `jsonb` | not null, default `{}` | 本次请求快照 |
|
||
| `response_snapshot` | `jsonb` | nullable | 本次响应快照 |
|
||
| `error_code` | `text` | nullable | 错误码 |
|
||
| `error_message` | `text` | nullable | 错误信息 |
|
||
| `started_at` | `timestamptz` | not null | 开始时间 |
|
||
| `finished_at` | `timestamptz` | nullable | 结束时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_gateway_attempt_no(task_id, attempt_no)`
|
||
- `idx_gateway_attempts_task(task_id)`
|
||
- `idx_gateway_attempts_client(client_id, started_at desc)`
|
||
|
||
#### 7.9.17 `gateway_task_events`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 事件 ID |
|
||
| `task_id` | `uuid` | FK | 所属任务 |
|
||
| `seq` | `bigint` | not null | 单任务递增序号 |
|
||
| `event_type` | `text` | not null | `queue_status`、`progress`、`partial_result`、`completed`、`failed` |
|
||
| `status` | `text` | nullable | 任务状态 |
|
||
| `phase` | `text` | nullable | `routing`、`queued`、`submit`、`polling`、`upload` |
|
||
| `progress` | `numeric` | nullable | 0 到 1 |
|
||
| `message` | `text` | nullable | 展示消息 |
|
||
| `payload` | `jsonb` | not null, default `{}` | 事件载荷 |
|
||
| `simulated` | `boolean` | not null, default `false` | 是否模拟 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_gateway_events_seq(task_id, seq)`
|
||
- `idx_gateway_events_task_created(task_id, created_at)`
|
||
|
||
#### 7.9.18 `gateway_task_callback_outbox`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 回调 outbox ID |
|
||
| `task_id` | `uuid` | FK | 任务 ID |
|
||
| `event_id` | `uuid` | FK nullable | 对应 `gateway_task_events.id` |
|
||
| `seq` | `bigint` | not null | 单任务事件序号 |
|
||
| `callback_url` | `text` | not null | 投递地址快照,来自 `TASK_PROGRESS_CALLBACK_URL` |
|
||
| `payload` | `jsonb` | not null | 回调载荷 |
|
||
| `status` | `text` | not null | `pending`、`delivering`、`delivered`、`failed`、`skipped` |
|
||
| `attempts` | `int` | not null, default `0` | 已投递次数 |
|
||
| `next_attempt_at` | `timestamptz` | not null | 下次投递时间 |
|
||
| `last_error` | `text` | nullable | 最近错误 |
|
||
| `delivered_at` | `timestamptz` | nullable | 投递成功时间 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `uniq_task_callback_seq(task_id, seq, callback_url)`
|
||
- `idx_task_callback_outbox_pending(status, next_attempt_at)`
|
||
- `idx_task_callback_outbox_task(task_id, seq)`
|
||
|
||
#### 7.9.19 `runtime_client_states`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `client_id` | `text` | PK | 客户端实例 ID |
|
||
| `platform_id` | `uuid` | nullable | 所属平台 |
|
||
| `provider` | `text` | not null | provider |
|
||
| `method_name` | `text` | not null | 方法名 |
|
||
| `queue_key` | `text` | not null | 队列 key |
|
||
| `running_count` | `int` | not null, default `0` | 当前运行数 |
|
||
| `waiting_count` | `int` | not null, default `0` | 当前等待数 |
|
||
| `limiter_ratio` | `numeric` | not null, default `0` | 负载比 |
|
||
| `cooldown_until` | `timestamptz` | nullable | 冷却截止 |
|
||
| `last_assigned_at` | `timestamptz` | nullable | 上次分配时间 |
|
||
| `last_error` | `text` | nullable | 最近错误 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
索引:
|
||
|
||
- `idx_runtime_client_queue(queue_key, cooldown_until)`
|
||
- `idx_runtime_client_platform(platform_id)`
|
||
|
||
#### 7.9.20 `gateway_upload_assets`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 资产 ID |
|
||
| `task_id` | `uuid` | FK nullable | 关联任务 |
|
||
| `source` | `text` | not null | 固定为 `server-main-open-upload` 或 `remote-url` / `simulation` |
|
||
| `server_main_file_id` | `text` | nullable | 主服务返回的 file id |
|
||
| `url` | `text` | not null | 主服务返回 URL |
|
||
| `object_key` | `text` | nullable | 主服务返回对象 key |
|
||
| `content_type` | `text` | nullable | MIME |
|
||
| `size` | `bigint` | nullable | 文件大小 |
|
||
| `checksum` | `text` | nullable | 校验和 |
|
||
| `metadata` | `jsonb` | not null, default `{}` | 上传响应与业务元数据 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
|
||
索引:
|
||
|
||
- `idx_gateway_upload_task(task_id)`
|
||
- `idx_gateway_upload_file(server_main_file_id)`
|
||
|
||
#### 7.9.21 `gateway_retry_policies`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 策略 ID |
|
||
| `scope_type` | `text` | not null | `global`、`provider`、`platform`、`model`、`method` |
|
||
| `scope_key` | `text` | not null | 作用域 key |
|
||
| `enabled` | `boolean` | not null | 是否启用重试 |
|
||
| `policy` | `jsonb` | not null | 重试策略 JSON |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
约束:
|
||
|
||
- `uniq_retry_policy_scope(scope_type, scope_key)`
|
||
|
||
#### 7.9.22 `gateway_rate_limit_policies`
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 策略 ID |
|
||
| `scope_type` | `text` | not null | `global`、`provider`、`platform`、`client`、`base_model`、`platform_model`、`method`、`tenant`、`user_group`、`user`、`api_key` |
|
||
| `scope_key` | `text` | not null | 作用域 key |
|
||
| `policy` | `jsonb` | not null | TPM、RPM、并发、队列长度、冷却策略 |
|
||
| `created_at` | `timestamptz` | not null | 创建时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
约束:
|
||
|
||
- `uniq_rate_limit_policy_scope(scope_type, scope_key)`
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"rules": [
|
||
{ "metric": "tpm_total", "limit": 90000, "windowSeconds": 60, "consume": "reserve_then_reconcile" },
|
||
{ "metric": "rpm", "limit": 600, "windowSeconds": 60, "consume": "fixed_window" },
|
||
{ "metric": "concurrent", "limit": 20, "leaseTtlSeconds": 900 },
|
||
{ "metric": "queue_size", "limit": 1000 }
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 7.9.23 `gateway_rate_limit_counters`
|
||
|
||
保存一分钟窗口内的 TPM/RPM 使用量。Redis 可做加速,但 PG counter 是重启恢复和审计的事实源。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `scope_type` | `text` | not null | 限流作用域 |
|
||
| `scope_key` | `text` | not null | 作用域 key |
|
||
| `metric` | `text` | not null | `tpm_total`、`tpm_input`、`tpm_output`、`rpm` |
|
||
| `window_start` | `timestamptz` | not null | 分钟窗口开始时间 |
|
||
| `limit_value` | `numeric` | not null | 当前窗口限制 |
|
||
| `used_value` | `numeric` | not null, default `0` | 已确认消耗 |
|
||
| `reserved_value` | `numeric` | not null, default `0` | 已预占但待回填消耗 |
|
||
| `reset_at` | `timestamptz` | not null | 窗口结束时间 |
|
||
| `updated_at` | `timestamptz` | not null | 更新时间 |
|
||
|
||
约束:
|
||
|
||
- `pk_rate_limit_counter(scope_type, scope_key, metric, window_start)`
|
||
|
||
#### 7.9.24 `gateway_concurrency_leases`
|
||
|
||
保存并发请求租约,服务异常重启后可释放过期并发。
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `id` | `uuid` | PK | 并发租约 ID |
|
||
| `task_id` | `uuid` | FK | 任务 ID |
|
||
| `attempt_id` | `uuid` | FK nullable | attempt ID |
|
||
| `scope_type` | `text` | not null | 限流作用域 |
|
||
| `scope_key` | `text` | not null | 作用域 key |
|
||
| `lease_value` | `numeric` | not null, default `1` | 占用并发数 |
|
||
| `acquired_at` | `timestamptz` | not null | 获取时间 |
|
||
| `expires_at` | `timestamptz` | not null | 过期时间 |
|
||
| `released_at` | `timestamptz` | nullable | 释放时间 |
|
||
|
||
索引:
|
||
|
||
- `idx_concurrency_leases_active(scope_type, scope_key, released_at, expires_at)`
|
||
- `idx_concurrency_leases_task(task_id)`
|
||
|
||
## 8. 模型路由、限流与失败切换
|
||
|
||
迁移时不要做简单的 first-match。路由器需要复用现有语义:
|
||
|
||
1. 先按模型名、模型类型、平台启用状态筛候选。
|
||
2. 保留平台维度权限过滤,同名模型不能跨平台混用。
|
||
3. 按平台优先级排序。
|
||
4. 结合并发是否已满、limiter ratio、等待队列、lastAssignedAt 做负载均衡。
|
||
5. 对 app-style provider,例如 RunningHub 模板类能力,使用 `provider + methodName` 的队列 key。
|
||
|
||
一期脚手架只预留接口,迁移实现时把 `IntegrationModelFactory.assignClientsByModelName` 与 `assignClientsByProviderMethod` 的行为写成 Go 版单元测试再实现。
|
||
|
||
### 8.1 限流维度
|
||
|
||
限流策略需要支持以下维度组合:
|
||
|
||
- 全局:`global`
|
||
- Provider 级:`provider_key`
|
||
- 平台级:`platform_id`
|
||
- 客户端级:`client_id`
|
||
- 基准模型级:`base_model_id` / `canonical_model_key`
|
||
- 平台模型级:`platform_model_id` / `model_name` / `model_type`
|
||
- 方法级:`method_name`,例如 `runAIApp$`
|
||
- 租户/用户级:`gateway_tenant_id` / `tenant_id` / `tenant_key` / `user_id`
|
||
- 用户组级:`user_group_id` / `user_group_key`
|
||
- OpenAPI Key 级:`api_key_id`
|
||
|
||
同一请求可能命中多个策略,默认以最严格策略为准;如后续需要不同优先级,可在 policy 中增加 `mergeStrategy`。
|
||
|
||
### 8.1.1 一分钟窗口核心指标
|
||
|
||
限流必须把 TPM、RPM、并发作为一等指标,而不是只看 running count:
|
||
|
||
| 指标 | 含义 | 适用能力 | 执行方式 |
|
||
| --- | --- | --- | --- |
|
||
| `tpm_total` | 一分钟内输入 + 输出 token 总数 | Chat、Responses、Embedding、可 token 化文本任务 | 提交前按 prompt / max_tokens 预估并预占,完成后用实际 token 回填 |
|
||
| `tpm_input` | 一分钟内输入 token 数 | Chat、Responses、Embedding | 提交前按 tokenizer 或近似估算预占 |
|
||
| `tpm_output` | 一分钟内输出 token 数 | Chat、Responses | stream 场景可按 chunk 增量累计,完成后 reconcile |
|
||
| `rpm` | 一分钟内请求数 | 所有同步/异步模型提交 | 任务 attempt 提交前扣减;失败切换的新 attempt 也算一次 provider 请求 |
|
||
| `concurrent` | 同时运行或轮询中的请求数 | 所有 provider client | 通过 `gateway_concurrency_leases` 获取租约,完成、失败、取消或过期释放 |
|
||
| `queue_size` | 等待队列长度 | 所有队列 | 超过后按策略返回兼容错误或降级到 simulation / fallback |
|
||
|
||
TPM 的预占策略:
|
||
|
||
- 文本任务:优先使用模型 tokenizer;没有 tokenizer 时用字符数估算,并把估算方式写入 event payload。
|
||
- Chat stream:提交前按 `input_tokens + max_tokens` 预占,stream 中可累计输出 token,完成后用 usage 精确回填。
|
||
- 未提供 `max_tokens` 的请求:使用模型默认输出上限或策略中的 `defaultOutputReserveTokens`。
|
||
- 失败且未提交到供应商:释放预占 token;已提交但失败的 attempt 按供应商是否计费决定保留或部分释放。
|
||
- 生图/生视频通常不计 TPM,但如果 provider 对多模态 token 有限制,可在 `capabilities.tokenPolicy` 中启用对应估算器。
|
||
|
||
RPM 和并发的边界:
|
||
|
||
- RPM 以“向 provider 发起一次 submit / sync request”为计数点;排队但未提交不消耗 RPM。
|
||
- 失败切换到下一个客户端时,新客户端 attempt 会重新消耗该客户端、平台、provider 维度的 RPM。
|
||
- 并发租约从 submit 前获取,到同步响应完成、异步任务进入可安全释放阶段或任务终态时释放;长轮询 provider 可拆成 `submit_concurrent` 与 `poll_concurrent` 两类 scope。
|
||
- 服务重启后扫描过期 lease,结合 `gateway_tasks.heartbeat_at` 判断释放或续租,避免并发永久泄漏。
|
||
|
||
除核心指标外,还保留每日/月度配额、错误冷却、余额不足拦截等策略,但这些属于调度前置校验,不替代 TPM/RPM/并发。
|
||
|
||
### 8.2 候选客户端选择
|
||
|
||
候选客户端排序规则必须可测试、可复现:
|
||
|
||
1. 过滤禁用平台、禁用模型、不可用凭证、权限不满足的客户端。
|
||
2. 过滤与请求能力不匹配的客户端,例如不支持视频参考、音频参考、多图输入时不能作为候选。
|
||
3. 根据指定平台 / 指定客户端优先收窄候选。
|
||
4. 按 `priority`、是否满载、`limiter_ratio`、等待队列长度、`last_assigned_at`、轻微 jitter 排序。
|
||
5. 选中客户端后在同一事务内写入 `last_assigned_at` 和任务锁,避免并发请求都抢到同一个客户端。
|
||
|
||
### 8.3 失败切换与重试策略
|
||
|
||
一个模型请求进来,如果多个客户端满足要求,必须具备“上一个客户端失败,下一个客户端重试”的能力,且可配置是否允许:
|
||
|
||
```json
|
||
{
|
||
"retry": {
|
||
"enabled": true,
|
||
"failoverClients": true,
|
||
"maxAttempts": 3,
|
||
"maxSameClientAttempts": 1,
|
||
"backoff": { "type": "exponential", "baseMs": 500, "maxMs": 5000 },
|
||
"retryableStatusCodes": [408, 409, 429, 500, 502, 503, 504],
|
||
"retryableErrorCodes": ["timeout", "rate_limit", "temporary_unavailable"],
|
||
"nonRetryableErrorCodes": ["invalid_request", "insufficient_quota", "content_policy"],
|
||
"retryOnSubmitUnknown": false
|
||
}
|
||
}
|
||
```
|
||
|
||
策略可挂在全局、provider、platform、model、method 四层,优先级为:请求显式配置 > 模型配置 > 平台配置 > provider 默认 > 全局默认。
|
||
|
||
重试要求:
|
||
|
||
- 每次尝试都写入 `gateway_task_attempts`,包括跳过原因和失败分类。
|
||
- 已明确提交成功但轮询失败的任务,优先继续轮询原 `remote_task_id`,不能盲目重复提交造成供应商侧重复任务。
|
||
- 提交状态未知时,根据 `retryOnSubmitUnknown` 决定是否切换客户端;默认保守处理,标记为 `unknown` 并进入人工/补偿检查。
|
||
- 非重试错误直接终止任务,不再切换客户端。
|
||
- 重试产生的新客户端必须仍满足同一请求的能力、权限和计费要求。
|
||
|
||
## 9. 基准模型库与定价体系
|
||
|
||
定价不直接散落在平台模型里。Gateway 先有一个基准模型库,基准模型库存所有 provider、模型、能力、默认限流模板和基准价格;平台创建和平台模型配置都基于这个库继承或覆盖。
|
||
|
||
### 9.1 基准模型库内容
|
||
|
||
每个基准模型至少包含:
|
||
|
||
- provider:如 `openai`、`runninghub`、`jimeng`。
|
||
- canonical model key:Gateway 内部稳定模型 key。
|
||
- provider model name:供应商真实请求模型名。
|
||
- model type:`chat`、`image`、`video`、`audio`、`embedding`、`music`、`digital_human`、`model_3d`。
|
||
- capabilities:上下文窗口、stream、多模态输入、参考图、参考视频、参考音频、支持尺寸、质量、时长、有无音频、最大 batch 等。
|
||
- base billing config:文本、图像、视频、音频等不同能力的基准价格。
|
||
- default rate limit policy:provider 或模型默认 TPM/RPM/并发模板。
|
||
|
||
### 9.2 平台价格继承与覆盖
|
||
|
||
创建平台时有三种方式:
|
||
|
||
| 模式 | 配置 | 说明 |
|
||
| --- | --- | --- |
|
||
| `inherit` | 不填价格 | 完全 follow 基准模型价格和能力 |
|
||
| `inherit_discount` | `default_discount_factor` 或模型级 `discount_factor` | 有效价 = 基准价 x 折扣系数;能力默认 follow 基准模型,可局部覆盖 |
|
||
| `custom` | `billing_config_override` / `capability_override` | 平台模型完全自定义价格或能力,未覆盖字段仍从基准模型补齐 |
|
||
|
||
有效配置解析顺序:
|
||
|
||
1. 平台模型 `capability_override` / `billing_config_override`。
|
||
2. 平台模型 `discount_factor`。
|
||
3. 平台 `default_discount_factor`。
|
||
4. 用户组 `billing_discount_policy`,用于用户组级模型调用折扣。
|
||
5. 基准模型 `capabilities` / `base_billing_config`。
|
||
|
||
如果没有配置平台模型价格,必须 follow 基准模型;不能出现空价格导致预估扣费为 0 的隐式行为。
|
||
|
||
接入 `server-main` 时,充值折扣不在 Gateway 内直接变更余额。Gateway 只保存 `recharge_discount_policy` 并与主服务同步;充值订单、资源包发放、余额流水仍由 `server-main` 作为事实源执行。独立模式下,充值、余额、订单和钱包流水由 Gateway 本地账务模块闭环。
|
||
|
||
### 9.3 各能力计价模型
|
||
|
||
文本类:
|
||
|
||
- 按 `1k_tokens` 计价。
|
||
- 输入和输出分开计价:`text_input`、`text_output`。
|
||
- 如果模型只配置总价,可 fallback 到 `text_total`,但 resolver 对外仍展开成输入/输出明细。
|
||
- 预估时用输入 token + 输出 token 预估值;最终结算用 provider usage 或 Gateway tokenizer 回填。
|
||
|
||
图像类:
|
||
|
||
- 基础单位为 `image`。
|
||
- 动态权重至少支持:分辨率、质量、张数、编辑/生成模式。
|
||
- 分辨率示例:`512x512`、`1024x1024`、`1K`、`2K`、`4K`、`8K`。
|
||
- 质量示例:`standard`、`hd`、`high`、`ultra`。
|
||
- 公式示例:`count * basePrice * resolutionWeight * qualityWeight * modeWeight`。
|
||
|
||
视频类:
|
||
|
||
- 基础单位建议为 `5s` 或 `second`,与原 provider 配置保持可映射。
|
||
- 动态权重至少支持:时长、分辨率、是否包含音频、是否使用参考视频、是否指定声音/音色、生成数量。
|
||
- 分辨率示例:`480p`、`720p`、`1080p`、`2160p`。
|
||
- 公式示例:`count * ceil(durationSeconds / unitSeconds) * basePrice * resolutionWeight * audioWeight * referenceWeight`。
|
||
|
||
音频、音乐、数字人、3D 模型:
|
||
|
||
- TTS / audio 可按 `character_1k` 或秒计费。
|
||
- 音乐可按首数、时长、模型权重计费。
|
||
- 数字人可按时长、分辨率、音频驱动方式计费。
|
||
- 3D / model 类按任务数、模型复杂度或 provider 返回的计费单位映射。
|
||
|
||
### 9.4 estimated billing 一致性
|
||
|
||
`/integration-platform/models/estimatedBilling`、`/integration-platform/models/estimatedBilling/:workflowId` 和真实任务结算必须使用同一个 resolver:
|
||
|
||
```text
|
||
request -> normalize params -> resolve effective platform model
|
||
-> resolve effective capabilities -> resolve effective pricing
|
||
-> estimate billing -> route / submit -> final billing reconcile
|
||
```
|
||
|
||
要求:
|
||
|
||
- 预估扣费不能只按模型名查第一条,必须使用真实候选平台过滤、权限过滤和优先级排序后的有效平台模型。
|
||
- 同名模型跨平台时,平台权限和平台模型价格都不能串。
|
||
- 测试模式使用同样的预估逻辑,但 billings 标记 `simulated=true`。
|
||
- 基准模型价格变更时,平台模型可选择自动 follow 最新版本或 pin 到指定 `pricing_version`,避免历史价格被静默改变。
|
||
|
||
## 10. Base Client 与 Provider 实现架构
|
||
|
||
Gateway 需要保留类似原 `BaseModelClient` 的整体架构:公共基类定义通用生命周期,不同 provider/client 只实现平台差异。这样才能把参数预处理、限流、日志、进度、重试、计费和结果归一放在同一层处理。
|
||
|
||
### 10.1 Client 接口
|
||
|
||
Go 侧建议抽象为:
|
||
|
||
```go
|
||
type ModelClient interface {
|
||
Provider() string
|
||
MethodName() string
|
||
BuildParams(ctx context.Context, task TaskContext) (VendorRequest, error)
|
||
SubmitTask(ctx context.Context, request VendorRequest) (RemoteTask, error)
|
||
PollResult(ctx context.Context, remote RemoteTask) (PollResult, error)
|
||
NormalizeResult(ctx context.Context, result PollResult) (NormalizedResult, error)
|
||
EstimateBilling(ctx context.Context, task TaskContext) ([]BillingItem, error)
|
||
Cancel(ctx context.Context, remote RemoteTask) error
|
||
}
|
||
```
|
||
|
||
### 10.2 Base Client 公共职责
|
||
|
||
`BaseClient` / `BaseModelClient` 负责:
|
||
|
||
- 调用参数处理链:尺寸、画幅、时长、多模态内容过滤、默认值填充。
|
||
- 校验模型能力:图片参考、视频参考、音频参考、文件输入、stream 支持等。
|
||
- 发出进度事件:构建参数、排队、提交、轮询、下载、上传、完成。
|
||
- 执行通用 timeout、backoff、错误分类、重试和 failover 策略。
|
||
- 统一记录 `gateway_task_attempts`、`gateway_task_events`、运行指标。
|
||
- 统一提取 `billings` 与 `result`,交给 outbox 做结算事件。
|
||
- 统一处理文件资产:已上传文件引用、远程 URL 转存、provider 临时 URL 拉取后调用 `server-main` `/v1/files/upload`。
|
||
|
||
### 10.3 Provider Client 实现职责
|
||
|
||
具体 client 只负责供应商协议:
|
||
|
||
- OpenAI-compatible:同步/stream chat、responses、image generation。
|
||
- Gemini:Chat、多模态图片输入、生图、图像编辑优先进入第一阶段迁移。
|
||
- RunningHub:模板类 app 提交、轮询、结果提取,队列 key 使用 `${provider}-${methodName}`。
|
||
- Jimeng / Vidu / Kling / Hunyuan Video:视频任务提交与轮询。
|
||
- Suno / Speech / Digital Human:各自的提交和结果归一。
|
||
- SimulationClient:用于测试模式和 dry-run,完整模拟提交、轮询、进度、失败、结果,不参与真实生产候选。
|
||
- MockTest:用于 provider contract 回归和 loopback,不能和生产路由混在同一个职责里;应由独立 registry / loopback service 管理。
|
||
|
||
每个 provider 至少需要:
|
||
|
||
- contract test:固定输入输出 DTO。
|
||
- retry classification test:哪些错误可重试、哪些不可重试。
|
||
- progress event snapshot:确保前端进度面板兼容。
|
||
- billing snapshot:确保预估扣费和最终 billings 语义一致。
|
||
|
||
## 11. 队列持久化、恢复与限流执行
|
||
|
||
### 11.1 持久化队列原则
|
||
|
||
队列必须以 PostgreSQL 为事实源,Redis / 内存只作为加速和通知层。服务异常重启后,任务状态必须可从 PG 恢复。
|
||
|
||
任务生命周期:
|
||
|
||
```text
|
||
created -> queued -> leased -> running -> polling -> succeeded
|
||
| |
|
||
| -> failed_retryable -> queued
|
||
-> failed_final / cancelled
|
||
```
|
||
|
||
核心规则:
|
||
|
||
- 入队时写 `gateway_tasks`,状态为 `queued`,并写初始 `gateway_task_events`。
|
||
- worker 获取任务时使用事务、`FOR UPDATE SKIP LOCKED` 或 advisory lock,写入 `locked_by`、`locked_at`、`heartbeat_at`。
|
||
- worker 运行中定期刷新 `heartbeat_at`。
|
||
- 服务启动时扫描 `running/polling/leased` 且 heartbeat 超时的任务。
|
||
- 有 `remote_task_id` 的任务优先进入 reconciliation poller,继续取回供应商结果。
|
||
- 没有 `remote_task_id` 的任务按 retry 策略回到 `queued` 或进入 `failed_final`。
|
||
- 结算 outbox 独立重试,任务成功不等于结算成功。
|
||
|
||
### 11.2 恢复策略
|
||
|
||
| 重启前状态 | 恢复动作 |
|
||
| --- | --- |
|
||
| `queued` | 保持队列,等待 worker 重新租约 |
|
||
| `leased` 但无 heartbeat | 释放锁,回到 `queued` |
|
||
| `running` 且无 `remote_task_id` | 视为提交前失败,按 retry 策略重试 |
|
||
| `running/polling` 且有 `remote_task_id` | 进入 poller 继续取回结果 |
|
||
| `succeeded` 但 settlement pending | outbox 继续补偿 |
|
||
| `failed_retryable` 且未超最大次数 | 按 `next_run_at` 回队列 |
|
||
| `failed_final/cancelled` | 不自动恢复 |
|
||
|
||
### 11.3 限流执行
|
||
|
||
worker 租约任务前必须先拿到限流令牌:
|
||
|
||
- TPM 令牌:按 `gateway_rate_limit_counters` 的一分钟窗口预占 token,完成后 reconcile 实际 token。
|
||
- RPM 令牌:提交 provider 前扣减一分钟窗口请求数,失败切换的新 attempt 重新评估。
|
||
- 并发令牌:按 `queue_key` / `client_id` / `method_name` / `platform_model_id` 维度写 `gateway_concurrency_leases`。
|
||
- 排队令牌:超过 `max_waiting` 后直接返回兼容错误。
|
||
- 冷却令牌:客户端错误达到阈值后设置 `cooldown_until`。
|
||
|
||
如果 worker 拿不到令牌,任务保持 `queued` 并更新 `next_run_at`,不能占用 worker 长时间空等。
|
||
|
||
## 12. 进度流与 RxJS-like 中间结果
|
||
|
||
原系统里很多 provider 使用 RxJS Observable 在中间过程返回进度。Go 侧需要提供等价语义:
|
||
|
||
```go
|
||
type ProgressPublisher interface {
|
||
Publish(ctx context.Context, event ProgressEvent) error
|
||
Subscribe(ctx context.Context, taskID string, afterSeq int64) (<-chan ProgressEvent, error)
|
||
}
|
||
```
|
||
|
||
### 12.1 ProgressEvent 结构
|
||
|
||
```json
|
||
{
|
||
"taskId": "uuid",
|
||
"seq": 12,
|
||
"event": "progress",
|
||
"status": "running",
|
||
"phase": "polling",
|
||
"progress": 0.42,
|
||
"message": "Generating video frames",
|
||
"payload": {},
|
||
"createdAt": "2026-05-09T12:00:00Z"
|
||
}
|
||
```
|
||
|
||
### 12.2 推送通道
|
||
|
||
- 业务实时进度主通道:Gateway 按 `TASK_PROGRESS_CALLBACK_URL` 回调 `server-main`,由 `server-main` 复用原 WebSocket 网关推送给业务前端。
|
||
- 控制台 SSE:`GET /api/v1/tasks/:taskId/events`,支持 `Last-Event-ID` 回放,用于 Gateway 控制台、调试和故障诊断。
|
||
- Gateway WebSocket:用于网关控制台高频队列监控、运行态监控;不替代业务前端现有 WebSocket 通道。
|
||
- OpenAI stream:`/chat/completions`、`/v1/chat/completions`、`/responses` 需要保持原 stream chunk 格式。
|
||
|
||
任务进度回调示例:
|
||
|
||
```http
|
||
POST ${TASK_PROGRESS_CALLBACK_URL}
|
||
Authorization: Bearer ${SERVER_MAIN_INTERNAL_TOKEN}
|
||
Content-Type: application/json
|
||
Idempotency-Key: ${taskId}:${seq}
|
||
X-EasyAI-Event-Type: task.progress
|
||
```
|
||
|
||
```json
|
||
{
|
||
"eventId": "uuid",
|
||
"taskId": "uuid",
|
||
"externalTaskId": "server-main-task-id",
|
||
"userId": "user-id",
|
||
"tenantId": "tenant-id",
|
||
"apiKeyId": "optional",
|
||
"kind": "images.generations",
|
||
"model": "gpt-image-1",
|
||
"seq": 12,
|
||
"event": "progress",
|
||
"status": "running",
|
||
"phase": "polling",
|
||
"progress": 0.42,
|
||
"message": "Generating video frames",
|
||
"payload": {},
|
||
"createdAt": "2026-05-09T12:00:00Z"
|
||
}
|
||
```
|
||
|
||
`server-main` 收到后不重新执行任务,只做三件事:
|
||
|
||
1. 按 `Idempotency-Key` 或 `taskId + seq` 幂等落库/去重。
|
||
2. 根据 `externalTaskId`、`taskId`、`userId`、`tenantId` 找到原业务会话或任务频道。
|
||
3. 通过原 WebSocket 网关推送给业务前端,保持前端订阅协议不大改。
|
||
|
||
### 12.3 进度持久化要求
|
||
|
||
- 所有对用户可见的状态变化都必须先写 `gateway_task_events`,再写 `gateway_task_callback_outbox`,最后广播/投递。
|
||
- 客户端断线后能从 `seq` 或 `Last-Event-ID` 补齐。
|
||
- 任务恢复后必须先重放最近状态,再继续发布新进度。
|
||
- 完成态事件必须包含足够的结果摘要,前端无需额外轮询才能更新卡片状态。
|
||
- 回调投递失败不能阻塞任务执行,但必须进入 outbox 重试,并在控制台显示滞留状态。
|
||
- 同一任务的回调按 `seq` 保序投递;允许不同任务并发投递。
|
||
|
||
## 13. 测试模式与全链路模拟
|
||
|
||
AI Gateway 需要内置测试模式,目标是在不触达真实供应商、不真实扣费、不调用主服务生产上传接口的前提下,把请求完整跑过路由、候选客户端选择、队列、限流、失败切换、进度流、结果归一、任务恢复等核心链路。
|
||
|
||
### 13.1 启用方式
|
||
|
||
支持三种启用粒度:
|
||
|
||
| 粒度 | 配置 | 说明 |
|
||
| --- | --- | --- |
|
||
| 全局 | `AI_GATEWAY_RUN_MODE=production|simulation` | 整个服务进入生产或模拟模式 |
|
||
| 请求级 | `X-EasyAI-Test-Mode: simulation` 或 body `test_mode=true` | 仅对单次请求模拟,默认只允许 `power/manager` 或内部服务使用 |
|
||
| 平台/模型级 | `platform.config.testMode`、`model.config.testMode` | 指定平台、模型在测试环境永远走模拟 |
|
||
|
||
安全要求:
|
||
|
||
- 生产环境默认禁止普通用户请求级 test mode;需要显式开启 `ALLOW_REQUEST_TEST_MODE=true`。
|
||
- OpenAPI `sk-*` 默认不能打开 test mode,除非 API Key 或租户配置允许。
|
||
- 进入 simulation 后必须在 task、attempt、event、response 中标记 `simulated=true`,避免和真实任务混淆。
|
||
|
||
### 13.2 模拟链路
|
||
|
||
测试模式仍走完整执行链:
|
||
|
||
```text
|
||
HTTP route -> auth -> route compatibility adapter -> model router
|
||
-> persistent queue -> rate limiter -> SimulationClient
|
||
-> progress events -> normalized result -> dry-run billing -> task completed
|
||
```
|
||
|
||
不会执行的动作:
|
||
|
||
- 不调用真实 provider 的 submit / poll / cancel API。
|
||
- 不生成真实供应商订单或任务。
|
||
- 不向 `server-main` 发真实扣费结算事件。
|
||
- 不调用 `server-main` 的生产文件上传,除非显式使用测试环境上传接口。
|
||
- 不消耗真实平台并发额度。
|
||
|
||
仍会执行的动作:
|
||
|
||
- 真实鉴权。
|
||
- 真实路由和候选客户端排序。
|
||
- 真实队列持久化、锁、heartbeat、恢复。
|
||
- 真实限流令牌计算。
|
||
- 真实进度事件持久化和推送。
|
||
- 真实任务记录、attempt 记录、dry-run outbox 记录。
|
||
|
||
### 13.3 Simulation Profile
|
||
|
||
SimulationClient 根据 `simulation_profile` 生成确定性行为:
|
||
|
||
```json
|
||
{
|
||
"simulation_profile": {
|
||
"latencyMs": [300, 800, 1200],
|
||
"progressSteps": [0.1, 0.35, 0.7, 1],
|
||
"resultKind": "image",
|
||
"resultCount": 2,
|
||
"failures": [
|
||
{ "attempt": 1, "errorCode": "timeout", "retryable": true },
|
||
{ "attempt": 2, "errorCode": "rate_limit", "retryable": true }
|
||
],
|
||
"seed": "task-id-or-custom-seed"
|
||
}
|
||
}
|
||
```
|
||
|
||
用途:
|
||
|
||
- 模拟成功:返回固定格式图片、视频、音频、文本或 embedding 结果。
|
||
- 模拟失败:按 attempt 注入可重试 / 不可重试错误。
|
||
- 模拟慢任务:验证进度、断线重连、任务恢复。
|
||
- 模拟限流:验证排队、等待上限、cooldown。
|
||
- 模拟未知提交态:验证 `retryOnSubmitUnknown` 和人工补偿路径。
|
||
|
||
### 13.4 失败切换测试
|
||
|
||
测试模式必须能验证“上一个客户端失败,下一个客户端重试”:
|
||
|
||
1. router 仍按真实规则选出多个候选客户端。
|
||
2. SimulationClient 为第一个候选返回 retryable error。
|
||
3. runtime 写入 failed attempt。
|
||
4. retry policy 判断可切换。
|
||
5. router 排除已失败且不可复用的 client,选下一个候选。
|
||
6. 第二个候选成功后,任务以 `succeeded` 完成,并记录完整 attempts。
|
||
|
||
禁用重试时,同样的失败应直接进入 `failed_final`,不能切换客户端。
|
||
|
||
### 13.5 Dry-run 计费与结算
|
||
|
||
- `EstimateBilling` 仍使用真实计费配置计算,结果标记 `simulated=true`。
|
||
- `settlement_outbox` 可写入 `dry_run` 事件,但默认状态为 `skipped`,不触发主服务扣费。
|
||
- 若需要联调主服务结算接口,只能调用 `server-main` 的 dry-run settlement endpoint,不能复用真实扣费 endpoint。
|
||
|
||
### 13.6 模拟结果资产
|
||
|
||
测试模式下的文件结果有三种来源:
|
||
|
||
- 内置静态占位 URL,例如 `/static/simulation/image.png`。
|
||
- data URL / inline metadata,仅用于接口契约测试。
|
||
- 测试文件上传接口:只有显式启用 `AI_GATEWAY_SIMULATION_UPLOAD=true` 时才允许调用 `server-main` 测试环境上传接口;默认使用内置静态占位资产。
|
||
|
||
结果必须带:
|
||
|
||
```json
|
||
{
|
||
"simulated": true,
|
||
"assetSource": "simulation",
|
||
"provider": "simulation"
|
||
}
|
||
```
|
||
|
||
### 13.7 观测与审计
|
||
|
||
控制台需要能筛选 `run_mode=simulation` 的任务,展示:
|
||
|
||
- 使用的 simulation profile。
|
||
- 每次 attempt 的模拟错误和重试决策。
|
||
- 生成的 progress events。
|
||
- dry-run billing。
|
||
- 是否触发了恢复流程。
|
||
|
||
## 14. server-main 对接方式
|
||
|
||
### 14.1 短期:server-main 仍保留入口
|
||
|
||
`server-main` 对外接口不变:
|
||
|
||
- `/chat/completions`
|
||
- `/images/generations`
|
||
- `/video/generations`
|
||
- `/v1/chat/completions`
|
||
- `/v1/images/generations`
|
||
- `/v1/video/generations`
|
||
|
||
内部 `OpenaiService` 变成薄门面:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant FE as Frontend
|
||
participant MAIN as server-main
|
||
participant GW as AI Gateway
|
||
participant V as Vendor
|
||
|
||
FE->>MAIN: POST /images/generations
|
||
MAIN->>MAIN: create history / task id
|
||
MAIN->>GW: POST /api/v1/images/generations
|
||
GW->>V: submit task
|
||
GW-->>MAIN: POST task progress callback
|
||
MAIN-->>FE: WebSocket task progress
|
||
V-->>GW: result
|
||
GW-->>MAIN: POST task completed callback
|
||
MAIN-->>FE: WebSocket task completed
|
||
GW-->>MAIN: settlement event with billings
|
||
MAIN->>MAIN: billing + history update
|
||
GW-->>MAIN: task result
|
||
MAIN-->>FE: response
|
||
```
|
||
|
||
### 14.2 实时任务进度回调
|
||
|
||
AI Gateway 不直接替换原业务前端的 WebSocket 订阅方式。Gateway 在配置中指定任务进度回调地址:
|
||
|
||
```env
|
||
TASK_PROGRESS_CALLBACK_URL=http://easyai-server-main:3000/internal/platform/task-progress-callbacks
|
||
TASK_PROGRESS_CALLBACK_ENABLED=true
|
||
TASK_PROGRESS_CALLBACK_TIMEOUT_MS=5000
|
||
TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS=10
|
||
```
|
||
|
||
运行时要求:
|
||
|
||
- `BaseClient` / runtime 每产生一个进度事件,先写 `gateway_task_events`,再写 `gateway_task_callback_outbox`。
|
||
- callback worker 使用 `SERVER_MAIN_INTERNAL_TOKEN` 调用 `TASK_PROGRESS_CALLBACK_URL`。
|
||
- `server-main` 收到事件后进入主服务内部推送流程,再由原 WebSocket 网关推送给业务前端。
|
||
- Gateway 控制台仍可通过 SSE 读 `gateway_task_events`,用于运维诊断,不影响业务前端推送通道。
|
||
- 如果 `server-main` 短暂不可用,Gateway 按 outbox 重试;超过最大次数后标记 `failed`,控制台可手动 replay。
|
||
|
||
### 14.3 中期:前端直接打 Gateway
|
||
|
||
前端配置 `VITE_GATEWAY_API_BASE_URL`,生成类请求直接走网关路由到 AI Gateway。`server-main` 只处理登录、刷新、余额、历史、开放文件上传、扣费。
|
||
|
||
即使生成请求改为前端直连 Gateway,业务实时进度默认仍回调 `server-main` 内部任务进度接口,除非某个新业务明确迁移到 Gateway 自身的事件通道。
|
||
|
||
### 14.4 结算事件
|
||
|
||
Gateway 任务成功后向 `server-main` 发送结算事件:
|
||
|
||
```json
|
||
{
|
||
"eventId": "uuid",
|
||
"taskId": "uuid",
|
||
"userId": "user-id",
|
||
"tenantId": "tenant-id",
|
||
"apiKeyId": "optional",
|
||
"kind": "images.generations",
|
||
"model": "gpt-image-1",
|
||
"billings": [
|
||
{
|
||
"resourceType": "image",
|
||
"amount": 1,
|
||
"unit": "item",
|
||
"cost": 100
|
||
}
|
||
],
|
||
"resultRef": {
|
||
"url": "https://..."
|
||
}
|
||
}
|
||
```
|
||
|
||
要求:
|
||
|
||
- `server-main` 以 `eventId` / `taskId` 做幂等。
|
||
- Gateway 保留 outbox 或重试表,避免结算事件丢失。
|
||
- 扣费失败时任务结果仍可先保存,但前端展示需要明确异常状态。
|
||
|
||
## 15. 文件上传
|
||
|
||
文件上传策略收敛为:**AI Gateway 不维护自己的 OSS/COS/S3 配置,不做预签名,不直接写对象存储;所有需要上传或转存的文件,统一调用 `server-main` 已开放的文件上传接口**。这样可以复用主服务现有 OSS 配置、权限、文件记录、MIME 推断和后续审计。
|
||
|
||
### 15.1 ServerMainUploadClient 抽象
|
||
|
||
```go
|
||
type ServerMainUploadClient interface {
|
||
UploadMultipart(ctx context.Context, input UploadMultipartInput) (UploadedAsset, error)
|
||
UploadFromURL(ctx context.Context, input UploadFromURLInput) (UploadedAsset, error)
|
||
ResolveAsset(ctx context.Context, ref AssetRef) (ResolvedAsset, error)
|
||
}
|
||
```
|
||
|
||
### 15.2 调用接口
|
||
|
||
默认调用 `server-main` 的开放上传接口:
|
||
|
||
| Method | Path | 说明 |
|
||
| --- | --- | --- |
|
||
| `POST` | `/v1/files/upload` | OpenAPI 兼容 multipart 文件上传 |
|
||
|
||
调用要求:
|
||
|
||
- 用户请求:透传用户 JWT 或 OpenAPI `sk-*`,保持与原主服务上传权限一致。
|
||
- Gateway 内部上传:使用服务令牌加原始用户上下文头,例如 `X-EasyAI-User-Id`、`X-EasyAI-Tenant-Id`、`X-EasyAI-Task-Id`,具体以 `server-main` 内部约定为准。
|
||
- 上传返回值以主服务响应为准,Gateway 只做字段归一并写入 `gateway_upload_assets`。
|
||
- 测试模式默认不调用上传接口,除非显式启用测试上传。
|
||
|
||
### 15.3 上传场景
|
||
|
||
| 场景 | 处理方式 |
|
||
| --- | --- |
|
||
| 前端已经通过主服务上传 | 请求中携带主服务返回的 URL / file id,Gateway 只校验和记录 |
|
||
| Gateway 需要转存远程 URL | Gateway 下载远程内容,作为 multipart 调 `/v1/files/upload` 上传到主服务 |
|
||
| Provider 返回临时 URL | Gateway 拉取结果,再调 `/v1/files/upload` 转存,最终结果使用主服务 URL |
|
||
| OpenAPI multipart 上传 | 继续由 `server-main` 的 `/v1/files/upload` 承接;Gateway 不新增并行上传入口 |
|
||
| base64 小文件 | Gateway 解码为 multipart,再调用 `/v1/files/upload` |
|
||
|
||
### 15.4 安全与可靠性
|
||
|
||
- 限制最大文件大小、content-type、扩展名。
|
||
- 远程 URL 下载必须禁止内网地址、metadata 地址、超长重定向链。
|
||
- 上传到 `server-main` 时带 `Idempotency-Key`,避免重启或 retry 造成重复文件记录。
|
||
- `gateway_upload_assets` 记录主服务返回的 file id、URL、object key、checksum、响应快照。
|
||
- 如果上传失败,任务按 provider retry 策略判断是否可重试;上传失败本身也要写 attempt/event。
|
||
- Gateway 不保存长期文件密钥,不暴露 OSS 配置页面。
|
||
|
||
## 16. 前端页面设计
|
||
|
||
前端使用 React + TypeScript + TSX + `shadcn-ui`,定位不是单纯后台控制台,而是“AI 能力门户 + 用户工作台 + 管理工作台 + API 文档中心”。页面根据 `IDENTITY_MODE` 切换身份来源:独立模式走 Gateway 本地登录 / API Key;接入 `server-main` 时走主服务 JWT / OpenAPI Key 授权。普通用户只能进入模型、用户工作台和 API 文档,管理员额外进入管理工作台。
|
||
|
||
### 16.1 一级导航
|
||
|
||
| 一级模块 | 路由 | 默认权限 | 定位 |
|
||
| --- | --- | --- | --- |
|
||
| 登录 | `/login`、`/register` | `public` | Gateway 本地账号登录注册、外部 token 入口 |
|
||
| 首页 | `/` | `public/basic` | 服务入口、模型能力概览、用量摘要、最近任务、快捷入口 |
|
||
| 模型 | `/models` | `public/basic` | 可用模型浏览、能力筛选、价格/限流展示、模型调用入口 |
|
||
| 用户工作台 | `/workspace` | `basic` | 个人中心、余额充值、API Key、任务记录 |
|
||
| 管理工作台 | `/admin` | `power/manager` | 用户与用户组、全局模型配置、平台管理、队列限流、结算与系统设置 |
|
||
| API 文档 | `/docs` | `public/basic` | 开放接口文档、鉴权说明、示例代码、在线调用测试 |
|
||
|
||
### 16.2 登录与注册
|
||
|
||
首期登录页先提供普通账号能力和可选邀请码注册,后续再接 SSO、验证码、MFA:
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 登录 | `/login` | `public` | 用户名/邮箱 + 密码登录 Gateway 本地账号 |
|
||
| 注册 | `/register` | `public` | 创建 Gateway 本地账号;可填写租户 key / 租户名称 / 邀请码,邀请码不是必填 |
|
||
| 外部 Token 入口 | `/login?mode=external` | `public` | 粘贴 `server-main` access token,用于接入模式和开发调试 |
|
||
|
||
登录成功后统一获得 Gateway 可校验的 access token;本地账号 token 中包含 `source=gateway`、`gatewayUserId`、`gatewayTenantId`、`tenantKey`。外部 token 保留 `source=server-main` 或主服务返回的 claim。
|
||
|
||
### 16.3 首页
|
||
|
||
首页不是营销页,首屏应直接提供可操作入口和运行状态。
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 首页总览 | `/` | `public/basic` | 推荐模型、最近任务、余额摘要、API Key 状态、服务公告 |
|
||
| 能力概览 | `/capabilities` | `public/basic` | Chat、生图、生视频、音频、Embedding 等能力入口 |
|
||
| 服务状态 | `/status` | `public/basic` | 网关健康、模型可用性、平台异常公告、限流提示 |
|
||
|
||
首页组件:
|
||
|
||
- `HeroStatusBand`:显示 Gateway 健康、可用模型数、今日任务数、当前余额。
|
||
- `QuickStartActions`:Chat 调用、生图调用、生视频调用、创建 API Key、查看 API 文档。
|
||
- `ModelCapabilityGrid`:按文本、图像、视频、音频、Embedding 展示可用能力。
|
||
- `RecentTasks`:最近任务状态、进度、失败原因和重试入口。
|
||
- `UsageSnapshot`:本月调用量、token、图片/视频任务、费用趋势。
|
||
|
||
### 16.4 模型
|
||
|
||
模型页面面向普通用户和管理员,但展示内容按权限裁剪。
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 模型广场 | `/models` | `public/basic` | 按能力、provider、价格、上下文、多模态筛选模型 |
|
||
| 模型详情 | `/models/:modelKey` | `public/basic` | 能力、价格、限流、参数 schema、示例请求、在线试用 |
|
||
| 模型价格 | `/models/:modelKey/pricing` | `basic` | 文本输入/输出、图像分辨率/质量、视频时长/分辨率等价格 |
|
||
| 模型调用测试 | `/models/:modelKey/playground` | `basic` | 选择 API Key 或 JWT,在线测试 Chat/生图/生视频 |
|
||
|
||
模型页能力:
|
||
|
||
- 筛选:模型类型、provider、是否支持 stream、多模态、参考图/视频/音频、上下文窗口、价格区间。
|
||
- 展示:有效模型价、TPM/RPM/并发限制、是否支持测试模式、常见错误码。
|
||
- 管理员视角:展示平台模型来源、基准模型映射、平台折扣、能力覆盖和实际候选平台。
|
||
|
||
### 16.5 用户工作台
|
||
|
||
用户工作台关注“我能用什么、用了多少、怎么调用、历史任务在哪里”。
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 个人中心总览 | `/workspace/overview` | `basic` | 账号信息、身份来源、角色、用户组、余额、API Key 数、最近任务、用量摘要 |
|
||
| 余额与充值 | `/workspace/billing` | `basic` | 余额、资源包、充值入口、消费记录、用户组充值折扣、发票/订单状态 |
|
||
| API Key 管理 | `/workspace/api-keys` | `basic` | API Key 列表、创建、禁用、重置、权限范围、最近调用 |
|
||
| 任务记录 | `/workspace/tasks` | `basic` | Chat/生图/生视频等任务列表,按状态、模型、时间筛选 |
|
||
| 任务详情 | `/workspace/tasks/:id` | `basic` | 请求、进度事件、结果、计费、上传资产、失败原因 |
|
||
|
||
边界:
|
||
|
||
- 独立模式下,用户资料、API Key、余额、充值订单和钱包流水由 Gateway 本地模块闭环;接入 `server-main` 时,余额、充值、订单、API Key 生命周期仍归 `server-main`,Gateway 前端可以通过 `server-main` API 或兼容代理展示。
|
||
- 用户组影响充值折扣、调用折扣和并发/限流;工作台需要展示当前用户命中的用户组、折扣说明、TPM/RPM/并发限制。
|
||
- 任务记录以 Gateway 任务为执行事实源,但业务历史最终仍可由 `server-main` 汇总。
|
||
- API Key 创建时必须显示一次性 secret,后续只展示 key 名称、前缀、权限和最近使用时间。
|
||
|
||
### 16.6 管理工作台
|
||
|
||
管理工作台只对 `power/manager` 开放,负责全局模型、平台、限流、重试、运行态和系统集成。当前阶段平台凭证和 provider 凭证仅允许全局管理员配置,租户管理员暂不拥有自助配置入口。
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 管理总览 | `/admin` | `power` | 服务健康、任务吞吐、成功率、队列积压、异常平台、回调 outbox |
|
||
| 租户管理 | `/admin/tenants` | `power` | 本地租户、同步租户、租户策略、状态和用量隔离 |
|
||
| 用户管理 | `/admin/users` | `power` | 本地用户、同步用户、角色、状态、同步差异和用户组命中 |
|
||
| 用户组管理 | `/admin/user-groups` | `power` | 用户组、成员关系、充值折扣、调用折扣、并发/限流策略 |
|
||
| 全局模型配置 | `/admin/models/global` | `power` | 基准模型库、能力 schema、基准定价、默认限流模板 |
|
||
| 基准 Provider | `/admin/catalog/providers` | `power` | provider 列表、协议类型、能力 schema、启停状态 |
|
||
| 基准模型详情 | `/admin/catalog/base-models/:id` | `power` | 能力、价格版本、默认 TPM/RPM/并发、引用平台模型 |
|
||
| 定价规则 | `/admin/pricing/rules` | `power` | 文本/图像/视频/音频价格、动态权重、版本生效时间 |
|
||
| 平台管理 | `/admin/platforms` | `power` | 平台 CRUD、启停、优先级、凭证状态、复制、异常重置 |
|
||
| 平台详情 | `/admin/platforms/:id` | `power` | 基础信息、凭证、模型、折扣、限流、重试、运行态、快照 |
|
||
| 平台模型 | `/admin/platform-models` | `power` | 平台模型列表、基准映射、能力覆盖、价格覆盖、权限过滤 |
|
||
| API 配置 | `/admin/platform-apis` | `power` | 兼容原 `integration/platform-api` 的 CRUD 与执行测试 |
|
||
| 队列监控 | `/admin/runtime/queues` | `power` | queue key、等待数、运行数、租约、恢复状态 |
|
||
| 客户端运行态 | `/admin/runtime/clients` | `power` | running、waiting、limiter ratio、cooldown、last error |
|
||
| 限流策略 | `/admin/policies/rate-limit` | `power` | TPM、RPM、并发、队列长度、冷却策略 |
|
||
| 重试策略 | `/admin/policies/retry` | `power` | global/provider/platform/model/method 重试策略 |
|
||
| 任务审计 | `/admin/tasks` | `power` | 全量任务、attempt、失败切换、用户/模型/平台筛选 |
|
||
| 上传记录 | `/admin/uploads` | `power` | 主服务上传记录映射、任务关联、失败重试 |
|
||
| 结算 outbox | `/admin/settlements` | `manager` | 结算事件、重试、跳过、幂等 key |
|
||
| 回调 outbox | `/admin/callbacks` | `power` | 任务进度回调状态、失败原因、手动 replay |
|
||
| 测试模式 | `/admin/simulation` | `power` | simulation profile、失败注入、慢任务、dry-run billing |
|
||
| 系统设置 | `/admin/settings` | `manager` | server-main 地址、内部令牌、运行模式、安全开关 |
|
||
|
||
关键管理页面:
|
||
|
||
- 全局模型配置:维护 `model_catalog_providers`、`base_model_catalog`、`model_pricing_rules`,支持导入旧项目配置。
|
||
- 租户管理:维护 `gateway_tenants`,支持本地租户 CRUD、从 `server-main` 增量同步、禁用状态同步、租户策略和用量隔离审计。
|
||
- 用户管理:维护 `gateway_users`,支持本地用户 CRUD、从 `server-main` 增量同步、禁用状态同步、角色同步和同步差异审计。
|
||
- 用户组管理:维护 `gateway_user_groups`、`gateway_user_group_memberships`,支持从 `server-main` 同步成员关系;独立模式执行全部策略,接入模式下充值折扣由 `server-main` 执行,Gateway 执行调用折扣和并发/限流。
|
||
- 平台管理:创建平台时选择基准 provider,配置默认折扣系数;平台模型可 follow 基准、按折扣继承或自定义覆盖。
|
||
- 队列与限流:展示 TPM/RPM 窗口、预占值、并发 lease、cooldown、next run。
|
||
- 回调 outbox:展示 Gateway 到进度回调目标的投递结果,支持按 task replay。
|
||
|
||
### 16.7 API 文档与在线调用测试
|
||
|
||
API 文档面向开发者,需要能完成从鉴权到真实调用的闭环。
|
||
|
||
| 页面 | 路由 | 权限 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 文档首页 | `/docs` | `public/basic` | 快速开始、鉴权方式、模型列表、错误码、限流说明 |
|
||
| 鉴权 | `/docs/auth` | `public/basic` | JWT、OpenAPI `sk-*`、Header、权限范围、API Key 安全 |
|
||
| Chat | `/docs/api/chat` | `public/basic` | `/v1/chat/completions`、stream、工具调用、示例代码 |
|
||
| Responses | `/docs/api/responses` | `public/basic` | `/v1/responses` 请求、取消、结构化输出 |
|
||
| 图片 | `/docs/api/images` | `public/basic` | `/v1/images/generations`、`/v1/images/edits`、文件输入 |
|
||
| 视频 | `/docs/api/videos` | `public/basic` | `/v1/video/generations`、进度查询、结果取回 |
|
||
| 音频/语音 | `/docs/api/audio` | `public/basic` | speech、music、digital human 等接口 |
|
||
| Embeddings | `/docs/api/embeddings` | `public/basic` | embedding 模型、批量输入、维度说明 |
|
||
| 文件上传 | `/docs/api/files` | `public/basic` | 说明仍调用 `server-main` `/v1/files/upload` |
|
||
| 任务结果 | `/docs/api/tasks` | `public/basic` | `/v1/ai/result/:taskId`、取消、状态、回调语义 |
|
||
| 错误码 | `/docs/errors` | `public/basic` | 兼容错误结构、retryable 分类、限流错误 |
|
||
| 在线调试 | `/docs/playground` | `basic` | 选择模型、API Key、请求模板,执行在线调用测试 |
|
||
|
||
在线调试器:
|
||
|
||
- 左侧选择接口类型:Chat、Responses、生图、图像编辑、生视频、Embedding、语音。
|
||
- 中间为参数表单 + JSON editor 双模式,表单字段来自模型能力 schema。
|
||
- 右侧展示 curl、JavaScript、Python 示例代码和实时响应。
|
||
- 支持 stream 结果面板、任务进度时间线、结果预览、usage / billings。
|
||
- 支持测试模式开关;开启后标记 `simulation`,不触达真实 provider、不真实扣费。
|
||
- 调试前展示当前 API Key 的权限、余额、TPM/RPM/并发限制。
|
||
|
||
### 16.8 前端模块目录
|
||
|
||
```text
|
||
apps/web/src/
|
||
app/
|
||
routes.tsx
|
||
providers.tsx
|
||
components/
|
||
ui/ # shadcn-ui generated components
|
||
layout/
|
||
data-table/
|
||
json-viewer/
|
||
status-badge/
|
||
features/
|
||
auth/
|
||
login/
|
||
register/
|
||
home/
|
||
models/
|
||
workspace/
|
||
overview/
|
||
billing/
|
||
api-keys/
|
||
tasks/
|
||
admin/
|
||
dashboard/
|
||
catalog/
|
||
tenants/
|
||
users/
|
||
user-groups/
|
||
pricing/
|
||
platforms/
|
||
platform-apis/
|
||
runtime/
|
||
policies/
|
||
tasks/
|
||
callbacks/
|
||
uploads/
|
||
settlements/
|
||
simulation/
|
||
settings/
|
||
docs/
|
||
api-reference/
|
||
playground/
|
||
lib/
|
||
api-client.ts
|
||
auth.ts
|
||
format.ts
|
||
query-keys.ts
|
||
contracts/
|
||
dto.ts # 或从 packages/contracts 引入
|
||
```
|
||
|
||
### 16.9 前端数据流
|
||
|
||
- 使用 `@tanstack/react-query` 管理列表、详情、轮询和 mutation。
|
||
- 表格使用 `@tanstack/react-table`,配合 shadcn `Table`。
|
||
- 表单使用 `react-hook-form` + `zod`,校验策略与后端 DTO 对齐。
|
||
- 用户工作台在独立模式下调用 Gateway 本地身份/账务模块;接入 `server-main` 时余额、充值、API Key 生命周期优先调用 `server-main` API,Gateway 展示执行侧任务、模型能力、用户同步状态和用户组执行策略。
|
||
- Gateway 控制台事件统一封装为 `useTaskEvents(taskId)`;业务前端实时进度仍由原 WebSocket 网关推送。
|
||
- 所有 destructive action 使用 shadcn `Dialog` 二次确认。
|
||
- 页面级权限由路由 loader 或 wrapper 判断,不在每个按钮里重复散落判断。
|
||
|
||
## 17. 迁移计划
|
||
|
||
### Phase 0:脚手架与契约
|
||
|
||
- 建立 monorepo、Go API、React 控制台、PG migration。
|
||
- 前端接入 `shadcn-ui`,建立基础组件目录和主题 token。
|
||
- PG 目标版本为 PostgreSQL 18;复用 Agent memory 的 `easyai-pgvector` 实例,但使用独立数据库 `easyai_ai_gateway`,避免与 `easyai_memory` 的记忆表混库。
|
||
- 容器网络内默认连接串为 `postgresql://easyai:easyai2025@easyai-pgvector:5432/easyai_ai_gateway?schema=public`;宿主机直跑时必须改成宿主机可访问的 host/port,例如 `localhost` 或实际映射端口。
|
||
- 如果只提供 `MEMORY_DATABASE_URL`,Go 侧只借用其中的 host/user/password,并按 `AI_GATEWAY_DATABASE_NAME` 替换数据库名;同时会把 Prisma 风格的 `schema=public` 转成 PostgreSQL `search_path` 参数。
|
||
- 建立 JWT / API Key 授权中间件骨架和 `standalone` / `server-main` / `hybrid` 身份模式配置,默认 `hybrid`。
|
||
- 固化 API、事件、数据库设计。
|
||
- 建立 simulation / dry-run 模式配置与安全开关,默认禁止生产环境普通用户随意开启。
|
||
- 建立基准 provider、基准模型库、价格规则、用户、用户组策略、邀请码、本地 API Key、钱包、充值订单和 TPM/RPM/并发限流表结构。
|
||
|
||
### Phase 1:模型库 + 首批生成能力
|
||
|
||
第一阶段只做可落地的核心闭环:模型库、大模型对话、生图、图像编辑。Client 只迁移 OpenAI 和 Gemini 两个,视频和其他 provider 后移。
|
||
|
||
- 导入 OpenAI、Gemini 的基准 provider、基准模型、能力 schema、默认限流模板,形成首批 `base_model_catalog`。
|
||
- 建立全局模型配置:模型类型、上下文、多模态能力、图片输入/输出能力、stream 支持、价格规则。
|
||
- 平台创建支持选择基准模型、设置默认折扣系数;平台模型支持 follow 基准模型、折扣继承和自定义覆盖。
|
||
- 建立 `gateway_users` 同步副本和 `gateway_user_groups` 策略解析,支持本地用户与 `server-main` 用户在同一套策略链路里命中不同折扣和限流。
|
||
- 建立本地账号闭环:普通注册可选邀请码,独立模式支持本地 API Key、余额、充值订单和钱包流水。
|
||
- 迁移大模型对话路由:`/chat/completions`、`/v1/chat/completions`,并为 `/responses` / `/v1/responses` 保留兼容契约。
|
||
- 迁移生图和图像编辑路由:`/images/generations`、`/v1/images/generations`、`/images/edits`、`/v1/images/edits`。
|
||
- 建立 OpenAI Chat / Image Client 与 Gemini Chat / Image Client 的 Base Client 实现和 contract test。
|
||
- 支持文本输入/输出 token 计费,图像按分辨率、质量、数量、生成/编辑模式计费。
|
||
- 实现调用 `server-main` 开放上传接口的 `ServerMainUploadClient`,覆盖参考图、mask、provider 临时 URL 转存、base64 小文件上传。
|
||
- 实现 SimulationClient,支持 Chat、生图、图像编辑的成功、失败、慢任务、限流、未知提交态等 profile。
|
||
|
||
### Phase 2:路由、队列与稳定性补强
|
||
|
||
- 迁移 `IntegrationModelFactory.assignClientsByModelName` 行为。
|
||
- 建立 Base Client 生命周期:构建参数、提交任务、轮询结果、归一化结果、估算计费、取消任务。
|
||
- 实现客户端失败切换策略:上一个客户端失败后按配置切到下一个候选客户端。
|
||
- 补队列持久化、TPM/RPM/并发限流、重启恢复、重试、超时、任务事件。
|
||
- 实现任务进度 callback outbox,将 Gateway 进度回调给实时推送通道;同时保留 Gateway 控制台 SSE / WebSocket 和 OpenAI stream。
|
||
- 为 OpenAI、Gemini 增加 retry classification test、billing snapshot、progress event snapshot。
|
||
|
||
### Phase 3:server-main 切薄门面与灰度
|
||
|
||
- `OpenaiService` 内部先将 Chat、生图、图像编辑切到 Gateway HTTP SDK。
|
||
- 结算事件接入 `server-main` 扣费链路。
|
||
- 前端逐步改 `Gateway API Base URL`。
|
||
- 开启 shadow / dry-run,比对旧实现和 Gateway 的候选模型、预估扣费、参数预处理结果。
|
||
|
||
### Phase 4:视频与更多 provider
|
||
|
||
- 在 Phase 1 稳定后再迁移生视频、音频、Embedding、音乐、数字人等能力。
|
||
- 迁移 RunningHub、Jimeng、Vidu、Kling、Hunyuan Video、Suno 等 provider。
|
||
- 对 app-style provider 补 `assignClientsByProviderMethod` 和 `provider + methodName` 队列 key。
|
||
- 每个 provider 增加 contract test、retry classification test、billing snapshot。
|
||
|
||
### Phase 5:server-main 清理旧实现
|
||
|
||
- 删除或冻结 `server-main` 中重复的 runtime client。
|
||
- 保留必要 BFF、历史、账单、文件上传能力。
|
||
|
||
## 18. 验收标准
|
||
|
||
- `pnpm nx run api:migrate` 可在 Agent memory PostgreSQL 中初始化 AI Gateway 表。
|
||
- `pnpm nx run api:test` 通过。
|
||
- `pnpm nx run web:build` 通过。
|
||
- 控制台使用 `shadcn-ui` 组件,不出现散落的独立 UI 体系。
|
||
- 使用 `server-main` 签发的 JWT 能访问 `/api/v1/me`。
|
||
- OpenAPI `sk-*` 能委托 `server-main` 校验并获得用户 claim。
|
||
- 原 `integration-platform`、站内生成、`/v1/*` 核心路由可用,DTO 与响应结构兼容。
|
||
- 基准 provider、基准模型库、基准价格和默认限流模板可在控制台维护。
|
||
- 用户、用户组、成员关系、充值折扣、调用折扣、TPM/RPM/并发策略可在管理工作台维护;独立模式可本地闭环,接入模式下充值执行仍以 `server-main` 为事实源。
|
||
- 接入 `server-main` 时,用户、角色、禁用状态和用户组成员关系支持增量同步;任务保存用户和用户组策略快照。
|
||
- 平台创建可设置默认折扣系数;平台模型未配置时 follow 基准模型,配置折扣时按基准价折扣计算,自定义覆盖时覆盖价格和能力。
|
||
- 文本定价支持输入/输出 token 分开计费;图像定价支持分辨率和质量权重;视频定价支持时长、分辨率、有无音频等权重。
|
||
- estimated billing 与真实任务 billings 使用同一个 effective pricing resolver,不能出现预估和真实路由价格来源不一致。
|
||
- 队列任务在服务异常重启后可恢复:已提交供应商的任务继续 poll,未提交任务重新排队。
|
||
- 限流策略覆盖 TPM、RPM、并发,并支持平台、客户端、基准模型、平台模型、方法、租户、用户组、用户、API Key 维度。
|
||
- TPM/RPM 一分钟窗口和并发 lease 在服务重启后可恢复或释放,不产生永久占用。
|
||
- 多客户端候选下,一个客户端可重试失败后按策略切换下一个客户端;禁用重试时必须直接失败。
|
||
- Base Client 架构覆盖构建参数、提交任务、取回结果、归一化结果、计费估算、取消任务。
|
||
- 任务中间进度可通过 SSE / WebSocket / stream 返回,并支持断线重放。
|
||
- 业务前端实时进度通过 `TASK_PROGRESS_CALLBACK_URL` 回调到 `server-main` 内部任务进度接口,再由主服务复用原 WebSocket 网关推送;callback outbox 支持失败重试和手动 replay。
|
||
- 文件上传统一调用 `server-main` 开放上传接口,Gateway 不维护自己的 OSS 配置。
|
||
- 测试模式下不会向真实平台提交任务、不会真实扣费、不会调用主服务生产上传接口,但会完整经过路由、队列、限流、重试、进度和结果归一。
|
||
- 测试模式支持 profile 注入可重试错误,能验证客户端失败切换;禁用重试时能验证直接失败。
|
||
- Phase 1 至少完成 Chat / 生图 / 图像编辑各一个 provider 的端到端任务;生视频放到 Phase 4 后续迁移。
|
||
- 结算事件在 `server-main` 幂等扣费。
|
||
- 同名模型跨平台权限过滤与旧逻辑一致。
|
||
|
||
## 19. 本轮设计复核结论与已确认决策
|
||
|
||
本轮复核后已修正:
|
||
|
||
- 身份边界:不再写成“用户/租户只能留在 `server-main`”,改为 `standalone`、`server-main`、`hybrid` 三种模式。
|
||
- 多租户:补 `gateway_tenants`,并把用户、任务、平台可见性、限流和策略解析都接入租户上下文。
|
||
- 登录注册:补 Gateway 本地账号注册登录接口和前端登录页规划。
|
||
- 进度回调:统一表述为回调 `server-main` 内部任务进度接口,不直接回调 ws-gateway。
|
||
- Phase 1 范围:继续限定为模型库、Chat、生图、图像编辑、OpenAI 和 Gemini;视频迁移放到后续阶段。
|
||
|
||
已确认决策:
|
||
|
||
1. 默认身份模式就是 `hybrid`,用于同时支持本地账号和 `server-main` token / API Key。
|
||
2. 普通注册支持填写邀请码,但邀请码不是必填项;填写后按邀请码加入指定租户和用户组。
|
||
3. 先做本地闭环:独立模式下本地 API Key、余额、充值订单和钱包流水由 Gateway 承接。
|
||
4. 当前阶段仅允许全局管理员配置 provider / platform 凭证和全局模型,不开放租户管理员自助配置凭证。
|