From 6f795186e842b5f3ad2ed77df94a21fdae803539 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 25 Aug 2025 22:08:08 +0800 Subject: [PATCH] reactor:【ai 大模型】AiModelFactory 调整基础路径 --- yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java | 3 +++ yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java | 4 ++-- yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java | 113 ----------------------------------------------------------------------------------------------------------------- yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java | 832 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java | 832 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java | 2 +- 7 files changed, 951 insertions(+), 948 deletions(-) delete mode 100644 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java delete mode 100644 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java create mode 100644 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java create mode 100644 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java index 8ba3ebf..06ce0d1 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java @@ -22,6 +22,9 @@ public class AiChatMessageSendReqVO { @Schema(description = "是否携带上下文", example = "true") private Boolean useContext; + @Schema(description = "是否联网搜索", example = "true") + private Boolean useSearch; + @Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png") private List attachmentUrls; diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java index 262668f..2d63df3 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.ai.framework.ai.config; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory; -import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactoryImpl; import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java deleted file mode 100644 index 659fa1f..0000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactory.java +++ /dev/null @@ -1,113 +0,0 @@ -package cn.iocoder.yudao.module.ai.framework.ai.core; - -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.vectorstore.VectorStore; - -import java.util.Map; - -/** - * AI Model 模型工厂的接口类 - * - * @author fansili - */ -public interface AiModelFactory { - - /** - * 基于指定配置,获得 ChatModel 对象 - * - * 如果不存在,则进行创建 - * - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @return ChatModel 对象 - */ - ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); - - /** - * 基于默认配置,获得 ChatModel 对象 - * - * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 - * - * @param platform 平台 - * @return ChatModel 对象 - */ - ChatModel getDefaultChatModel(AiPlatformEnum platform); - - /** - * 基于默认配置,获得 ImageModel 对象 - * - * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 - * - * @param platform 平台 - * @return ImageModel 对象 - */ - ImageModel getDefaultImageModel(AiPlatformEnum platform); - - /** - * 基于指定配置,获得 ImageModel 对象 - * - * 如果不存在,则进行创建 - * - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @return ImageModel 对象 - */ - ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url); - - /** - * 基于指定配置,获得 MidjourneyApi 对象 - * - * 如果不存在,则进行创建 - * - * @param apiKey API KEY - * @param url API URL - * @return MidjourneyApi 对象 - */ - MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url); - - /** - * 基于指定配置,获得 SunoApi 对象 - * - * 如果不存在,则进行创建 - * - * @param apiKey API KEY - * @param url API URL - * @return SunoApi 对象 - */ - SunoApi getOrCreateSunoApi(String apiKey, String url); - - /** - * 基于指定配置,获得 EmbeddingModel 对象 - * - * 如果不存在,则进行创建 - * - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @param model 模型 - * @return ChatModel 对象 - */ - EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model); - - /** - * 基于指定配置,获得 VectorStore 对象 - * - * 如果不存在,则进行创建 - * - * @param type 向量存储类型 - * @param embeddingModel 向量模型 - * @param metadataFields 元数据字段 - * @return VectorStore 对象 - */ - VectorStore getOrCreateVectorStore(Class type, - EmbeddingModel embeddingModel, - Map> metadataFields); - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java deleted file mode 100644 index 924f0f7..0000000 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java +++ /dev/null @@ -1,832 +0,0 @@ -package cn.iocoder.yudao.module.ai.framework.ai.core; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Singleton; -import cn.hutool.core.lang.func.Func0; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.RuntimeUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; -import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration; -import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; -import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; -import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; -import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; -import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; -import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; -import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; -import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; -import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; -import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.KeyCredential; -import io.micrometer.observation.ObservationRegistry; -import io.milvus.client.MilvusServiceClient; -import io.qdrant.client.QdrantClient; -import io.qdrant.client.QdrantGrpcClient; -import lombok.SneakyThrows; -import org.springaicommunity.moonshot.MoonshotChatModel; -import org.springaicommunity.moonshot.MoonshotChatOptions; -import org.springaicommunity.moonshot.api.MoonshotApi; -import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration; -import org.springaicommunity.qianfan.QianFanChatModel; -import org.springaicommunity.qianfan.QianFanEmbeddingModel; -import org.springaicommunity.qianfan.QianFanEmbeddingOptions; -import org.springaicommunity.qianfan.QianFanImageModel; -import org.springaicommunity.qianfan.api.QianFanApi; -import org.springaicommunity.qianfan.api.QianFanImageApi; -import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration; -import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.deepseek.DeepSeekChatModel; -import org.springframework.ai.deepseek.DeepSeekChatOptions; -import org.springframework.ai.deepseek.api.DeepSeekApi; -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.embedding.BatchingStrategy; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.minimax.MiniMaxEmbeddingModel; -import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; -import org.springframework.ai.minimax.api.MiniMaxApi; -import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; -import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration; -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration; -import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; -import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; -import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; -import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; -import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.OllamaEmbeddingModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiEmbeddingOptions; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.openai.api.common.OpenAiApiConstants; -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.stabilityai.StabilityAiImageModel; -import org.springframework.ai.stabilityai.api.StabilityAiApi; -import org.springframework.ai.vectorstore.SimpleVectorStore; -import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; -import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientConnectionDetails; -import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties; -import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration; -import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties; -import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; -import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; -import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration; -import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties; -import org.springframework.ai.vectorstore.redis.RedisVectorStore; -import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; -import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; -import org.springframework.ai.zhipuai.*; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; -import org.springframework.web.client.RestClient; -import redis.clients.jedis.JedisPooled; - -import java.io.File; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; - -/** - * AI Model 模型工厂的实现类 - * - * @author 芋道源码 - */ -public class AiModelFactoryImpl implements AiModelFactory { - - @Override - public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { - String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> { - // noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return buildTongYiChatModel(apiKey); - case YI_YAN: - return buildYiYanChatModel(apiKey); - case DEEP_SEEK: - return buildDeepSeekChatModel(apiKey); - case DOU_BAO: - return buildDouBaoChatModel(apiKey); - case HUN_YUAN: - return buildHunYuanChatModel(apiKey, url); - case SILICON_FLOW: - return buildSiliconFlowChatModel(apiKey); - case ZHI_PU: - return buildZhiPuChatModel(apiKey, url); - case MINI_MAX: - return buildMiniMaxChatModel(apiKey, url); - case MOONSHOT: - return buildMoonshotChatModel(apiKey, url); - case XING_HUO: - return buildXingHuoChatModel(apiKey); - case BAI_CHUAN: - return buildBaiChuanChatModel(apiKey); - case OPENAI: - return buildOpenAiChatModel(apiKey, url); - case AZURE_OPENAI: - return buildAzureOpenAiChatModel(apiKey, url); - case ANTHROPIC: - return buildAnthropicChatModel(apiKey, url); - case GEMINI: - return buildGeminiChatModel(apiKey); - case OLLAMA: - return buildOllamaChatModel(url); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - }); - } - - @Override - public ChatModel getDefaultChatModel(AiPlatformEnum platform) { - // noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return SpringUtil.getBean(DashScopeChatModel.class); - case YI_YAN: - return SpringUtil.getBean(QianFanChatModel.class); - case DEEP_SEEK: - return SpringUtil.getBean(DeepSeekChatModel.class); - case DOU_BAO: - return SpringUtil.getBean(DouBaoChatModel.class); - case HUN_YUAN: - return SpringUtil.getBean(HunYuanChatModel.class); - case SILICON_FLOW: - return SpringUtil.getBean(SiliconFlowChatModel.class); - case ZHI_PU: - return SpringUtil.getBean(ZhiPuAiChatModel.class); - case MINI_MAX: - return SpringUtil.getBean(MiniMaxChatModel.class); - case MOONSHOT: - return SpringUtil.getBean(MoonshotChatModel.class); - case XING_HUO: - return SpringUtil.getBean(XingHuoChatModel.class); - case BAI_CHUAN: - return SpringUtil.getBean(BaiChuanChatModel.class); - case OPENAI: - return SpringUtil.getBean(OpenAiChatModel.class); - case AZURE_OPENAI: - return SpringUtil.getBean(AzureOpenAiChatModel.class); - case ANTHROPIC: - return SpringUtil.getBean(AnthropicChatModel.class); - case GEMINI: - return SpringUtil.getBean(GeminiChatModel.class); - case OLLAMA: - return SpringUtil.getBean(OllamaChatModel.class); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public ImageModel getDefaultImageModel(AiPlatformEnum platform) { - // noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return SpringUtil.getBean(DashScopeImageModel.class); - case YI_YAN: - return SpringUtil.getBean(QianFanImageModel.class); - case ZHI_PU: - return SpringUtil.getBean(ZhiPuAiImageModel.class); - case SILICON_FLOW: - return SpringUtil.getBean(SiliconFlowImageModel.class); - case OPENAI: - return SpringUtil.getBean(OpenAiImageModel.class); - case STABLE_DIFFUSION: - return SpringUtil.getBean(StabilityAiImageModel.class); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { - // noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return buildTongYiImagesModel(apiKey); - case YI_YAN: - return buildQianFanImageModel(apiKey); - case ZHI_PU: - return buildZhiPuAiImageModel(apiKey, url); - case OPENAI: - return buildOpenAiImageModel(apiKey, url); - case SILICON_FLOW: - return buildSiliconFlowImageModel(apiKey,url); - case STABLE_DIFFUSION: - return buildStabilityAiImageModel(apiKey, url); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { - String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, - url); - return Singleton.get(cacheKey, (Func0) () -> { - YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class) - .getMidjourney(); - return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); - }); - } - - @Override - public SunoApi getOrCreateSunoApi(String apiKey, String url) { - String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> new SunoApi(url)); - } - - @Override - @SuppressWarnings("EnhancedSwitchMigration") - public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) { - String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); - return Singleton.get(cacheKey, (Func0) () -> { - switch (platform) { - case TONG_YI: - return buildTongYiEmbeddingModel(apiKey, model); - case YI_YAN: - return buildYiYanEmbeddingModel(apiKey, model); - case ZHI_PU: - return buildZhiPuEmbeddingModel(apiKey, url, model); - case MINI_MAX: - return buildMiniMaxEmbeddingModel(apiKey, url, model); - case OPENAI: - return buildOpenAiEmbeddingModel(apiKey, url, model); - case AZURE_OPENAI: - return buildAzureOpenAiEmbeddingModel(apiKey, url, model); - case OLLAMA: - return buildOllamaEmbeddingModel(url, model); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - }); - } - - @Override - public VectorStore getOrCreateVectorStore(Class type, - EmbeddingModel embeddingModel, - Map> metadataFields) { - String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel, type); - return Singleton.get(cacheKey, (Func0) () -> { - if (type == SimpleVectorStore.class) { - return buildSimpleVectorStore(embeddingModel); - } - if (type == QdrantVectorStore.class) { - return buildQdrantVectorStore(embeddingModel); - } - if (type == RedisVectorStore.class) { - return buildRedisVectorStore(embeddingModel, metadataFields); - } - if (type == MilvusVectorStore.class) { - return buildMilvusVectorStore(embeddingModel); - } - throw new IllegalArgumentException(StrUtil.format("未知类型({})", type)); - }); - } - - private static String buildClientCacheKey(Class clazz, Object... params) { - if (ArrayUtil.isEmpty(params)) { - return clazz.getName(); - } - return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); - } - - // ========== 各种创建 spring-ai 客户端的方法 ========== - - /** - * 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法 - */ - private static DashScopeChatModel buildTongYiChatModel(String key) { - DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build(); - DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL) - .withTemperature(0.7).build(); - return DashScopeChatModel.builder() - .dashScopeApi(dashScopeApi) - .defaultOptions(options) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link DashScopeImageAutoConfiguration} 的 dashScopeImageModel 方法 - */ - private static DashScopeImageModel buildTongYiImagesModel(String key) { - DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(key).build(); - return DashScopeImageModel.builder() - .dashScopeApi(dashScopeImageApi) - .build(); - } - - /** - * 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法 - */ - private static QianFanChatModel buildYiYanChatModel(String key) { - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); - String appKey = keys.get(0); - String secretKey = keys.get(1); - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); - return new QianFanChatModel(qianFanApi); - } - - /** - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法 - */ - private QianFanImageModel buildQianFanImageModel(String key) { - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); - String appKey = keys.get(0); - String secretKey = keys.get(1); - QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey); - return new QianFanImageModel(qianFanApi); - } - - /** - * 可参考 {@link DeepSeekChatAutoConfiguration} 的 deepSeekChatModel 方法 - */ - private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { - DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build(); - DeepSeekChatOptions options = DeepSeekChatOptions.builder().model(DeepSeekApi.DEFAULT_CHAT_MODEL) - .temperature(0.7).build(); - return DeepSeekChatModel.builder() - .deepSeekApi(deepSeekApi) - .defaultOptions(options) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)} - */ - private ChatModel buildDouBaoChatModel(String apiKey) { - YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties() - .setApiKey(apiKey); - return new AiAutoConfiguration().buildDouBaoChatClient(properties); - } - - /** - * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)} - */ - private ChatModel buildHunYuanChatModel(String apiKey, String url) { - YudaoAiProperties.HunYuanProperties properties = new YudaoAiProperties.HunYuanProperties() - .setBaseUrl(url).setApiKey(apiKey); - return new AiAutoConfiguration().buildHunYuanChatClient(properties); - } - - /** - * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} - */ - private ChatModel buildSiliconFlowChatModel(String apiKey) { - YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties() - .setApiKey(apiKey); - return new AiAutoConfiguration().buildSiliconFlowChatClient(properties); - } - - /** - * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 - */ - private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { - ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) - : new ZhiPuAiApi(url, apiKey); - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); - return new ZhiPuAiChatModel(zhiPuAiApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE, - getObservationRegistry().getIfAvailable()); - } - - /** - * 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法 - */ - private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { - ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey) - : new ZhiPuAiImageApi(url, apiKey, RestClient.builder()); - return new ZhiPuAiImageModel(zhiPuAiApi); - } - - /** - * 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法 - */ - private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey) - : new MiniMaxApi(url, apiKey); - MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); - return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE); - } - - /** - * 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法 - */ - private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { - MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder() - .apiKey(apiKey); - if (StrUtil.isNotEmpty(url)) { - moonshotApiBuilder.baseUrl(url); - } - MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build(); - return MoonshotChatModel.builder() - .moonshotApi(moonshotApiBuilder.build()) - .defaultOptions(options) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} - */ - private static XingHuoChatModel buildXingHuoChatModel(String key) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); - YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties() - .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); - return new AiAutoConfiguration().buildXingHuoChatClient(properties); - } - - /** - * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} - */ - private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { - YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties() - .setApiKey(apiKey); - return new AiAutoConfiguration().buildBaiChuanChatClient(properties); - } - - /** - * 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法 - */ - private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { - url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); - OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); - return OpenAiChatModel.builder() - .openAiApi(openAiApi) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link AzureOpenAiChatAutoConfiguration} - */ - private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) { - // TODO @芋艿:使用前,请测试,暂时没密钥!!! - OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() - .endpoint(url).credential(new KeyCredential(apiKey)); - return AzureOpenAiChatModel.builder() - .openAIClientBuilder(openAIClientBuilder) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link AnthropicChatAutoConfiguration} 的 anthropicApi 方法 - */ - private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) { - AnthropicApi.Builder builder = AnthropicApi.builder().apiKey(apiKey); - if (StrUtil.isNotEmpty(url)) { - builder.baseUrl(url); - } - AnthropicApi anthropicApi = builder.build(); - return AnthropicChatModel.builder() - .anthropicApi(anthropicApi) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.GeminiProperties)} - */ - private static GeminiChatModel buildGeminiChatModel(String apiKey) { - YudaoAiProperties.GeminiProperties properties = SpringUtil.getBean(YudaoAiProperties.class) - .getGemini().setApiKey(apiKey); - return new AiAutoConfiguration().buildGeminiChatClient(properties); - } - - /** - * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法 - */ - private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { - url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); - OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build(); - return new OpenAiImageModel(openAiApi); - } - - /** - * 创建 SiliconFlowImageModel 对象 - */ - private SiliconFlowImageModel buildSiliconFlowImageModel(String apiToken, String url) { - url = StrUtil.blankToDefault(url, SiliconFlowApiConstants.DEFAULT_BASE_URL); - SiliconFlowImageApi openAiApi = new SiliconFlowImageApi(url, apiToken); - return new SiliconFlowImageModel(openAiApi); - } - - /** - * 可参考 {@link OllamaChatAutoConfiguration} 的 ollamaChatModel 方法 - */ - private static OllamaChatModel buildOllamaChatModel(String url) { - OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); - return OllamaChatModel.builder() - .ollamaApi(ollamaApi) - .toolCallingManager(getToolCallingManager()) - .build(); - } - - /** - * 可参考 {@link StabilityAiImageAutoConfiguration} 的 stabilityAiImageModel 方法 - */ - private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { - url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL); - StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url); - return new StabilityAiImageModel(stabilityAiApi); - } - - // ========== 各种创建 EmbeddingModel 的方法 ========== - - /** - * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法 - */ - private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { - DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); - DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build(); - return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); - } - - /** - * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法 - */ - private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { - ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) - : new ZhiPuAiApi(url, apiKey); - ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build(); - return new ZhiPuAiEmbeddingModel(zhiPuAiApi, MetadataMode.EMBED, zhiPuAiEmbeddingOptions); - } - - /** - * 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法 - */ - private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) { - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey) - : new MiniMaxApi(url, apiKey); - MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build(); - return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions); - } - - /** - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法 - */ - private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); - String appKey = keys.get(0); - String secretKey = keys.get(1); - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); - QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build(); - return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions); - } - - private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { - OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); - OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build(); - return OllamaEmbeddingModel.builder() - .ollamaApi(ollamaApi) - .defaultOptions(ollamaOptions) - .build(); - } - - /** - * 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法 - */ - private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) { - url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); - OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); - OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build(); - return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties); - } - - /** - * 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法 - */ - private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) { - // TODO @芋艿:手头暂时没密钥,使用建议再测试下 - AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration(); - // 创建 OpenAIClientBuilder 对象 - OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() - .endpoint(url).credential(new KeyCredential(apiKey)); - // 获取 AzureOpenAiChatProperties 对象 - AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class); - return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties, - getObservationRegistry(), getEmbeddingModelObservationConvention()); - } - - // ========== 各种创建 VectorStore 的方法 ========== - - /** - * 注意:仅适合本地测试使用,生产建议还是使用 Qdrant、Milvus 等 - */ - @SneakyThrows - @SuppressWarnings("ResultOfMethodCallIgnored") - private SimpleVectorStore buildSimpleVectorStore(EmbeddingModel embeddingModel) { - SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build(); - // 启动加载 - File file = new File(StrUtil.format("{}/vector_store/simple_{}.json", - FileUtil.getUserHomePath(), embeddingModel.getClass().getSimpleName())); - if (!file.exists()) { - FileUtil.mkParentDirs(file); - file.createNewFile(); - } else if (file.length() > 0) { - vectorStore.load(file); - } - // 定时持久化,每分钟一次 - Timer timer = new Timer("SimpleVectorStoreTimer-" + file.getAbsolutePath()); - timer.scheduleAtFixedRate(new TimerTask() { - - @Override - public void run() { - vectorStore.save(file); - } - - }, Duration.ofMinutes(1).toMillis(), Duration.ofMinutes(1).toMillis()); - // 关闭时,进行持久化 - RuntimeUtil.addShutdownHook(() -> vectorStore.save(file)); - return vectorStore; - } - - /** - * 参考 {@link QdrantVectorStoreAutoConfiguration} 的 vectorStore 方法 - */ - @SneakyThrows - private QdrantVectorStore buildQdrantVectorStore(EmbeddingModel embeddingModel) { - QdrantVectorStoreAutoConfiguration configuration = new QdrantVectorStoreAutoConfiguration(); - QdrantVectorStoreProperties properties = SpringUtil.getBean(QdrantVectorStoreProperties.class); - // 参考 QdrantVectorStoreAutoConfiguration 实现,创建 QdrantClient 对象 - QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder( - properties.getHost(), properties.getPort(), properties.isUseTls()); - if (StrUtil.isNotEmpty(properties.getApiKey())) { - grpcClientBuilder.withApiKey(properties.getApiKey()); - } - QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build()); - // 创建 QdrantVectorStore 对象 - QdrantVectorStore vectorStore = configuration.vectorStore(embeddingModel, properties, qdrantClient, - getObservationRegistry(), getCustomObservationConvention(), getBatchingStrategy()); - // 初始化索引 - vectorStore.afterPropertiesSet(); - return vectorStore; - } - - /** - * 参考 {@link RedisVectorStoreAutoConfiguration} 的 vectorStore 方法 - */ - private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel, - Map> metadataFields) { - // 创建 JedisPooled 对象 - RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); - JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(), - redisProperties.getUsername(), redisProperties.getPassword()); - // 创建 RedisVectorStoreProperties 对象 - RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class); - RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel) - .indexName(properties.getIndexName()).prefix(properties.getPrefix()) - .initializeSchema(properties.isInitializeSchema()) - .metadataFields(convertList(metadataFields.entrySet(), entry -> { - String fieldName = entry.getKey(); - Class fieldType = entry.getValue(); - if (Number.class.isAssignableFrom(fieldType)) { - return RedisVectorStore.MetadataField.numeric(fieldName); - } - if (Boolean.class.isAssignableFrom(fieldType)) { - return RedisVectorStore.MetadataField.tag(fieldName); - } - return RedisVectorStore.MetadataField.text(fieldName); - })) - .observationRegistry(getObservationRegistry().getObject()) - .customObservationConvention(getCustomObservationConvention().getObject()) - .batchingStrategy(getBatchingStrategy()) - .build(); - // 初始化索引 - redisVectorStore.afterPropertiesSet(); - return redisVectorStore; - } - - /** - * 参考 {@link MilvusVectorStoreAutoConfiguration} 的 vectorStore 方法 - */ - @SneakyThrows - private MilvusVectorStore buildMilvusVectorStore(EmbeddingModel embeddingModel) { - MilvusVectorStoreAutoConfiguration configuration = new MilvusVectorStoreAutoConfiguration(); - // 获取配置属性 - MilvusVectorStoreProperties serverProperties = SpringUtil.getBean(MilvusVectorStoreProperties.class); - MilvusServiceClientProperties clientProperties = SpringUtil.getBean(MilvusServiceClientProperties.class); - - // 创建 MilvusServiceClient 对象 - MilvusServiceClient milvusClient = configuration.milvusClient(serverProperties, clientProperties, - new MilvusServiceClientConnectionDetails() { - - @Override - public String getHost() { - return clientProperties.getHost(); - } - - @Override - public int getPort() { - return clientProperties.getPort(); - } - - } - ); - // 创建 MilvusVectorStore 对象 - MilvusVectorStore vectorStore = configuration.vectorStore(milvusClient, embeddingModel, serverProperties, - getBatchingStrategy(), getObservationRegistry(), getCustomObservationConvention()); - - // 初始化索引 - vectorStore.afterPropertiesSet(); - return vectorStore; - } - - private static ObjectProvider getObservationRegistry() { - return new ObjectProvider<>() { - - @Override - public ObservationRegistry getObject() throws BeansException { - return SpringUtil.getBean(ObservationRegistry.class); - } - - }; - } - - private static ObjectProvider getCustomObservationConvention() { - return new ObjectProvider<>() { - - @Override - public VectorStoreObservationConvention getObject() throws BeansException { - return new DefaultVectorStoreObservationConvention(); - } - - }; - } - - private static BatchingStrategy getBatchingStrategy() { - return SpringUtil.getBean(BatchingStrategy.class); - } - - private static ToolCallingManager getToolCallingManager() { - return SpringUtil.getBean(ToolCallingManager.class); - } - - private static ObjectProvider getEmbeddingModelObservationConvention() { - return new ObjectProvider<>() { - - @Override - public EmbeddingModelObservationConvention getObject() throws BeansException { - return SpringUtil.getBean(EmbeddingModelObservationConvention.class); - } - - }; - } - -} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java new file mode 100644 index 0000000..1c0b808 --- /dev/null +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactory.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.ai.framework.ai.core.model; + +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.vectorstore.VectorStore; + +import java.util.Map; + +/** + * AI Model 模型工厂的接口类 + * + * @author fansili + */ +public interface AiModelFactory { + + /** + * 基于指定配置,获得 ChatModel 对象 + * + * 如果不存在,则进行创建 + * + * @param platform 平台 + * @param apiKey API KEY + * @param url API URL + * @return ChatModel 对象 + */ + ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); + + /** + * 基于默认配置,获得 ChatModel 对象 + * + * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 + * + * @param platform 平台 + * @return ChatModel 对象 + */ + ChatModel getDefaultChatModel(AiPlatformEnum platform); + + /** + * 基于默认配置,获得 ImageModel 对象 + * + * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 + * + * @param platform 平台 + * @return ImageModel 对象 + */ + ImageModel getDefaultImageModel(AiPlatformEnum platform); + + /** + * 基于指定配置,获得 ImageModel 对象 + * + * 如果不存在,则进行创建 + * + * @param platform 平台 + * @param apiKey API KEY + * @param url API URL + * @return ImageModel 对象 + */ + ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url); + + /** + * 基于指定配置,获得 MidjourneyApi 对象 + * + * 如果不存在,则进行创建 + * + * @param apiKey API KEY + * @param url API URL + * @return MidjourneyApi 对象 + */ + MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url); + + /** + * 基于指定配置,获得 SunoApi 对象 + * + * 如果不存在,则进行创建 + * + * @param apiKey API KEY + * @param url API URL + * @return SunoApi 对象 + */ + SunoApi getOrCreateSunoApi(String apiKey, String url); + + /** + * 基于指定配置,获得 EmbeddingModel 对象 + * + * 如果不存在,则进行创建 + * + * @param platform 平台 + * @param apiKey API KEY + * @param url API URL + * @param model 模型 + * @return ChatModel 对象 + */ + EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model); + + /** + * 基于指定配置,获得 VectorStore 对象 + * + * 如果不存在,则进行创建 + * + * @param type 向量存储类型 + * @param embeddingModel 向量模型 + * @param metadataFields 元数据字段 + * @return VectorStore 对象 + */ + VectorStore getOrCreateVectorStore(Class type, + EmbeddingModel embeddingModel, + Map> metadataFields); + +} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java new file mode 100644 index 0000000..03ebb31 --- /dev/null +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java @@ -0,0 +1,832 @@ +package cn.iocoder.yudao.module.ai.framework.ai.core.model; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.lang.func.Func0; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.RuntimeUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; +import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration; +import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; +import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.KeyCredential; +import io.micrometer.observation.ObservationRegistry; +import io.milvus.client.MilvusServiceClient; +import io.qdrant.client.QdrantClient; +import io.qdrant.client.QdrantGrpcClient; +import lombok.SneakyThrows; +import org.springaicommunity.moonshot.MoonshotChatModel; +import org.springaicommunity.moonshot.MoonshotChatOptions; +import org.springaicommunity.moonshot.api.MoonshotApi; +import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration; +import org.springaicommunity.qianfan.QianFanChatModel; +import org.springaicommunity.qianfan.QianFanEmbeddingModel; +import org.springaicommunity.qianfan.QianFanEmbeddingOptions; +import org.springaicommunity.qianfan.QianFanImageModel; +import org.springaicommunity.qianfan.api.QianFanApi; +import org.springaicommunity.qianfan.api.QianFanImageApi; +import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration; +import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration; +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.deepseek.DeepSeekChatModel; +import org.springframework.ai.deepseek.DeepSeekChatOptions; +import org.springframework.ai.deepseek.api.DeepSeekApi; +import org.springframework.ai.document.MetadataMode; +import org.springframework.ai.embedding.BatchingStrategy; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; +import org.springframework.ai.image.ImageModel; +import org.springframework.ai.minimax.MiniMaxChatModel; +import org.springframework.ai.minimax.MiniMaxChatOptions; +import org.springframework.ai.minimax.MiniMaxEmbeddingModel; +import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; +import org.springframework.ai.minimax.api.MiniMaxApi; +import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; +import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; +import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration; +import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration; +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; +import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; +import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.OllamaEmbeddingModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiEmbeddingModel; +import org.springframework.ai.openai.OpenAiEmbeddingOptions; +import org.springframework.ai.openai.OpenAiImageModel; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.openai.api.common.OpenAiApiConstants; +import org.springframework.ai.anthropic.AnthropicChatModel; +import org.springframework.ai.anthropic.api.AnthropicApi; +import org.springframework.ai.stabilityai.StabilityAiImageModel; +import org.springframework.ai.stabilityai.api.StabilityAiApi; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientConnectionDetails; +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties; +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration; +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties; +import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; +import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; +import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; +import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration; +import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties; +import org.springframework.ai.vectorstore.redis.RedisVectorStore; +import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; +import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; +import org.springframework.ai.zhipuai.*; +import org.springframework.ai.zhipuai.api.ZhiPuAiApi; +import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.web.client.RestClient; +import redis.clients.jedis.JedisPooled; + +import java.io.File; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; + +/** + * AI Model 模型工厂的实现类 + * + * @author 芋道源码 + */ +public class AiModelFactoryImpl implements AiModelFactory { + + @Override + public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { + String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); + return Singleton.get(cacheKey, (Func0) () -> { + // noinspection EnhancedSwitchMigration + switch (platform) { + case TONG_YI: + return buildTongYiChatModel(apiKey); + case YI_YAN: + return buildYiYanChatModel(apiKey); + case DEEP_SEEK: + return buildDeepSeekChatModel(apiKey); + case DOU_BAO: + return buildDouBaoChatModel(apiKey); + case HUN_YUAN: + return buildHunYuanChatModel(apiKey, url); + case SILICON_FLOW: + return buildSiliconFlowChatModel(apiKey); + case ZHI_PU: + return buildZhiPuChatModel(apiKey, url); + case MINI_MAX: + return buildMiniMaxChatModel(apiKey, url); + case MOONSHOT: + return buildMoonshotChatModel(apiKey, url); + case XING_HUO: + return buildXingHuoChatModel(apiKey); + case BAI_CHUAN: + return buildBaiChuanChatModel(apiKey); + case OPENAI: + return buildOpenAiChatModel(apiKey, url); + case AZURE_OPENAI: + return buildAzureOpenAiChatModel(apiKey, url); + case ANTHROPIC: + return buildAnthropicChatModel(apiKey, url); + case GEMINI: + return buildGeminiChatModel(apiKey); + case OLLAMA: + return buildOllamaChatModel(url); + default: + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); + } + }); + } + + @Override + public ChatModel getDefaultChatModel(AiPlatformEnum platform) { + // noinspection EnhancedSwitchMigration + switch (platform) { + case TONG_YI: + return SpringUtil.getBean(DashScopeChatModel.class); + case YI_YAN: + return SpringUtil.getBean(QianFanChatModel.class); + case DEEP_SEEK: + return SpringUtil.getBean(DeepSeekChatModel.class); + case DOU_BAO: + return SpringUtil.getBean(DouBaoChatModel.class); + case HUN_YUAN: + return SpringUtil.getBean(HunYuanChatModel.class); + case SILICON_FLOW: + return SpringUtil.getBean(SiliconFlowChatModel.class); + case ZHI_PU: + return SpringUtil.getBean(ZhiPuAiChatModel.class); + case MINI_MAX: + return SpringUtil.getBean(MiniMaxChatModel.class); + case MOONSHOT: + return SpringUtil.getBean(MoonshotChatModel.class); + case XING_HUO: + return SpringUtil.getBean(XingHuoChatModel.class); + case BAI_CHUAN: + return SpringUtil.getBean(BaiChuanChatModel.class); + case OPENAI: + return SpringUtil.getBean(OpenAiChatModel.class); + case AZURE_OPENAI: + return SpringUtil.getBean(AzureOpenAiChatModel.class); + case ANTHROPIC: + return SpringUtil.getBean(AnthropicChatModel.class); + case GEMINI: + return SpringUtil.getBean(GeminiChatModel.class); + case OLLAMA: + return SpringUtil.getBean(OllamaChatModel.class); + default: + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); + } + } + + @Override + public ImageModel getDefaultImageModel(AiPlatformEnum platform) { + // noinspection EnhancedSwitchMigration + switch (platform) { + case TONG_YI: + return SpringUtil.getBean(DashScopeImageModel.class); + case YI_YAN: + return SpringUtil.getBean(QianFanImageModel.class); + case ZHI_PU: + return SpringUtil.getBean(ZhiPuAiImageModel.class); + case SILICON_FLOW: + return SpringUtil.getBean(SiliconFlowImageModel.class); + case OPENAI: + return SpringUtil.getBean(OpenAiImageModel.class); + case STABLE_DIFFUSION: + return SpringUtil.getBean(StabilityAiImageModel.class); + default: + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); + } + } + + @Override + public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { + // noinspection EnhancedSwitchMigration + switch (platform) { + case TONG_YI: + return buildTongYiImagesModel(apiKey); + case YI_YAN: + return buildQianFanImageModel(apiKey); + case ZHI_PU: + return buildZhiPuAiImageModel(apiKey, url); + case OPENAI: + return buildOpenAiImageModel(apiKey, url); + case SILICON_FLOW: + return buildSiliconFlowImageModel(apiKey,url); + case STABLE_DIFFUSION: + return buildStabilityAiImageModel(apiKey, url); + default: + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); + } + } + + @Override + public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { + String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, + url); + return Singleton.get(cacheKey, (Func0) () -> { + YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class) + .getMidjourney(); + return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); + }); + } + + @Override + public SunoApi getOrCreateSunoApi(String apiKey, String url) { + String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); + return Singleton.get(cacheKey, (Func0) () -> new SunoApi(url)); + } + + @Override + @SuppressWarnings("EnhancedSwitchMigration") + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) { + String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); + return Singleton.get(cacheKey, (Func0) () -> { + switch (platform) { + case TONG_YI: + return buildTongYiEmbeddingModel(apiKey, model); + case YI_YAN: + return buildYiYanEmbeddingModel(apiKey, model); + case ZHI_PU: + return buildZhiPuEmbeddingModel(apiKey, url, model); + case MINI_MAX: + return buildMiniMaxEmbeddingModel(apiKey, url, model); + case OPENAI: + return buildOpenAiEmbeddingModel(apiKey, url, model); + case AZURE_OPENAI: + return buildAzureOpenAiEmbeddingModel(apiKey, url, model); + case OLLAMA: + return buildOllamaEmbeddingModel(url, model); + default: + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); + } + }); + } + + @Override + public VectorStore getOrCreateVectorStore(Class type, + EmbeddingModel embeddingModel, + Map> metadataFields) { + String cacheKey = buildClientCacheKey(VectorStore.class, embeddingModel, type); + return Singleton.get(cacheKey, (Func0) () -> { + if (type == SimpleVectorStore.class) { + return buildSimpleVectorStore(embeddingModel); + } + if (type == QdrantVectorStore.class) { + return buildQdrantVectorStore(embeddingModel); + } + if (type == RedisVectorStore.class) { + return buildRedisVectorStore(embeddingModel, metadataFields); + } + if (type == MilvusVectorStore.class) { + return buildMilvusVectorStore(embeddingModel); + } + throw new IllegalArgumentException(StrUtil.format("未知类型({})", type)); + }); + } + + private static String buildClientCacheKey(Class clazz, Object... params) { + if (ArrayUtil.isEmpty(params)) { + return clazz.getName(); + } + return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); + } + + // ========== 各种创建 spring-ai 客户端的方法 ========== + + /** + * 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法 + */ + private static DashScopeChatModel buildTongYiChatModel(String key) { + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build(); + DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL) + .withTemperature(0.7).build(); + return DashScopeChatModel.builder() + .dashScopeApi(dashScopeApi) + .defaultOptions(options) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link DashScopeImageAutoConfiguration} 的 dashScopeImageModel 方法 + */ + private static DashScopeImageModel buildTongYiImagesModel(String key) { + DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(key).build(); + return DashScopeImageModel.builder() + .dashScopeApi(dashScopeImageApi) + .build(); + } + + /** + * 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法 + */ + private static QianFanChatModel buildYiYanChatModel(String key) { + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 + List keys = StrUtil.split(key, '|'); + Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); + String appKey = keys.get(0); + String secretKey = keys.get(1); + QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); + return new QianFanChatModel(qianFanApi); + } + + /** + * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法 + */ + private QianFanImageModel buildQianFanImageModel(String key) { + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6 + List keys = StrUtil.split(key, '|'); + Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); + String appKey = keys.get(0); + String secretKey = keys.get(1); + QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey); + return new QianFanImageModel(qianFanApi); + } + + /** + * 可参考 {@link DeepSeekChatAutoConfiguration} 的 deepSeekChatModel 方法 + */ + private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { + DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build(); + DeepSeekChatOptions options = DeepSeekChatOptions.builder().model(DeepSeekApi.DEFAULT_CHAT_MODEL) + .temperature(0.7).build(); + return DeepSeekChatModel.builder() + .deepSeekApi(deepSeekApi) + .defaultOptions(options) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)} + */ + private ChatModel buildDouBaoChatModel(String apiKey) { + YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties() + .setApiKey(apiKey); + return new AiAutoConfiguration().buildDouBaoChatClient(properties); + } + + /** + * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)} + */ + private ChatModel buildHunYuanChatModel(String apiKey, String url) { + YudaoAiProperties.HunYuanProperties properties = new YudaoAiProperties.HunYuanProperties() + .setBaseUrl(url).setApiKey(apiKey); + return new AiAutoConfiguration().buildHunYuanChatClient(properties); + } + + /** + * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} + */ + private ChatModel buildSiliconFlowChatModel(String apiKey) { + YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties() + .setApiKey(apiKey); + return new AiAutoConfiguration().buildSiliconFlowChatClient(properties); + } + + /** + * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 + */ + private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { + ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) + : new ZhiPuAiApi(url, apiKey); + ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); + return new ZhiPuAiChatModel(zhiPuAiApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE, + getObservationRegistry().getIfAvailable()); + } + + /** + * 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法 + */ + private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { + ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey) + : new ZhiPuAiImageApi(url, apiKey, RestClient.builder()); + return new ZhiPuAiImageModel(zhiPuAiApi); + } + + /** + * 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法 + */ + private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { + MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey) + : new MiniMaxApi(url, apiKey); + MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); + return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE); + } + + /** + * 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法 + */ + private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { + MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder() + .apiKey(apiKey); + if (StrUtil.isNotEmpty(url)) { + moonshotApiBuilder.baseUrl(url); + } + MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build(); + return MoonshotChatModel.builder() + .moonshotApi(moonshotApiBuilder.build()) + .defaultOptions(options) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} + */ + private static XingHuoChatModel buildXingHuoChatModel(String key) { + List keys = StrUtil.split(key, '|'); + Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); + YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties() + .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); + return new AiAutoConfiguration().buildXingHuoChatClient(properties); + } + + /** + * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} + */ + private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { + YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties() + .setApiKey(apiKey); + return new AiAutoConfiguration().buildBaiChuanChatClient(properties); + } + + /** + * 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法 + */ + private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { + url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); + OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); + return OpenAiChatModel.builder() + .openAiApi(openAiApi) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link AzureOpenAiChatAutoConfiguration} + */ + private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) { + // TODO @芋艿:使用前,请测试,暂时没密钥!!! + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() + .endpoint(url).credential(new KeyCredential(apiKey)); + return AzureOpenAiChatModel.builder() + .openAIClientBuilder(openAIClientBuilder) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link AnthropicChatAutoConfiguration} 的 anthropicApi 方法 + */ + private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) { + AnthropicApi.Builder builder = AnthropicApi.builder().apiKey(apiKey); + if (StrUtil.isNotEmpty(url)) { + builder.baseUrl(url); + } + AnthropicApi anthropicApi = builder.build(); + return AnthropicChatModel.builder() + .anthropicApi(anthropicApi) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.GeminiProperties)} + */ + private static GeminiChatModel buildGeminiChatModel(String apiKey) { + YudaoAiProperties.GeminiProperties properties = SpringUtil.getBean(YudaoAiProperties.class) + .getGemini().setApiKey(apiKey); + return new AiAutoConfiguration().buildGeminiChatClient(properties); + } + + /** + * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法 + */ + private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { + url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); + OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build(); + return new OpenAiImageModel(openAiApi); + } + + /** + * 创建 SiliconFlowImageModel 对象 + */ + private SiliconFlowImageModel buildSiliconFlowImageModel(String apiToken, String url) { + url = StrUtil.blankToDefault(url, SiliconFlowApiConstants.DEFAULT_BASE_URL); + SiliconFlowImageApi openAiApi = new SiliconFlowImageApi(url, apiToken); + return new SiliconFlowImageModel(openAiApi); + } + + /** + * 可参考 {@link OllamaChatAutoConfiguration} 的 ollamaChatModel 方法 + */ + private static OllamaChatModel buildOllamaChatModel(String url) { + OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); + return OllamaChatModel.builder() + .ollamaApi(ollamaApi) + .toolCallingManager(getToolCallingManager()) + .build(); + } + + /** + * 可参考 {@link StabilityAiImageAutoConfiguration} 的 stabilityAiImageModel 方法 + */ + private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { + url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL); + StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url); + return new StabilityAiImageModel(stabilityAiApi); + } + + // ========== 各种创建 EmbeddingModel 的方法 ========== + + /** + * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法 + */ + private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); + DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build(); + return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); + } + + /** + * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法 + */ + private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { + ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) + : new ZhiPuAiApi(url, apiKey); + ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build(); + return new ZhiPuAiEmbeddingModel(zhiPuAiApi, MetadataMode.EMBED, zhiPuAiEmbeddingOptions); + } + + /** + * 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法 + */ + private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) { + MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey) + : new MiniMaxApi(url, apiKey); + MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build(); + return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions); + } + + /** + * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法 + */ + private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) { + List keys = StrUtil.split(key, '|'); + Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); + String appKey = keys.get(0); + String secretKey = keys.get(1); + QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); + QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build(); + return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions); + } + + private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { + OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); + OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build(); + return OllamaEmbeddingModel.builder() + .ollamaApi(ollamaApi) + .defaultOptions(ollamaOptions) + .build(); + } + + /** + * 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法 + */ + private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) { + url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); + OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); + OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build(); + return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties); + } + + /** + * 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法 + */ + private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) { + // TODO @芋艿:手头暂时没密钥,使用建议再测试下 + AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration(); + // 创建 OpenAIClientBuilder 对象 + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder() + .endpoint(url).credential(new KeyCredential(apiKey)); + // 获取 AzureOpenAiChatProperties 对象 + AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class); + return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties, + getObservationRegistry(), getEmbeddingModelObservationConvention()); + } + + // ========== 各种创建 VectorStore 的方法 ========== + + /** + * 注意:仅适合本地测试使用,生产建议还是使用 Qdrant、Milvus 等 + */ + @SneakyThrows + @SuppressWarnings("ResultOfMethodCallIgnored") + private SimpleVectorStore buildSimpleVectorStore(EmbeddingModel embeddingModel) { + SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build(); + // 启动加载 + File file = new File(StrUtil.format("{}/vector_store/simple_{}.json", + FileUtil.getUserHomePath(), embeddingModel.getClass().getSimpleName())); + if (!file.exists()) { + FileUtil.mkParentDirs(file); + file.createNewFile(); + } else if (file.length() > 0) { + vectorStore.load(file); + } + // 定时持久化,每分钟一次 + Timer timer = new Timer("SimpleVectorStoreTimer-" + file.getAbsolutePath()); + timer.scheduleAtFixedRate(new TimerTask() { + + @Override + public void run() { + vectorStore.save(file); + } + + }, Duration.ofMinutes(1).toMillis(), Duration.ofMinutes(1).toMillis()); + // 关闭时,进行持久化 + RuntimeUtil.addShutdownHook(() -> vectorStore.save(file)); + return vectorStore; + } + + /** + * 参考 {@link QdrantVectorStoreAutoConfiguration} 的 vectorStore 方法 + */ + @SneakyThrows + private QdrantVectorStore buildQdrantVectorStore(EmbeddingModel embeddingModel) { + QdrantVectorStoreAutoConfiguration configuration = new QdrantVectorStoreAutoConfiguration(); + QdrantVectorStoreProperties properties = SpringUtil.getBean(QdrantVectorStoreProperties.class); + // 参考 QdrantVectorStoreAutoConfiguration 实现,创建 QdrantClient 对象 + QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder( + properties.getHost(), properties.getPort(), properties.isUseTls()); + if (StrUtil.isNotEmpty(properties.getApiKey())) { + grpcClientBuilder.withApiKey(properties.getApiKey()); + } + QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build()); + // 创建 QdrantVectorStore 对象 + QdrantVectorStore vectorStore = configuration.vectorStore(embeddingModel, properties, qdrantClient, + getObservationRegistry(), getCustomObservationConvention(), getBatchingStrategy()); + // 初始化索引 + vectorStore.afterPropertiesSet(); + return vectorStore; + } + + /** + * 参考 {@link RedisVectorStoreAutoConfiguration} 的 vectorStore 方法 + */ + private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel, + Map> metadataFields) { + // 创建 JedisPooled 对象 + RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); + JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(), + redisProperties.getUsername(), redisProperties.getPassword()); + // 创建 RedisVectorStoreProperties 对象 + RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class); + RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel) + .indexName(properties.getIndexName()).prefix(properties.getPrefix()) + .initializeSchema(properties.isInitializeSchema()) + .metadataFields(convertList(metadataFields.entrySet(), entry -> { + String fieldName = entry.getKey(); + Class fieldType = entry.getValue(); + if (Number.class.isAssignableFrom(fieldType)) { + return RedisVectorStore.MetadataField.numeric(fieldName); + } + if (Boolean.class.isAssignableFrom(fieldType)) { + return RedisVectorStore.MetadataField.tag(fieldName); + } + return RedisVectorStore.MetadataField.text(fieldName); + })) + .observationRegistry(getObservationRegistry().getObject()) + .customObservationConvention(getCustomObservationConvention().getObject()) + .batchingStrategy(getBatchingStrategy()) + .build(); + // 初始化索引 + redisVectorStore.afterPropertiesSet(); + return redisVectorStore; + } + + /** + * 参考 {@link MilvusVectorStoreAutoConfiguration} 的 vectorStore 方法 + */ + @SneakyThrows + private MilvusVectorStore buildMilvusVectorStore(EmbeddingModel embeddingModel) { + MilvusVectorStoreAutoConfiguration configuration = new MilvusVectorStoreAutoConfiguration(); + // 获取配置属性 + MilvusVectorStoreProperties serverProperties = SpringUtil.getBean(MilvusVectorStoreProperties.class); + MilvusServiceClientProperties clientProperties = SpringUtil.getBean(MilvusServiceClientProperties.class); + + // 创建 MilvusServiceClient 对象 + MilvusServiceClient milvusClient = configuration.milvusClient(serverProperties, clientProperties, + new MilvusServiceClientConnectionDetails() { + + @Override + public String getHost() { + return clientProperties.getHost(); + } + + @Override + public int getPort() { + return clientProperties.getPort(); + } + + } + ); + // 创建 MilvusVectorStore 对象 + MilvusVectorStore vectorStore = configuration.vectorStore(milvusClient, embeddingModel, serverProperties, + getBatchingStrategy(), getObservationRegistry(), getCustomObservationConvention()); + + // 初始化索引 + vectorStore.afterPropertiesSet(); + return vectorStore; + } + + private static ObjectProvider getObservationRegistry() { + return new ObjectProvider<>() { + + @Override + public ObservationRegistry getObject() throws BeansException { + return SpringUtil.getBean(ObservationRegistry.class); + } + + }; + } + + private static ObjectProvider getCustomObservationConvention() { + return new ObjectProvider<>() { + + @Override + public VectorStoreObservationConvention getObject() throws BeansException { + return new DefaultVectorStoreObservationConvention(); + } + + }; + } + + private static BatchingStrategy getBatchingStrategy() { + return SpringUtil.getBean(BatchingStrategy.class); + } + + private static ToolCallingManager getToolCallingManager() { + return SpringUtil.getBean(ToolCallingManager.class); + } + + private static ObjectProvider getEmbeddingModelObservationConvention() { + return new ObjectProvider<>() { + + @Override + public EmbeddingModelObservationConvention getObject() throws BeansException { + return SpringUtil.getBean(EmbeddingModelObservationConvention.class); + } + + }; + } + +} diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java index ec807cf..235e54a 100644 --- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java +++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.ai.service.model; import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; -import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -- libgit2 0.21.4