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.text.DashScopeEmbeddingModel; import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingOptions; import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; import com.anthropic.client.okhttp.AnthropicOkHttpClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.KeyCredential; import com.openai.client.OpenAIClient; import com.openai.client.okhttp.OpenAIOkHttpClient; 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.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.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.OllamaEmbeddingOptions; 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.anthropic.AnthropicChatModel; 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.data.redis.autoconfigure.DataRedisProperties; 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; /** * 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); case GROK: return buildGrokChatModel(apiKey,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.Midjourney 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() .model(DashScopeApi.DEFAULT_CHAT_MODEL) .temperature(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(); } /** * 可参考 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); } /** * 可参考 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.DouBao properties = new YudaoAiProperties.DouBao() .setApiKey(apiKey); return new AiAutoConfiguration().buildDouBaoChatClient(properties); } /** * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)} */ private ChatModel buildHunYuanChatModel(String apiKey, String url) { YudaoAiProperties.HunYuan properties = new YudaoAiProperties.HunYuan() .setBaseUrl(url).setApiKey(apiKey); return new AiAutoConfiguration().buildHunYuanChatClient(properties); } /** * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} */ private ChatModel buildSiliconFlowChatModel(String apiKey) { YudaoAiProperties.SiliconFlow properties = new YudaoAiProperties.SiliconFlow() .setApiKey(apiKey); return new AiAutoConfiguration().buildSiliconFlowChatClient(properties); } /** * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 */ private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); if (StrUtil.isNotEmpty(url)) { zhiPuAiApiBuilder.baseUrl(url); } ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), new org.springframework.core.retry.RetryTemplate(), 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(), new org.springframework.core.retry.RetryTemplate()); } /** * 可参考 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.XingHuo properties = new YudaoAiProperties.XingHuo() .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); return new AiAutoConfiguration().buildXingHuoChatClient(properties); } /** * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} */ private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { YudaoAiProperties.BaiChuan properties = new YudaoAiProperties.BaiChuan() .setApiKey(apiKey); return new AiAutoConfiguration().buildBaiChuanChatClient(properties); } /** * 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法 */ private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { return OpenAiChatModel.builder() .openAiClient(buildOpenAiClient(openAiToken, url)) .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) { AnthropicOkHttpClient.Builder builder = AnthropicOkHttpClient.builder().apiKey(apiKey); if (StrUtil.isNotEmpty(url)) { builder.baseUrl(url); } return AnthropicChatModel.builder() .anthropicClient(builder.build()) .toolCallingManager(getToolCallingManager()) .build(); } /** * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.Gemini)} */ private static GeminiChatModel buildGeminiChatModel(String apiKey) { YudaoAiProperties.Gemini properties = SpringUtil.getBean(YudaoAiProperties.class) .getGemini().setApiKey(apiKey); return new AiAutoConfiguration().buildGeminiChatClient(properties); } /** * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法 */ private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { return new OpenAiImageModel(buildOpenAiClient(openAiToken, url)); } /** * 创建 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); } private ChatModel buildGrokChatModel(String apiKey,String url) { YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() .setBaseUrl(url) .setApiKey(apiKey); return new AiAutoConfiguration().buildGrokChatClient(properties); } // ========== 各种创建 EmbeddingModel 的方法 ========== /** * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 DashScopeEmbeddingModel 方法 */ private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().model(model).build(); return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); } /** * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 ZhiPuAiEmbeddingModel 方法 */ private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); if (StrUtil.isNotEmpty(url)) { zhiPuAiApiBuilder.baseUrl(url); } ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build(); return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), 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 QianFanEmbeddingModel} 的 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(); OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); return OllamaEmbeddingModel.builder() .ollamaApi(ollamaApi) .defaultOptions(ollamaOptions) .build(); } /** * 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法 */ private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) { OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build(); return new OpenAiEmbeddingModel(buildOpenAiClient(openAiToken, url), MetadataMode.EMBED, openAiEmbeddingProperties); } private static OpenAIClient buildOpenAiClient(String apiKey, String url) { OpenAIOkHttpClient.Builder builder = OpenAIOkHttpClient.builder().apiKey(apiKey); if (StrUtil.isNotEmpty(url)) { builder.baseUrl(url); } return builder.build(); } /** * 可参考 {@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 对象 DataRedisProperties redisProperties = SpringUtils.getBean(DataRedisProperties.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); } }; } }