# Model Management

## Purpose

管理模型的增删改查，通过 handler → service → repository 分层实现业务逻辑和数据访问，支持供应商关联验证。

### Requirement: 前端适配统一模型 ID 显示

前端 SHALL 在模型管理界面显示统一模型 ID。

#### Scenario: 模型列表返回统一 ID

- **WHEN** 向 `/api/models` 发送 GET 请求
- **THEN** 每个模型 SHALL 包含 unified_id 字段
- **THEN** unified_id 格式 SHALL 为 `{provider_id}/{model_name}`

#### Scenario: 创建模型返回统一 ID

- **WHEN** 向 `/api/models` 发送 POST 请求创建模型
- **THEN** 返回的模型 SHALL 包含 unified_id 字段
- **THEN** unified_id SHALL 由后端根据 provider_id 和 model_name 生成

#### Scenario: 更新模型返回统一 ID

- **WHEN** 向 `/api/models/:id` 发送 PUT 请求更新模型
- **THEN** 返回的模型 SHALL 包含更新后的 unified_id
- **THEN** unified_id SHALL 反映最新的 provider_id 和 model_name 组合

### Requirement: 创建模型配置

网关 SHALL 允许为供应商创建新的模型配置。

#### Scenario: 使用有效数据创建模型

- **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}`
- **THEN** 前端 SHALL NOT 在请求体中发送 id 字段

#### Scenario: 使用不存在的供应商创建模型

- **WHEN** 向 `/api/models` 发送 POST 请求，携带不存在的 provider_id
- **THEN** 网关 SHALL 返回错误，状态码为 400 (Bad Request)
- **THEN** 错误 SHALL 指示供应商不存在

#### Scenario: 创建重复模型

- **WHEN** 向 `/api/models` 发送 POST 请求，携带已存在的 provider_id + model_name 组合
- **THEN** 网关 SHALL 返回错误，状态码为 409 (Conflict)
- **THEN** 错误 SHALL 指示同一供应商下模型名称已存在

### Requirement: 列出所有模型

网关 SHALL 允许获取所有模型配置。

#### Scenario: 成功列出模型

- **WHEN** 向 `/api/models` 发送 GET 请求
- **THEN** 网关 SHALL 返回所有模型的列表
- **THEN** 每个模型 SHALL 包含 id, provider_id, model_name, unified_id, enabled, created_at

### Requirement: 按供应商列出模型

网关 SHALL 允许获取特定供应商的模型。

#### Scenario: 列出存在供应商的模型

- **WHEN** 向 `/api/models?provider_id=<provider_id>` 发送 GET 请求
- **THEN** 网关 SHALL 返回指定供应商的模型列表
- **THEN** 每个模型 SHALL 包含 unified_id 字段

### Requirement: 更新模型配置

网关 SHALL 允许更新现有模型配置。

#### Scenario: 使用有效数据更新模型

- **WHEN** 向 `/api/models/:id` 发送 PUT 请求，携带有效的模型数据
- **THEN** 网关 SHALL 更新数据库中的模型记录
- **THEN** 网关 SHALL 返回更新后的模型
- **THEN** 返回的模型 SHALL 包含更新后的 unified_id

#### Scenario: 更新模型为重复组合

- **WHEN** 向 `/api/models/:id` 发送 PUT 请求，更新 provider_id 或 model_name 导致与已有记录重复
- **THEN** 网关 SHALL 返回错误，状态码为 409 (Conflict)

### Requirement: 删除模型配置

网关 SHALL 允许删除模型配置。

#### Scenario: 删除存在的模型

- **WHEN** 向 `/api/models/:id` 发送 DELETE 请求，携带有效的模型 ID
- **THEN** 网关 SHALL 删除模型记录
- **THEN** 网关 SHALL 返回状态码 204 (No Content)

### Requirement: 使用 service 层处理业务逻辑

Handler SHALL 通过 ModelService 处理业务逻辑。

#### Scenario: 调用 service 方法

- **WHEN** handler 收到请求
- **THEN** SHALL 调用对应的 ModelService 方法（Create、Get、List、Update、Delete）
- **THEN** SHALL 使用 domain.Model 类型
- **THEN** Create 时 SHALL 调用 `uuid.New()` 生成 id

#### Scenario: 供应商验证和唯一性校验

- **WHEN** 创建或更新模型
- **THEN** SHALL 在 service 层验证供应商存在
- **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: 联合查询

- **WHEN** service 需要按 provider 和 model_name 查询模型
- **THEN** SHALL 调用 `FindByProviderAndModelName(providerID, modelName)` 方法

#### Scenario: 查询所有启用模型

- **WHEN** proxy handler 需要聚合模型列表
- **THEN** SHALL 调用 `ListEnabled()` 方法，返回所有 enabled 的模型（关联 enabled 的供应商）
