From 433e91da8e0d9b683e7375a2668e8c4b01b5472f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 3 Mar 2025 21:55:49 +0800 Subject: [PATCH] 【代码重构】AI:“聊天模型”重构为“模型”,支持 type 模型类型 --- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java | 9 +++++---- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java | 6 +++--- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java | 168 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java | 8 ++++---- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java | 4 ---- 6 files changed, 180 insertions(+), 183 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java index 792a8ae..6483166 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java @@ -45,7 +45,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService private AiChatConversationMapper chatConversationMapper; @Resource - private AiModelService chatModalService; + private AiModelService modalService; @Resource private AiChatRoleService chatRoleService; @Resource @@ -56,8 +56,8 @@ public class AiChatConversationServiceImpl implements AiChatConversationService // 1.1 获得 AiChatRoleDO 聊天角色 AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null; // 1.2 获得 AiModelDO 聊天模型 - AiModelDO model = role != null && role.getModelId() != null ? chatModalService.validateModel(role.getModelId()) - : chatModalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); + AiModelDO model = role != null && role.getModelId() != null ? modalService.validateModel(role.getModelId()) + : modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); Assert.notNull(model, "必须找到默认模型"); validateChatModel(model); @@ -89,7 +89,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService // 1.2 校验模型是否存在(修改模型的情况) AiModelDO model = null; if (updateReqVO.getModelId() != null) { - model = chatModalService.validateModel(updateReqVO.getModelId()); + model = modalService.validateModel(updateReqVO.getModelId()); } // 1.3 校验知识库是否存在 @@ -144,6 +144,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) { return; } + Assert.equals(model.getType(), AiModelTypeEnum.CHAT.getType(), "模型类型不正确:" + model); throw exception(CHAT_CONVERSATION_MODEL_ERROR); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java index 5582279..0bd4029 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeServiceImpl.java @@ -28,12 +28,12 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { private AiKnowledgeMapper knowledgeMapper; @Resource - private AiModelService chatModelService; + private AiModelService modelService; @Override public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) { // 1. 校验模型配置 - AiModelDO model = chatModelService.validateModel(createReqVO.getEmbeddingModelId()); + AiModelDO model = modelService.validateModel(createReqVO.getEmbeddingModelId()); // 2. 插入知识库 AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) @@ -47,7 +47,7 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService { // 1.1 校验知识库存在 validateKnowledgeExists(updateReqVO.getId()); // 1.2 校验模型配置 - AiModelDO model = chatModelService.validateModel(updateReqVO.getEmbeddingModelId()); + AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId()); // 2. 更新知识库 AiKnowledgeDO updateObj = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java deleted file mode 100644 index 2b38a68..0000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java +++ /dev/null @@ -1,168 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -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.dal.dataobject.model.AiApiKeyDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper; -import jakarta.annotation.Resource; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.vectorstore.SimpleVectorStore; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; - -/** - * AI 模型 Service 实现类 - * - * @author fansili - */ -@Service -@Validated -public class AiChatModelServiceImpl implements AiModelService { - - @Resource - private AiApiKeyService apiKeyService; - - @Resource - private AiChatMapper modelMapper; - - @Resource - private AiModelFactory modelFactory; - - @Override - public Long createModel(AiModelSaveReqVO createReqVO) { - // 1. 校验 - AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); - apiKeyService.validateApiKey(createReqVO.getKeyId()); - - // 2. 插入 - AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); - modelMapper.insert(model); - return model.getId(); - } - - @Override - public void updateModel(AiModelSaveReqVO updateReqVO) { - // 1. 校验 - validateModelExists(updateReqVO.getId()); - AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); - apiKeyService.validateApiKey(updateReqVO.getKeyId()); - - // 2. 更新 - AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); - modelMapper.updateById(updateObj); - } - - @Override - public void deleteModel(Long id) { - // 校验存在 - validateModelExists(id); - // 删除 - modelMapper.deleteById(id); - } - - private AiModelDO validateModelExists(Long id) { - AiModelDO model = modelMapper.selectById(id); - if (modelMapper.selectById(id) == null) { - throw exception(MODEL_NOT_EXISTS); - } - return model; - } - - @Override - public AiModelDO getModel(Long id) { - return modelMapper.selectById(id); - } - - @Override - public AiModelDO getRequiredDefaultModel(Integer type) { - AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus()); - if (model == null) { - throw exception(MODEL_DEFAULT_NOT_EXISTS); - } - return model; - } - - @Override - public PageResult getModelPage(AiModelPageReqVO pageReqVO) { - return modelMapper.selectPage(pageReqVO); - } - - @Override - public AiModelDO validateModel(Long id) { - AiModelDO model = validateModelExists(id); - if (CommonStatusEnum.isDisable(model.getStatus())) { - throw exception(MODEL_DISABLE); - } - return model; - } - - @Override - public List getModelListByStatusAndType(Integer status, Integer type, - String platform) { - return modelMapper.selectListByStatusAndType(status, type, platform); - } - - // ========== 与 Spring AI 集成 ========== - - @Override - public ChatModel getChatModel(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public ImageModel getImageModel(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public MidjourneyApi getMidjourneyApi(Long id) { - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public SunoApi getSunoApi() { - AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey( - AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); - return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public VectorStore getOrCreateVectorStore(Long id) { - // 获取模型 + 密钥 - AiModelDO model = validateModel(id); - AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - - // 创建或获取 EmbeddingModel 对象 - EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( - platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); - - // 创建或获取 VectorStore 对象 - return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java new file mode 100644 index 0000000..8583984 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java @@ -0,0 +1,168 @@ +package cn.iocoder.yudao.module.ai.service.model; + +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +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.dal.dataobject.model.AiApiKeyDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; +import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; + +/** + * AI 模型 Service 实现类 + * + * @author fansili + */ +@Service +@Validated +public class AiModelServiceImpl implements AiModelService { + + @Resource + private AiApiKeyService apiKeyService; + + @Resource + private AiChatMapper modelMapper; + + @Resource + private AiModelFactory modelFactory; + + @Override + public Long createModel(AiModelSaveReqVO createReqVO) { + // 1. 校验 + AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); + apiKeyService.validateApiKey(createReqVO.getKeyId()); + + // 2. 插入 + AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); + modelMapper.insert(model); + return model.getId(); + } + + @Override + public void updateModel(AiModelSaveReqVO updateReqVO) { + // 1. 校验 + validateModelExists(updateReqVO.getId()); + AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); + apiKeyService.validateApiKey(updateReqVO.getKeyId()); + + // 2. 更新 + AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); + modelMapper.updateById(updateObj); + } + + @Override + public void deleteModel(Long id) { + // 校验存在 + validateModelExists(id); + // 删除 + modelMapper.deleteById(id); + } + + private AiModelDO validateModelExists(Long id) { + AiModelDO model = modelMapper.selectById(id); + if (modelMapper.selectById(id) == null) { + throw exception(MODEL_NOT_EXISTS); + } + return model; + } + + @Override + public AiModelDO getModel(Long id) { + return modelMapper.selectById(id); + } + + @Override + public AiModelDO getRequiredDefaultModel(Integer type) { + AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus()); + if (model == null) { + throw exception(MODEL_DEFAULT_NOT_EXISTS); + } + return model; + } + + @Override + public PageResult getModelPage(AiModelPageReqVO pageReqVO) { + return modelMapper.selectPage(pageReqVO); + } + + @Override + public AiModelDO validateModel(Long id) { + AiModelDO model = validateModelExists(id); + if (CommonStatusEnum.isDisable(model.getStatus())) { + throw exception(MODEL_DISABLE); + } + return model; + } + + @Override + public List getModelListByStatusAndType(Integer status, Integer type, + String platform) { + return modelMapper.selectListByStatusAndType(status, type, platform); + } + + // ========== 与 Spring AI 集成 ========== + + @Override + public ChatModel getChatModel(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public ImageModel getImageModel(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public MidjourneyApi getMidjourneyApi(Long id) { + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public SunoApi getSunoApi() { + AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey( + AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); + return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); + } + + @Override + public VectorStore getOrCreateVectorStore(Long id) { + // 获取模型 + 密钥 + AiModelDO model = validateModel(id); + AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); + + // 创建或获取 EmbeddingModel 对象 + EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( + platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); + + // 创建或获取 VectorStore 对象 + return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel); + } + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java index ceae2fc..4c38cd9 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java @@ -54,7 +54,7 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXIS public class AiWriteServiceImpl implements AiWriteService { @Resource - private AiModelService chatModalService; + private AiModelService modalService; @Resource private AiChatRoleService chatRoleService; @@ -76,7 +76,7 @@ public class AiWriteServiceImpl implements AiWriteService { ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage(); // 1.3 校验平台 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - StreamingChatModel chatModel = chatModalService.getChatModel(model.getKeyId()); + StreamingChatModel chatModel = modalService.getChatModel(model.getKeyId()); // 2. 插入写作信息 AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class, write -> write.setUserId(userId) @@ -110,10 +110,10 @@ public class AiWriteServiceImpl implements AiWriteService { private AiModelDO getModel(AiChatRoleDO writeRole) { AiModelDO model = null; if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) { - model = chatModalService.getModel(writeRole.getModelId()); + model = modalService.getModel(writeRole.getModelId()); } if (model == null) { - model = chatModalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); + model = modalService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); } Assert.notNull(model, "[AI] 获取不到模型"); return model; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java index f7a64e6..64e921a 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageModelTests.java @@ -5,13 +5,9 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.image.ImageOptions; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.web.client.RestClient; /** * {@link OpenAiImageModel} 集成测试类 -- libgit2 0.21.4