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:
@@ -269,4 +269,91 @@ Encoder SHALL 维护状态:
|
||||
#### Scenario: /rerank 接口
|
||||
|
||||
- **WHEN** 解码/编码 rerank 请求和响应
|
||||
- **THEN** SHALL 使用 CanonicalRerankRequest/Response 做字段映射
|
||||
- **THEN** SHALL 使用 CanonicalRerankRequest/Response 做字段映射
|
||||
### Requirement: 模型详情路径识别
|
||||
|
||||
OpenAI 适配器 SHALL 正确识别包含统一模型 ID 的模型详情路径。
|
||||
|
||||
#### Scenario: 含斜杠的统一模型 ID 路径
|
||||
|
||||
- **WHEN** 路径为 `/v1/models/openai/gpt-4`
|
||||
- **THEN** `DetectInterfaceType` SHALL 返回 `InterfaceTypeModelInfo`
|
||||
|
||||
#### Scenario: 含多段斜杠的统一模型 ID 路径
|
||||
|
||||
- **WHEN** 路径为 `/v1/models/azure/accounts/org-123/models/gpt-4`
|
||||
- **THEN** `DetectInterfaceType` SHALL 返回 `InterfaceTypeModelInfo`
|
||||
|
||||
#### Scenario: 模型列表路径不受影响
|
||||
|
||||
- **WHEN** 路径为 `/v1/models`
|
||||
- **THEN** `DetectInterfaceType` SHALL 返回 `InterfaceTypeModels`
|
||||
|
||||
### Requirement: 提取统一模型 ID
|
||||
|
||||
OpenAI 适配器 SHALL 从路径中提取统一模型 ID。
|
||||
|
||||
#### Scenario: 标准路径提取
|
||||
|
||||
- **WHEN** 调用 `ExtractUnifiedModelID("/v1/models/openai/gpt-4")`
|
||||
- **THEN** SHALL 返回 `"openai/gpt-4"`
|
||||
|
||||
#### Scenario: 复杂路径提取
|
||||
|
||||
- **WHEN** 调用 `ExtractUnifiedModelID("/v1/models/azure/accounts/org/models/gpt-4")`
|
||||
- **THEN** SHALL 返回 `"azure/accounts/org/models/gpt-4"`
|
||||
|
||||
#### Scenario: 非模型详情路径
|
||||
|
||||
- **WHEN** 调用 `ExtractUnifiedModelID("/v1/models")`
|
||||
- **THEN** SHALL 返回错误
|
||||
|
||||
### Requirement: 从请求体提取 model
|
||||
|
||||
OpenAI 适配器 SHALL 按 InterfaceType 从请求体中提取 model 值。
|
||||
|
||||
#### Scenario: Chat 请求提取 model
|
||||
|
||||
- **WHEN** 调用 `ExtractModelName(body, InterfaceTypeChat)`,body 为 `{"model":"openai/gpt-4","messages":[...]}`
|
||||
- **THEN** SHALL 返回 `"openai/gpt-4"`
|
||||
|
||||
#### Scenario: Embedding 请求提取 model
|
||||
|
||||
- **WHEN** 调用 `ExtractModelName(body, InterfaceTypeEmbeddings)`,body 为 `{"model":"openai/text-embedding-3","input":"text"}`
|
||||
- **THEN** SHALL 返回 `"openai/text-embedding-3"`
|
||||
|
||||
#### Scenario: 无 model 字段
|
||||
|
||||
- **WHEN** 调用 `ExtractModelName(body, ifaceType)`,body 中不含 model 字段
|
||||
- **THEN** SHALL 返回错误
|
||||
|
||||
### Requirement: 最小化改写请求体 model 字段
|
||||
|
||||
OpenAI 适配器 SHALL 按 InterfaceType 用 `json.RawMessage` 最小化改写请求体中的 model 字段,其余字段保持原始 bytes。
|
||||
|
||||
#### Scenario: 请求体 model 改写(统一 ID → 上游名)
|
||||
|
||||
- **WHEN** 调用 `RewriteRequestModelName(body, "gpt-4", InterfaceTypeChat)`,body 为 `{"model":"openai/gpt-4","messages":[...],"some_param":"value"}`
|
||||
- **THEN** SHALL 返回 `{"model":"gpt-4","messages":[...],"some_param":"value"}`
|
||||
- **THEN** 除 model 外的字段 SHALL 保持原始 bytes 不变
|
||||
|
||||
#### Scenario: 不同 InterfaceType 的请求改写
|
||||
|
||||
- **WHEN** 调用 `RewriteRequestModelName(body, "gpt-4", InterfaceTypeEmbeddings)`
|
||||
- **THEN** SHALL 按 Embedding 接口的请求体 model 字段位置进行改写
|
||||
- **WHEN** 调用 `RewriteRequestModelName(body, "gpt-4", InterfaceTypeRerank)`
|
||||
- **THEN** SHALL 按 Rerank 接口的请求体 model 字段位置进行改写
|
||||
|
||||
### Requirement: 最小化改写响应体 model 字段
|
||||
|
||||
OpenAI 适配器 SHALL 按 InterfaceType 用 `json.RawMessage` 最小化改写响应体中的 model 字段,其余字段保持原始 bytes。请求体和响应体的 model 字段位置可能不同,各自独立实现。
|
||||
|
||||
#### Scenario: 响应体 model 改写(上游名 → 统一 ID)
|
||||
|
||||
- **WHEN** 调用 `RewriteResponseModelName(body, "openai/gpt-4", InterfaceTypeChat)`,body 为上游 Chat 响应
|
||||
- **THEN** SHALL 将 model 字段替换为 `"openai/gpt-4"`,其余字段原样保留
|
||||
|
||||
#### Scenario: 不同 InterfaceType 的响应改写
|
||||
|
||||
- **WHEN** 调用 `RewriteResponseModelName(body, "openai/gpt-4", InterfaceTypeEmbeddings)`
|
||||
- **THEN** SHALL 按 Embedding 接口的响应体 model 字段位置进行改写
|
||||
|
||||
Reference in New Issue
Block a user