Files
sub2api-cn-relay-manager/docs/2026-05-21-BATCH_AUTO_IMPORT_SPEC.md
2026-05-22 14:15:41 +08:00

16 KiB
Raw Permalink Blame History

SPEC: Batch Auto-Import by URL + Key (v2)

日期2026-05-21 技术架构:docs/2026-05-22-BATCH_AUTO_IMPORT_V2_ARCHITECTURE.md

1. Objective

V2 的目标不是“又一条导入命令”,而是把这件事做成稳定、可恢复、可追踪的控制面能力:

  1. 上游发现:基于 (base_url, api_key) 自动发现模型,而不是默认信任人工输入
  2. 模型纠错:自动归一化、别名匹配、推荐正确模型名
  3. 兼容画像:记录每个上游和每个模型的兼容能力,避免重复踩坑
  4. 宿主演化:自动创建/更新 channel、account、provider binding
  5. 异步确认:吸收宿主异步 probe、首次 403/503 的预热窗口
  6. 闭环验证:以宿主网关真实 /v1/chat/completions 结果作为最终可用性判断
  7. 结果可视:提供 run 列表、run 详情、item 详情,而不是只靠日志和 artifact
  8. 重复导入复用:已成功导入且模型已覆盖的 provider再次添加时应自动复用而不是重复创建

2. Scope

V2 在现有 v1 pack-based 路径旁边新增一条URL + key auto-import 路径。

In Scope

  • 批量输入 (base_url, api_key, requested_models?)
  • 兼容 subscriptionself_service
  • 运行态状态持久化
  • 后台异步确认与有限重试
  • 最小结果 API 与结果页
  • 模型纠错与 capability profile 持久化

Out of Scope

  • 多 key 自动负载均衡
  • 宿主数据库直连
  • 自动价格发现/自动调价
  • 实时 WebSocket 推送
  • 复杂工作台前端

3. Canonical Contract

V2 从这里开始只认一套 canonical contract后续所有文档、API、页面、状态库都必须遵循这套命名。

3.1 ID 规则

  • run_id:一次批量导入任务 ID字符串
  • item_idrun 内单条导入记录 ID字符串
  • provider_id{normalized_host}-{url_hash_last8}

provider_id 生成规则:

  1. 取完整 base_url 规范化后参与计算,不能只取 host
  2. normalized_host 用于可读性
  3. url_hash_last8 用于区分同 host 不同 path

示例:

  • https://api.deepseek.com/v1api-deepseek-9f31c2ab
  • https://api.deepseek.com/proxy/v1api-deepseek-4a2d88f1

3.2 Run 级状态

run.state 固定为:

  • running
  • completed
  • completed_with_warnings
  • failed
  • cancelled

说明:

  • completed_with_warnings 是 run 级总状态
  • 页面可以显示成黄色 badge warning
  • 但 API/状态库里一律写全量枚举值 completed_with_warnings

3.3 Item 级状态

item.current_stage 固定为:

  • probe
  • provision
  • confirm
  • validate
  • done

item.confirmation_status 固定为:

  • pending
  • confirmed
  • advisory
  • failed

item.access_status 固定为:

  • unknown
  • active
  • degraded
  • broken

约束:

  • confirmation_status 只描述“宿主异步窗口是否已确认稳定”
  • access_status 只描述“最终网关真实可用性”
  • Validation Engineaccess_status 的唯一写入方

3.4 Legacy 兼容规则

V1 的 import_batches / import_batch_items / managed_resources 继续保留,但在 V2 中:

  • 仅作为 legacy execution evidence 或资源关联来源
  • 不再作为结果页主数据源
  • V2 结果页/API 只读 import_runs / import_run_items / import_run_item_events

4. Request / Result Contract

4.1 Batch Import Request

BatchImportRunRequest
  - host_id: string
  - mode: "strict" | "partial"
  - access_mode: "subscription" | "self_service"
  - confirm_wait_timeout_sec: int           # CLI/HTTP 可选等待时间
  - entries: []BatchImportEntry
  - subscription_users: []string            # access_mode=subscription 必填
  - subscription_days: int                  # access_mode=subscription 必填
  - probe_api_key: string                   # access_mode=self_service 必填

BatchImportEntry
  - base_url: string
  - api_key: string
  - requested_models: []string              # 可选,仅作为提示

