# V2 技术架构 — Batch Auto-Import 日期:2026-05-22 状态:设计中 关联文档: - `docs/2026-05-21-BATCH_AUTO_IMPORT_SPEC.md` - `docs/2026-05-21-BATCH_AUTO_IMPORT_TDD_PLAN.md` - `docs/EXECUTION_BOARD.md` ## 1. 文档目标 这份文档回答 4 个问题: 1. V2 在系统里由哪些组件组成 2. 运行状态如何持久化,如何保证导入任务更稳定 3. 结果页到底展示什么,字段如何组织 4. 后续实现时,后端、存储、页面应该如何分工 这份文档不重复 `SPEC` 的产品动机,也不替代 `TDD plan` 的编码顺序。它只负责把 V2 的技术结构和结果页设计讲清楚。 ## 2. 设计目标 V2 必须同时满足这 5 个目标: 1. **稳定导入** - 单条 URL + key 的 probe / provision / confirm / validate 全过程可控 - 批量导入时,单条失败不应让整批状态丢失 2. **状态可恢复** - 控制面重启后,历史 run 和 item 结果仍可查看 - 对 transient 失败要有明确 retry 轨迹 3. **模型纠错** - 人工填写的模型名不是最终事实 - 必须基于 key 实探结果自动归一化、匹配、推荐 4. **兼容分流** - 对国产模型 / 第三方 OpenAI-compatible 上游,必须记录 capability profile - 后续 routing、确认逻辑、warning 解释都依赖 profile 5. **结果可视** - 不再只靠 CLI 和日志 - 控制面必须提供 run 列表和 run 详情页 ## 3. 非目标 这版架构明确不做: - 多 key 自动负载均衡 - 高级调度系统 - 实时 WebSocket 推送 - 复杂前端工作台 - 自动价格发现与自动调价 - 宿主数据库直连或宿主 DB 读写 ## 4. 逻辑组件 V2 在控制面内部拆成 6 个逻辑层: ```text operator input ↓ batch import orchestrator ├── probe layer ├── capability profiler ├── provision adapter ├── confirmation engine ├── validation engine └── run state store ↓ result projection ↓ HTTP API / result pages / CLI output ``` ### 4.1 Probe Layer 职责: - 调用 upstream `/v1/models` - 探测 `/v1/chat/completions` - 探测 `/v1/responses` - 探测 `/v1/messages` - 抽取原始模型列表 - 计算模型归一化结果 输入: - `base_url` - `api_key` - `requested_models`(可选) 输出: - `raw_models` - `normalized_models` - `resolved_smoke_model` - `capability_profile` - `probe_summary` ### 4.2 Provision Adapter 职责: - 计算 `provider_id` - 查找或创建目标 channel - patch `model_mapping` - patch `model_pricing` - 创建或更新 account - 绑定 provider 资源关系 输入: - `normalized_models` - `capability_profile` - `resolved_smoke_model` 输出: - `channel_id` - `account_id` - `provider_id` - `managed_resources` ### 4.3 Confirmation Engine 职责: - 处理宿主异步探测窗口 - 处理首次 `403 probe race` - 处理首次 `503 no available accounts` - 决定 `confirmation_status` 输出状态: - `confirmed_active` - `confirmed_warning` - `confirmed_broken` ### 4.4 Validation Engine 职责: - 使用托管 key 对宿主 gateway 真实发起 `/v1/chat/completions` - 决定最终 `access_status` - 将 validation 结果写入结果页投影 ### 4.5 Run State Store 职责: - 持久化 run 和 item 的阶段状态 - 持久化 retry、warning、错误阶段 - 为结果页提供可直接读取的数据源 ### 4.6 Result Projection 职责: - 将低层运行状态整理成运营和开发都能直接看的摘要 - 生成 run 列表统计 - 生成 item 详情视图 - 不暴露内部实现噪音 ## 5. 运行时状态模型 ### 5.1 Run 级状态 `ImportRun.state` - `running` - `completed` - `completed_with_warnings` - `failed` - `cancelled` `ImportRun` 核心字段: | 字段 | 含义 | |---|---| | `run_id` | 一次批量导入任务的主键 | | `mode` | `strict` / `partial` | | `access_mode` | `subscription` / `self_service` | | `total_items` | 总条目数 | | `completed_items` | 已完成条目数 | | `active_items` | 最终 active 条目数 | | `degraded_items` | 最终 degraded 条目数 | | `broken_items` | 最终 broken 条目数 | | `state` | run 总状态 | | `started_at` | 开始时间 | | `updated_at` | 最近更新时间 | | `finished_at` | 完成时间 | ### 5.2 Item 级状态 `ImportRunItem.current_stage` - `probe` - `provision` - `confirm` - `validate` - `done` `ImportRunItem.stage_status` - `discovered` - `provisioned` - `confirming` - `confirmed_active` - `confirmed_warning` - `confirmed_broken` `ImportRunItem` 核心字段: | 字段 | 含义 | |---|---| | `item_id` | 单条导入记录 ID | | `base_url` | 当前导入目标 URL | | `provider_id` | 自动生成的 provider 标识 | | `requested_models` | 人工请求模型名 | | `raw_models` | upstream 原始模型列表 | | `normalized_models` | 归一化后模型列表 | | `resolved_smoke_model` | 最终用于 smoke 的模型 | | `capability_profile_json` | 能力画像 | | `channel_id` | 宿主 channel | | `account_id` | 宿主 account | | `confirmation_status` | 确认状态 | | `access_status` | 最终访问状态 | | `retry_count` | 当前总重试次数 | | `advisory_messages` | warning / advisory 列表 | | `last_error_stage` | 最近错误发生阶段 | | `last_error` | 最近错误文本 | | `created_at` | 创建时间 | | `updated_at` | 更新时间 | ## 6. 状态库设计 推荐在控制面 SQLite 中新增两张表: ### 6.1 `import_runs` ```text id TEXT PRIMARY KEY mode TEXT NOT NULL access_mode TEXT NOT NULL state TEXT NOT NULL total_items INTEGER NOT NULL completed_items INTEGER NOT NULL active_items INTEGER NOT NULL degraded_items INTEGER NOT NULL broken_items INTEGER NOT NULL started_at DATETIME NOT NULL updated_at DATETIME NOT NULL finished_at DATETIME NULL ``` 索引: - `idx_import_runs_started_at` - `idx_import_runs_state` ### 6.2 `import_run_items` ```text id TEXT PRIMARY KEY run_id TEXT NOT NULL base_url TEXT NOT NULL provider_id TEXT NOT NULL current_stage TEXT NOT NULL stage_status TEXT NOT NULL requested_models_json TEXT NOT NULL raw_models_json TEXT NOT NULL normalized_models_json TEXT NOT NULL resolved_smoke_model TEXT NOT NULL capability_profile_json TEXT NOT NULL channel_id INTEGER NULL account_id INTEGER NULL confirmation_status TEXT NOT NULL access_status TEXT NOT NULL retry_count INTEGER NOT NULL advisory_messages_json TEXT NOT NULL last_error_stage TEXT NULL last_error TEXT NULL created_at DATETIME NOT NULL updated_at DATETIME NOT NULL ``` 索引: - `idx_import_run_items_run_id` - `idx_import_run_items_provider_id` - `idx_import_run_items_stage_status` - `idx_import_run_items_access_status` 约束: - 所有页面和 API 只读这两张控制面表 - 不直接读宿主数据库 - 宿主状态变化要通过控制面自己的确认与验证结果回写 ## 7. 稳定性机制 ### 7.1 阶段落盘 每个 item 每完成一个阶段都立刻落盘: - probe 完成后写 `discovered` - provision 完成后写 `provisioned` - confirm 开始写 `confirming` - confirm/validate 结束后写 `confirmed_*` 这样做的价值: - 进程中断后不丢历史轨迹 - 页面可以显示“卡在哪一阶段” - 后续如果支持 resume,有阶段边界可用 ### 7.2 Advisory 与 Blocking 分离 V2 必须显式区分: - **blocking error** - 真正阻止继续执行 - **advisory** - 不阻止完成,但需要展示原因 典型 advisory: - 第三方 upstream 首次 `403 Forbidden` probe race - 首次 `503 no available accounts` 但重试后恢复 - `/responses` 不支持,但 `/chat/completions` 可用 ### 7.3 Retry Policy 不是所有失败都重试。 建议策略: | 错误类型 | 处理 | |---|---| | `401/403 unauthorized` | 不重试,直接失败 | | 首次 `403 probe race` | advisory,等待异步确认,再测 | | `429 rate_limit` | 记录 warning,可选限次重试 | | 首次 `503 no available accounts` | 短时重试 | | `502/503 upstream unreachable` | 按策略有限重试 | 每次 retry 都必须写入: - `retry_count` - `last_error_stage` - `last_error` - 最近 retry 时间 ### 7.4 Restart Safety 控制面重启后至少保证: - 历史 run 列表还能查看 - 历史 item 详情还能查看 - 若 run 在中途停止,页面能看到“最后停在什么阶段” V2 第一阶段可以不做自动 resume,但必须让“失败现场可见”。 ## 8. 结果页设计 V2 最少提供两个页面: - `/batch-import/runs` - `/batch-import/runs/{run_id}` ### 8.1 批次列表页 页面目标: - 快速看最近有哪些导入批次 - 快速判断哪一批成功、哪一批有 warning、哪一批失败 #### 页面结构 ```text 页面标题:Batch Import Runs [筛选区] - 状态筛选:all / running / completed / warning / failed - access_mode 筛选:all / subscription / self_service - 时间范围 - 关键字搜索:run_id / provider_id / base_url [列表表格] - Run ID - Started At - Finished At - Mode - Access Mode - Total - Active - Degraded - Broken - State - Actions ``` #### 字段布局 | 列 | 说明 | |---|---| | `Run ID` | 可点击进入详情 | | `Started At` | 开始时间 | | `Finished At` | 完成时间,运行中为空 | | `Mode` | `strict` / `partial` | | `Access Mode` | `subscription` / `self_service` | | `Total` | item 总数 | | `Active` | 绿色数值 | | `Degraded` | 黄色数值 | | `Broken` | 红色数值 | | `State` | run 总状态 badge | | `Actions` | 查看详情 | #### 状态表现 - `running`:蓝色 badge - `completed`:绿色 badge - `completed_with_warnings`:黄色 badge - `failed`:红色 badge - `cancelled`:灰色 badge ### 8.2 批次详情页 页面目标: - 快速回答“这批次具体哪条 URL 出了什么问题” - 能看到模型纠错、compatibility、warning 和 retry 轨迹 #### 页面结构 ```text 页面标题:Batch Import Run Detail [头部摘要卡] - Run ID - State - Started At / Finished At - Mode / Access Mode - Total / Active / Degraded / Broken [Item 列表表格] - Item ID - Base URL - Provider ID - Requested Models - Resolved Smoke Model - Current Stage - Confirmation Status - Access Status - Retry Count - Last Error Stage - Last Error - Actions [点击某个 Item 后展开侧栏/详情区] - URL 与 provider 基础信息 - 模型纠错结果 - capability profile 摘要 - channel/account 资源绑定 - advisory messages - retry timeline - 原始错误与最终结论 ``` #### Item 表格字段 | 列 | 说明 | |---|---| | `Item ID` | 单条记录标识 | | `Base URL` | 当前 upstream | | `Provider ID` | 自动生成 provider | | `Requested Models` | 人工输入 | | `Resolved Smoke Model` | 最终 smoke 模型 | | `Current Stage` | `probe/provision/confirm/validate/done` | | `Confirmation Status` | `confirmed_active/warning/broken` | | `Access Status` | `active/degraded/broken` | | `Retry Count` | 已重试次数 | | `Last Error Stage` | 最近错误阶段 | | `Last Error` | 最近错误摘要 | #### Item 详情区字段 1. **基础信息** - `base_url` - `provider_id` - `channel_id` - `account_id` 2. **模型信息** - `requested_models` - `raw_models` - `normalized_models` - `resolved_smoke_model` - `recommended_models`(如果发生纠错) 3. **兼容能力摘要** - `supports_openai_models` - `supports_openai_chat_completions` - `supports_openai_responses` - `supports_anthropic_messages` - `model_id_style` - `known_advisories` 4. **确认与访问结果** - `confirmation_status` - `probe_ok` - `access_status` - `retry_count` - `last_error_stage` - `last_error` 5. **说明区** - warning 为什么是 warning - broken 为什么是 broken - 这个 item 是否建议人工介入 ### 8.3 页面交互原则 - 默认先显示摘要,不先展示原始 JSON - 详情区优先显示“结论”和“原因” - 原始 capability profile / 模型列表可以折叠查看 - warning 要能解释成一句人能读懂的话 示例: - `responses_unsupported_but_chat_ok` - 页面说明:`该上游不支持 /v1/responses,系统已自动回退到 /v1/chat/completions` - `initial_probe_race_expected` - 页面说明:`账号创建后宿主异步探测尚未稳定,首次 /test 结果已按 advisory 处理` ## 9. API 设计 ### 9.1 列表 API `GET /api/batch-import/runs` 返回结构: ```json { "runs": [ { "run_id": "batch-20260522-001", "state": "completed_with_warnings", "mode": "partial", "access_mode": "subscription", "total_items": 12, "active_items": 9, "degraded_items": 2, "broken_items": 1, "started_at": "2026-05-22T10:00:00+08:00", "finished_at": "2026-05-22T10:02:12+08:00" } ] } ``` ### 9.2 详情 API `GET /api/batch-import/runs/{run_id}` 返回: - run summary - count summary - recent warnings ### 9.3 Item 列表 API `GET /api/batch-import/runs/{run_id}/items` 支持筛选: - `stage_status` - `access_status` - `provider_id` - `has_warning` ### 9.4 Item 详情 API `GET /api/batch-import/runs/{run_id}/items/{item_id}` 返回: - item 全量详情 - capability profile - advisory - retry trail ## 10. 后端模块映射 建议模块划分: ### `internal/batch/run_state.go` 职责: - run / item 状态仓储接口 - run summary 聚合 - 状态投影基础函数 ### `internal/batch/status_projection.go` 职责: - 将底层字段转换成页面友好的摘要 - 统一 warning 文案 - 统一 badge / state 映射 ### `internal/app/http_batch_import.go` 职责: - run 列表 API - run 详情 API - item 列表 API - item 详情 API ### `internal/app/http_batch_runs.go` 职责: - 渲染最小结果页 - 不关心 probe/provision 细节 - 只依赖 projection 层输出 ## 11. 页面草图 ### 11.1 列表页草图 ```text +----------------------------------------------------------------------------------+ | Batch Import Runs | +----------------------------------------------------------------------------------+ | Status: [all v] Access Mode: [all v] Search: [__________________________] | +----------------------------------------------------------------------------------+ | Run ID | State | Total | Active | Degraded | Broken | Started At | | batch-20260522-001 | warning | 12 | 9 | 2 | 1 | 10:00:00 | | batch-20260522-002 | running | 4 | 1 | 0 | 0 | 10:05:13 | +----------------------------------------------------------------------------------+ ``` ### 11.2 详情页草图 ```text +----------------------------------------------------------------------------------+ | Batch Import Run: batch-20260522-001 | +----------------------------------------------------------------------------------+ | State: completed_with_warnings Mode: partial Access: subscription | | Total: 12 Active: 9 Degraded: 2 Broken: 1 | +----------------------------------------------------------------------------------+ | Items | +----------------------------------------------------------------------------------+ | URL | Provider | Stage | Confirm | Access | Retry | ... | | api.deepseek.com | deepseek | done | warning | active | 1 | ... | | api.53hk.cn/v1 | minimax | done | active | active | 0 | ... | +----------------------------------------------------------------------------------+ | Selected Item Detail | +----------------------------------------------------------------------------------+ | Requested Models: [minimax-m27-highspeed] | | Resolved Smoke Model: MiniMax-M2.7-highspeed | | Capability: responses=false, chat=true, messages=false | | Advisory: 首次 /test 403 已按异步 probe race 处理 | | Last Error Stage: confirm | | Last Error: API returned 403: Forbidden | +----------------------------------------------------------------------------------+ ``` ## 12. 实施建议 按最小可落地顺序做: 1. 先实现 `run_state` 表与 repo 2. 再让 batch service 在每阶段写入状态 3. 再做 API 4. 最后做最小页面 不要先做页面再补状态库。否则页面会重新依赖日志和运行态对象,后面还得推倒重来。 ## 13. 验收标准 这份架构落地后,至少应满足: 1. 导入过程中可以查询到 run 和 item 状态 2. 导入完成后可以通过页面复盘每个 item 的结果 3. 页面可以看出模型纠错是否发生 4. 页面可以看出 capability profile 的关键摘要 5. warning 和 broken 有可读解释 6. 控制面重启后,历史结果仍可查看 7. 页面和 API 只依赖控制面状态库,不依赖宿主数据库