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 规范文件
This commit is contained in:
@@ -10,10 +10,12 @@
|
||||
|
||||
#### Scenario: 使用有效数据创建模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带有效的模型数据(id, provider_id, model_name)
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带有效的模型数据(provider_id, model_name),不提供 id 字段
|
||||
- **THEN** 网关 SHALL 自动生成 UUID 作为模型 id
|
||||
- **THEN** 网关 SHALL 在数据库中创建新的模型记录
|
||||
- **THEN** 网关 SHALL 返回创建的模型,状态码为 201
|
||||
- **THEN** 模型 SHALL 默认启用
|
||||
- **THEN** 返回的模型 SHALL 包含 `unified_id` 字段,值为 `{provider_id}/{model_name}`
|
||||
|
||||
#### Scenario: 使用不存在的供应商创建模型
|
||||
|
||||
@@ -21,7 +23,11 @@
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
|
||||
- **THEN** 错误 SHALL 指示供应商不存在
|
||||
|
||||
**变更说明:** handler 通过 ModelService 调用,数据访问通过 ModelRepository 和 ProviderRepository。API 接口保持不变。
|
||||
#### Scenario: 创建重复模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带已存在的 provider_id + model_name 组合
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 409 (Conflict)
|
||||
- **THEN** 错误 SHALL 指示同一供应商下模型名称已存在
|
||||
|
||||
### Requirement: 列出所有模型
|
||||
|
||||
@@ -31,9 +37,7 @@
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回所有模型的列表
|
||||
- **THEN** 每个模型 SHALL 包含 id, provider_id, model_name, enabled, created_at
|
||||
|
||||
**变更说明:** 数据访问从 config 包迁移到 ModelRepository。API 接口保持不变。
|
||||
- **THEN** 每个模型 SHALL 包含 id, provider_id, model_name, unified_id, enabled, created_at
|
||||
|
||||
### Requirement: 按供应商列出模型
|
||||
|
||||
@@ -43,8 +47,7 @@
|
||||
|
||||
- **WHEN** 向 `/api/models?provider_id=<provider_id>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回指定供应商的模型列表
|
||||
|
||||
**变更说明:** 通过 ModelService 和 ModelRepository 实现。API 接口保持不变。
|
||||
- **THEN** 每个模型 SHALL 包含 unified_id 字段
|
||||
|
||||
### Requirement: 更新模型配置
|
||||
|
||||
@@ -55,14 +58,12 @@
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,携带有效的模型数据
|
||||
- **THEN** 网关 SHALL 更新数据库中的模型记录
|
||||
- **THEN** 网关 SHALL 返回更新后的模型
|
||||
- **THEN** 返回的模型 SHALL 包含更新后的 unified_id
|
||||
|
||||
#### Scenario: 更新模型供应商
|
||||
#### Scenario: 更新模型为重复组合
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,携带新的 provider_id
|
||||
- **THEN** 网关 SHALL 验证新供应商是否存在
|
||||
- **THEN** 网关 SHALL 更新模型的供应商关联
|
||||
|
||||
**变更说明:** 通过 ModelService、ModelRepository 和 ProviderRepository 实现。API 接口保持不变。
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,更新 provider_id 或 model_name 导致与已有记录重复
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 409 (Conflict)
|
||||
|
||||
### Requirement: 删除模型配置
|
||||
|
||||
@@ -74,8 +75,6 @@
|
||||
- **THEN** 网关 SHALL 删除模型记录
|
||||
- **THEN** 网关 SHALL 返回状态码 204 (No Content)
|
||||
|
||||
**变更说明:** 通过 ModelService 和 ModelRepository 实现。API 接口保持不变。
|
||||
|
||||
### Requirement: 使用 service 层处理业务逻辑
|
||||
|
||||
Handler SHALL 通过 ModelService 处理业务逻辑。
|
||||
@@ -85,25 +84,60 @@ Handler SHALL 通过 ModelService 处理业务逻辑。
|
||||
- **WHEN** handler 收到请求
|
||||
- **THEN** SHALL 调用对应的 ModelService 方法(Create、Get、List、Update、Delete)
|
||||
- **THEN** SHALL 使用 domain.Model 类型
|
||||
- **THEN** Create 时 SHALL 调用 `uuid.New()` 生成 id
|
||||
|
||||
#### Scenario: 供应商验证
|
||||
#### Scenario: 供应商验证和唯一性校验
|
||||
|
||||
- **WHEN** 创建或更新模型
|
||||
- **THEN** SHALL 在 service 层验证供应商存在
|
||||
- **THEN** SHALL 通过 ProviderRepository 查询供应商
|
||||
- **THEN** SHALL 在 service 层验证 provider_id + model_name 联合唯一
|
||||
|
||||
### Requirement: 联合唯一约束并发处理
|
||||
|
||||
创建或更新模型时,SHALL 使用应用层校验 + 数据库约束双重保险处理联合唯一约束。
|
||||
|
||||
#### Scenario: 应用层快速失败
|
||||
|
||||
- **WHEN** 创建或更新模型前
|
||||
- **THEN** SHALL 先检查 provider_id + model_name 是否已存在
|
||||
- **THEN** 如已存在,SHALL 返回 HTTP 409 Conflict
|
||||
- **THEN** SHALL 返回错误格式:
|
||||
```json
|
||||
{
|
||||
"error": "同一供应商下模型名称已存在",
|
||||
"code": "duplicate_model"
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: 数据库约束兜底
|
||||
|
||||
- **WHEN** 并发创建导致应用层校验通过但数据库写入失败
|
||||
- **THEN** SHALL 捕获数据库 UNIQUE 约束错误
|
||||
- **THEN** SHALL 转换为 HTTP 409 Conflict 错误返回
|
||||
- **THEN** SHALL 返回错误格式:
|
||||
```json
|
||||
{
|
||||
"error": "同一供应商下模型名称已存在",
|
||||
"code": "duplicate_model"
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: SQLite UNIQUE 约束错误检测
|
||||
|
||||
- **WHEN** 捕获数据库错误
|
||||
- **THEN** SHALL 检查错误信息是否包含 "UNIQUE constraint failed"
|
||||
- **THEN** 如匹配,SHALL 识别为联合唯一约束冲突
|
||||
|
||||
### Requirement: 使用 repository 层访问数据
|
||||
|
||||
Service SHALL 通过 ModelRepository 访问数据。
|
||||
|
||||
#### Scenario: 调用 repository 方法
|
||||
#### Scenario: 联合查询
|
||||
|
||||
- **WHEN** service 处理业务逻辑
|
||||
- **THEN** SHALL 调用对应的 ModelRepository 方法
|
||||
- **THEN** SHALL 使用 domain.Model 类型
|
||||
- **WHEN** service 需要按 provider 和 model_name 查询模型
|
||||
- **THEN** SHALL 调用 `FindByProviderAndModelName(providerID, modelName)` 方法
|
||||
|
||||
#### Scenario: 数据验证
|
||||
#### Scenario: 查询所有启用模型
|
||||
|
||||
- **WHEN** 创建或更新模型
|
||||
- **THEN** SHALL 在 service 层验证业务规则
|
||||
- **THEN** SHALL 在 repository 层执行数据库操作
|
||||
- **WHEN** proxy handler 需要聚合模型列表
|
||||
- **THEN** SHALL 调用 `ListEnabled()` 方法,返回所有 enabled 的模型(关联 enabled 的供应商)
|
||||
|
||||
Reference in New Issue
Block a user