package cn.iocoder.yudao.module.ai.service.model; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelUpdateStatusReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; import cn.iocoder.yudao.module.ai.dal.mysql.model.AiModelMapper; import cn.iocoder.yudao.module.ai.enums.model.AiModelCapabilityEnum; import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; @Service @Validated public class AiModelServiceImpl implements AiModelService { @Resource private AiApiKeyService apiKeyService; @Resource private AiModelMapper modelMapper; @Resource private AiModelFactory modelFactory; @Resource private YudaoAiProperties aiProperties; @Override @Transactional(rollbackFor = Exception.class) public Long createModel(AiModelSaveReqVO createReqVO) { AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); apiKeyService.validateApiKey(createReqVO.getKeyId()); AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); model.setCapability(resolveCapability(createReqVO.getCapability())); if (CommonStatusEnum.isEnable(model.getStatus())) { disableOtherEnabledModels(model); } modelMapper.insert(model); return model.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void updateModel(AiModelSaveReqVO updateReqVO) { AiModelDO existing = validateModelExists(updateReqVO.getId()); AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); apiKeyService.validateApiKey(updateReqVO.getKeyId()); AiModelDO model = BeanUtils.toBean(updateReqVO, AiModelDO.class); // 未传能力时沿用原值,避免老前端提交把视觉模型降级成文本能力 model.setCapability(resolveCapability( updateReqVO.getCapability() == null || updateReqVO.getCapability().isBlank() ? existing.getCapability() : updateReqVO.getCapability())); if (CommonStatusEnum.isEnable(model.getStatus())) { disableOtherEnabledModels(model); } modelMapper.updateById(model); } @Override public void deleteModel(Long id) { validateModelExists(id); modelMapper.deleteById(id); } @Override public AiModelDO getModel(Long id) { return modelMapper.selectById(id); } @Override public AiModelDO getRequiredDefaultModel(Integer type) { return getRequiredDefaultModel(type, AiModelCapabilityEnum.defaultCapability()); } @Override public AiModelDO getRequiredDefaultModel(Integer type, String capability) { AiModelDO model = modelMapper.selectFirstByTypeAndCapabilityAndStatus( type, resolveCapability(capability), CommonStatusEnum.ENABLE.getStatus()); if (model == null) { throw exception(MODEL_NOT_ENABLED, AiModelTypeEnum.getName(type), AiModelCapabilityEnum.getName(resolveCapability(capability))); } return model; } @Override public PageResult getModelPage(AiModelPageReqVO pageReqVO) { return modelMapper.selectPage(pageReqVO); } @Override @Transactional(rollbackFor = Exception.class) public void updateModelStatus(AiModelUpdateStatusReqVO updateReqVO) { AiModelDO model = validateModelExists(updateReqVO.getId()); model.setStatus(updateReqVO.getStatus()); if (CommonStatusEnum.isEnable(updateReqVO.getStatus())) { // 启用:自动关闭同「类型 + 能力」下的其他模型,保证同组只有一个启用 disableOtherEnabledModels(model); } modelMapper.updateById(model); } @Override public AiModelDO validateModel(Long id) { AiModelDO model = validateModelExists(id); if (CommonStatusEnum.isDisable(model.getStatus())) throw exception(MODEL_DISABLE, model.getName()); return model; } @Override public List getModelListByStatusAndType(Integer status, Integer type, String platform) { return modelMapper.selectListByStatusAndType(status, type, platform); } /** * 关闭同「类型 + 能力」下已启用的其他模型(不含自身) */ private void disableOtherEnabledModels(AiModelDO model) { List sameGroup = modelMapper.selectListByTypeAndCapabilityAndStatus( model.getType(), resolveCapability(model.getCapability()), CommonStatusEnum.ENABLE.getStatus()); for (AiModelDO other : sameGroup) { if (other.getId().equals(model.getId())) { continue; } AiModelDO update = new AiModelDO(); update.setId(other.getId()); update.setStatus(CommonStatusEnum.DISABLE.getStatus()); modelMapper.updateById(update); } } private static String resolveCapability(String capability) { return capability == null || capability.isBlank() ? AiModelCapabilityEnum.defaultCapability() : capability; } private AiModelDO validateModelExists(Long id) { AiModelDO model = modelMapper.selectById(id); if (model == null) throw exception(MODEL_NOT_EXISTS); return model; } // ========== 与 Spring AI 集成 ========== @Override public ChatModel getChatModel(Long id) { AiModelDO model = validateModel(id); AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); // temperature / maxTokens 是 AI 模型表上一直存在、却从没人读过的两列: // 不读 temperature,库里配的值对调用方是假的;不读 maxTokens,长输出会被静默截断 return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel(), model.getTemperature(), model.getMaxTokens(), aiProperties.getTimeout()); } @Override public VectorStore getOrCreateVectorStore(Long id, Map> metadataFields) { AiModelDO model = validateModel(id); AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields); } }