diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
index 09de7263c5..e979056d4e 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
@@ -1,4 +1,4 @@
/**
- * crm 模块的 web 拓展封装
+ * ai 模块的 web 拓展封装
*/
-package cn.iocoder.yudao.module.crm.framework.web;
+package cn.iocoder.yudao.module.ai.framework.web;
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
index e8532a5762..3e8c09f079 100644
--- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
-import com.alibaba.cloud.ai.tongyi.image.TongYiImagesOptions;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.image.ImageModel;
@@ -144,7 +143,7 @@ public class AiImageServiceImpl implements AiImageService {
.withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
.build();
} else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
- return TongYiImagesOptions.builder()
+ return DashScopeImageOptions.builder()
.withModel(draw.getModel()).withN(1)
.withHeight(draw.getHeight()).withWidth(draw.getWidth())
.build();
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
index a270f76558..156d3f0769 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -14,8 +14,8 @@
${project.artifactId}
AI 大模型拓展,接入国内外大模型
- group.springframework.ai
- 1.1.0
+ org.springframework.ai
+ 1.0.0-M6
@@ -90,6 +90,11 @@
spring-ai-qianfan-spring-boot-starter
${spring-ai.version}
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-starter
+ 1.0.0-M5.1
+
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
index 0d2620b0cb..3aed3ee1b9 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
@@ -1,15 +1,16 @@
package cn.iocoder.yudao.framework.ai.config;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
-import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
-import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
-import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
import org.springframework.ai.tokenizer.TokenCountEstimator;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
@@ -17,7 +18,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
/**
@@ -28,7 +28,6 @@ import org.springframework.context.annotation.Lazy;
@AutoConfiguration
@EnableConfigurationProperties(YudaoAiProperties.class)
@Slf4j
-@Import(TongYiAutoConfiguration.class)
public class YudaoAiAutoConfiguration {
@Bean
@@ -43,26 +42,52 @@ public class YudaoAiAutoConfiguration {
@ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek();
- DeepSeekChatOptions options = DeepSeekChatOptions.builder()
- .model(properties.getModel())
- .temperature(properties.getTemperature())
- .maxTokens(properties.getMaxTokens())
- .topP(properties.getTopP())
+ return buildDeepSeekChatModel(properties);
+ }
+
+ public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(DeepSeekChatModel.BASE_URL)
+ .apiKey(properties.getApiKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
.build();
- return new DeepSeekChatModel(properties.getApiKey(), options);
+ return new DeepSeekChatModel(openAiChatModel);
}
@Bean
@ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
- XingHuoChatOptions options = XingHuoChatOptions.builder()
- .model(properties.getModel())
- .temperature(properties.getTemperature())
- .maxTokens(properties.getMaxTokens())
- .topK(properties.getTopK())
+ return buildXingHuoChatClient(properties);
+ }
+
+ public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
+ if (StrUtil.isEmpty(properties.getModel())) {
+ properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
+ }
+ OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(XingHuoChatModel.BASE_URL)
+ .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model(properties.getModel())
+ .temperature(properties.getTemperature())
+ .maxTokens(properties.getMaxTokens())
+ .topP(properties.getTopP())
+ .build())
.build();
- return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options);
+ return new XingHuoChatModel(openAiChatModel);
}
@Bean
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
index 82c74b0c64..7a98c4376a 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
@@ -42,9 +42,9 @@ public class YudaoAiProperties {
private String secretKey;
private String model;
- private Float temperature;
+ private Double temperature;
private Integer maxTokens;
- private Integer topK;
+ private Double topP;
}
@@ -55,9 +55,9 @@ public class YudaoAiProperties {
private String apiKey;
private String model;
- private Float temperature;
+ private Double temperature;
private Integer maxTokens;
- private Float topP;
+ private Double topP;
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
index 7acd247691..985d8fd041 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
@@ -13,60 +13,45 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
-import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
-import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
-import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties;
-import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;
-import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties;
-import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;
-import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;
-import com.alibaba.dashscope.aigc.generation.Generation;
-import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
-import com.alibaba.dashscope.embeddings.TextEmbedding;
-import com.azure.ai.openai.OpenAIClient;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration;
+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.embedding.DashScopeEmbeddingModel;
+import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
+import com.azure.ai.openai.OpenAIClientBuilder;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
-import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties;
-import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties;
-import org.springframework.ai.autoconfigure.qianfan.QianFanImageProperties;
import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
-import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties;
import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
-import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.image.ImageModel;
-import org.springframework.ai.model.function.FunctionCallbackContext;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiImageModel;
-import org.springframework.ai.openai.api.ApiUtils;
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.qianfan.QianFanChatModel;
import org.springframework.ai.qianfan.QianFanImageModel;
import org.springframework.ai.qianfan.api.QianFanApi;
import org.springframework.ai.qianfan.api.QianFanImageApi;
import org.springframework.ai.stabilityai.StabilityAiImageModel;
import org.springframework.ai.stabilityai.api.StabilityAiApi;
-import org.springframework.ai.vectorstore.RedisVectorStore;
+import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
-import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
-import org.springframework.retry.support.RetryTemplate;
-import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
-import redis.clients.jedis.JedisPooled;
-import redis.clients.jedis.search.Schema;
import java.util.List;
@@ -110,7 +95,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
//noinspection EnhancedSwitchMigration
switch (platform) {
case TONG_YI:
- return SpringUtil.getBean(TongYiChatModel.class);
+ return SpringUtil.getBean(DashScopeChatModel.class);
case YI_YAN:
return SpringUtil.getBean(QianFanChatModel.class);
case DEEP_SEEK:
@@ -135,7 +120,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
//noinspection EnhancedSwitchMigration
switch (platform) {
case TONG_YI:
- return SpringUtil.getBean(TongYiImagesModel.class);
+ return SpringUtil.getBean(DashScopeImageModel.class);
case YI_YAN:
return SpringUtil.getBean(QianFanImageModel.class);
case ZHI_PU:
@@ -202,17 +187,20 @@ public class AiModelFactoryImpl implements AiModelFactory {
String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
return Singleton.get(cacheKey, (Func0) () -> {
String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
- var config = RedisVectorStore.RedisVectorStoreConfig.builder()
- .withIndexName(cacheKey)
- .withPrefix(prefix)
- .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
- .build();
- RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
- RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
- new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
- true);
- redisVectorStore.afterPropertiesSet();
- return redisVectorStore;
+ // TODO @芋艿:先临时使用 store
+ return SimpleVectorStore.builder(embeddingModel).build();
+ // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的
+// var config = RedisVectorStore.RedisVectorStoreConfig.builder()
+// .withIndexName(cacheKey)
+// .withPrefix(prefix)
+// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
+// .build();
+// RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
+// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
+// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
+// true);
+// redisVectorStore.afterPropertiesSet();
+// return redisVectorStore;
});
}
@@ -226,29 +214,23 @@ public class AiModelFactoryImpl implements AiModelFactory {
// ========== 各种创建 spring-ai 客户端的方法 ==========
/**
- * 可参考 {@link TongYiAutoConfiguration#tongYiChatClient(Generation, TongYiChatProperties, TongYiConnectionProperties)}
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
*/
- private static TongYiChatModel buildTongYiChatModel(String key) {
- com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class);
- TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class);
- // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下
- // TODO @芋艿:貌似阿里云不是增量返回的
- // 该 issue 进行跟进中 https://github.com/alibaba/spring-cloud-alibaba/issues/3790
- TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
- connectionProperties.setApiKey(key);
- return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties);
- }
-
- private static TongYiImagesModel buildTongYiImagesModel(String key) {
- ImageSynthesis imageSynthesis = SpringUtil.getBean(ImageSynthesis.class);
- TongYiImagesProperties imagesOptions = SpringUtil.getBean(TongYiImagesProperties.class);
- TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
- connectionProperties.setApiKey(key);
- return new TongYiAutoConfiguration().tongYiImagesClient(imageSynthesis, imagesOptions, connectionProperties);
+ private static DashScopeChatModel buildTongYiChatModel(String key) {
+ DashScopeApi dashScopeApi = new DashScopeApi(key);
+ return new DashScopeChatModel(dashScopeApi);
}
/**
- * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashScopeImageModel 方法
+ */
+ private static DashScopeImageModel buildTongYiImagesModel(String key) {
+ DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
+ return new DashScopeImageModel(dashScopeImageApi);
+ }
+
+ /**
+ * 可参考 {@link QianFanAutoConfiguration} 的 qianFanChatModel 方法
*/
private static QianFanChatModel buildYiYanChatModel(String key) {
List keys = StrUtil.split(key, '|');
@@ -260,7 +242,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
}
/**
- * 可参考 {@link QianFanAutoConfiguration#qianFanImageModel(QianFanConnectionProperties, QianFanImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
+ * 可参考 {@link QianFanAutoConfiguration} 的 qianFanImageModel 方法
*/
private QianFanImageModel buildQianFanImageModel(String key) {
List keys = StrUtil.split(key, '|');
@@ -275,11 +257,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
* 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)}
*/
private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
- return new DeepSeekChatModel(apiKey);
+ YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()
+ .setApiKey(apiKey);
+ return new YudaoAiAutoConfiguration().buildDeepSeekChatModel(properties);
}
/**
- * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)}
+ * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
*/
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -288,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
}
/**
- * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
+ * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiImageModel 方法
*/
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -301,21 +285,22 @@ public class AiModelFactoryImpl implements AiModelFactory {
*/
private static XingHuoChatModel buildXingHuoChatModel(String key) {
List keys = StrUtil.split(key, '|');
- Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式");
- String appKey = keys.get(1);
- String secretKey = keys.get(2);
- return new XingHuoChatModel(appKey, secretKey);
+ Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
+ YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties()
+ .setAppKey(keys.get(0)).setSecretKey(keys.get(1));
+ return new YudaoAiAutoConfiguration().buildXingHuoChatClient(properties);
}
/**
- * 可参考 {@link OpenAiAutoConfiguration}
+ * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法
*/
private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
- url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);
- OpenAiApi openAiApi = new OpenAiApi(url, openAiToken);
- return new OpenAiChatModel(openAiApi);
+ url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
+ OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
+ return OpenAiChatModel.builder().openAiApi(openAiApi).build();
}
+ // TODO @芋艿:手头暂时没密钥,使用建议再测试下
/**
* 可参考 {@link AzureOpenAiAutoConfiguration}
*/
@@ -325,27 +310,28 @@ public class AiModelFactoryImpl implements AiModelFactory {
AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
connectionProperties.setApiKey(apiKey);
connectionProperties.setEndpoint(url);
- OpenAIClient openAIClient = azureOpenAiAutoConfiguration.openAIClient(connectionProperties);
+ OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
// 获取 AzureOpenAiChatProperties 对象
AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
- return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties, null, null);
+ return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,
+ null, null, null);
}
/**
- * 可参考 {@link OpenAiAutoConfiguration}
+ * 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法
*/
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
- url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);
- OpenAiImageApi openAiApi = new OpenAiImageApi(url, openAiToken, RestClient.builder());
+ url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
+ OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build();
return new OpenAiImageModel(openAiApi);
}
/**
- * 可参考 {@link OllamaAutoConfiguration}
+ * 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法
*/
private static OllamaChatModel buildOllamaChatModel(String url) {
OllamaApi ollamaApi = new OllamaApi(url);
- return new OllamaChatModel(ollamaApi);
+ return OllamaChatModel.builder().ollamaApi(ollamaApi).build();
}
private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) {
@@ -356,13 +342,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
// ========== 各种创建 EmbeddingModel 的方法 ==========
+ // TODO @芋艿:需要测试下
/**
- * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)}
+ * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
*/
private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
- TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
- connectionProperties.setApiKey(apiKey);
- return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties);
+ DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
+ return new DashScopeEmbeddingModel(dashScopeApi);
}
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
index 55091c78d4..a03a0c844f 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
@@ -8,9 +8,10 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.ai.openai.api.ApiUtils;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@@ -18,6 +19,7 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -50,11 +52,19 @@ public class MidjourneyApi {
public MidjourneyApi(String baseUrl, String apiKey, String notifyUrl) {
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
- .defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey))
+ .defaultHeaders(getJsonContentHeaders(apiKey))
.build();
this.notifyUrl = notifyUrl;
}
+ // TODO @芋艿:这里,看看怎么调整下???https://github.com/spring-projects/spring-ai/issues/741
+ public static Consumer getJsonContentHeaders(String apiKey) {
+ return (headers) -> {
+ headers.setBearerAuth(apiKey);
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ };
+ };
+
/**
* imagine - 根据提示词提交绘画任务
*
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
index e18f100156..7203db4593 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
@@ -2,9 +2,7 @@ package cn.iocoder.yudao.framework.ai.core.util;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
-import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;
-import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
-import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
+import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.ChatOptions;
@@ -21,26 +19,24 @@ import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
public class AiUtils {
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
- Float temperatureF = temperature != null ? temperature.floatValue() : null;
//noinspection EnhancedSwitchMigration
switch (platform) {
case TONG_YI:
- return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();
+ // TODO @芋艿:tongyi 暂时没 maxTokens 选项
+ return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).build();
case YI_YAN:
- return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
- case DEEP_SEEK:
- return DeepSeekChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build();
+ return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
case ZHI_PU:
- return ZhiPuAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
- case XING_HUO:
- return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build();
+ return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
case OPENAI:
- return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
+ case DEEP_SEEK: // 复用 OpenAI 客户端
+ case XING_HUO: // 复用 OpenAI 客户端
+ return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
case AZURE_OPENAI:
// TODO 芋艿:貌似没 model 字段???!
- return AzureOpenAiChatOptions.builder().withDeploymentName(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
+ return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens).build();
case OLLAMA:
- return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens);
+ return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens).build();
default:
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
}
@@ -56,8 +52,8 @@ public class AiUtils {
if (MessageType.SYSTEM.getValue().equals(type)) {
return new SystemMessage(content);
}
- if (MessageType.FUNCTION.getValue().equals(type)) {
- return new FunctionMessage(content);
+ if (MessageType.TOOL.getValue().equals(type)) {
+ throw new UnsupportedOperationException("暂不支持 tool 消息:" + content);
}
throw new IllegalArgumentException(StrUtil.format("未知消息类型({})", type));
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java
deleted file mode 100644
index a72d50c4a8..0000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2023 - 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.ai.autoconfigure.vectorstore.redis;
-
-import org.springframework.ai.embedding.EmbeddingModel;
-import org.springframework.ai.vectorstore.RedisVectorStore;
-import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
-import redis.clients.jedis.JedisPooled;
-
-/**
- * TODO @xin 先拿 spring-ai 最新代码覆盖,1.0.0-M1 跟 redis 自动配置会冲突
- *
- * TODO 这个官方,有说啥时候 fix 哇?
- * TODO 看着是列在1.0.0-M2版本
- *
- * @author Christian Tzolov
- * @author Eddú Meléndez
- */
-@AutoConfiguration(after = RedisAutoConfiguration.class)
-@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})
-@ConditionalOnBean(JedisConnectionFactory.class)
-@EnableConfigurationProperties(RedisVectorStoreProperties.class)
-public class RedisVectorStoreAutoConfiguration {
-
- @Bean
- @ConditionalOnMissingBean
- public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,
- JedisConnectionFactory jedisConnectionFactory) {
-
- var config = RedisVectorStoreConfig.builder()
- .withIndexName(properties.getIndex())
- .withPrefix(properties.getPrefix())
- .build();
-
- return new RedisVectorStore(config, embeddingModel,
- new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),
- properties.isInitializeSchema());
- }
-
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java
deleted file mode 100644
index de80401ed1..0000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * Copyright 2023 - 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.springframework.ai.vectorstore;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.ai.document.Document;
-import org.springframework.ai.embedding.EmbeddingModel;
-import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-import redis.clients.jedis.JedisPooled;
-import redis.clients.jedis.Pipeline;
-import redis.clients.jedis.json.Path2;
-import redis.clients.jedis.search.*;
-import redis.clients.jedis.search.Schema.FieldType;
-import redis.clients.jedis.search.schemafields.*;
-import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;
-
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * The RedisVectorStore is for managing and querying vector data in a Redis database. It
- * offers functionalities like adding, deleting, and performing similarity searches on
- * documents.
- *
- * The store utilizes RedisJSON and RedisSearch to handle JSON documents and to index and
- * search vector data. It supports various vector algorithms (e.g., FLAT, HSNW) for
- * efficient similarity searches. Additionally, it allows for custom metadata fields in
- * the documents to be stored alongside the vector and content data.
- *
- * This class requires a RedisVectorStoreConfig configuration object for initialization,
- * which includes settings like Redis URI, index name, field names, and vector algorithms.
- * It also requires an EmbeddingModel to convert documents into embeddings before storing
- * them.
- *
- * @author Julien Ruaux
- * @author Christian Tzolov
- * @author Eddú Meléndez
- * @see VectorStore
- * @see RedisVectorStoreConfig
- * @see EmbeddingModel
- */
-public class RedisVectorStore implements VectorStore, InitializingBean {
-
- public enum Algorithm {
-
- FLAT, HSNW
-
- }
-
- public record MetadataField(String name, FieldType fieldType) {
-
- public static MetadataField text(String name) {
- return new MetadataField(name, FieldType.TEXT);
- }
-
- public static MetadataField numeric(String name) {
- return new MetadataField(name, FieldType.NUMERIC);
- }
-
- public static MetadataField tag(String name) {
- return new MetadataField(name, FieldType.TAG);
- }
-
- }
-
- /**
- * Configuration for the Redis vector store.
- */
- public static final class RedisVectorStoreConfig {
-
- private final String indexName;
-
- private final String prefix;
-
- private final String contentFieldName;
-
- private final String embeddingFieldName;
-
- private final Algorithm vectorAlgorithm;
-
- private final List metadataFields;
-
- private RedisVectorStoreConfig() {
- this(builder());
- }
-
- private RedisVectorStoreConfig(Builder builder) {
- this.indexName = builder.indexName;
- this.prefix = builder.prefix;
- this.contentFieldName = builder.contentFieldName;
- this.embeddingFieldName = builder.embeddingFieldName;
- this.vectorAlgorithm = builder.vectorAlgorithm;
- this.metadataFields = builder.metadataFields;
- }
-
- /**
- * Start building a new configuration.
- * @return The entry point for creating a new configuration.
- */
- public static Builder builder() {
-
- return new Builder();
- }
-
- /**
- * {@return the default config}
- */
- public static RedisVectorStoreConfig defaultConfig() {
-
- return builder().build();
- }
-
- public static class Builder {
-
- private String indexName = DEFAULT_INDEX_NAME;
-
- private String prefix = DEFAULT_PREFIX;
-
- private String contentFieldName = DEFAULT_CONTENT_FIELD_NAME;
-
- private String embeddingFieldName = DEFAULT_EMBEDDING_FIELD_NAME;
-
- private Algorithm vectorAlgorithm = DEFAULT_VECTOR_ALGORITHM;
-
- private List metadataFields = new ArrayList<>();
-
- private Builder() {
- }
-
- /**
- * Configures the Redis index name to use.
- * @param name the index name to use
- * @return this builder
- */
- public Builder withIndexName(String name) {
- this.indexName = name;
- return this;
- }
-
- /**
- * Configures the Redis key prefix to use (default: "embedding:").
- * @param prefix the prefix to use
- * @return this builder
- */
- public Builder withPrefix(String prefix) {
- this.prefix = prefix;
- return this;
- }
-
- /**
- * Configures the Redis content field name to use.
- * @param name the content field name to use
- * @return this builder
- */
- public Builder withContentFieldName(String name) {
- this.contentFieldName = name;
- return this;
- }
-
- /**
- * Configures the Redis embedding field name to use.
- * @param name the embedding field name to use
- * @return this builder
- */
- public Builder withEmbeddingFieldName(String name) {
- this.embeddingFieldName = name;
- return this;
- }
-
- /**
- * Configures the Redis vector algorithmto use.
- * @param algorithm the vector algorithm to use
- * @return this builder
- */
- public Builder withVectorAlgorithm(Algorithm algorithm) {
- this.vectorAlgorithm = algorithm;
- return this;
- }
-
- public Builder withMetadataFields(MetadataField... fields) {
- return withMetadataFields(Arrays.asList(fields));
- }
-
- public Builder withMetadataFields(List fields) {
- this.metadataFields = fields;
- return this;
- }
-
- /**
- * {@return the immutable configuration}
- */
- public RedisVectorStoreConfig build() {
-
- return new RedisVectorStoreConfig(this);
- }
-
- }
-
- }
-
- private final boolean initializeSchema;
-
- public static final String DEFAULT_INDEX_NAME = "spring-ai-index";
-
- public static final String DEFAULT_CONTENT_FIELD_NAME = "content";
-
- public static final String DEFAULT_EMBEDDING_FIELD_NAME = "embedding";
-
- public static final String DEFAULT_PREFIX = "embedding:";
-
- public static final Algorithm DEFAULT_VECTOR_ALGORITHM = Algorithm.HSNW;
-
- private static final String QUERY_FORMAT = "%s=>[KNN %s @%s $%s AS %s]";
-
- private static final Path2 JSON_SET_PATH = Path2.of("$");
-
- private static final String JSON_PATH_PREFIX = "$.";
-
- private static final Logger logger = LoggerFactory.getLogger(RedisVectorStore.class);
-
- private static final Predicate