chore: bootstrap repository
This commit is contained in:
66
README.md
Normal file
66
README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# sub2api-cn-relay-manager
|
||||
|
||||
`sub2api-cn-relay-manager` 是一个独立于 `sub2api` 宿主仓库的外部伴生安装器 / 控制面项目。
|
||||
|
||||
目标不是修改 `sub2api`,也不是把国产模型能力硬塞进宿主源码,而是通过 `sub2api` 已有的管理 API,把“国产模型 OpenAI 兼容中转能力”以独立交付物的形式安装到任意一台已部署的 `sub2api` 实例上。
|
||||
|
||||
## 项目定位
|
||||
|
||||
- 宿主:第三方开源系统 `sub2api`
|
||||
- 约束:不修改宿主代码,不 fork 宿主,不要求宿主内置原生插件运行时
|
||||
- 交付:一个独立控制面项目 + 一个或多个 `model_pack`
|
||||
- 结果:管理员可一键导入多个国产模型 key,普通用户继续只用 `sub2api` 标准 API
|
||||
|
||||
## 这个项目解决的问题
|
||||
|
||||
- 不同机器部署了 `sub2api` 后,如何用同一套交付物补齐国产模型中转能力
|
||||
- 如何把多个国产模型 key 批量导入为可用的宿主资源
|
||||
- 如何在不改宿主的前提下实现热生效、探测、对账和漂移发现
|
||||
- 如何让普通用户完全无感,继续走 `sub2api` 标准接口
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不做宿主原生插件系统
|
||||
- 不做任意第三方 Go 代码动态加载
|
||||
- 不要求宿主识别“插件”概念
|
||||
- 不接管宿主网关流量
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
sub2api-cn-relay-manager/
|
||||
cmd/ # 未来 CLI / server 入口
|
||||
internal/ # 未来控制面后端实现
|
||||
web/ # 未来管理端前端
|
||||
docs/
|
||||
2026-05-12-sub2api-cn-relay-manager-solution.md
|
||||
packs/
|
||||
openai-cn-pack/
|
||||
README.md
|
||||
pack.json.example
|
||||
providers/
|
||||
deepseek.json.example
|
||||
```
|
||||
|
||||
## 交付模型
|
||||
|
||||
本项目最终会拆成两个可独立发布的产物:
|
||||
|
||||
1. `sub2api-cn-relay-manager`
|
||||
- 外部控制面 / 安装器
|
||||
- 连接已有 `sub2api`
|
||||
- 安装模型包
|
||||
- 调用宿主管理 API 创建资源
|
||||
- 做 smoke test、对账和状态展示
|
||||
|
||||
2. `model_pack`
|
||||
- 只包含 provider 定义、默认模板、导入规则和校验信息
|
||||
- 不携带宿主执行代码
|
||||
- 可以跨机器复用
|
||||
|
||||
## 当前文档
|
||||
|
||||
完整方案见:
|
||||
|
||||
- [docs/2026-05-12-sub2api-cn-relay-manager-solution.md](./docs/2026-05-12-sub2api-cn-relay-manager-solution.md)
|
||||
|
||||
0
cmd/.gitkeep
Normal file
0
cmd/.gitkeep
Normal file
619
docs/2026-05-12-sub2api-cn-relay-manager-solution.md
Normal file
619
docs/2026-05-12-sub2api-cn-relay-manager-solution.md
Normal file
@@ -0,0 +1,619 @@
|
||||
# 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
|
||||
@@ -0,0 +1,973 @@
|
||||
# sub2api-cn-relay-manager Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 构建一个完全独立于宿主 `sub2api` 的外部伴生控制面,在不修改宿主代码的前提下,通过安装 `model_pack`、批量导入国产模型 key、自动创建宿主资源并完成用户访问闭环,让普通用户直接通过 `sub2api` 标准 API 使用国产模型。
|
||||
|
||||
**Architecture:** 采用“外部控制面 + `model_pack` + 宿主 HTTP API 适配器”架构。控制面只通过 `sub2api` 公开管理 API 和标准 API 工作,负责模型包安装、宿主能力探测、资源编排、导入回滚、访问闭环验证和持续对账;宿主继续承担标准 API 网关职责,不做任何源码、数据库或运行时注入修改。
|
||||
|
||||
**Tech Stack:** Go 1.24、Chi、`database/sql` + SQLite 驱动、PostgreSQL 兼容预留、OpenAPI 3.1、Go `testing` + `httptest`、前端首版后置,MVP 先提供 HTTP API、CLI 和 Docker 交付物。
|
||||
|
||||
---
|
||||
|
||||
## 1. 实施边界
|
||||
|
||||
### 1.1 必须满足
|
||||
|
||||
- 不修改宿主 `sub2api` 源码
|
||||
- 不 fork 宿主并运行自定义二进制
|
||||
- 不直接写宿主数据库
|
||||
- 不向宿主目录写入运行时代码、插件文件或配置补丁
|
||||
- 只通过宿主现有管理 API 和标准 API 工作
|
||||
- 首版必须完成从“导入 key”到“普通用户可实际调用”的闭环
|
||||
|
||||
### 1.2 首版不做
|
||||
|
||||
- 宿主原生插件中心
|
||||
- 任意后端代码插件加载
|
||||
- 代替用户签发宿主最终 API key
|
||||
- 多宿主编排联邦
|
||||
- 自定义计费、结算或审计系统
|
||||
|
||||
## 2. 目标目录结构
|
||||
|
||||
```text
|
||||
sub2api-cn-relay-manager/
|
||||
cmd/
|
||||
server/main.go
|
||||
cli/main.go
|
||||
internal/
|
||||
app/
|
||||
app.go
|
||||
bootstrap.go
|
||||
config/
|
||||
config.go
|
||||
domain/
|
||||
host.go
|
||||
pack.go
|
||||
provider.go
|
||||
resource.go
|
||||
import_batch.go
|
||||
reconcile.go
|
||||
access_closure.go
|
||||
host/
|
||||
sub2api/
|
||||
client.go
|
||||
capability_probe.go
|
||||
groups.go
|
||||
channels.go
|
||||
plans.go
|
||||
accounts.go
|
||||
subscriptions.go
|
||||
gateway_probe.go
|
||||
pack/
|
||||
manifest.go
|
||||
provider_manifest.go
|
||||
checksum.go
|
||||
validator.go
|
||||
loader.go
|
||||
provision/
|
||||
preview_service.go
|
||||
import_service.go
|
||||
rollback_service.go
|
||||
naming.go
|
||||
access/
|
||||
planner.go
|
||||
subscription_service.go
|
||||
self_service_checker.go
|
||||
reconcile/
|
||||
runner.go
|
||||
drift_checker.go
|
||||
probe_runner.go
|
||||
store/
|
||||
migrations/
|
||||
sqlite/
|
||||
db.go
|
||||
hosts_repo.go
|
||||
host_capability_snapshots_repo.go
|
||||
packs_repo.go
|
||||
providers_repo.go
|
||||
installs_repo.go
|
||||
import_batches_repo.go
|
||||
resources_repo.go
|
||||
reconcile_runs_repo.go
|
||||
probe_results_repo.go
|
||||
access_closure_records_repo.go
|
||||
api/
|
||||
http/
|
||||
router.go
|
||||
middleware.go
|
||||
hosts_handler.go
|
||||
packs_handler.go
|
||||
providers_handler.go
|
||||
imports_handler.go
|
||||
access_handler.go
|
||||
reconcile_handler.go
|
||||
dto/
|
||||
hosts.go
|
||||
packs.go
|
||||
providers.go
|
||||
imports.go
|
||||
access.go
|
||||
reconcile.go
|
||||
worker/
|
||||
scheduler.go
|
||||
jobs.go
|
||||
docs/
|
||||
api/openapi.yaml
|
||||
plans/
|
||||
2026-05-12-sub2api-cn-relay-manager-implementation-plan.md
|
||||
packs/
|
||||
openai-cn-pack/
|
||||
pack.json.example
|
||||
providers/
|
||||
deepseek.json.example
|
||||
tests/
|
||||
integration/
|
||||
host_stub_test.go
|
||||
install_pack_test.go
|
||||
import_keys_test.go
|
||||
access_closure_test.go
|
||||
reconcile_test.go
|
||||
```
|
||||
|
||||
## 3. 核心接口与契约
|
||||
|
||||
### 3.1 控制面对外 API
|
||||
|
||||
#### 宿主管理
|
||||
|
||||
- `POST /api/hosts`
|
||||
- `GET /api/hosts`
|
||||
- `GET /api/hosts/{host_id}`
|
||||
- `POST /api/hosts/{host_id}/probe`
|
||||
|
||||
#### 模型包管理
|
||||
|
||||
- `POST /api/packs/install`
|
||||
- `GET /api/packs`
|
||||
- `GET /api/packs/{pack_id}`
|
||||
- `GET /api/packs/{pack_id}/providers`
|
||||
|
||||
#### Provider 导入
|
||||
|
||||
- `POST /api/providers/{provider_id}/preview-import`
|
||||
- `POST /api/providers/{provider_id}/import`
|
||||
- `GET /api/providers/{provider_id}/import-batches`
|
||||
- `GET /api/import-batches/{batch_id}`
|
||||
- `POST /api/import-batches/{batch_id}/rollback`
|
||||
|
||||
#### 访问闭环
|
||||
|
||||
- `POST /api/providers/{provider_id}/access/preview`
|
||||
- `POST /api/providers/{provider_id}/access/assign-subscriptions`
|
||||
- `GET /api/providers/{provider_id}/access/status`
|
||||
|
||||
#### 对账与状态
|
||||
|
||||
- `POST /api/providers/{provider_id}/reconcile`
|
||||
- `GET /api/providers/{provider_id}/status`
|
||||
- `GET /api/providers/{provider_id}/resources`
|
||||
|
||||
### 3.2 宿主适配器接口
|
||||
|
||||
`internal/host/sub2api/client.go` 中定义单一适配接口:
|
||||
|
||||
```go
|
||||
type HostAdapter interface {
|
||||
GetHostVersion(ctx context.Context) (string, error)
|
||||
ProbeCapabilities(ctx context.Context) (HostCapabilities, error)
|
||||
CreateGroup(ctx context.Context, req CreateGroupRequest) (GroupRef, error)
|
||||
CreateChannel(ctx context.Context, req CreateChannelRequest) (ChannelRef, error)
|
||||
CreatePlan(ctx context.Context, req CreatePlanRequest) (PlanRef, error)
|
||||
CreateAccount(ctx context.Context, req CreateAccountRequest) (AccountRef, error)
|
||||
BatchCreateAccounts(ctx context.Context, req BatchCreateAccountsRequest) ([]AccountRef, error)
|
||||
TestAccount(ctx context.Context, accountID string) (ProbeResult, error)
|
||||
GetAccountModels(ctx context.Context, accountID string) ([]string, error)
|
||||
AssignSubscription(ctx context.Context, req AssignSubscriptionRequest) (SubscriptionRef, error)
|
||||
CheckGatewayAccess(ctx context.Context, req GatewayAccessCheckRequest) (GatewayAccessResult, error)
|
||||
DeleteGroup(ctx context.Context, groupID string) error
|
||||
DeleteChannel(ctx context.Context, channelID string) error
|
||||
DeletePlan(ctx context.Context, planID string) error
|
||||
DeleteAccount(ctx context.Context, accountID string) error
|
||||
ListManagedResources(ctx context.Context, req ListManagedResourcesRequest) (ManagedResourceSnapshot, error)
|
||||
}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 所有宿主调用都必须通过该接口进入
|
||||
- 任何绕过该接口的 HTTP 调用都视为实现缺陷
|
||||
- 所有宿主 API 路径、请求体和响应体在 `internal/host/sub2api/` 内部封装,不向业务层泄漏
|
||||
|
||||
## 4. 状态机
|
||||
|
||||
### 4.1 Provider 安装状态
|
||||
|
||||
```text
|
||||
discovered -> validated -> installed -> active
|
||||
| |
|
||||
v v
|
||||
failed <-> degraded <-> drifted
|
||||
|
|
||||
v
|
||||
disabled
|
||||
```
|
||||
|
||||
状态定义:
|
||||
|
||||
- `discovered`:模型包已上传,尚未完成结构校验
|
||||
- `validated`:包结构、校验和、版本兼容、宿主能力探测通过
|
||||
- `installed`:provider 元数据和模板已写入控制面状态库
|
||||
- `active`:宿主资源存在,账号探测成功,至少一种用户访问模式已验证可用
|
||||
- `degraded`:核心资源存在但 smoke test、模型探测或访问闭环部分失败
|
||||
- `drifted`:宿主资源与控制面记录不一致,例如资源被人工改动或删除
|
||||
- `failed`:安装、导入、回滚、对账任一关键步骤失败
|
||||
- `disabled`:provider 被显式停用,不再调度对账任务
|
||||
|
||||
### 4.2 导入批次状态
|
||||
|
||||
```text
|
||||
pending -> previewed -> running -> succeeded
|
||||
| | |
|
||||
| | v
|
||||
| -> partially_succeeded
|
||||
v
|
||||
failed -> rollback_running -> rolled_back
|
||||
```
|
||||
|
||||
### 4.3 访问闭环状态
|
||||
|
||||
- `not_configured`
|
||||
- `subscription_ready`
|
||||
- `self_service_ready`
|
||||
- `fully_ready`
|
||||
- `broken`
|
||||
|
||||
判定规则:
|
||||
|
||||
- 仅 `subscription` 模式可用:`subscription_ready`
|
||||
- 仅 `self-service` 模式可用:`self_service_ready`
|
||||
- 两者都可用:`fully_ready`
|
||||
- 两者都不可用:`broken`
|
||||
|
||||
## 5. 校验流程
|
||||
|
||||
### 5.1 宿主接入校验
|
||||
|
||||
请求:`POST /api/hosts`
|
||||
|
||||
流程:
|
||||
|
||||
1. 校验 `base_url`、认证信息格式
|
||||
2. 拉取宿主版本
|
||||
3. 探测宿主管理 API 能力:
|
||||
- group 创建
|
||||
- channel 创建
|
||||
- plan 创建
|
||||
- account 创建 / 测试 / 模型列举
|
||||
- subscription 分配
|
||||
4. 生成能力矩阵并落库
|
||||
5. 若任一关键能力缺失,宿主状态标记为 `unsupported`
|
||||
|
||||
通过条件:
|
||||
|
||||
- 宿主版本在 `pack.json` 兼容区间内
|
||||
- 所有首版硬依赖 API 均可调用
|
||||
|
||||
### 5.2 模型包装载校验
|
||||
|
||||
请求:`POST /api/packs/install`
|
||||
|
||||
流程:
|
||||
|
||||
1. 解压上传包到临时目录
|
||||
2. 校验 `pack.json` 存在且字段完整
|
||||
3. 校验 `providers/*.json` 至少存在一个 provider
|
||||
4. 校验 `checksums.txt`
|
||||
5. 校验 provider schema
|
||||
6. 校验模型包声明的宿主版本兼容性
|
||||
7. 将包元数据、provider 元数据写入状态库
|
||||
|
||||
拒绝条件:
|
||||
|
||||
- 缺少 `pack.json`
|
||||
- provider 文件重复或 `provider_id` 冲突
|
||||
- `base_url` 非 HTTPS
|
||||
- `default_models` 为空
|
||||
- `smoke_test_model` 不在 `default_models` 内
|
||||
|
||||
### 5.3 导入预检
|
||||
|
||||
请求:`POST /api/providers/{provider_id}/preview-import`
|
||||
|
||||
流程:
|
||||
|
||||
1. 规范化 key 列表:去空白、去重、去 BOM、过滤空行
|
||||
2. 校验导入模式是否允许:`strict` 或 `partial`
|
||||
3. 生成建议资源名
|
||||
4. 检查宿主是否已有同名 group / channel / plan
|
||||
5. 检查当前 provider 是否已有历史导入资源
|
||||
6. 计算执行计划:
|
||||
- create
|
||||
- reuse
|
||||
- conflict
|
||||
7. 输出预检报告,不落宿主资源
|
||||
|
||||
### 5.4 导入执行
|
||||
|
||||
请求:`POST /api/providers/{provider_id}/import`
|
||||
|
||||
流程:
|
||||
|
||||
1. 创建批次记录,状态 `pending`
|
||||
2. 执行预检,成功后切换为 `previewed`
|
||||
3. 创建或复用 group
|
||||
4. 创建或复用 channel
|
||||
5. 按访问模式决定是否创建或复用 plan
|
||||
6. 批量创建 accounts
|
||||
7. 对每个 account 执行:
|
||||
- 宿主 `/test`
|
||||
- 宿主 `/models`
|
||||
- 关键模型 smoke probe
|
||||
8. 若访问模式为 `subscription`:
|
||||
- 预览待分配用户列表
|
||||
- 调用宿主分配接口
|
||||
9. 执行网关访问探测
|
||||
10. 写入资源映射、探测结果、访问闭环状态
|
||||
11. 根据模式决定成功、部分成功或回滚
|
||||
|
||||
### 5.5 回滚校验
|
||||
|
||||
触发条件:
|
||||
|
||||
- `strict` 模式下任一关键步骤失败
|
||||
- 管理员主动回滚批次
|
||||
|
||||
流程:
|
||||
|
||||
1. 读取批次关联资源
|
||||
2. 按依赖逆序删除:
|
||||
- subscriptions
|
||||
- accounts
|
||||
- channel
|
||||
- plan
|
||||
- group
|
||||
3. 删除成功后标记 `rolled_back`
|
||||
4. 若部分删除失败,批次状态标记 `failed`,provider 状态标记 `drifted`
|
||||
|
||||
### 5.6 对账校验
|
||||
|
||||
请求:`POST /api/providers/{provider_id}/reconcile`
|
||||
|
||||
流程:
|
||||
|
||||
1. 读取控制面记录的宿主资源快照
|
||||
2. 拉取宿主实际资源
|
||||
3. 核对 group / channel / plan / account 是否仍存在
|
||||
4. 对 active account 重新执行 `/test` 和 `/models`
|
||||
5. 重新执行至少一种用户访问模式探测
|
||||
6. 汇总为:
|
||||
- `active`
|
||||
- `degraded`
|
||||
- `drifted`
|
||||
- `failed`
|
||||
|
||||
## 6. API 请求与响应最小契约
|
||||
|
||||
### 6.1 `POST /api/hosts`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "prod-sub2api",
|
||||
"base_url": "https://sub2api.example.com",
|
||||
"auth": {
|
||||
"type": "bearer",
|
||||
"token": "xxxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"host_id": "host_01",
|
||||
"version": "0.1.126",
|
||||
"status": "supported",
|
||||
"capabilities": {
|
||||
"groups": true,
|
||||
"channels": true,
|
||||
"plans": true,
|
||||
"accounts": true,
|
||||
"account_test": true,
|
||||
"account_models": true,
|
||||
"subscriptions": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 `POST /api/providers/{provider_id}/import`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"host_id": "host_01",
|
||||
"mode": "partial",
|
||||
"access_mode": "subscription",
|
||||
"keys": [
|
||||
"sk-1",
|
||||
"sk-2"
|
||||
],
|
||||
"subscription_targets": [
|
||||
{
|
||||
"user_id": "user_01",
|
||||
"duration_days": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"batch_id": "batch_01",
|
||||
"status": "succeeded",
|
||||
"provider_status": "active",
|
||||
"access_closure_status": "subscription_ready",
|
||||
"created": {
|
||||
"group_id": "g_01",
|
||||
"channel_id": "c_01",
|
||||
"plan_id": "p_01",
|
||||
"account_ids": ["a_01", "a_02"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 数据库与迁移计划
|
||||
|
||||
### 7.1 必建表
|
||||
|
||||
- `hosts`
|
||||
- `host_capability_snapshots`
|
||||
- `packs`
|
||||
- `providers`
|
||||
- `provider_installs`
|
||||
- `import_batches`
|
||||
- `import_batch_items`
|
||||
- `managed_resources`
|
||||
- `probe_results`
|
||||
- `access_closure_records`
|
||||
- `reconcile_runs`
|
||||
|
||||
### 7.2 约束
|
||||
|
||||
- `providers.provider_id` 在同一 `pack_id` 下唯一
|
||||
- `managed_resources` 以 `host_id + provider_id + resource_type + logical_key` 唯一
|
||||
- `import_batch_items.raw_key_hash` 唯一约束只用于同一批次内去重
|
||||
- 密钥明文不落库,只存掩码、哈希和宿主侧 account ID
|
||||
|
||||
## 8. 测试策略
|
||||
|
||||
### 8.1 单元测试
|
||||
|
||||
- `internal/pack/validator.go`
|
||||
- `internal/provision/naming.go`
|
||||
- `internal/access/planner.go`
|
||||
- `internal/reconcile/drift_checker.go`
|
||||
|
||||
### 8.2 宿主适配器契约测试
|
||||
|
||||
- 使用 `httptest.Server` 模拟 `sub2api` 管理 API
|
||||
- 覆盖成功、鉴权失败、字段缺失、版本不兼容、接口漂移
|
||||
|
||||
### 8.3 集成测试
|
||||
|
||||
- 安装模型包
|
||||
- 多 key 导入
|
||||
- `strict` 失败回滚
|
||||
- `partial` 部分成功
|
||||
- `subscription` 访问闭环
|
||||
- 对账发现漂移
|
||||
|
||||
### 8.4 验收测试
|
||||
|
||||
- 真实连接一套测试版 `sub2api`
|
||||
- 导入 `deepseek` 两个 key
|
||||
- 为测试用户分配 subscription
|
||||
- 使用该用户实际标准 API key 发起一次 `/v1/chat/completions`
|
||||
- 验证请求成功进入目标 provider
|
||||
|
||||
## 9. 分阶段里程碑
|
||||
|
||||
### Milestone 1:项目骨架与状态库
|
||||
|
||||
验收标准:
|
||||
|
||||
- 控制面可启动
|
||||
- SQLite 可自动迁移
|
||||
- `POST /api/hosts`、`GET /api/hosts` 可用
|
||||
- 宿主能力探测结果可持久化
|
||||
|
||||
### Milestone 2:模型包安装与校验
|
||||
|
||||
验收标准:
|
||||
|
||||
- 可安装 `openai-cn-pack`
|
||||
- 可列出 provider
|
||||
- 版本兼容和 checksum 校验有效
|
||||
- 非法 provider 包会被拒绝
|
||||
|
||||
### Milestone 3:多 key 导入与资源编排
|
||||
|
||||
验收标准:
|
||||
|
||||
- 可对单个 provider 执行预检
|
||||
- 可批量导入多个 key
|
||||
- 可创建 group / channel / plan / account
|
||||
- `strict` / `partial` 两种模式行为正确
|
||||
|
||||
### Milestone 4:访问闭环
|
||||
|
||||
验收标准:
|
||||
|
||||
- `subscription` 模式打通
|
||||
- 至少一个测试用户可通过宿主标准 API 调用目标国产模型
|
||||
- 控制面可返回 `subscription_ready` 或 `fully_ready`
|
||||
|
||||
### Milestone 5:对账、漂移与回滚
|
||||
|
||||
验收标准:
|
||||
|
||||
- 定时对账可运行
|
||||
- 宿主资源被人工删除时,provider 状态变为 `drifted`
|
||||
- 可对批次执行回滚
|
||||
- 回滚后状态库与宿主资源一致
|
||||
|
||||
### Milestone 6:独立交付与部署文档
|
||||
|
||||
验收标准:
|
||||
|
||||
- 可生成单二进制和 Docker 镜像
|
||||
- 提供 `.env.example` 和最小部署说明
|
||||
- 可通过 CLI 完成宿主接入、模型包装载和导入
|
||||
- 文档明确宿主零改动边界和支持矩阵
|
||||
|
||||
## 10. 逐任务实施清单
|
||||
|
||||
### Task 1: 建立项目骨架与配置加载
|
||||
|
||||
**Files:**
|
||||
- Create: `cmd/server/main.go`
|
||||
- Create: `cmd/cli/main.go`
|
||||
- Create: `internal/app/app.go`
|
||||
- Create: `internal/app/bootstrap.go`
|
||||
- Create: `internal/config/config.go`
|
||||
|
||||
**Step 1: 写配置加载单元测试**
|
||||
|
||||
- Test: `tests/integration/config_bootstrap_test.go`
|
||||
- 覆盖环境变量、默认值、缺失必填项
|
||||
|
||||
**Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestConfigBootstrap -v`
|
||||
Expected: FAIL,提示配置加载尚未实现
|
||||
|
||||
**Step 3: 写最小实现**
|
||||
|
||||
- 完成配置结构、启动参数、默认 SQLite DSN
|
||||
|
||||
**Step 4: 运行测试验证通过**
|
||||
|
||||
Run: `go test ./tests/integration -run TestConfigBootstrap -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add cmd internal tests
|
||||
git commit -m "feat: bootstrap control plane app skeleton"
|
||||
```
|
||||
|
||||
### Task 2: 建立状态库与迁移
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/store/migrations/0001_init.sql`
|
||||
- Create: `internal/store/sqlite/db.go`
|
||||
- Create: `internal/store/sqlite/hosts_repo.go`
|
||||
- Create: `internal/store/sqlite/packs_repo.go`
|
||||
- Create: `internal/store/sqlite/providers_repo.go`
|
||||
|
||||
**Step 1: 写迁移与仓储测试**
|
||||
|
||||
- Test: `tests/integration/store_init_test.go`
|
||||
- 覆盖建表、唯一约束、事务回滚
|
||||
|
||||
**Step 2: 跑失败测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestStoreInit -v`
|
||||
Expected: FAIL,表不存在
|
||||
|
||||
**Step 3: 实现最小迁移与仓储**
|
||||
|
||||
- 先实现 `hosts`、`packs`、`providers`
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestStoreInit -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/store tests
|
||||
git commit -m "feat: add state store migrations and repositories"
|
||||
```
|
||||
|
||||
### Task 3: 实现宿主适配器与能力探测
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/host/sub2api/client.go`
|
||||
- Create: `internal/host/sub2api/capability_probe.go`
|
||||
- Create: `internal/host/sub2api/groups.go`
|
||||
- Create: `internal/host/sub2api/channels.go`
|
||||
- Create: `internal/host/sub2api/plans.go`
|
||||
- Create: `internal/host/sub2api/accounts.go`
|
||||
- Create: `internal/host/sub2api/subscriptions.go`
|
||||
|
||||
**Step 1: 写 `httptest` 宿主桩和适配器测试**
|
||||
|
||||
- Test: `tests/integration/host_stub_test.go`
|
||||
- 覆盖版本获取、能力探测、错误映射
|
||||
|
||||
**Step 2: 先跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestSub2APIHostAdapter -v`
|
||||
Expected: FAIL,适配器未实现
|
||||
|
||||
**Step 3: 写最小适配器实现**
|
||||
|
||||
- 只支持首版必需 API
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestSub2APIHostAdapter -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/host tests
|
||||
git commit -m "feat: add sub2api host adapter and capability probe"
|
||||
```
|
||||
|
||||
### Task 4: 实现模型包协议与安装校验
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/pack/manifest.go`
|
||||
- Create: `internal/pack/provider_manifest.go`
|
||||
- Create: `internal/pack/checksum.go`
|
||||
- Create: `internal/pack/validator.go`
|
||||
- Create: `internal/pack/loader.go`
|
||||
|
||||
**Step 1: 写模型包校验测试**
|
||||
|
||||
- Test: `tests/integration/install_pack_test.go`
|
||||
- 覆盖合法包、缺失字段、坏 checksum、版本不兼容
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestInstallPack -v`
|
||||
Expected: FAIL,包解析和校验未实现
|
||||
|
||||
**Step 3: 实现最小装载器**
|
||||
|
||||
- 支持从 zip 和目录读取
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestInstallPack -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/pack tests
|
||||
git commit -m "feat: add model pack loader and validator"
|
||||
```
|
||||
|
||||
### Task 5: 实现导入预检与命名策略
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/provision/preview_service.go`
|
||||
- Create: `internal/provision/naming.go`
|
||||
- Create: `internal/domain/import_batch.go`
|
||||
|
||||
**Step 1: 写预检测试**
|
||||
|
||||
- Test: `tests/integration/import_preview_test.go`
|
||||
- 覆盖 key 规范化、冲突判定、create/reuse/conflict 输出
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestImportPreview -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现预检逻辑**
|
||||
|
||||
- 只做只读分析,不写宿主
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestImportPreview -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/provision internal/domain tests
|
||||
git commit -m "feat: add provider import preview flow"
|
||||
```
|
||||
|
||||
### Task 6: 实现多 key 导入、探测与回滚
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/provision/import_service.go`
|
||||
- Create: `internal/provision/rollback_service.go`
|
||||
- Create: `internal/store/sqlite/import_batches_repo.go`
|
||||
- Create: `internal/store/sqlite/resources_repo.go`
|
||||
- Create: `internal/store/sqlite/probe_results_repo.go`
|
||||
|
||||
**Step 1: 写导入测试**
|
||||
|
||||
- Test: `tests/integration/import_keys_test.go`
|
||||
- 覆盖 `strict` 成功、`strict` 回滚、`partial` 部分成功
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestImportKeys -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现导入与回滚服务**
|
||||
|
||||
- 创建宿主资源
|
||||
- 记录资源映射
|
||||
- 运行账号测试和模型探测
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestImportKeys -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/provision internal/store tests
|
||||
git commit -m "feat: add multi-key import and rollback flow"
|
||||
```
|
||||
|
||||
### Task 7: 实现访问闭环
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/access/planner.go`
|
||||
- Create: `internal/access/subscription_service.go`
|
||||
- Create: `internal/access/self_service_checker.go`
|
||||
- Create: `internal/domain/access_closure.go`
|
||||
|
||||
**Step 1: 写访问闭环测试**
|
||||
|
||||
- Test: `tests/integration/access_closure_test.go`
|
||||
- 覆盖 `subscription_ready`、`self_service_ready`、`broken`
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestAccessClosure -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现最小闭环服务**
|
||||
|
||||
- 首版先保证 `subscription` 模式打通
|
||||
- `self-service` 只做绑定状态检查,不代用户签发 key
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestAccessClosure -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/access internal/domain tests
|
||||
git commit -m "feat: add access closure services"
|
||||
```
|
||||
|
||||
### Task 8: 实现对账与漂移检测
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/reconcile/runner.go`
|
||||
- Create: `internal/reconcile/drift_checker.go`
|
||||
- Create: `internal/reconcile/probe_runner.go`
|
||||
- Create: `internal/store/sqlite/reconcile_runs_repo.go`
|
||||
|
||||
**Step 1: 写对账测试**
|
||||
|
||||
- Test: `tests/integration/reconcile_test.go`
|
||||
- 覆盖正常、资源丢失、账号失效、访问闭环破坏
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestReconcile -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现对账器**
|
||||
|
||||
- 拉取宿主资源快照
|
||||
- 比对状态库
|
||||
- 重新执行探测
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestReconcile -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/reconcile internal/store tests
|
||||
git commit -m "feat: add provider reconcile and drift detection"
|
||||
```
|
||||
|
||||
### Task 9: 暴露 HTTP API 与 OpenAPI 文档
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/api/http/router.go`
|
||||
- Create: `internal/api/http/middleware.go`
|
||||
- Create: `internal/api/http/hosts_handler.go`
|
||||
- Create: `internal/api/http/packs_handler.go`
|
||||
- Create: `internal/api/http/providers_handler.go`
|
||||
- Create: `internal/api/http/imports_handler.go`
|
||||
- Create: `internal/api/http/access_handler.go`
|
||||
- Create: `internal/api/http/reconcile_handler.go`
|
||||
- Create: `docs/api/openapi.yaml`
|
||||
|
||||
**Step 1: 写 handler 测试**
|
||||
|
||||
- Test: `tests/integration/http_api_test.go`
|
||||
- 覆盖主要成功与失败路径
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestHTTPAPI -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现路由和 DTO**
|
||||
|
||||
- 保持 handler 只做参数解析和错误映射
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestHTTPAPI -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/api docs/api tests
|
||||
git commit -m "feat: add control plane HTTP API"
|
||||
```
|
||||
|
||||
### Task 10: 增加调度器、CLI 和端到端验收脚本
|
||||
|
||||
**Files:**
|
||||
- Create: `internal/worker/scheduler.go`
|
||||
- Create: `internal/worker/jobs.go`
|
||||
- Modify: `cmd/cli/main.go`
|
||||
- Create: `scripts/e2e/verify_with_host_stub.sh`
|
||||
|
||||
**Step 1: 写调度与 CLI 测试**
|
||||
|
||||
- Test: `tests/integration/cli_scheduler_test.go`
|
||||
- 覆盖手动 reconcile 和定时调度
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestCLIScheduler -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现最小调度与 CLI**
|
||||
|
||||
- 支持 `host add`、`pack install`、`provider import`、`reconcile run`
|
||||
|
||||
**Step 4: 跑全量测试**
|
||||
|
||||
Run: `go test ./...`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add cmd internal scripts tests
|
||||
git commit -m "feat: add scheduler cli and e2e verification script"
|
||||
```
|
||||
|
||||
### Task 11: 补齐独立交付物与部署文档
|
||||
|
||||
**Files:**
|
||||
- Create: `Dockerfile`
|
||||
- Create: `.env.example`
|
||||
- Create: `deploy/docker-compose.yml`
|
||||
- Create: `docs/deployment.md`
|
||||
- Modify: `README.md`
|
||||
|
||||
**Step 1: 写交付物检查测试**
|
||||
|
||||
- Test: `tests/integration/distribution_smoke_test.go`
|
||||
- 覆盖配置样例、镜像启动参数、CLI 帮助输出
|
||||
|
||||
**Step 2: 跑失败**
|
||||
|
||||
Run: `go test ./tests/integration -run TestDistributionSmoke -v`
|
||||
Expected: FAIL
|
||||
|
||||
**Step 3: 实现最小交付物**
|
||||
|
||||
- 提供可运行镜像
|
||||
- 提供本地 SQLite 默认部署
|
||||
- 文档明确对接宿主所需环境变量
|
||||
|
||||
**Step 4: 跑测试**
|
||||
|
||||
Run: `go test ./tests/integration -run TestDistributionSmoke -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add Dockerfile .env.example deploy docs README.md tests
|
||||
git commit -m "docs: add distribution artifacts and deployment guide"
|
||||
```
|
||||
|
||||
## 11. 最终验收清单
|
||||
|
||||
- 从空状态启动控制面
|
||||
- 连接一套兼容版本的 `sub2api`
|
||||
- 安装 `openai-cn-pack`
|
||||
- 导入 `deepseek` 两个 key
|
||||
- 成功创建宿主资源并记录映射
|
||||
- 至少一个账号通过测试与模型探测
|
||||
- 至少一种用户访问模式验证成功
|
||||
- 使用宿主标准 API 成功调用国产模型
|
||||
- 人工删除一个宿主 account 后,对账将 provider 状态标记为 `drifted`
|
||||
- 执行批次回滚后,宿主残留资源清理完成
|
||||
|
||||
## 12. 风险与收敛策略
|
||||
|
||||
- 宿主管理 API 漂移风险:所有 HTTP 路径和字段通过 `HostAdapter` 封装,并为版本差异保留适配层
|
||||
- 宿主权限模型不稳定:在 `POST /api/hosts` 阶段强制能力探测,不满足即拒绝接入
|
||||
- 不同 provider 规则差异:统一约束到 `providers/*.json`,首版只支持 OpenAI-compatible provider
|
||||
- 访问闭环误判:首版必须落真实标准 API 探测,不能只以资源存在判定成功
|
||||
0
internal/.gitkeep
Normal file
0
internal/.gitkeep
Normal file
18
packs/openai-cn-pack/README.md
Normal file
18
packs/openai-cn-pack/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# openai-cn-pack
|
||||
|
||||
这是 `sub2api-cn-relay-manager` 的最小模型包样例。
|
||||
|
||||
它不是宿主原生插件,而是一个可被控制面读取的 `model_pack`,用于描述国产模型 provider 的默认接入模板、默认模型映射、默认套餐和导入约束。
|
||||
|
||||
当前目录仅提供协议样例:
|
||||
|
||||
- `pack.json.example`
|
||||
- `providers/deepseek.json.example`
|
||||
|
||||
后续真实交付时,可以扩展更多 provider:
|
||||
|
||||
- `kimi.json`
|
||||
- `qwen.json`
|
||||
- `glm.json`
|
||||
- `minimax.json`
|
||||
|
||||
10
packs/openai-cn-pack/pack.json.example
Normal file
10
packs/openai-cn-pack/pack.json.example
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
37
packs/openai-cn-pack/providers/deepseek.json.example
Normal file
37
packs/openai-cn-pack/providers/deepseek.json.example
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
0
web/.gitkeep
Normal file
0
web/.gitkeep
Normal file
Reference in New Issue
Block a user