diff --git a/docs/EXECUTION_BOARD.md b/docs/EXECUTION_BOARD.md index ad4dadb5..a48bd37b 100644 --- a/docs/EXECUTION_BOARD.md +++ b/docs/EXECUTION_BOARD.md @@ -14,6 +14,7 @@ - MiniMax 上游 `/chat/completions` = `HTTP 403 insufficient_user_quota`,因此当前验证 key 本身不具备真实 completion 流量能力。 - 汇总证据:`artifacts/real-host-acceptance/20260521_064910_completion_smoke_calibration.md` - 调通细节与经验沉淀:`docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md` +- 代码门禁与本地运行态已于 2026-05-21 再次独立复跑:`gofmt -l .`、`go vet ./...`、`go test ./... -count=1`、`go test -race ./... -count=1`、`go test -cover ./internal/... -count=1`、`go test ./tests/integration/... -count=1` 全通过;本机 CRM(18100) 的 `GET /healthz` / 带 token 的 `GET /api/hosts` = `200`,另起 fresh smoke 实例 `127.0.0.1:18101` 也成功返回 `GET /healthz = ok`、`GET /api/hosts = {"hosts":[]}`。 ## 本轮已完成 @@ -65,11 +66,13 @@ - `internal/provision`: `74.6%` - `internal/store/sqlite`: `61.3%` - `go test ./tests/integration/... -count=1` ✅ +- `bash ./scripts/test_real_host_scripts.sh` ✅ ## 本轮真实宿主复验结果 0. `ca1d448` latest-head stale-process 阻断已关闭(2026-05-21) - 本地 CRM(18100) 新进程启动时间:`2026-05-21 01:08` + - 当前 18100 使用的真实 DB:`/tmp/sub2api-relay-manager-realhost-18100.db` - fresh remote43 证据目录: - `artifacts/real-host-acceptance/20260521_011544_remote43_minimax_key_import` - `artifacts/real-host-acceptance/20260521_011717_remote43_deepseek_key_import` @@ -78,6 +81,8 @@ - 宿主 admin 侧直接复核: - `GET /api/v1/admin/channels/5`(MiniMax)已含非空 `model_pricing` 与 `model_mapping` - `GET /api/v1/admin/channels/4`(DeepSeek)已含非空 `model_pricing` 与 `model_mapping` + - 旧现象的根因时间线已确认:`artifacts/real-host-acceptance/20260520_222713_crm18100_live_model_mapping_validation/summary.json` 中 MiniMax channel 5 仍是 `model_pricing=[]`,且 `created_at=updated_at=2026-05-20T20:39:23Z`,说明它是旧 CRM 进程创建出的半成品 channel,而不是 current-code rerun 后依然未纠偏 + - 当前 live host 上同一 channel 5 的 `updated_at=2026-05-21T06:45:00Z`,且 `model_pricing` 已非空,证明新进程已真正执行 `UpdateChannel` 纠偏 - 结论:`ca1d448` 对 channel pricing / hosted access 的修正已在真实宿主 fresh rerun 上落地,之前“旧 CRM 进程导致 MiniMax channel 仍空 pricing”的阻断已消失 1. `self_service`(最新 fresh redeploy 复验) diff --git a/docs/PRODUCTION_CLOSURE_BOARD.md b/docs/PRODUCTION_CLOSURE_BOARD.md index e6671b8a..526dde2b 100644 --- a/docs/PRODUCTION_CLOSURE_BOARD.md +++ b/docs/PRODUCTION_CLOSURE_BOARD.md @@ -14,6 +14,7 @@ - MiniMax 上游 `/chat/completions` = `HTTP 403 insufficient_user_quota`,当前验证 key 不具备真实 completion 流量能力。 - 汇总证据:`artifacts/real-host-acceptance/20260521_064910_completion_smoke_calibration.md` - 调通细节与经验沉淀:`docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md` +- 代码/本地运行态门禁已于 2026-05-21 再次独立复跑:`gofmt -l .`、`go vet ./...`、`go test ./... -count=1`、`go test -race ./... -count=1`、`go test -cover ./internal/... -count=1`、`go test ./tests/integration/... -count=1` 全通过;并额外验证了本机 CRM(18100) `GET /healthz` / `GET /api/hosts` = `200`,以及 fresh smoke 实例 `127.0.0.1:18101` 可启动并返回 `GET /healthz = ok`、`GET /api/hosts = {"hosts":[]}`。 ## 当前门控结论 @@ -32,6 +33,7 @@ | Local runtime smoke | ✅ PASS | `go build ./cmd/{server,cli}`、`GET /healthz`、`GET /api/hosts` | | Local OCI image | ✅ PASS | `docker build -f Dockerfile.local -t sub2api-cn-relay-manager:local .` | | Real-host acceptance tooling | ✅ READY | `docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md` + `scripts/real_host_acceptance.sh` | +| Harness regression self-check | ✅ PASS | `bash ./scripts/test_real_host_scripts.sh` | | `self_service` 真实宿主 fresh redeploy 复验 | ⚠️ HISTORICAL PASS | `artifacts/real-host-acceptance/20260518_redeploy_matrix`:历史 fresh redeploy host 可打通;当前不再作为唯一真相来源 | | `subscription` 真实宿主 latest-head fresh host 复验 | ✅ PASS | MiniMax:`artifacts/real-host-acceptance/20260521_011544_remote43_minimax_key_import`;DeepSeek:`artifacts/real-host-acceptance/20260521_011717_remote43_deepseek_key_import`;两条 provider 均 `subscription_ready` | | stale CRM / channel pricing 缺口 | ✅ CLOSED | 宿主 `GET /api/v1/admin/channels/5` 与 `/channels/4` 已返回非空 `model_pricing` + `model_mapping` | diff --git a/docs/README.md b/docs/README.md index 07b3cbd6..5e79a5fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,12 +23,16 @@ ## 真实宿主验收入口 -4. [REAL_HOST_ACCEPTANCE_RUNBOOK.md](./REAL_HOST_ACCEPTANCE_RUNBOOK.md) +4. [REAL_HOST_ACCEPTANCE_CHECKLIST.md](./REAL_HOST_ACCEPTANCE_CHECKLIST.md) + - 每次验收先过这一页 + - 最短闭环 / 红线 / 三层证据 / 诊断顺序 + +5. [REAL_HOST_ACCEPTANCE_RUNBOOK.md](./REAL_HOST_ACCEPTANCE_RUNBOOK.md) - 标准验收步骤 - artifact 结构 - 证据分层要求 -5. [REAL_HOST_ACCEPTANCE_LEARNINGS.md](./REAL_HOST_ACCEPTANCE_LEARNINGS.md) +6. [REAL_HOST_ACCEPTANCE_LEARNINGS.md](./REAL_HOST_ACCEPTANCE_LEARNINGS.md) - 已调通细节 - 高频误判点 - 推荐诊断顺序 diff --git a/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md b/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md new file mode 100644 index 00000000..072c6f11 --- /dev/null +++ b/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md @@ -0,0 +1,147 @@ +# 真实宿主验收速查清单 + +日期:2026-05-21 + +用途: +- 给后续每次 real-host 验收直接复用 +- 只保留最短闭环,不展开历史背景 +- 详细解释仍看:`REAL_HOST_ACCEPTANCE_RUNBOOK.md` 与 `REAL_HOST_ACCEPTANCE_LEARNINGS.md` + +## 0. 先确认你看的是真相文档 + +按顺序先读: +1. `docs/SOURCE_OF_TRUTH.md` +2. `docs/EXECUTION_BOARD.md` +3. `docs/PRODUCTION_CLOSURE_BOARD.md` + +禁止: +- 直接拿历史 review/task board 顶部 gate 当当前状态 +- 直接拿历史 PASS artifact 当 latest-head 当前真相 + +## 1. 环境前置检查 + +每次验收前先确认 4 件事: +1. 在线 CRM 进程是不是最新提交对应进程 +2. `PACK_PATH` 是不是 CRM 进程本机可读路径 +3. `CRM_HOST_BASE` 是不是 CRM 到宿主真实可达地址 +4. 验收脚本命中的 Postgres/Redis/host 是不是目标 fresh host,而不是旧环境 + +若这 4 项没先确认,后续结论一律不可信。 + +## 2. channel 宿主契约检查 + +对每个导入 provider,必须确认 channel 最终同时具备: +- `model_mapping` +- `model_pricing` +- `restrict_models=true` +- `billing_model_source=channel_mapped` + +补充规则: +- `model_mapping` 真实宿主接口是按 platform 嵌套结构 +- `model_pricing[].platform` 不能留空 +- existing channel 不能只复用名字,必须执行纠偏更新 + +一句话: +- 新建即完整 +- 已存在可纠偏 + +## 3. access / key 语义检查 + +### self_service +必须同时满足: +1. 普通用户已创建 +2. 普通用户 key 已创建 +3. key 已绑定目标 standard group +4. 用户有可用余额 + +### subscription +必须同时满足: +1. 普通用户已创建 +2. 普通用户 key 已创建 +3. 目标 group 是 `subscription` 类型 +4. 用户有 active subscription +5. key 已绑定该 subscription group + +关键规则: +- subscription 场景最终 probe key 是宿主 managed key +- 不是外部传入原始 `ACCESS_API_KEY` + +## 4. 每次都要落的三层证据 + +不能只看一层,必须分开留证据: + +1. account 视角 + - `GET /api/v1/admin/accounts/:id` + - `GET /api/v1/admin/accounts/:id/models` + +2. 普通用户 / managed key 视角 + - `GET /v1/models` + +3. completion 视角 + - `POST /v1/chat/completions` + +禁止混用: +- `accounts/:id/models` 正确 ≠ `/v1/models` 正确 +- `/v1/models` 正确 ≠ `/v1/chat/completions` 正确 +- admin API 成功 ≠ 普通用户链路成功 + +## 5. 最短诊断顺序 + +如果验收失败,按这个顺序查: + +1. 先查环境 + - CRM 进程版本/启动时间 + - `PACK_PATH` + - `CRM_HOST_BASE` + - fresh host 目标是否打对 + +2. 再查宿主落库 + - account `credentials.model_mapping` + - `GET /api/v1/admin/accounts/:id/models` + - channel `model_mapping/model_pricing/restrict_models/billing_model_source` + +3. 最后查普通用户流量 + - `/v1/models` + - `/v1/chat/completions` + +## 6. 常见现象 → 优先判断 + +### 现象 A:`/accounts/:id/models` 对,但 `/v1/models` 403 +优先判断: +- key 没绑 group +- subscription 没分配 +- 用错了原始 probe key,没用 managed key +- self_service 缺余额 + +### 现象 B:模型列表返回成 GPT 系,而不是目标 provider 模型 +优先判断: +- account `credentials.model_mapping` 没落对 +- channel 缺 `model_pricing` +- channel 缺 `restrict_models=true` +- channel 缺 `billing_model_source=channel_mapped` +- 在线 CRM 还是旧进程 + +### 现象 C:`/v1/models` 已 200,但 `/v1/chat/completions` 失败 +优先判断: +- host provider 兼容性 +- 上游 key/quota +- 不要先回退归因为 CRM 导入失败 + +## 7. 一次验收的最小通过标准 + +至少同时满足: +1. import 成功,且 batch/provider 不为 `failed` +2. account `/models` 返回目标模型 +3. channel 回读包含完整 routing/pricing 字段 +4. `/v1/models` 返回目标模型 + +若要宣称“真实可用通过”,还必须再加: +5. `/v1/chat/completions` 成功 + +## 8. 一句话红线 + +- 不要把 `/v1/models` 当 completion 成功 +- 不要把 account 成功当普通用户成功 +- 不要把原始 `ACCESS_API_KEY` 当 subscription 最终 probe key +- 不要看到旧 artifact 就直接判定 current-code 失效 +- 不要在没核对在线 CRM 进程版本前下 live 结论 diff --git a/docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md b/docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md index 2569520f..e62a32d2 100644 --- a/docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md +++ b/docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md @@ -41,6 +41,46 @@ 2) 再看 managed key 视角 `/v1/models` 是否正确 3) 最后才看 completion smoke 是否通过 +## 宿主源码再次确认的设计逻辑 + +这部分不是基于 artifact 推断,而是直接对照 `sub2api-official-fresh` 宿主源码确认: + +1. channel admin handler 的真实入参契约 + - `backend/internal/handler/admin/channel_handler.go` + - `model_mapping` 的真实结构是 `map[string]map[string]string` + - `model_pricing` 是独立数组字段,不会从 `model_mapping` 自动推导 + - `billing_model_source` 合法值包括 `channel_mapped` + - `restrict_models` 是独立布尔开关 + +2. channel pricing platform 为空时,宿主会回退到 `anthropic` + - create/update handler 都会在入参 platform 为空时补默认值 + - repository `createModelPricingExec` 也会把空 platform 写成 `anthropic` + - 这意味着 CRM 若不给 OpenAI-compatible provider 显式写 platform,宿主会按 anthropic 语义处理,不能接受 + - 因此 CRM 当前策略必须是: + 1) 先用 provider platform + 2) 若调用侧仍为空,再回退 `openai` + +3. gateway `/v1/models` 与 completion 共享同一套 API key middleware 前置校验 + - `backend/internal/server/middleware/api_key_auth.go` + - 它先校验: + - key 有效 + - user active + - IP 限制 + - group / subscription / balance 前置 + - 所以 `/v1/models` 的 403/429 通常首先反映的是 key/group/subscription/balance 约束,而不等同于 account/channel 落库失败 + +4. subscription group 的 key 绑定条件与 standard group 不同 + - `backend/internal/service/api_key_service.go` + - standard group:走 `user.CanBindGroup(...)` + - subscription group:走 `GetActiveByUserIDAndGroupID(...)` + - 也就是说,subscription 场景里“group 已存在”或“allowed_groups 已写入”都不够,必须有 active subscription + +5. user 自助创建 key 与 admin 绑定 group 是两步 + - `backend/internal/handler/api_key_handler.go` + `api_key_service.go` + - user 侧 `POST /api/v1/api-keys` 可创建带 `custom_key` 的 key + - CRM managed key 流程里,先以普通用户身份创建 key,再用 admin `PUT /api/v1/admin/api-keys/:id` 绑定 group + - 这与我们当前 `EnsureSubscriptionAccess` 的两阶段实现一致 + ## 已调通的宿主侧前置动作 ### self_service @@ -80,6 +120,35 @@ 3. 旧 CRM 进程误导当前结论 - live 行为必须先确认运行中的 CRM 进程是否真的包含最新提交 - 之前 MiniMax existing channel 没自动补 `model_pricing`,最终确认根因就是在线 CRM 进程早于修复提交 `ca1d448` + - 如果看到“有 `model_mapping` 但 `model_pricing=[]`”,不要立刻判定 current-code 仍未执行 `UpdateChannel`;先核对该 artifact 是否本来就是旧进程产物 + +### MiniMax `model_pricing=[]` 误判的已确认根因时间线 + +1. 旧进程先创建了半成品 channel + - 证据:`artifacts/real-host-acceptance/20260520_222713_crm18100_live_model_mapping_validation/summary.json` + - 其中 MiniMax `host_channel.data.id=5` + - `model_mapping` 已有值,但 `model_pricing=[]` + - 且 `created_at=updated_at=2026-05-20T20:39:23Z` + - 这说明当时只是旧逻辑创建了 channel,没有发生后续 `UpdateChannel` 纠偏 + +2. 新代码已经具备纠偏能力,但必须由新进程实际执行 + - `ca1d448` 之后,代码路径已改为: + - 新建 channel 时直接携带完整 `model_pricing` + - 命中既有 channel 时执行 `UpdateChannel` + - 所以判断“修复是否生效”时,不能只看仓库 HEAD,必须看 18100 监听进程的真实启动时间与实际 DB + +3. 当前 18100 新进程已在 live host 上完成纠偏 + - 18100 新进程启动时间:`2026-05-21 01:08` + - 当前真实 DB:`/tmp/sub2api-relay-manager-realhost-18100.db` + - 当前 host admin 直查 `GET /api/v1/admin/channels/5` 可见: + - `model_pricing` 非空 + - `model_mapping` 仍正确 + - `updated_at=2026-05-21T06:45:00Z` + - 这证明新进程已经真正执行过 `UpdateChannel`,MiniMax 既有 channel 已被纠偏 + +4. 最终结论 + - “MiniMax channel 有 mapping 但无 pricing”不是 current-code 仍缺失 `UpdateChannel` + - 真相是:旧 artifact 反映的是旧 CRM 进程产物;切到新进程并 fresh rerun 后,该问题已被 live 修复 4. `PACK_PATH` 使用了 operator 机器的概念路径,而不是 CRM 进程本机可读路径 - 当 CRM 改在本机运行时,继续传远端 `/home/ubuntu/...` 会直接触发 `stat pack path ... no such file or directory` @@ -161,6 +230,310 @@ - 目标 Postgres 容器 - 目标 Redis 容器 +## 凭据与可用性判断矩阵 + +先记住:本项目里最容易混淆的不是 API 本身,而是“看起来都像 key,但其实职责完全不同”的几类凭据。 + +| 凭据/身份 | 属于谁 | 主要用途 | 正确验证方式 | 不能直接证明什么 | 最常见误判 | +|---|---|---|---|---|---| +| 供应链 key / 上游 key | 供应商账号 / 中转账号 | 写入 host account `credentials.api_key`,供宿主向上游 provider 发请求 | 1. account 创建成功 2. `POST /api/v1/admin/accounts/:id/test` 成功 3. `GET /api/v1/admin/accounts/:id/models` 返回目标模型 | 不能直接证明普通用户一定能看到模型,也不能直接证明 `/v1/chat/completions` 一定可用 | 把 account `/test` 成功误说成“普通用户已可用” | +| 普通用户 key | 宿主普通用户 | 走宿主网关访问 `/v1/models`、`/v1/chat/completions` | 1. key/group 绑定正确 2. `GET /v1/models` 返回目标模型 3. 推荐继续测 `/v1/chat/completions` | 不能直接证明供应链 account 本身健康 | 把普通用户 403 直接归因为供应链 key 无效 | +| subscription 原始外部 `ACCESS_API_KEY` | 调用方传入的外部 probe key | subscription 请求输入,可能只用于触发流程,不一定是最终探测 key | 不能单独用它判断最终 gateway closure;必须先确认是否被 managed key 覆盖 | 不能直接代表最终 subscription 场景的普通用户访问结果 | 拿它直打 `/v1/models` 收到 403,就误判 CRM 主链路失败 | +| managed key (`sk-relay-*`) | CRM 在宿主侧创建/查找的托管普通用户 key | subscription 场景最终 gateway probe / managed 普通用户访问 | 1. managed user / key 已创建 2. group/subscription 已绑定 3. `GET /v1/models` 返回目标模型 4. 推荐继续测 `/v1/chat/completions` | 不能直接证明上游 provider 一定有 quota 或 host completion 一定兼容 | 把它和外部原始 `ACCESS_API_KEY` 混为一谈 | +| host admin token / bearer | 宿主管理员 | 创建 group/channel/plan/account、分配 subscription、读取 admin API | 看 admin API 是否能成功执行管理动作 | 不能直接证明普通用户访问已可用 | 以为“管理接口全成功 = 普通用户链路也成功” | + +### 一眼区分规则 + +1. 供应链 key + - 验证的是“上游供应商 account 是否健康” + - 不直接验证普通用户访问 +2. 普通用户 key + - 验证的是“宿主网关路径是否对普通用户开放” + - 不直接验证上游供应链是否健康 +3. subscription 原始外部 key + - 只是一种流程输入 + - 不一定等于最终探测 key +4. managed key + - 才是 subscription 场景里更接近“最终真实普通用户访问”的 key +5. admin token + - 只证明管理面可用 + - 不证明用户面可用 + +### 两套最小判断口径 + +#### 口径 A:供应链账号是否成功 +看: +1. account 是否创建成功 +2. `/api/v1/admin/accounts/:id/test` 是否成功 +3. `/api/v1/admin/accounts/:id/models` 是否返回目标模型 + +#### 口径 B:普通用户是否真的可用 +看: +1. key/group/subscription/balance 前置是否到位 +2. `/v1/models` 是否返回目标模型 +3. `/v1/chat/completions` 是否成功 + +### 明确禁止的混用 + +- ❌ 用 account `/test` 成功替代普通用户 `/v1/models` +- ❌ 用 `/v1/models` 成功替代 `/v1/chat/completions` +- ❌ 用外部原始 `ACCESS_API_KEY` 替代 subscription managed key +- ❌ 用 admin API 成功替代普通用户链路成功 +- ❌ 看到普通用户 403 就直接判定供应链 key 不可用 + +## FAQ:新增模型 / 新增供应链账号 / 普通用户访问 + +### 1. “新增模型参数”到底指什么? + +这里至少分四层,不能混成一句“模型加上了”: + +1. pack/provider 定义层 + - `base_url` + - `default_models` + - `smoke_test_model` + - `channel_template.model_mapping` +2. host 落库层 + - account `credentials.model_mapping` + - channel `model_mapping` + - channel `model_pricing` + - `restrict_models` + - `billing_model_source` +3. 模型暴露层 + - `GET /api/v1/admin/accounts/:id/models` + - `GET /v1/models` +4. completion 层 + - `POST /v1/chat/completions` + +经验结论: +- 前三层正确,不等于第四层一定正确 +- 当前项目最新真相就是:模型暴露层大体已经打通,但 completion 层仍可能受 host 兼容性或上游 quota 影响 + +### 2. “新增供应链账号成功”到底以什么为准? + +建议区分三档成功标准: + +1. 窄口径成功(只看供应链 account) + - account 创建成功 + - `POST /api/v1/admin/accounts/:id/test` 成功 + - `GET /api/v1/admin/accounts/:id/models` 返回目标模型 +2. 完整接入成功(看普通用户是否能看到模型) + - 上述 account 条件成立 + - 普通用户或 managed key 的 `GET /v1/models` 返回目标模型 +3. 业务可用成功(看真实调用) + - 上述条件都成立 + - `POST /v1/chat/completions` 成功 + +经验结论: +- 如果你只说“供应链账号成功”,默认最多只能代表前两档 +- 如果要说“模型完全可用”,必须把 completion smoke 也过掉 + +### 3. 新增供应链账号时,系统会不会自动补 group / channel / plan? + +会,但要分资源类型看: + +1. group + - 一定会先 ensure + - 不存在就创建,存在就复用 +2. channel + - 一定会先 ensure + - 不存在就创建,存在就 `UpdateChannel` 纠偏 +3. plan + - 只在 `subscription` 模式下需要 + - 不存在就创建,存在就复用 +4. account + - 最后创建,并绑定到目标 `group_ids` + +经验结论: +- 新增供应链账号不是“只加 account” +- 它本质上是“确保资源面完整后再挂 account” + +### 4. 新增模型时,哪些字段必须同时对齐? + +至少要对齐: +- provider `base_url` +- `default_models` +- `smoke_test_model` +- `channel_template.model_mapping` +- account `credentials.model_mapping` +- channel `model_mapping` +- channel `model_pricing` +- `restrict_models=true` +- `billing_model_source=channel_mapped` + +经验结论: +- 少了 `model_mapping`,模型列表可能回退到默认集合 +- 少了 `model_pricing`,`/v1/models` 可能看起来没问题,但实际聊天流量可能仍失败 +- 只修 account 不修 channel,或者只修 channel 不修 account,都会留下半通不通的假阳性 + +### 5. 如果是“中转 URL / relay URL”,而且不在宿主官方已知库里,标准会不会不一样? + +本项目的标准本质不变,但前提是: +- 这个 provider 必须先在本项目的 pack/provider manifest 中被正确定义 + +也就是说: +- “不在宿主官方库里”没关系 +- “没有在本项目 pack 中定义”才不行 + +只要 manifest 正确提供了: +- `base_url` +- `default_models` +- `smoke_test_model` +- `channel_template.model_mapping` + +系统就仍然可以自动做: +- account 创建 +- account `/test` +- account `/models` +- 普通用户 `/v1/models` +- completion smoke(如果你把这一步也纳入验收) + +经验结论: +- 系统不是“任意 URL 自动猜测器” +- 系统是“pack/provider 驱动的导入与验证器” + +### 6. 只要 `/v1/models` 成功,是不是就说明新模型已经完全可用了? + +不是。 + +`/v1/models` 成功,只能证明: +- 普通用户或 managed key 访问路径至少已经看到了模型列表 + +它不能自动证明: +- provider 上游有 quota +- host 对该 provider 的 completion 兼容性没问题 +- `POST /v1/chat/completions` 一定能成功 + +经验结论: +- `/v1/models = 200` 是“模型暴露通过” +- `/v1/chat/completions = 200` 才更接近“模型可用通过” + +### 7. 新增供应链账号后,普通用户 key 的 group 信息要不要更新? + +通常要,取决于 access mode。 + +#### self_service +- 普通用户 key 必须绑定目标标准 group +- 如果新模型落在新的 group,而 key 没绑定过去,普通用户就看不到或用不到它 +- 若目标 group 是标准计费组,通常还需要余额 + +#### subscription +- 目标 group 必须是 `subscription` 类型 +- 普通用户必须完成 subscription 分配 +- 普通用户 key 必须绑定该 group +- 当前 closure 最终优先使用宿主 managed key,而不是外部原始 `access_api_key` + +经验结论: +- “新增了供应链模型”不等于“所有普通用户 key 自动获得访问权” +- 最终是否能访问,取决于 key/group/subscription 这条链是否同步完成 + +### 8. 新增供应链账号后,如果普通用户看不到模型,优先查哪里? + +建议按这个顺序查: + +1. account 视角 + - `GET /api/v1/admin/accounts/:id` + - `GET /api/v1/admin/accounts/:id/models` +2. channel 视角 + - `GET /api/v1/admin/channels/:id` + - 看 `model_mapping/model_pricing/restrict_models/billing_model_source` +3. 普通用户视角 + - `GET /v1/models` +4. completion 视角 + - `POST /v1/chat/completions` +5. 环境与运行时 + - CRM 是否是最新提交 + - `PACK_PATH` 是否正确 + - `CRM_HOST_BASE` 是否正确 + +经验结论: +- 不要一上来就看普通用户 403/502 +- 先查 account 和 channel 落库,更容易快速定位根因 + +### 9. 什么时候应该判定是“运营前置没做”,而不是“导入代码失败”? + +常见场景: + +1. `self_service` + - key 没绑 group + - 用户没余额 +2. `subscription` + - group 不是 subscription 类型 + - user subscription 没写入 + - key 没绑 group +3. probe key 用错 + - subscription 场景拿外部原始 key 去打 `/v1/models` +4. 脚本参数错 + - `PACK_PATH` 错 + - 命中错的 Postgres/Redis 容器 + - probe auth 用错 + +经验结论: +- 如果 account `/models` 已对、channel 落库也对,但普通用户流量不对,优先怀疑运营前置或 harness 参数 +- 不要立即重开“导入代码失效”的结论 + +### 10. 新增供应链账号或模型后,哪些结果可以算“已确认”,哪些只能算“部分确认”? + +#### 可算“已确认” +- account 已创建 +- account `/test` 成功 +- account `/models` 返回目标模型 +- channel 落库包含完整 routing/pricing 字段 +- 普通用户 `/v1/models` 返回目标模型 + +#### 只能算“部分确认” +- 只有 import API 返回成功 +- 只有 batch status 成功 +- 只有 account 创建成功但还没测 `/models` +- 只有 `/v1/models` 成功但没测 `/v1/chat/completions` + +经验结论: +- “新增模型成功”这句话必须说明你指的是哪一层成功 +- 最容易误导人的说法,就是把“导入成功”直接说成“模型可用成功” + +### 11. 如果新增供应链账号是复用旧 channel,而不是新建 channel,需要特别注意什么? + +特别注意两件事: + +1. 旧 channel 不能只复用名字 + - 必须做配置纠偏 + - 至少要补齐 `model_mapping + model_pricing + restrict_models + billing_model_source` +2. 不能默认“有 model_mapping 就够了” + - 这正是之前 MiniMax live 问题踩过的坑 + +经验结论: +- 旧 channel 复用比新建更危险 +- 因为它最容易留下“看起来有模型,实际上定价/路由没补齐”的半漂移状态 + +### 12. 如果我要把“新增模型/新增供应链账号”做成标准验收 checklist,最小应包含哪些项? + +最小 checklist: + +1. provider manifest 已更新 + - `base_url` + - `default_models` + - `smoke_test_model` + - `channel_template.model_mapping` +2. import 成功 + - group/channel/(subscription: plan)/accounts 已生成或被正确复用 +3. account 验证成功 + - `/test` + - `/models` +4. channel 回读成功 + - `model_mapping` + - `model_pricing` + - `restrict_models` + - `billing_model_source` +5. 普通用户路径验证成功 + - `/v1/models` +6. 业务路径验证成功(推荐) + - `/v1/chat/completions` +7. 若失败,明确归类 + - provider definition drift + - host compatibility + - upstream quota/key 问题 + - key/group/subscription/balance 前置问题 + - harness 参数问题 + ## 相关证据入口 - 当前执行真相:`docs/EXECUTION_BOARD.md` diff --git a/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md b/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md index 364ffbcc..d3c57313 100644 --- a/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md +++ b/docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md @@ -8,7 +8,10 @@ - 当前 gate、最新阻断、最新 live 真相以它为准。 2. `docs/PRODUCTION_CLOSURE_BOARD.md` - 看是否已经达到可上线口径,以及哪些只是历史 PASS。 -3. `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md` +3. `docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md` + - 每次 real-host 验收先走这一页。 + - 适合快速确认红线、三层证据和最短诊断顺序。 +4. `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md` - 看已经调通的细节、典型误判点、推荐诊断顺序。 ## 目标 @@ -39,6 +42,17 @@ ## 推荐执行方式 +### 0. 先跑脚本回归自检(避免把 harness 漂移带进真实宿主结论) + +```bash +cd /path/to/sub2api-cn-relay-manager +bash ./scripts/test_real_host_scripts.sh +``` + +说明: +- 当前推荐显式用 `bash` 调起,确保在不同机器上不会因为脚本执行位差异把 harness 回归误报成逻辑失败。 +- 只有这一步通过后,再继续真实宿主验收。 + ### 1. 构建本地容器镜像(适用于代理/离线开发机) ```bash