4.2 Access Mode 必填规则

subscription

  • 必填:subscription_users
  • 必填:subscription_days
  • 不接受只写 access_mode=subscription 但不带订阅目标

self_service

  • 必填:probe_api_key
  • probe_api_key 用于最终 gateway access validation

4.3 Batch Import Result

BatchImportRunResult
  - run_id: string
  - state: string
  - total_items: int
  - active_items: int
  - degraded_items: int
  - broken_items: int
  - warning_items: int
  - result_page: string

4.4 Item Projection

BatchImportRunItemView
  - item_id: string
  - base_url: string
  - provider_id: string
  - api_key_fingerprint: string
  - requested_models: []string
  - raw_models: []string
  - normalized_models: []string
  - canonical_model_families: []string
  - resolved_smoke_model: string | null
  - recommended_models: []string
  - current_stage: string
  - confirmation_status: string
  - access_status: string
  - matched_account_state: string
  - account_resolution: string
  - retry_count: int
  - last_retry_at: string | null
  - advisory_messages: []string
  - last_error_stage: string | null
  - last_error: string | null
  - channel_id: int64 | null
  - account_id: int64 | null
  - provision_reused: bool
  - reused_from_provider_id: string | null
  - reused_from_account_id: int64 | null
  - capability_profile: object

5. Core Pipeline

5.1 Five-stage pipeline

Stage 0: Run Setup
  create import_run + import_run_items
  persist operator input

Stage 1: Probe
  /v1/models
  capability probe
  completion smoke
  normalize aliases

Stage 2: Provision
  find/create channel
  patch model_mapping + model_pricing + restrict_models + billing_model_source
  create/update account
  persist managed resource link

Stage 3: Confirm
  background confirmer absorbs async probe race / warmup window
  writes confirmation_status

Stage 4: Validate
  host gateway real /v1/chat/completions
  writes final access_status

Stage 5: Project
  update run summary
  serve result API / pages

5.2 Ownership boundaries

  • Probe Layer 负责发现和分类,不决定最终 access_status
  • Provision Adapter 负责创建/更新宿主资源
  • Confirmation Engine 负责把瞬时 403/503 吸收到 pending/advisory/failed
  • Validation Engine 负责最终 access_status
  • Result Projection 负责把状态库转换成页面/API 视图

6. Capability Profile

6.1 为什么要分两层

真实场景里兼容能力不是“一个 key 一个总画像”就能表达清楚的。必须拆成:

  1. transport profile:这个 upstream 支不支持 /models/chat/completions/responses/messages
  2. model profiles:这个 upstream 下的具体模型,在 stream/tools/reasoning 字段上是否可用

6.1.1 为什么还要有 canonical model family

不同中转对同一个模型的命名可能有轻微差异,但 API 和能力集本质一致,例如:

  • kimi 2.6
  • kimi-2.6
  • kimi-k2.6
  • Kimi-K2.6

V2 不能把这些名字当成完全不同的模型,而要继续归并到同一个 canonical_model_family,用于:

  • 重复导入复用判断
  • 模型覆盖判断
  • 别名 patch 判断
  • 推荐模型名输出

6.2 Canonical schema

{
  "transport_profile": {
    "supports_openai_models": true,
    "supports_openai_chat_completions": true,
    "supports_openai_responses": false,
    "supports_anthropic_messages": false,
    "auth_style": "bearer",
    "model_id_style": "vendor_prefixed",
    "known_advisories": [
      "responses_unsupported_but_chat_ok",
      "initial_probe_race_expected"
    ]
  },
  "model_profiles": [
    {
      "raw_model_id": "deepseek-ai/DeepSeek-V4-Pro",
      "normalized_model_id": "deepseek-v4-pro",
      "canonical_model_family": "deepseek-v4-pro",
      "supports_stream": true,
      "supports_tools": "unknown",
      "supports_reasoning_fields": "unknown",
      "smoke_chat_ok": true
    }
  ]
}

6.3 用途

  • 决定是否跳过 /responses
  • 决定是否直接走 raw /chat/completions
  • 决定 warning 文案
  • 决定推荐 smoke model
  • 决定后续快速匹配“哪个模型在哪种兼容层下靠谱”

6.4 Canonical model family 规则

V2 对模型名做三层处理:

  1. raw_model_id
  2. normalized_model_id
  3. canonical_model_family

