Files
sub2api-cn-relay-manager/docs/2026-05-12-sub2api-cn-relay-manager-solution.md

620 lines
15 KiB
Markdown
Raw Normal View History

2026-05-12 21:46:19 +08:00
# sub2api-cn-relay-manager 方案文档
日期2026-05-12
## 1. 背景
宿主 `sub2api` 是一个第三方开源系统,用户较多,迭代很快,且每周都会发布新版本。
当前目标不是修改宿主源码,也不是等待宿主内置原生插件运行时,而是建立一个完全独立交付的外部伴生项目,让任意一台已部署的 `sub2api` 都能通过安装该项目和导入模型包,快速具备国产模型 OpenAI 兼容中转能力。
## 2. 目标
本方案必须满足以下业务目标:
1. 独立交付,不修改宿主 `sub2api` 源码
2. 可跨机器复用,在任意一台兼容版本的 `sub2api` 上重复安装
3. 可一键导入多个国产模型 key
4. 导入后让普通用户继续通过 `sub2api` 标准 API 使用国产模型
5. 尽量热生效,不依赖宿主重编译
6. 能主动发现“内容未真正生效”“生效后漂移”“部分账号失效”
## 3. 核心结论
在“不改宿主代码”的前提下,不能把该能力定义为“宿主原生插件”。
正确做法是:
- 把这套能力定义为一个独立的外部伴生控制面项目
- 把国产模型接入定义为可安装的 `model_pack`
- 由控制面调用宿主已有管理 API 自动创建 `group / channel / account / plan`
- 由控制面维护安装状态、导入批次、探测结果和对账结果
因此,业务上它表现得像“独立安装插件”,但技术上它是“外部伴生安装器 + 模型资源包”。
## 3.1 宿主零改动硬约束
为了确保方案始终满足“绝不改宿主代码”,补充以下硬约束:
1. 不修改宿主源码
2. 不 fork 宿主并运行自定义二进制
3. 不直接写宿主数据库
4. 不向宿主容器或宿主目录写入运行时代码或配置补丁
5. 不依赖宿主未公开的动态加载、初始化钩子或内部运行时对象
6. 只通过宿主现有 HTTP 管理 API 和宿主公开标准 API 工作
这意味着本方案的本质是:
- 技术上:宿主外部自动化编排
- 业务上:像独立插件一样交付和安装
## 3.2 审核后的最终结论
经过对宿主现有能力复核后,可以确认:
- 该方案可以在零宿主代码改动前提下完成国产模型中转能力增加
- 该方案不能伪装成宿主原生插件中心
- 该方案必须把“访问闭环”和“对账闭环”都纳入首版
## 4. 为什么这条路线成立
虽然宿主没有原生插件运行时,但它已有足够的管理 API
- 创建 group`POST /api/v1/admin/groups`
- 创建 account`POST /api/v1/admin/accounts`
- 批量创建 account`POST /api/v1/admin/accounts/batch`
- 测试 account`POST /api/v1/admin/accounts/:id/test`
- 查询 account 模型:`GET /api/v1/admin/accounts/:id/models`
- 创建 channel`POST /api/v1/admin/channels`
- 创建 plan`POST /api/v1/admin/plans`
这意味着控制面完全可以把 `sub2api` 当作一个可编排宿主,而不需要侵入其源码。
但这里有一个经过审核后必须写明的宿主约束:
- 宿主标准 API 网关要求请求最终落到“已分组 API key”或“有效 subscription”
- 仅仅创建 `group / channel / account / plan`,还不足以保证普通用户已经可以调用国产模型
因此,本方案不能只做资源创建,还必须补齐用户访问闭环。
## 5. 总体架构
整体拆成两个发布物:
### 5.1 控制面 / 安装器
项目名:
`sub2api-cn-relay-manager`
职责:
- 连接宿主 `sub2api`
- 安装 `model_pack`
- 预检宿主版本与 API 能力
- 一键导入多个 key
- 通过宿主管理 API 创建和维护资源
- 做 smoke test
- 做持续对账
- 对外展示“可用 / 降级 / 漂移 / 失败”状态
### 5.2 模型包
项目内置样例:
`packs/openai-cn-pack/`
职责:
- 提供 provider 定义
- 提供默认 group / channel / plan / account 模板
- 提供 model mapping 和 smoke test model
- 提供导入策略和校验约束
模型包不携带宿主执行逻辑。
## 6. 运行时组件
控制面内部建议拆成以下模块:
### 6.1 Host Adapter
宿主适配器,只负责把控制面操作翻译成 `sub2api` admin API 调用。
接口建议:
- `GetHostVersion()`
- `ProbeCapabilities()`
- `CreateGroup()`
- `CreateChannel()`
- `CreatePlan()`
- `CreateAccount()`
- `BatchCreateAccounts()`
- `TestAccount()`
- `GetAccountModels()`
- `AssignSubscription()`
- `CheckAccessPath()`
- `DeleteGroup()`
- `DeleteChannel()`
- `DeletePlan()`
- `DeleteAccount()`
- `ListManagedResources()`
### 6.2 Pack Runtime
负责读取和校验 `model_pack`
- `pack.json`
- `providers/*.json`
- `checksums.txt`
### 6.3 Provision Engine
负责把一个 provider + 一批 key 变成宿主真实资源:
- 创建 group
- 创建 channel
- 创建 plan
- 创建 account
- 绑定 group / model mapping / pricing
- 调用测试接口验证账号
### 6.4 Reconciler
负责持续对账,主动发现:
- 资源是否还存在
- key 是否仍可用
- 模型是否仍可列出
- 测试是否通过
- 宿主资源是否被人工改坏
### 6.5 State Store
负责维护控制面自己的状态,因为宿主不知道“插件”概念。
## 7. 模型包结构
建议结构:
```text
openai-cn-pack/
pack.json
providers/
deepseek.json
kimi.json
qwen.json
glm.json
minimax.json
checksums.txt
docs/
```
## 8. 模型包协议
### 8.1 pack.json
```json
{
"pack_id": "openai-cn-pack",
"version": "1.0.0",
"vendor": "YourTeam",
"target_host": "sub2api",
"min_host_version": "0.1.126",
"max_host_version": "0.2.x",
"providers_dir": "providers",
"checksum_file": "checksums.txt"
}
```
### 8.2 provider 定义
每个 provider 文件至少包含:
- `provider_id`
- `display_name`
- `base_url`
- `platform`
- `account_type`
- `default_models`
- `smoke_test_model`
- `group_template`
- `channel_template`
- `plan_template`
- `import`
示例字段:
```json
{
"provider_id": "deepseek",
"display_name": "DeepSeek OpenAI Compatible",
"base_url": "https://api.deepseek.com",
"platform": "openai",
"account_type": "api",
"default_models": ["deepseek-chat", "deepseek-reasoner"],
"smoke_test_model": "deepseek-chat",
"group_template": {
"name": "DeepSeek 默认分组",
"subscription_type": "subscription",
"rate_multiplier": 1.0
},
"channel_template": {
"name": "DeepSeek 默认渠道",
"billing_model_source": "channel_mapped",
"restrict_models": true,
"model_mapping": {
"deepseek-chat": "deepseek-chat",
"deepseek-reasoner": "deepseek-reasoner"
}
},
"plan_template": {
"name": "DeepSeek 默认套餐",
"price": 19.9,
"validity_days": 30,
"for_sale": true
},
"import": {
"supports_multi_key": true,
"supports_strict": true,
"supports_partial": true
}
}
```
## 9. 一键导入流程
### 9.1 安装模型包
1. 上传 `openai-cn-pack.zip`
2. 控制面解压并校验
3. 读取 `pack.json`
4. 校验宿主版本兼容
5. 执行宿主 API 能力探测
6. 注册 provider 定义到控制面状态库
### 9.2 导入多个 key
1. 管理员选择 provider
2. 粘贴多个 key 或上传 `txt / csv`
3. 选择导入模式:
- `strict`
- `partial`
4. 运行预检
5. 创建或复用 group
6. 创建或复用 channel
7. 创建或复用 plan
8. 批量创建 accounts
9. 逐个运行账号测试
10. 汇总结果并写入批次记录
### 9.3 普通用户使用
导入完成后,普通用户继续使用宿主标准 API
- `/v1/chat/completions`
- `/v1/responses`
- 宿主已支持的其他 OpenAI 标准接口
控制面不接管用户流量,只负责把宿主配置成“可中转国产模型”。
### 9.4 用户访问闭环
这是方案审核后新增的强制章节。
宿主网关不是只要存在上游 account 就能调用。
实际还需要满足以下至少一条:
1. 用户持有的 API key 已绑定到目标 group
2. 用户在目标 group 上拥有有效 subscription
否则,普通用户即使拿到宿主标准 API 地址,也不能真正使用国产模型。
因此控制面必须明确支持两种访问模式:
#### 模式 ASubscription 模式
适用场景:
- SaaS 运营
- 后台给用户开通套餐
- 管理员按用户分配访问能力
控制面职责:
- 创建 subscription 类型 group
- 创建默认可售或可分配 plan
- 调用宿主订阅分配接口,把 group 分配给目标用户
#### 模式 BUser Self-Service API Key 模式
适用场景:
- 用户已经登录宿主后台
- 用户自己创建 API key
- 用户自己把 key 绑定到控制面准备好的 group
控制面职责:
- 负责准备好 group / channel / account / plan
- 确保目标 group 对用户可见、可分配、可购买或可授权
#### 首版限制
在零宿主代码改动前提下,控制面不能把“管理员代用户签发最终 API key”作为首版硬依赖。
因此首版应优先保证:
- subscription 模式闭环可用
- user self-service API key 模式可用
## 10. 导入模式
### 10.1 strict
- 任一 key 创建失败或测试失败
- 整个批次回滚
- 适合正式生产导入
### 10.2 partial
- 成功的保留
- 失败的单独记录
- 适合大批量导入
### 10.3 导入完成判定
经过审核,单纯“资源创建成功”不再视为导入完成。
导入完成至少需要满足:
1. 目标 group 存在
2. 目标 channel 存在并已绑定 group
3. 目标 plan 存在,若当前访问模式依赖 plan
4. 至少一个 account 创建成功
5. 至少一个 account smoke test 或模型探测通过
6. 至少一种用户访问模式已经被验证可用
不满足上述条件时,只能标记为:
- `degraded`
- `failed`
## 11. 数据模型
控制面至少需要以下表:
### 11.1 hosts
- 宿主实例信息
- base_url
- 宿主版本
- 最近能力探测结果
### 11.2 packs
- 模型包版本
- checksum
- 安装时间
### 11.3 providers
- provider 元信息
- pack 归属
### 11.4 provider_installs
- 某台宿主安装了哪些 provider
- 当前状态
### 11.5 import_batches
- 一次导入批次
- provider
- 模式
- 请求条数
- 成功条数
- 失败条数
### 11.6 managed_resources
- 宿主侧 group/channel/plan/account 映射
- 宿主资源 ID
- 控制面资源键
### 11.7 reconcile_runs
- 每次对账执行结果
### 11.8 probe_results
- 账号测试与模型探测结果
## 12. 状态机
### 12.1 provider 安装状态
- `discovered`
- `validated`
- `installed`
- `active`
- `degraded`
- `drifted`
- `failed`
- `disabled`
### 12.2 导入批次状态
- `pending`
- `running`
- `succeeded`
- `partially_succeeded`
- `rolled_back`
- `failed`
## 13. 热生效与重启建议
在本方案下,大多数操作应视为热生效:
- 创建 group
- 创建 channel
- 创建 plan
- 创建 account
- 更新 model mapping
- 导入多个 key
因为这些动作都是通过宿主已有 admin API 写入运行时资源。
因此默认策略是:
- 成功写入宿主并通过测试 = 热生效
- 如果出现宿主缓存、运行时异常、版本兼容问题,控制面只给出 `restart_recommended`
- 控制面可选支持宿主重启适配器,但不作为首版必需能力
审核补充:
- `restart_recommended` 只是运维建议,不应成为主成功路径
- 只要核心能力依赖“必须重启宿主后才能导入成功”,就视为不满足首版目标
- 因此首版所有核心链路必须默认按热生效设计
## 14. 主动发现未生效
控制面必须周期性对账,而不是只看“导入成功”。
检查项至少包括:
1. 记录中的 group 是否还存在
2. channel 是否还存在
3. plan 是否还存在
4. account 是否还存在
5. account 测试是否通过
6. account 模型列表是否仍可读取
7. provider 的关键模型是否能通过一次标准测试请求
8. 控制面记录与宿主实际资源是否一致
9. 至少一种用户访问模式仍然成立
对于第 9 条,控制面至少要检查一种访问路径:
- subscription 模式下:目标用户对该 group 是否仍有有效 subscription
- user self-service API key 模式下:是否存在已绑定目标 group 的有效用户 API key 样本
只要任一项不一致,就应标记:
- `degraded`
- `drifted`
- `failed`
## 15. 外部 API 设计
控制面对外至少提供:
### 15.1 宿主管理
- `POST /api/hosts`
- `GET /api/hosts`
- `POST /api/hosts/:id/probe`
### 15.2 模型包管理
- `POST /api/packs/install`
- `GET /api/packs`
- `GET /api/packs/:id/providers`
### 15.3 provider 导入
- `POST /api/providers/:provider_id/preview-import`
- `POST /api/providers/:provider_id/import`
- `GET /api/providers/:provider_id/import-batches`
- `POST /api/import-batches/:id/rollback`
### 15.4 访问闭环管理
- `POST /api/providers/:provider_id/access/preview`
- `POST /api/providers/:provider_id/access/assign-subscriptions`
- `GET /api/providers/:provider_id/access/status`
说明:
- 首版优先支持 subscription 访问闭环
- 不把“管理员代用户创建最终 API key”作为首版必需能力
### 15.5 对账与探测
- `POST /api/providers/:provider_id/reconcile`
- `GET /api/providers/:provider_id/status`
- `GET /api/providers/:provider_id/resources`
## 16. 首版 CLI
建议首版同时提供 CLI降低自动化接入门槛
```bash
cnrelay host add --base-url https://your-sub2api --admin-token xxx
cnrelay pack install ./openai-cn-pack.zip
cnrelay provider import deepseek --keys-file deepseek.txt --mode partial --smoke-test
cnrelay provider status deepseek
cnrelay reconcile run
```
## 17. 与宿主升级的兼容策略
由于宿主 `sub2api` 每周发布新版本,控制面必须内置兼容矩阵:
1. 安装前先探测宿主版本
2. 再跑一组能力探针:
- groups create/list
- accounts create/test/models
- channels create/list
- plans create/list
- subscriptions assign/list
- 至少一种用户访问闭环探测
3. 任何一项不满足,即阻断安装或阻断导入
4. 对宿主版本维护 `supported / warning / unsupported`
这样可以避免宿主快速迭代导致导入流程静默失效。
## 18. MVP 范围
首版只支持:
- 一个宿主适配器:`sub2api`
- 一种包类型:`openai-cn-pack`
- 5 个 provider 模板以内
- 多 key 导入
- strict / partial 两种模式
- account 测试与模型探测
- subscription 访问闭环
- 对账与漂移发现
首版不做:
- 多宿主类型
- 在线插件市场
- 远程代码执行
- 宿主内嵌 UI
- 宿主数据库直写
- 管理员代用户签发最终 API key
## 19. 验收标准
满足以下标准即视为方案落地成功:
1. 任意一台兼容版本的 `sub2api`,只通过控制面和模型包即可接入至少一个国产模型 provider
2. 支持一次导入多个 key并得到逐 key 成功/失败结果
3. 导入成功后,至少一种用户访问模式已经被控制面验证可用
4. 普通用户继续通过 `sub2api` 标准 API 成功调用目标模型
5. 控制面可检测删除、失效、模型缺失、测试失败、访问闭环断裂等漂移
6. 宿主升级后,控制面能在导入前给出兼容性结论,而不是静默失败
## 20. 下一步
建议下一步直接进入实现计划拆解,顺序如下:
1. 定义控制面状态库 schema
2. 实现 `sub2api` 宿主适配器
3. 实现 `model_pack` schema 校验器
4. 实现 provider 导入引擎
5. 实现对账器
6. 再补 CLI 和最小 Web UI