实现统一模型 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 规范文件
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package service
|
||
|
||
import (
|
||
"github.com/google/uuid"
|
||
appErrors "nex/backend/pkg/errors"
|
||
|
||
"nex/backend/internal/domain"
|
||
"nex/backend/internal/repository"
|
||
)
|
||
|
||
type modelService struct {
|
||
modelRepo repository.ModelRepository
|
||
providerRepo repository.ProviderRepository
|
||
}
|
||
|
||
func NewModelService(modelRepo repository.ModelRepository, providerRepo repository.ProviderRepository) ModelService {
|
||
return &modelService{modelRepo: modelRepo, providerRepo: providerRepo}
|
||
}
|
||
|
||
func (s *modelService) Create(model *domain.Model) error {
|
||
// 校验供应商存在
|
||
if _, err := s.providerRepo.GetByID(model.ProviderID); err != nil {
|
||
return appErrors.ErrProviderNotFound
|
||
}
|
||
|
||
// 联合唯一校验:同一供应商下 model_name 不重复
|
||
if err := s.checkDuplicateModelName(model.ProviderID, model.ModelName, ""); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 自动生成 UUID 作为 id
|
||
model.ID = uuid.New().String()
|
||
model.Enabled = true
|
||
return s.modelRepo.Create(model)
|
||
}
|
||
|
||
func (s *modelService) Get(id string) (*domain.Model, error) {
|
||
return s.modelRepo.GetByID(id)
|
||
}
|
||
|
||
func (s *modelService) List(providerID string) ([]domain.Model, error) {
|
||
return s.modelRepo.List(providerID)
|
||
}
|
||
|
||
func (s *modelService) ListEnabled() ([]domain.Model, error) {
|
||
return s.modelRepo.ListEnabled()
|
||
}
|
||
|
||
func (s *modelService) Update(id string, updates map[string]interface{}) error {
|
||
// 获取当前模型
|
||
current, err := s.modelRepo.GetByID(id)
|
||
if err != nil {
|
||
return appErrors.ErrModelNotFound
|
||
}
|
||
|
||
// 如果更新 provider_id,校验新供应商存在
|
||
if providerID, ok := updates["provider_id"].(string); ok {
|
||
if _, err := s.providerRepo.GetByID(providerID); err != nil {
|
||
return appErrors.ErrProviderNotFound
|
||
}
|
||
}
|
||
|
||
// 确定更新后的 provider_id 和 model_name
|
||
newProviderID := current.ProviderID
|
||
if v, ok := updates["provider_id"].(string); ok {
|
||
newProviderID = v
|
||
}
|
||
newModelName := current.ModelName
|
||
if v, ok := updates["model_name"].(string); ok {
|
||
newModelName = v
|
||
}
|
||
|
||
// 如果 provider_id 或 model_name 发生变化,校验联合唯一
|
||
if newProviderID != current.ProviderID || newModelName != current.ModelName {
|
||
if err := s.checkDuplicateModelName(newProviderID, newModelName, id); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return s.modelRepo.Update(id, updates)
|
||
}
|
||
|
||
func (s *modelService) Delete(id string) error {
|
||
return s.modelRepo.Delete(id)
|
||
}
|
||
|
||
// checkDuplicateModelName 校验同一供应商下 model_name 是否重复
|
||
// excludeID 用于更新时排除自身
|
||
func (s *modelService) checkDuplicateModelName(providerID, modelName, excludeID string) error {
|
||
existing, err := s.modelRepo.FindByProviderAndModelName(providerID, modelName)
|
||
if err != nil {
|
||
return nil // 未找到,不重复
|
||
}
|
||
if excludeID != "" && existing.ID == excludeID {
|
||
return nil // 排除自身
|
||
}
|
||
return appErrors.ErrDuplicateModel
|
||
}
|