- Add BATCH_AUTO_IMPORT_SPEC.md: 3-stage pipeline (probe/provision/validate), provider_id=host+hash, smoke_model=find-first-usable, pricing=defaults - Add BATCH_AUTO_IMPORT_TDD_PLAN.md: 5-stage implementation plan, 10 tasks - Update EXECUTION_BOARD.md: add v2 section with resolved open questions
11 KiB
SPEC: Batch Auto-Import by URL + Key (v2)
日期:2026-05-21
1. Objective
让管理员只提供一批 (base_url, api_key) 对,就能自动完成:
- 上游探测 — 调用
GET {base_url}/v1/models动态获取该 key 支持的模型列表 - 宿主演化 — 将发现的模型与宿主 channel 配置对比,自动扩展
model_mapping - 供应商注册 — 把 URL+key 注册为可控可管的 provider
- 中转闭环验证 — 用该 key 跑一次
/v1/chat/completions确认真实可用
全程无需预置 provider manifest,不依赖 pack,零人工判断。
2. 为什么现在需要这个
当前 v1 依赖预定义 provider manifest(packs/openai-cn-pack/providers/*.json),每个 provider 必须手动写好 base_url / default_models / smoke_test_model / channel_template。这带来三个问题:
- 新 key 无法即插即用:每次接一个陌生 provider URL,都得先查文档再写 manifest
- 模型列表人工维护:provider 上游升级模型,pack 里不会自动同步
- 调试链路长:假设备注 manifest → 导入 → 发现 channel 缺少模型 → 手动补 → 重新导入
v2 把"探测 → 配置 → 注册 → 验证"压缩成一键闭环。
3. 核心用户故事
作为管理员,我有了一批新的中转 key(URL + token),我想在已经运行的宿主上快速开通这些模型。理想情况是我把这批 key 列出来,系统自动探测每个 key 支持什么模型、自动配置宿主 channel、自动注册为可控 provider、自动跑一遍真实 completion 测试,最后告诉我哪些真正可用。
4. 技术方案
4.1 三阶段管道
输入: [(base_url, api_key), ...]
Stage 1: Probe ─────────────────────────────────────────────────
for each (url, key):
upstream_models = GET {url}/v1/models
→ extract model list
upstream_completion = POST {url}/v1/chat/completions (smoke)
→ HTTP status, latency, error_type
classify: models_ok | models_fail | completion_fail | unreachable
Stage 2: Provision ──────────────────────────────────────────────
for each (url, key) where upstream_models != models_fail:
host_channel = find_or_create_channel(provider_id, url)
missing_models = upstream_models - host_channel.model_mapping.keys
if missing_models:
patch_channel(host_channel, add model_mapping entries)
managed_account = create_or_update_account(url, key)
probe_result = account_test(managed_account, smoke_test_model)
register_provider_binding(provider_id, url, key, upstream_models)
Stage 3: Validate ───────────────────────────────────────────────
for each registered (url, key):
final_completion = POST host_gw/v1/chat/completions
via managed_account key
→ write access_status: active | broken | degraded
output: per-url status + summary
输出: BatchImportResult {
total: int
active: int
broken: int
degraded: int
details: [{url, upstream_models, channel_config, access_status, error}]
}
4.2 关键设计决策
Q1: 如何从 /v1/models 提取模型列表?
OpenAI-compatible 上游返回格式为:
{
"data": [{"id": "gpt-4", "object": "model", ...}, ...]
}
提取策略:
- 取
data[].id作为模型名 - 过滤掉以
gpt-/claude-/text-/embedding-开头的明显非目标模型 - 保留其余作为"发现的模型列表"
Q2: 如何把上游模型写入宿主 channel?
宿主 channel 有两个相关字段:
model_mapping: map[string]string—{upstream_model: gateway_model}restrict_models: bool— true 时 gateway 只路由 mapping 内的模型
策略:
model_mapping[key] = key(一对一映射,上游模型名即 gateway 模型名)model_pricing填默认值(price_per_1m=0,max_batch=0),不阻塞导入- 如果 channel 不存在,创建新 channel(
name = host_registered_{provider_id})
Q3: Provider ID 如何生成?
自动生成规则:
- 取
base_url的 host 部分,规范化(去掉https://、去除尾部/) - 去除常见后缀(
.com、.cn) - 转小写 + 中划线连接
- 示例:
https://api.deepseek.com→api-deepseek
这样同一 URL 的多次导入会命中同一个 provider_id,实现增量更新。
Q4: 如何避免重复 key 覆盖已有配置?
导入前执行 reconcile:
- 如果
base_url + key对应的 account 已存在,且upstream_models与已有 account 的credentials.model_mapping一致 → 跳过 - 如果 account 存在但模型列表变长了 → patch channel 扩展 model_mapping
- 如果 account 存在但 key 已失效 → 标记为
broken,新建 account
Q5: 验证 key 失效 vs 上游断连如何区分?
Stage 1 的 smoke test 需要区分错误类型:
401/403 unauthorized→ key 无效429 rate_limit→ key 有额度但被限流 → 记录,不阻塞502/503/connection_error→ 上游不可达 → 降级处理200 + valid response→ key 可用
Stage 3 的 host relay smoke 测试结果才决定最终 access_status。
4.3 数据流
BatchImportRequest
├── base_url: string
├── api_key: string
└── access_mode: "subscription" | "self_service" (可选,默认 subscription)
BatchImportResult
├── batch_id: string
├── total: int
├── active: int
├── broken: int
├── degraded: int
└── results: []ImportItemResult
ImportItemResult
├── base_url: string
├── provider_id: string (自动生成)
├── upstream_models: []string (Stage 1 发现)
├── channel_id: int64 (Stage 2 创建/更新)
├── account_id: int64 (Stage 2 创建/更新)
├── probe_ok: bool (Stage 2 account test)
├── access_status: string (Stage 3 最终)
└── error: string | null
4.4 CLI 接口
# 单条
go run ./cmd/cli batch-import \
--host-base-url http://localhost:18097 \
--host-api-key <admin-key> \
--entry "https://api.deepseek.com,<deepseek-key>" \
--access-mode subscription
# 批量(文件,每行 url,key)
go run ./cmd/cli batch-import \
--host-base-url http://localhost:18097 \
--host-api-key <admin-key> \
--batch-file ./keys.csv \
--access-mode subscription
# 批量(stdin)
cat keys.txt | xargs -I{} go run ./cmd/cli batch-import \
--host-base-url http://localhost:18097 \
--host-api-key <admin-key> \
--batch-stdin
keys.csv 格式:
https://api.deepseek.com,sk-xxx
https://api.completion.com,sk-yyy
5. 宿主硬约束(继承自 v1)
- 不修改宿主源码
- 不直接写宿主数据库
- 只通过宿主 HTTP Admin API 和 Gateway API 工作
- channel 完整收口字段必须同时存在:
model_mapping+model_pricing+restrict_models=true+billing_model_source=channel_mapped /v1/models和/v1/chat/completions是两个独立验收层
6. 访问闭环
Stage 3 的 access_status 决定真实可用性:
| access_status | 含义 | 用户可使用 |
|---|---|---|
active |
Stage1 probe OK + Stage2 account OK + Stage3 completion OK | ✅ |
degraded |
Stage1/2 OK,但 Stage3 completion 异常 | ⚠️ 限流/不稳定 |
broken |
Stage1 probe 失败或 Stage2 account test 失败 | ❌ |
7. 错误恢复策略
- Stage 1 失败:记录
upstream_unreachable,跳过 Stage 2/3 - Stage 2 部分失败:已完成资源保留(不自动回滚)
- Stage 3 失败:access_status 降级,但已创建资源不删除
- 整批中断:按
--mode strict | partial处理strict:任一 item 失败,整批停止,报告已完成的partial(默认):失败 item 单独记录,成功的继续
8. 与 v1 的关系
v2 不取代 v1,而是新增一条并行入口:
| v1 (Pack-Based) | v2 (Auto-Import) | |
|---|---|---|
| 输入 | provider manifest | URL + API key |
| 模型来源 | pack 内置 | 上游动态探测 |
| 适用场景 | 已知 provider,批量标准化导入 | 新 provider,即插即用 |
| channel 配置 | manifest 预定义 | 自动发现 + 扩展 |
v2 的 provider binding 复用 v1 已有 managed_resources 和 import_batches 表,只是入口不同。
9. 项目结构变化
internal/
probe/ # 新增:上游探测模块
models.go # GET /v1/models 解析
completion.go # smoke test POST /v1/chat/completions
classifier.go # 错误分类(auth/rate_limit/upstream/unreachable)
batch/ # 新增:批量导入编排
service.go # BatchImportService: 管道编排
provider_id.go # URL → provider_id 规范化
channel_evolution.go # model_mapping 扩展逻辑
host/sub2api/
channel.go # 新增: PatchChannel(channel_id, add_model_mapping)
cmd/
cli/
batch_import.go # 新增: batch-import 命令
tests/integration/
batch_import_test.go # 新增: 批量导入集成测试
10. 测试策略
单测
probe/models_test.go— 模型列表解析,覆盖 OpenAI 格式变体probe/classifier_test.go— 错误类型分类batch/provider_id_test.go— URL → provider_id 规范化batch/channel_evolution_test.go— model_mapping 扩展差异计算batch/service_test.go— 管道编排 mock 测试
集成测
tests/integration/batch_import_test.go- 两组 (url, key),probe + provision + validate 全流程
- strict 模式任一失败整批停止
- partial 模式失败 item 隔离
11. 暂不做(v2 范围外)
- Web UI / HTTP API 入口(CLI 先跑通)
- 自动发现 provider 的 channel pricing(model pricing 留空,等用户配置)
- 多 key 之间的负载均衡策略
- 对账调度器( reconcile 由 v1 提供)
12. 成功标准
- CLI
batch-import可接受单条和文件批量输入 - Stage 1 probe 能在 10s 内返回上游模型列表(超时控制)
- 重复导入同一 URL+key 时,不重复创建 channel/account(幂等)
- Stage 3 completion 测试通过时,
access_status=active - Stage 3 失败时,access_status 正确降级(broken/degraded)
strict模式下,任一 item 失败整批停止并报告partial模式下,成功的 item 不因失败 item 而中断- 全流程不修改宿主源码,不写宿主数据库
13. 开放问题(已决策)
- provider_id 策略:选 B(host + hash),
{normalized_host}-{url_hash_last8} - model_pricing 为空:选 B,自动补空 pricing(填默认值,不阻塞导入)
- smoke test model:选 C,遍历 data 找第一个能完成 chat completion 的模型