示例:

raw_model_id normalized_model_id canonical_model_family
kimi 2.6 kimi-2.6 kimi-2.6
kimi-k2.6 kimi-k2.6 kimi-2.6
Kimi-K2.6 kimi-k2.6 kimi-2.6
deepseek-ai/DeepSeek-V4-Pro deepseek-v4-pro deepseek-v4-pro

约束:

  • canonical_model_family 用于跨中转识别“是否同一个模型族”
  • normalized_model_id 用于控制面和 channel 落盘
  • raw_model_id 用于保留 upstream 原始路由

7. Existing Provider Reuse / Idempotent Re-import

7.1 目标

如果某个 provider 已成功导入,且现有模型族已覆盖本次请求模型,则再次添加时应:

  • 不重复创建 channel/account/provider
  • 直接复用既有成功链路
  • 必要时仅 patch 新 alias / 新模型映射

7.2 预检查顺序

每个 item 在 Stage 2 前必须按顺序执行:

  1. host_id + provider_id 查现有 provider
  2. host_id + base_url + api_key_fingerprint 查现有 account
  3. 比较:
    • canonical_model_families
    • normalized_models
    • 既有 access_status
    • 既有账号健康状态

7.3 决策表

场景 行为
provider 已存在,access_status=active,且既有 canonical_model_families 覆盖本次请求 直接复用,不再 provision
命中现有 account且账号状态为 active 标记为重复已启用账号,直接复用并提示 duplicate_active_account
命中现有 account且账号状态为 disableddeprecated,但 key 仍健康 reactivated 路径,快速启用已有账号,不新建账号
provider 已存在,账号健康,但只缺少部分 alias / mapping 只 patch不重建
provider 已存在,但 key 已失效或 access_status=broken 不复用,进入 repair/replace
同 host 同 URL但 access_mode 不同 不直接复用 access 结果,按 mode 分别确认

7.4 复用后的 item 投影

若命中复用item 仍要生成新的 V2 记录,并写明:

  • provision_reused = true
  • reused_from_provider_id
  • reused_from_account_id
  • matched_account_state
  • account_resolution

7.4.1 已存在账号的处理原则

V2 必须同时回答两件事:

  1. 这次 provider 是否被复用
  2. 命中的既有账号当前是什么状态

对于 host_id + base_url + api_key_fingerprint 命中的账号:

  • active
    • 不重复创建账号
    • matched_account_state=active
    • account_resolution=reused
    • UI 文案显示“重复,已启用”
  • disabled / deprecated
    • 优先尝试启用已有账号
    • matched_account_state=disabled|deprecated
    • account_resolution=reactivated
    • UI 文案显示“已弃用,已快速启用”
  • broken
    • 不直接复用
    • matched_account_state=broken
    • account_resolution=replaced
    • 进入 repair/replace 流程

7.5 Key fingerprint

V2 不以原始 key 字符串作为重复匹配依据,而保存:

  • api_key_fingerprint

用于区分:

  • 同一把 key 的重复导入
  • 同 URL 下新增另一把 key

8. Channel / Account Evolution Contract

V2 不再使用“薄 patch 接口”表达 channel 更新。宿主 patch 必须以完整 contract 表达:

ChannelPatchContract
  - model_mapping: map[string]string
  - model_pricing: map[string]PriceSpec
  - restrict_models: true
  - billing_model_source: "channel_mapped"

约束:

  • model_mapping 同时记录 raw → canonical
  • model_pricing 默认可填零值,但字段必须完整存在
  • patch 不得破坏旧模型
  • PatchChannel(addModels []string) 这类接口不再作为 V2 canonical contract

9. Async Confirmation Mechanism

8.1 为什么 V2 必须有后台 confirmer

V2 的稳定性目标不能建立在“请求线程里顺序 sleep + retry”。必须有独立后台机制推进

  • confirming item
  • 因 probe race 暂时 advisory 的 item
  • 503 no available accounts 等待预热的 item

8.2 Canonical executor

V2 必须实现 ConfirmationWorker

ConfirmationWorker
  - poll import_run_items where current_stage='confirm'
  - condition: next_retry_at <= now
  - acquire lease
  - run confirm logic
  - update item state
  - release lease

8.3 必需字段

import_run_items 至少要有:

  • confirmation_attempts
  • retry_count
  • last_retry_at
  • next_retry_at
  • lease_owner
  • lease_until

