1
0
Files
nex/openspec/specs/provider-management/spec.md
lanyuanxiaoyao 395887667d feat: 实现统一模型 ID 机制
实现统一模型 ID 格式 (provider_id/model_name),支持跨协议模型标识和 Smart Passthrough。

核心变更:
- 新增 pkg/modelid 包:解析、格式化、校验统一模型 ID
- 数据库迁移:models 表使用 UUID 主键 + UNIQUE(provider_id, model_name) 约束
- Repository 层:FindByProviderAndModelName、ListEnabled 方法
- Service 层:联合唯一校验、provider ID 字符集校验
- Conversion 层:ExtractModelName、RewriteRequestModelName/RewriteResponseModelName 方法
- Handler 层:统一模型 ID 路由、Smart Passthrough、Models API 本地聚合
- 新增 error-responses、unified-model-id 规范

测试覆盖:
- 单元测试:modelid、conversion、handler、service、repository
- 集成测试:统一模型 ID 路由、Smart Passthrough 保真性、跨协议转换
- 迁移测试:UUID 主键、UNIQUE 约束、级联删除

OpenSpec:
- 归档 unified-model-id 变更到 archive/2026-04-21-unified-model-id
- 同步 11 个 delta specs 到 main specs
- 新增 error-responses、unified-model-id 规范文件
2026-04-21 18:14:10 +08:00

159 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Provider Management
## Purpose
管理 AI 供应商的完整生命周期包括创建、查询、更新、删除供应商配置通过分层架构Handler -> Service -> Repository处理业务逻辑和数据访问。
## Requirements
### Requirement: 创建供应商配置
网关 SHALL 允许通过管理 API 创建新的供应商配置。
#### Scenario: 使用有效数据创建供应商
- **WHEN** 向 `/api/providers` 发送 POST 请求携带有效的供应商数据id, name, api_key, base_url, protocol
- **THEN** 网关 SHALL 在数据库中创建新的供应商记录
- **THEN** 网关 SHALL 返回创建的供应商,状态码为 201
- **THEN** 供应商 SHALL 默认启用
- **THEN** protocol 字段 SHALL 默认为 "openai"
#### Scenario: 使用重复 ID 创建供应商
- **WHEN** 向 `/api/providers` 发送 POST 请求,携带已存在的 ID
- **THEN** 网关 SHALL 返回错误,状态码为 409 (Conflict)
#### Scenario: 创建供应商时缺少必需字段
- **WHEN** 向 `/api/providers` 发送 POST 请求缺少必需字段id, name, api_key 或 base_url
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
- **THEN** 错误 SHALL 指示缺少哪些字段
#### Scenario: 创建供应商时 ID 包含非法字符
- **WHEN** 向 `/api/providers` 发送 POST 请求id 包含非 `[a-zA-Z0-9_]` 字符
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
- **THEN** 错误 SHALL 指示 id 仅允许字母、数字、下划线
#### Scenario: 创建供应商时 ID 过长
- **WHEN** 向 `/api/providers` 发送 POST 请求id 长度超过 64
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
### Requirement: 供应商 ID 不允许修改
供应商 ID 是主键,用于构建统一模型 ID不允许修改。
#### Scenario: 尝试修改供应商 ID
- **WHEN** 向 `/api/providers/:id` 发送 PUT 请求,请求体中包含 `id` 字段
- **THEN** ProviderService.Update SHALL 在 service 层校验并返回错误
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
- **THEN** 错误 SHALL 指示供应商 ID 不允许修改
- **THEN** 错误格式 SHALL 为:
```json
{
"error": "供应商 ID 不允许修改",
"code": "IMMUTABLE_FIELD"
}
```
**校验位置:** Service 层(`ProviderService.Update`
- Service 层校验保证所有调用方handler、CLI、未来 API统一遵守
- Handler 层负责捕获 `ErrImmutableField` 并转换为 HTTP 400 响应
**原因:**
- 供应商 ID 是主键,用于构建统一模型 IDprovider_id/model_name
- 修改 ID 会导致所有统一模型 ID 失效
- 客户端缓存的模型 ID 全部失效
- 如需修改,应创建新供应商并迁移模型
### Requirement: 列出所有供应商
网关 SHALL 允许获取所有供应商配置。
#### Scenario: 成功列出供应商
- **WHEN** 向 `/api/providers` 发送 GET 请求
- **THEN** 网关 SHALL 返回所有供应商的列表
- **THEN** 每个供应商 SHALL 包含 id, name, api_key已掩码, base_url, protocol, enabled, created_at, updated_at
- **THEN** api_key SHALL 被掩码(仅显示最后 4 个字符)
**变更说明:** 数据访问从 config 包迁移到 ProviderRepository。API 接口保持不变。
### Requirement: 获取特定供应商
网关 SHALL 允许通过 ID 获取特定供应商。
#### Scenario: 获取存在的供应商
- **WHEN** 向 `/api/providers/:id` 发送 GET 请求,携带有效的供应商 ID
- **THEN** 网关 SHALL 返回供应商详情
- **THEN** SHALL 包含 protocol 字段
- **THEN** api_key SHALL 被掩码
#### Scenario: 获取不存在的供应商
- **WHEN** 向 `/api/providers/:id` 发送 GET 请求,携带不存在的 ID
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
**变更说明:** 通过 ProviderService 和 ProviderRepository 实现。API 接口保持不变。
### Requirement: 更新供应商配置
网关 SHALL 允许更新现有供应商配置。
#### Scenario: 使用有效数据更新供应商
- **WHEN** 向 `/api/providers/:id` 发送 PUT 请求,携带有效的供应商数据
- **THEN** 网关 SHALL 更新数据库中的供应商记录
- **THEN** 网关 SHALL 返回更新后的供应商
- **THEN** 更新 SHALL 支持修改 protocol 字段
**变更说明:** 通过 ProviderService 和 ProviderRepository 实现。API 接口保持不变。
### Requirement: 删除供应商配置
网关 SHALL 允许删除供应商配置。
#### Scenario: 删除存在的供应商
- **WHEN** 向 `/api/providers/:id` 发送 DELETE 请求,携带有效的供应商 ID
- **THEN** 网关 SHALL 删除供应商记录
- **THEN** 网关 SHALL 删除所有关联的模型CASCADE
- **THEN** 网关 SHALL 返回状态码 204 (No Content)
**变更说明:** 通过 ProviderService 和 ProviderRepository 实现。API 接口保持不变。
### Requirement: 使用 service 层处理业务逻辑
Handler SHALL 通过 ProviderService 处理业务逻辑。
#### Scenario: 调用 service 方法
- **WHEN** handler 收到请求
- **THEN** SHALL 调用对应的 ProviderService 方法Create、Get、List、Update、Delete
- **THEN** SHALL 使用 domain.Provider 类型
#### Scenario: 错误处理
- **WHEN** service 返回错误
- **THEN** SHALL 转换为 HTTP 错误响应
- **THEN** SHALL 使用结构化错误处理
### Requirement: 使用 repository 层访问数据
Service SHALL 通过 ProviderRepository 访问数据。
#### Scenario: 调用 repository 方法
- **WHEN** service 处理业务逻辑
- **THEN** SHALL 调用对应的 ProviderRepository 方法
- **THEN** SHALL 使用 domain.Provider 类型
#### Scenario: 数据验证
- **WHEN** 创建或更新供应商
- **THEN** SHALL 在 service 层验证业务规则
- **THEN** SHALL 在 repository 层执行数据库操作