# 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 地址，也不能真正使用国产模型。

因此控制面必须明确支持两种访问模式：

#### 模式 A：Subscription 模式

适用场景：

- SaaS 运营
- 后台给用户开通套餐
- 管理员按用户分配访问能力

控制面职责：

- 创建 subscription 类型 group
- 创建默认可售或可分配 plan
- 调用宿主订阅分配接口，把 group 分配给目标用户

#### 模式 B：User 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