8.4 Restart safety

V2 第一版即要求:

  • 进程重启后 unfinished confirm item 会被 worker 重新拾取
  • 页面能看到 item 停在哪个阶段
  • CLI --confirm-wait-timeout 只是“等待窗口”,不是确认机制本身

10. Single Source of Truth

9.1 Canonical runtime tables

V2 运行态只认三类表:

  • import_runs
  • import_run_items
  • import_run_item_events

9.2 Legacy linkage

若某个 V2 item 调用了现有 v1 provision 流程,可在 item 上保留:

  • legacy_batch_id
  • legacy_provider_id

但这些字段仅作为追溯链接,不能替代 V2 状态源。

9.3 Result page data source

结果页/API 只读 V2 canonical tables不直接拼接

  • import_batches
  • probe_results
  • access_closure_records
  • 宿主数据库

11. Result API and Pages

10.1 API

V2 标准 API

POST /api/batch-import/runs
GET  /api/batch-import/runs
GET  /api/batch-import/runs/{run_id}
GET  /api/batch-import/runs/{run_id}/items
GET  /api/batch-import/runs/{run_id}/items/{item_id}

Legacy API /api/import-batches/* 保留,但标为 v1/legacy。

10.2 Pages

/batch-import/runs
/batch-import/runs/{run_id}

结果页必须能直接回答:

  • 哪条 URL 导入成功
  • 哪条卡在 probe/provision/confirm/validate
  • 哪条发生模型纠错
  • 哪条是 advisory 而不是 broken
  • 重试过几次
  • 当前 warning 的原因是什么

12. CLI Contract

go run ./cmd/cli batch-import \
  --host-id "<host_id>" \
  --entry "https://example.com/v1,sk-xxx" \
  --batch-file "./keys.csv" \
  --mode "strict|partial" \
  --access-mode "subscription|self_service" \
  --subscription-users "u1,u2" \
  --subscription-days 30 \
  --probe-api-key "<user_gateway_key>" \
  --confirm-wait-timeout 15s

CLI 输出必须至少包含:

  • run_id
  • result_page
  • 每个 entry 的 resolved_smoke_model
  • capability 摘要
  • confirmation_status
  • access_status
  • 推荐模型名(若发生纠错)

13. Error Policy

Blocking

  • 401/403 unauthorized 且证据表明 key 无效
  • /v1/models 完全不可用且无替代路径
  • provision 明确失败

Advisory

  • 第三方 upstream /responses=403/chat/completions=200
  • 首次 /accounts/:id/test=403,但 probe race 已被识别
  • 首次 /v1/chat/completions=503 no available accounts,且重试后恢复
  • 429 rate_limit

Access status ownership

  • confirmation_status=advisory 不自动等于 access_status=degraded
  • 只有 Validation Engine 可以把 item 标成 active/degraded/broken

14. Success Criteria

  1. access_mode 输入契约完整,subscription / self_service 都可单独落地
  2. run / item 状态、重试、warning、错误阶段能持久化并在重启后恢复可见
  3. 结果页和 API 只读 V2 canonical tables
  4. 模型纠错结果、capability profile、推荐模型名可追溯
  5. 第三方兼容 upstream 的 /responses 误判和宿主异步窗口不会把可用链路直接打成最终失败
  6. 页面可以清楚地区分 confirmed/advisory/failedactive/degraded/broken
  7. OpenAPI、SPEC、TDD、Architecture 对同一字段和同一状态枚举保持一致
  8. 已成功导入的 provider 再次添加时,若模型族已覆盖,应自动复用,不重复创建
  9. 同模型在不同中转下的轻微命名差异,能通过 canonical_model_family 快速识别为同一模型族

15. Non-goals for first implementation

  • 多 key 自动调度
  • 实时推送
  • 自动定价策略
  • 自动负载均衡

16. Final decisions

  1. provider_id 采用 normalized_host + url_hash_last8
  2. requested_models 仅作提示,不作为事实源
  3. Validation Engineaccess_status 唯一写入方
  4. V2 runtime canonical tables 为 import_runs/import_run_items/import_run_item_events
  5. ConfirmationWorker 是 V2 必备组件,不是可选增强
  6. 同模型跨中转匹配以 canonical_model_family 为准,而不是只看原始模型名