Commit 7ef73b7d0992486c380c49e7f214f48ebf0f29d4

Authored by YunaiV
1 parent 3b7b8182

【代码重构】AI:spring ai 依赖,升级到 1.0.0-M6

Showing 18 changed files with 197 additions and 712 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java
1 /** 1 /**
2 - * crm 模块的 web 拓展封装 2 + * ai 模块的 web 拓展封装
3 */ 3 */
4 -package cn.iocoder.yudao.module.crm.framework.web; 4 +package cn.iocoder.yudao.module.ai.framework.web;
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; @@ -20,7 +20,6 @@ import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
20 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 20 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
21 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; 21 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
22 import cn.iocoder.yudao.module.infra.api.file.FileApi; 22 import cn.iocoder.yudao.module.infra.api.file.FileApi;
23 -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesOptions;  
24 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
25 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
26 import org.springframework.ai.image.ImageModel; 25 import org.springframework.ai.image.ImageModel;
@@ -144,7 +143,7 @@ public class AiImageServiceImpl implements AiImageService { @@ -144,7 +143,7 @@ public class AiImageServiceImpl implements AiImageService {
144 .withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset"))) 143 .withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset")))
145 .build(); 144 .build();
146 } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) { 145 } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
147 - return TongYiImagesOptions.builder() 146 + return DashScopeImageOptions.builder()
148 .withModel(draw.getModel()).withN(1) 147 .withModel(draw.getModel()).withN(1)
149 .withHeight(draw.getHeight()).withWidth(draw.getWidth()) 148 .withHeight(draw.getHeight()).withWidth(draw.getWidth())
150 .build(); 149 .build();
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -14,8 +14,8 @@ @@ -14,8 +14,8 @@
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 <description>AI 大模型拓展,接入国内外大模型</description> 15 <description>AI 大模型拓展,接入国内外大模型</description>
16 <properties> 16 <properties>
17 - <spring-ai.groupId>group.springframework.ai</spring-ai.groupId>  
18 - <spring-ai.version>1.1.0</spring-ai.version> 17 + <spring-ai.groupId>org.springframework.ai</spring-ai.groupId>
  18 + <spring-ai.version>1.0.0-M6</spring-ai.version>
19 </properties> 19 </properties>
20 20
21 <dependencies> 21 <dependencies>
@@ -90,6 +90,11 @@ @@ -90,6 +90,11 @@
90 <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId> 90 <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
91 <version>${spring-ai.version}</version> 91 <version>${spring-ai.version}</version>
92 </dependency> 92 </dependency>
  93 + <dependency>
  94 + <groupId>com.alibaba.cloud.ai</groupId>
  95 + <artifactId>spring-ai-alibaba-starter</artifactId>
  96 + <version>1.0.0-M5.1</version>
  97 + </dependency>
93 98
94 <!-- 阿里云 通义千问 --> 99 <!-- 阿里云 通义千问 -->
95 <!-- TODO 芋艿:等 spring cloud alibaba ai 发布最新的时候,可以替换掉这个依赖,并且删除我们直接 cv 的代码 --> 100 <!-- TODO 芋艿:等 spring cloud alibaba ai 发布最新的时候,可以替换掉这个依赖,并且删除我们直接 cv 的代码 -->
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
1 package cn.iocoder.yudao.framework.ai.config; 1 package cn.iocoder.yudao.framework.ai.config;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; 5 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
5 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; 6 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
6 -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;  
7 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 7 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
8 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 8 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
9 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; 9 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
10 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;  
11 -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;  
12 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.ai.openai.OpenAiChatModel;
  12 +import org.springframework.ai.openai.OpenAiChatOptions;
  13 +import org.springframework.ai.openai.api.OpenAiApi;
13 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; 14 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
14 import org.springframework.ai.tokenizer.TokenCountEstimator; 15 import org.springframework.ai.tokenizer.TokenCountEstimator;
15 import org.springframework.ai.transformer.splitter.TokenTextSplitter; 16 import org.springframework.ai.transformer.splitter.TokenTextSplitter;
@@ -17,7 +18,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -17,7 +18,6 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
17 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
18 import org.springframework.boot.context.properties.EnableConfigurationProperties; 19 import org.springframework.boot.context.properties.EnableConfigurationProperties;
19 import org.springframework.context.annotation.Bean; 20 import org.springframework.context.annotation.Bean;
20 -import org.springframework.context.annotation.Import;  
21 import org.springframework.context.annotation.Lazy; 21 import org.springframework.context.annotation.Lazy;
22 22
23 /** 23 /**
@@ -28,7 +28,6 @@ import org.springframework.context.annotation.Lazy; @@ -28,7 +28,6 @@ import org.springframework.context.annotation.Lazy;
28 @AutoConfiguration 28 @AutoConfiguration
29 @EnableConfigurationProperties(YudaoAiProperties.class) 29 @EnableConfigurationProperties(YudaoAiProperties.class)
30 @Slf4j 30 @Slf4j
31 -@Import(TongYiAutoConfiguration.class)  
32 public class YudaoAiAutoConfiguration { 31 public class YudaoAiAutoConfiguration {
33 32
34 @Bean 33 @Bean
@@ -43,26 +42,52 @@ public class YudaoAiAutoConfiguration { @@ -43,26 +42,52 @@ public class YudaoAiAutoConfiguration {
43 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") 42 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
44 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) { 43 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
45 YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek(); 44 YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek();
46 - DeepSeekChatOptions options = DeepSeekChatOptions.builder()  
47 - .model(properties.getModel())  
48 - .temperature(properties.getTemperature())  
49 - .maxTokens(properties.getMaxTokens())  
50 - .topP(properties.getTopP()) 45 + return buildDeepSeekChatModel(properties);
  46 + }
  47 +
  48 + public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
  49 + if (StrUtil.isEmpty(properties.getModel())) {
  50 + properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
  51 + }
  52 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  53 + .openAiApi(OpenAiApi.builder()
  54 + .baseUrl(DeepSeekChatModel.BASE_URL)
  55 + .apiKey(properties.getApiKey())
  56 + .build())
  57 + .defaultOptions(OpenAiChatOptions.builder()
  58 + .model(properties.getModel())
  59 + .temperature(properties.getTemperature())
  60 + .maxTokens(properties.getMaxTokens())
  61 + .topP(properties.getTopP())
  62 + .build())
51 .build(); 63 .build();
52 - return new DeepSeekChatModel(properties.getApiKey(), options); 64 + return new DeepSeekChatModel(openAiChatModel);
53 } 65 }
54 66
55 @Bean 67 @Bean
56 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") 68 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
57 public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { 69 public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
58 YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); 70 YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
59 - XingHuoChatOptions options = XingHuoChatOptions.builder()  
60 - .model(properties.getModel())  
61 - .temperature(properties.getTemperature())  
62 - .maxTokens(properties.getMaxTokens())  
63 - .topK(properties.getTopK()) 71 + return buildXingHuoChatClient(properties);
  72 + }
  73 +
  74 + public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
  75 + if (StrUtil.isEmpty(properties.getModel())) {
  76 + properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
  77 + }
  78 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  79 + .openAiApi(OpenAiApi.builder()
  80 + .baseUrl(XingHuoChatModel.BASE_URL)
  81 + .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
  82 + .build())
  83 + .defaultOptions(OpenAiChatOptions.builder()
  84 + .model(properties.getModel())
  85 + .temperature(properties.getTemperature())
  86 + .maxTokens(properties.getMaxTokens())
  87 + .topP(properties.getTopP())
  88 + .build())
64 .build(); 89 .build();
65 - return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options); 90 + return new XingHuoChatModel(openAiChatModel);
66 } 91 }
67 92
68 @Bean 93 @Bean
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 { @@ -42,9 +42,9 @@ public class YudaoAiProperties {
42 private String secretKey; 42 private String secretKey;
43 43
44 private String model; 44 private String model;
45 - private Float temperature; 45 + private Double temperature;
46 private Integer maxTokens; 46 private Integer maxTokens;
47 - private Integer topK; 47 + private Double topP;
48 48
49 } 49 }
50 50
@@ -55,9 +55,9 @@ public class YudaoAiProperties { @@ -55,9 +55,9 @@ public class YudaoAiProperties {
55 private String apiKey; 55 private String apiKey;
56 56
57 private String model; 57 private String model;
58 - private Float temperature; 58 + private Double temperature;
59 private Integer maxTokens; 59 private Integer maxTokens;
60 - private Float topP; 60 + private Double topP;
61 61
62 } 62 }
63 63
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; @@ -13,60 +13,45 @@ import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
13 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 13 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
14 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 14 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
15 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; 15 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
16 -import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;  
17 -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;  
18 -import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties;  
19 -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;  
20 -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties;  
21 -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel;  
22 -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties;  
23 -import com.alibaba.dashscope.aigc.generation.Generation;  
24 -import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;  
25 -import com.alibaba.dashscope.embeddings.TextEmbedding;  
26 -import com.azure.ai.openai.OpenAIClient; 16 +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration;
  17 +import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
  18 +import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
  19 +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
  20 +import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
  21 +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
  22 +import com.azure.ai.openai.OpenAIClientBuilder;
27 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration; 23 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
28 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties; 24 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
29 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties; 25 import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
30 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; 26 import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
31 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; 27 import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
32 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; 28 import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
33 -import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties;  
34 -import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties;  
35 -import org.springframework.ai.autoconfigure.qianfan.QianFanImageProperties;  
36 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; 29 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
37 -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties;  
38 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; 30 import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties;
39 -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties;  
40 import org.springframework.ai.azure.openai.AzureOpenAiChatModel; 31 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
41 import org.springframework.ai.chat.model.ChatModel; 32 import org.springframework.ai.chat.model.ChatModel;
42 import org.springframework.ai.embedding.EmbeddingModel; 33 import org.springframework.ai.embedding.EmbeddingModel;
43 import org.springframework.ai.image.ImageModel; 34 import org.springframework.ai.image.ImageModel;
44 -import org.springframework.ai.model.function.FunctionCallbackContext;  
45 import org.springframework.ai.ollama.OllamaChatModel; 35 import org.springframework.ai.ollama.OllamaChatModel;
46 import org.springframework.ai.ollama.api.OllamaApi; 36 import org.springframework.ai.ollama.api.OllamaApi;
47 import org.springframework.ai.openai.OpenAiChatModel; 37 import org.springframework.ai.openai.OpenAiChatModel;
48 import org.springframework.ai.openai.OpenAiImageModel; 38 import org.springframework.ai.openai.OpenAiImageModel;
49 -import org.springframework.ai.openai.api.ApiUtils;  
50 import org.springframework.ai.openai.api.OpenAiApi; 39 import org.springframework.ai.openai.api.OpenAiApi;
51 import org.springframework.ai.openai.api.OpenAiImageApi; 40 import org.springframework.ai.openai.api.OpenAiImageApi;
  41 +import org.springframework.ai.openai.api.common.OpenAiApiConstants;
52 import org.springframework.ai.qianfan.QianFanChatModel; 42 import org.springframework.ai.qianfan.QianFanChatModel;
53 import org.springframework.ai.qianfan.QianFanImageModel; 43 import org.springframework.ai.qianfan.QianFanImageModel;
54 import org.springframework.ai.qianfan.api.QianFanApi; 44 import org.springframework.ai.qianfan.api.QianFanApi;
55 import org.springframework.ai.qianfan.api.QianFanImageApi; 45 import org.springframework.ai.qianfan.api.QianFanImageApi;
56 import org.springframework.ai.stabilityai.StabilityAiImageModel; 46 import org.springframework.ai.stabilityai.StabilityAiImageModel;
57 import org.springframework.ai.stabilityai.api.StabilityAiApi; 47 import org.springframework.ai.stabilityai.api.StabilityAiApi;
58 -import org.springframework.ai.vectorstore.RedisVectorStore; 48 +import org.springframework.ai.vectorstore.SimpleVectorStore;
59 import org.springframework.ai.vectorstore.VectorStore; 49 import org.springframework.ai.vectorstore.VectorStore;
60 import org.springframework.ai.zhipuai.ZhiPuAiChatModel; 50 import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
61 import org.springframework.ai.zhipuai.ZhiPuAiImageModel; 51 import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
62 import org.springframework.ai.zhipuai.api.ZhiPuAiApi; 52 import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
63 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; 53 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
64 -import org.springframework.boot.autoconfigure.data.redis.RedisProperties;  
65 -import org.springframework.retry.support.RetryTemplate;  
66 -import org.springframework.web.client.ResponseErrorHandler;  
67 import org.springframework.web.client.RestClient; 54 import org.springframework.web.client.RestClient;
68 -import redis.clients.jedis.JedisPooled;  
69 -import redis.clients.jedis.search.Schema;  
70 55
71 import java.util.List; 56 import java.util.List;
72 57
@@ -110,7 +95,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -110,7 +95,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
110 //noinspection EnhancedSwitchMigration 95 //noinspection EnhancedSwitchMigration
111 switch (platform) { 96 switch (platform) {
112 case TONG_YI: 97 case TONG_YI:
113 - return SpringUtil.getBean(TongYiChatModel.class); 98 + return SpringUtil.getBean(DashScopeChatModel.class);
114 case YI_YAN: 99 case YI_YAN:
115 return SpringUtil.getBean(QianFanChatModel.class); 100 return SpringUtil.getBean(QianFanChatModel.class);
116 case DEEP_SEEK: 101 case DEEP_SEEK:
@@ -135,7 +120,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -135,7 +120,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
135 //noinspection EnhancedSwitchMigration 120 //noinspection EnhancedSwitchMigration
136 switch (platform) { 121 switch (platform) {
137 case TONG_YI: 122 case TONG_YI:
138 - return SpringUtil.getBean(TongYiImagesModel.class); 123 + return SpringUtil.getBean(DashScopeImageModel.class);
139 case YI_YAN: 124 case YI_YAN:
140 return SpringUtil.getBean(QianFanImageModel.class); 125 return SpringUtil.getBean(QianFanImageModel.class);
141 case ZHI_PU: 126 case ZHI_PU:
@@ -202,17 +187,20 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -202,17 +187,20 @@ public class AiModelFactoryImpl implements AiModelFactory {
202 String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url); 187 String cacheKey = buildClientCacheKey(VectorStore.class, platform, apiKey, url);
203 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> { 188 return Singleton.get(cacheKey, (Func0<VectorStore>) () -> {
204 String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey); 189 String prefix = StrUtil.format("{}#{}:", platform.getPlatform(), apiKey);
205 - var config = RedisVectorStore.RedisVectorStoreConfig.builder()  
206 - .withIndexName(cacheKey)  
207 - .withPrefix(prefix)  
208 - .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))  
209 - .build();  
210 - RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);  
211 - RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,  
212 - new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),  
213 - true);  
214 - redisVectorStore.afterPropertiesSet();  
215 - return redisVectorStore; 190 + // TODO @芋艿:先临时使用 store
  191 + return SimpleVectorStore.builder(embeddingModel).build();
  192 + // TODO @芋艿:@xin:后续看看,是不是切到阿里云之类的
  193 +// var config = RedisVectorStore.RedisVectorStoreConfig.builder()
  194 +// .withIndexName(cacheKey)
  195 +// .withPrefix(prefix)
  196 +// .withMetadataFields(new RedisVectorStore.MetadataField("knowledgeId", Schema.FieldType.NUMERIC))
  197 +// .build();
  198 +// RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
  199 +// RedisVectorStore redisVectorStore = new RedisVectorStore(config, embeddingModel,
  200 +// new JedisPooled(redisProperties.getHost(), redisProperties.getPort()),
  201 +// true);
  202 +// redisVectorStore.afterPropertiesSet();
  203 +// return redisVectorStore;
216 }); 204 });
217 } 205 }
218 206
@@ -226,29 +214,23 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -226,29 +214,23 @@ public class AiModelFactoryImpl implements AiModelFactory {
226 // ========== 各种创建 spring-ai 客户端的方法 ========== 214 // ========== 各种创建 spring-ai 客户端的方法 ==========
227 215
228 /** 216 /**
229 - * 可参考 {@link TongYiAutoConfiguration#tongYiChatClient(Generation, TongYiChatProperties, TongYiConnectionProperties)} 217 + * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
230 */ 218 */
231 - private static TongYiChatModel buildTongYiChatModel(String key) {  
232 - com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class);  
233 - TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class);  
234 - // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下  
235 - // TODO @芋艿:貌似阿里云不是增量返回的  
236 - // 该 issue 进行跟进中 https://github.com/alibaba/spring-cloud-alibaba/issues/3790  
237 - TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();  
238 - connectionProperties.setApiKey(key);  
239 - return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties); 219 + private static DashScopeChatModel buildTongYiChatModel(String key) {
  220 + DashScopeApi dashScopeApi = new DashScopeApi(key);
  221 + return new DashScopeChatModel(dashScopeApi);
240 } 222 }
241 223
242 - private static TongYiImagesModel buildTongYiImagesModel(String key) {  
243 - ImageSynthesis imageSynthesis = SpringUtil.getBean(ImageSynthesis.class);  
244 - TongYiImagesProperties imagesOptions = SpringUtil.getBean(TongYiImagesProperties.class);  
245 - TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();  
246 - connectionProperties.setApiKey(key);  
247 - return new TongYiAutoConfiguration().tongYiImagesClient(imageSynthesis, imagesOptions, connectionProperties); 224 + /**
  225 + * 可参考 {@link DashScopeAutoConfiguration} 的 dashScopeImageModel 方法
  226 + */
  227 + private static DashScopeImageModel buildTongYiImagesModel(String key) {
  228 + DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
  229 + return new DashScopeImageModel(dashScopeImageApi);
248 } 230 }
249 231
250 /** 232 /**
251 - * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 233 + * 可参考 {@link QianFanAutoConfiguration} 的 qianFanChatModel 方法
252 */ 234 */
253 private static QianFanChatModel buildYiYanChatModel(String key) { 235 private static QianFanChatModel buildYiYanChatModel(String key) {
254 List<String> keys = StrUtil.split(key, '|'); 236 List<String> keys = StrUtil.split(key, '|');
@@ -260,7 +242,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -260,7 +242,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
260 } 242 }
261 243
262 /** 244 /**
263 - * 可参考 {@link QianFanAutoConfiguration#qianFanImageModel(QianFanConnectionProperties, QianFanImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 245 + * 可参考 {@link QianFanAutoConfiguration} 的 qianFanImageModel 方法
264 */ 246 */
265 private QianFanImageModel buildQianFanImageModel(String key) { 247 private QianFanImageModel buildQianFanImageModel(String key) {
266 List<String> keys = StrUtil.split(key, '|'); 248 List<String> keys = StrUtil.split(key, '|');
@@ -275,11 +257,13 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -275,11 +257,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
275 * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)} 257 * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)}
276 */ 258 */
277 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { 259 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
278 - return new DeepSeekChatModel(apiKey); 260 + YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()
  261 + .setApiKey(apiKey);
  262 + return new YudaoAiAutoConfiguration().buildDeepSeekChatModel(properties);
279 } 263 }
280 264
281 /** 265 /**
282 - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel(ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)} 266 + * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
283 */ 267 */
284 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { 268 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
285 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); 269 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -288,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -288,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
288 } 272 }
289 273
290 /** 274 /**
291 - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel(ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 275 + * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiImageModel 方法
292 */ 276 */
293 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { 277 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
294 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); 278 url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL);
@@ -301,21 +285,22 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -301,21 +285,22 @@ public class AiModelFactoryImpl implements AiModelFactory {
301 */ 285 */
302 private static XingHuoChatModel buildXingHuoChatModel(String key) { 286 private static XingHuoChatModel buildXingHuoChatModel(String key) {
303 List<String> keys = StrUtil.split(key, '|'); 287 List<String> keys = StrUtil.split(key, '|');
304 - Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式");  
305 - String appKey = keys.get(1);  
306 - String secretKey = keys.get(2);  
307 - return new XingHuoChatModel(appKey, secretKey); 288 + Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
  289 + YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties()
  290 + .setAppKey(keys.get(0)).setSecretKey(keys.get(1));
  291 + return new YudaoAiAutoConfiguration().buildXingHuoChatClient(properties);
308 } 292 }
309 293
310 /** 294 /**
311 - * 可参考 {@link OpenAiAutoConfiguration} 295 + * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法
312 */ 296 */
313 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { 297 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
314 - url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);  
315 - OpenAiApi openAiApi = new OpenAiApi(url, openAiToken);  
316 - return new OpenAiChatModel(openAiApi); 298 + url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
  299 + OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
  300 + return OpenAiChatModel.builder().openAiApi(openAiApi).build();
317 } 301 }
318 302
  303 + // TODO @芋艿:手头暂时没密钥,使用建议再测试下
319 /** 304 /**
320 * 可参考 {@link AzureOpenAiAutoConfiguration} 305 * 可参考 {@link AzureOpenAiAutoConfiguration}
321 */ 306 */
@@ -325,27 +310,28 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -325,27 +310,28 @@ public class AiModelFactoryImpl implements AiModelFactory {
325 AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties(); 310 AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
326 connectionProperties.setApiKey(apiKey); 311 connectionProperties.setApiKey(apiKey);
327 connectionProperties.setEndpoint(url); 312 connectionProperties.setEndpoint(url);
328 - OpenAIClient openAIClient = azureOpenAiAutoConfiguration.openAIClient(connectionProperties); 313 + OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
329 // 获取 AzureOpenAiChatProperties 对象 314 // 获取 AzureOpenAiChatProperties 对象
330 AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class); 315 AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
331 - return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties, null, null); 316 + return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,
  317 + null, null, null);
332 } 318 }
333 319
334 /** 320 /**
335 - * 可参考 {@link OpenAiAutoConfiguration} 321 + * 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法
336 */ 322 */
337 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { 323 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
338 - url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL);  
339 - OpenAiImageApi openAiApi = new OpenAiImageApi(url, openAiToken, RestClient.builder()); 324 + url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
  325 + OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build();
340 return new OpenAiImageModel(openAiApi); 326 return new OpenAiImageModel(openAiApi);
341 } 327 }
342 328
343 /** 329 /**
344 - * 可参考 {@link OllamaAutoConfiguration} 330 + * 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法
345 */ 331 */
346 private static OllamaChatModel buildOllamaChatModel(String url) { 332 private static OllamaChatModel buildOllamaChatModel(String url) {
347 OllamaApi ollamaApi = new OllamaApi(url); 333 OllamaApi ollamaApi = new OllamaApi(url);
348 - return new OllamaChatModel(ollamaApi); 334 + return OllamaChatModel.builder().ollamaApi(ollamaApi).build();
349 } 335 }
350 336
351 private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { 337 private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) {
@@ -356,13 +342,13 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -356,13 +342,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
356 342
357 // ========== 各种创建 EmbeddingModel 的方法 ========== 343 // ========== 各种创建 EmbeddingModel 的方法 ==========
358 344
  345 + // TODO @芋艿:需要测试下
359 /** 346 /**
360 - * 可参考 {@link TongYiAutoConfiguration#tongYiTextEmbeddingClient(TextEmbedding, TongYiConnectionProperties)} 347 + * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
361 */ 348 */
362 private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) { 349 private EmbeddingModel buildTongYiEmbeddingModel(String apiKey) {
363 - TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();  
364 - connectionProperties.setApiKey(apiKey);  
365 - return new TongYiAutoConfiguration().tongYiTextEmbeddingClient(SpringUtil.getBean(TextEmbedding.class), connectionProperties); 350 + DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
  351 + return new DashScopeEmbeddingModel(dashScopeApi);
366 } 352 }
367 353
368 } 354 }
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; @@ -8,9 +8,10 @@ import lombok.AllArgsConstructor;
8 import lombok.Data; 8 import lombok.Data;
9 import lombok.Getter; 9 import lombok.Getter;
10 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
11 -import org.springframework.ai.openai.api.ApiUtils; 11 +import org.springframework.http.HttpHeaders;
12 import org.springframework.http.HttpRequest; 12 import org.springframework.http.HttpRequest;
13 import org.springframework.http.HttpStatusCode; 13 import org.springframework.http.HttpStatusCode;
  14 +import org.springframework.http.MediaType;
14 import org.springframework.web.reactive.function.client.ClientResponse; 15 import org.springframework.web.reactive.function.client.ClientResponse;
15 import org.springframework.web.reactive.function.client.WebClient; 16 import org.springframework.web.reactive.function.client.WebClient;
16 import reactor.core.publisher.Mono; 17 import reactor.core.publisher.Mono;
@@ -18,6 +19,7 @@ import reactor.core.publisher.Mono; @@ -18,6 +19,7 @@ import reactor.core.publisher.Mono;
18 import java.util.Collection; 19 import java.util.Collection;
19 import java.util.List; 20 import java.util.List;
20 import java.util.Map; 21 import java.util.Map;
  22 +import java.util.function.Consumer;
21 import java.util.function.Function; 23 import java.util.function.Function;
22 import java.util.function.Predicate; 24 import java.util.function.Predicate;
23 25
@@ -50,11 +52,19 @@ public class MidjourneyApi { @@ -50,11 +52,19 @@ public class MidjourneyApi {
50 public MidjourneyApi(String baseUrl, String apiKey, String notifyUrl) { 52 public MidjourneyApi(String baseUrl, String apiKey, String notifyUrl) {
51 this.webClient = WebClient.builder() 53 this.webClient = WebClient.builder()
52 .baseUrl(baseUrl) 54 .baseUrl(baseUrl)
53 - .defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey)) 55 + .defaultHeaders(getJsonContentHeaders(apiKey))
54 .build(); 56 .build();
55 this.notifyUrl = notifyUrl; 57 this.notifyUrl = notifyUrl;
56 } 58 }
57 59
  60 + // TODO @芋艿:这里,看看怎么调整下???https://github.com/spring-projects/spring-ai/issues/741
  61 + public static Consumer<HttpHeaders> getJsonContentHeaders(String apiKey) {
  62 + return (headers) -> {
  63 + headers.setBearerAuth(apiKey);
  64 + headers.setContentType(MediaType.APPLICATION_JSON);
  65 + };
  66 + };
  67 +
58 /** 68 /**
59 * imagine - 根据提示词提交绘画任务 69 * imagine - 根据提示词提交绘画任务
60 * 70 *
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; @@ -2,9 +2,7 @@ package cn.iocoder.yudao.framework.ai.core.util;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions;  
6 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;  
7 -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; 5 +import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
8 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; 6 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
9 import org.springframework.ai.chat.messages.*; 7 import org.springframework.ai.chat.messages.*;
10 import org.springframework.ai.chat.prompt.ChatOptions; 8 import org.springframework.ai.chat.prompt.ChatOptions;
@@ -21,26 +19,24 @@ import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; @@ -21,26 +19,24 @@ import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
21 public class AiUtils { 19 public class AiUtils {
22 20
23 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) { 21 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
24 - Float temperatureF = temperature != null ? temperature.floatValue() : null;  
25 //noinspection EnhancedSwitchMigration 22 //noinspection EnhancedSwitchMigration
26 switch (platform) { 23 switch (platform) {
27 case TONG_YI: 24 case TONG_YI:
28 - return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build(); 25 + // TODO @芋艿:tongyi 暂时没 maxTokens 选项
  26 + return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).build();
29 case YI_YAN: 27 case YI_YAN:
30 - return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
31 - case DEEP_SEEK:  
32 - return DeepSeekChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build(); 28 + return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
33 case ZHI_PU: 29 case ZHI_PU:
34 - return ZhiPuAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();  
35 - case XING_HUO:  
36 - return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build(); 30 + return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
37 case OPENAI: 31 case OPENAI:
38 - return OpenAiChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); 32 + case DEEP_SEEK: // 复用 OpenAI 客户端
  33 + case XING_HUO: // 复用 OpenAI 客户端
  34 + return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
39 case AZURE_OPENAI: 35 case AZURE_OPENAI:
40 // TODO 芋艿:貌似没 model 字段???! 36 // TODO 芋艿:貌似没 model 字段???!
41 - return AzureOpenAiChatOptions.builder().withDeploymentName(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); 37 + return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens).build();
42 case OLLAMA: 38 case OLLAMA:
43 - return OllamaOptions.create().withModel(model).withTemperature(temperatureF).withNumPredict(maxTokens); 39 + return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens).build();
44 default: 40 default:
45 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 41 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
46 } 42 }
@@ -56,8 +52,8 @@ public class AiUtils { @@ -56,8 +52,8 @@ public class AiUtils {
56 if (MessageType.SYSTEM.getValue().equals(type)) { 52 if (MessageType.SYSTEM.getValue().equals(type)) {
57 return new SystemMessage(content); 53 return new SystemMessage(content);
58 } 54 }
59 - if (MessageType.FUNCTION.getValue().equals(type)) {  
60 - return new FunctionMessage(content); 55 + if (MessageType.TOOL.getValue().equals(type)) {
  56 + throw new UnsupportedOperationException("暂不支持 tool 消息:" + content);
61 } 57 }
62 throw new IllegalArgumentException(StrUtil.format("未知消息类型({})", type)); 58 throw new IllegalArgumentException(StrUtil.format("未知消息类型({})", type));
63 } 59 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfiguration.java deleted
1 -/*  
2 - * Copyright 2023 - 2024 the original author or authors.  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * https://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -package org.springframework.ai.autoconfigure.vectorstore.redis;  
17 -  
18 -import org.springframework.ai.embedding.EmbeddingModel;  
19 -import org.springframework.ai.vectorstore.RedisVectorStore;  
20 -import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig;  
21 -import org.springframework.boot.autoconfigure.AutoConfiguration;  
22 -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;  
23 -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;  
24 -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;  
25 -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;  
26 -import org.springframework.boot.context.properties.EnableConfigurationProperties;  
27 -import org.springframework.context.annotation.Bean;  
28 -import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;  
29 -import redis.clients.jedis.JedisPooled;  
30 -  
31 -/**  
32 - * TODO @xin 先拿 spring-ai 最新代码覆盖,1.0.0-M1 跟 redis 自动配置会冲突  
33 - *  
34 - * TODO 这个官方,有说啥时候 fix 哇?  
35 - * TODO 看着是列在1.0.0-M2版本  
36 - *  
37 - * @author Christian Tzolov  
38 - * @author Eddú Meléndez  
39 - */  
40 -@AutoConfiguration(after = RedisAutoConfiguration.class)  
41 -@ConditionalOnClass({JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class})  
42 -@ConditionalOnBean(JedisConnectionFactory.class)  
43 -@EnableConfigurationProperties(RedisVectorStoreProperties.class)  
44 -public class RedisVectorStoreAutoConfiguration {  
45 -  
46 - @Bean  
47 - @ConditionalOnMissingBean  
48 - public RedisVectorStore vectorStore(EmbeddingModel embeddingModel, RedisVectorStoreProperties properties,  
49 - JedisConnectionFactory jedisConnectionFactory) {  
50 -  
51 - var config = RedisVectorStoreConfig.builder()  
52 - .withIndexName(properties.getIndex())  
53 - .withPrefix(properties.getPrefix())  
54 - .build();  
55 -  
56 - return new RedisVectorStore(config, embeddingModel,  
57 - new JedisPooled(jedisConnectionFactory.getHostName(), jedisConnectionFactory.getPort()),  
58 - properties.isInitializeSchema());  
59 - }  
60 -  
61 -}  
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java deleted
1 -/*  
2 - * Copyright 2023 - 2024 the original author or authors.  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * https://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -package org.springframework.ai.vectorstore;  
17 -  
18 -import org.slf4j.Logger;  
19 -import org.slf4j.LoggerFactory;  
20 -import org.springframework.ai.document.Document;  
21 -import org.springframework.ai.embedding.EmbeddingModel;  
22 -import org.springframework.ai.vectorstore.filter.FilterExpressionConverter;  
23 -import org.springframework.beans.factory.InitializingBean;  
24 -import org.springframework.util.Assert;  
25 -import org.springframework.util.CollectionUtils;  
26 -import redis.clients.jedis.JedisPooled;  
27 -import redis.clients.jedis.Pipeline;  
28 -import redis.clients.jedis.json.Path2;  
29 -import redis.clients.jedis.search.*;  
30 -import redis.clients.jedis.search.Schema.FieldType;  
31 -import redis.clients.jedis.search.schemafields.*;  
32 -import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm;  
33 -  
34 -import java.text.MessageFormat;  
35 -import java.util.*;  
36 -import java.util.function.Function;  
37 -import java.util.function.Predicate;  
38 -import java.util.stream.Collectors;  
39 -  
40 -/**  
41 - * The RedisVectorStore is for managing and querying vector data in a Redis database. It  
42 - * offers functionalities like adding, deleting, and performing similarity searches on  
43 - * documents.  
44 - *  
45 - * The store utilizes RedisJSON and RedisSearch to handle JSON documents and to index and  
46 - * search vector data. It supports various vector algorithms (e.g., FLAT, HSNW) for  
47 - * efficient similarity searches. Additionally, it allows for custom metadata fields in  
48 - * the documents to be stored alongside the vector and content data.  
49 - *  
50 - * This class requires a RedisVectorStoreConfig configuration object for initialization,  
51 - * which includes settings like Redis URI, index name, field names, and vector algorithms.  
52 - * It also requires an EmbeddingModel to convert documents into embeddings before storing  
53 - * them.  
54 - *  
55 - * @author Julien Ruaux  
56 - * @author Christian Tzolov  
57 - * @author Eddú Meléndez  
58 - * @see VectorStore  
59 - * @see RedisVectorStoreConfig  
60 - * @see EmbeddingModel  
61 - */  
62 -public class RedisVectorStore implements VectorStore, InitializingBean {  
63 -  
64 - public enum Algorithm {  
65 -  
66 - FLAT, HSNW  
67 -  
68 - }  
69 -  
70 - public record MetadataField(String name, FieldType fieldType) {  
71 -  
72 - public static MetadataField text(String name) {  
73 - return new MetadataField(name, FieldType.TEXT);  
74 - }  
75 -  
76 - public static MetadataField numeric(String name) {  
77 - return new MetadataField(name, FieldType.NUMERIC);  
78 - }  
79 -  
80 - public static MetadataField tag(String name) {  
81 - return new MetadataField(name, FieldType.TAG);  
82 - }  
83 -  
84 - }  
85 -  
86 - /**  
87 - * Configuration for the Redis vector store.  
88 - */  
89 - public static final class RedisVectorStoreConfig {  
90 -  
91 - private final String indexName;  
92 -  
93 - private final String prefix;  
94 -  
95 - private final String contentFieldName;  
96 -  
97 - private final String embeddingFieldName;  
98 -  
99 - private final Algorithm vectorAlgorithm;  
100 -  
101 - private final List<MetadataField> metadataFields;  
102 -  
103 - private RedisVectorStoreConfig() {  
104 - this(builder());  
105 - }  
106 -  
107 - private RedisVectorStoreConfig(Builder builder) {  
108 - this.indexName = builder.indexName;  
109 - this.prefix = builder.prefix;  
110 - this.contentFieldName = builder.contentFieldName;  
111 - this.embeddingFieldName = builder.embeddingFieldName;  
112 - this.vectorAlgorithm = builder.vectorAlgorithm;  
113 - this.metadataFields = builder.metadataFields;  
114 - }  
115 -  
116 - /**  
117 - * Start building a new configuration.  
118 - * @return The entry point for creating a new configuration.  
119 - */  
120 - public static Builder builder() {  
121 -  
122 - return new Builder();  
123 - }  
124 -  
125 - /**  
126 - * {@return the default config}  
127 - */  
128 - public static RedisVectorStoreConfig defaultConfig() {  
129 -  
130 - return builder().build();  
131 - }  
132 -  
133 - public static class Builder {  
134 -  
135 - private String indexName = DEFAULT_INDEX_NAME;  
136 -  
137 - private String prefix = DEFAULT_PREFIX;  
138 -  
139 - private String contentFieldName = DEFAULT_CONTENT_FIELD_NAME;  
140 -  
141 - private String embeddingFieldName = DEFAULT_EMBEDDING_FIELD_NAME;  
142 -  
143 - private Algorithm vectorAlgorithm = DEFAULT_VECTOR_ALGORITHM;  
144 -  
145 - private List<MetadataField> metadataFields = new ArrayList<>();  
146 -  
147 - private Builder() {  
148 - }  
149 -  
150 - /**  
151 - * Configures the Redis index name to use.  
152 - * @param name the index name to use  
153 - * @return this builder  
154 - */  
155 - public Builder withIndexName(String name) {  
156 - this.indexName = name;  
157 - return this;  
158 - }  
159 -  
160 - /**  
161 - * Configures the Redis key prefix to use (default: "embedding:").  
162 - * @param prefix the prefix to use  
163 - * @return this builder  
164 - */  
165 - public Builder withPrefix(String prefix) {  
166 - this.prefix = prefix;  
167 - return this;  
168 - }  
169 -  
170 - /**  
171 - * Configures the Redis content field name to use.  
172 - * @param name the content field name to use  
173 - * @return this builder  
174 - */  
175 - public Builder withContentFieldName(String name) {  
176 - this.contentFieldName = name;  
177 - return this;  
178 - }  
179 -  
180 - /**  
181 - * Configures the Redis embedding field name to use.  
182 - * @param name the embedding field name to use  
183 - * @return this builder  
184 - */  
185 - public Builder withEmbeddingFieldName(String name) {  
186 - this.embeddingFieldName = name;  
187 - return this;  
188 - }  
189 -  
190 - /**  
191 - * Configures the Redis vector algorithmto use.  
192 - * @param algorithm the vector algorithm to use  
193 - * @return this builder  
194 - */  
195 - public Builder withVectorAlgorithm(Algorithm algorithm) {  
196 - this.vectorAlgorithm = algorithm;  
197 - return this;  
198 - }  
199 -  
200 - public Builder withMetadataFields(MetadataField... fields) {  
201 - return withMetadataFields(Arrays.asList(fields));  
202 - }  
203 -  
204 - public Builder withMetadataFields(List<MetadataField> fields) {  
205 - this.metadataFields = fields;  
206 - return this;  
207 - }  
208 -  
209 - /**  
210 - * {@return the immutable configuration}  
211 - */  
212 - public RedisVectorStoreConfig build() {  
213 -  
214 - return new RedisVectorStoreConfig(this);  
215 - }  
216 -  
217 - }  
218 -  
219 - }  
220 -  
221 - private final boolean initializeSchema;  
222 -  
223 - public static final String DEFAULT_INDEX_NAME = "spring-ai-index";  
224 -  
225 - public static final String DEFAULT_CONTENT_FIELD_NAME = "content";  
226 -  
227 - public static final String DEFAULT_EMBEDDING_FIELD_NAME = "embedding";  
228 -  
229 - public static final String DEFAULT_PREFIX = "embedding:";  
230 -  
231 - public static final Algorithm DEFAULT_VECTOR_ALGORITHM = Algorithm.HSNW;  
232 -  
233 - private static final String QUERY_FORMAT = "%s=>[KNN %s @%s $%s AS %s]";  
234 -  
235 - private static final Path2 JSON_SET_PATH = Path2.of("$");  
236 -  
237 - private static final String JSON_PATH_PREFIX = "$.";  
238 -  
239 - private static final Logger logger = LoggerFactory.getLogger(RedisVectorStore.class);  
240 -  
241 - private static final Predicate<Object> RESPONSE_OK = Predicate.isEqual("OK");  
242 -  
243 - private static final Predicate<Object> RESPONSE_DEL_OK = Predicate.isEqual(1l);  
244 -  
245 - private static final String VECTOR_TYPE_FLOAT32 = "FLOAT32";  
246 -  
247 - private static final String EMBEDDING_PARAM_NAME = "BLOB";  
248 -  
249 - public static final String DISTANCE_FIELD_NAME = "vector_score";  
250 -  
251 - private static final String DEFAULT_DISTANCE_METRIC = "COSINE";  
252 -  
253 - private final JedisPooled jedis;  
254 -  
255 - private final EmbeddingModel embeddingModel;  
256 -  
257 - private final RedisVectorStoreConfig config;  
258 -  
259 - private FilterExpressionConverter filterExpressionConverter;  
260 -  
261 - public RedisVectorStore(RedisVectorStoreConfig config, EmbeddingModel embeddingModel, JedisPooled jedis,  
262 - boolean initializeSchema) {  
263 -  
264 - Assert.notNull(config, "Config must not be null");  
265 - Assert.notNull(embeddingModel, "Embedding model must not be null");  
266 - this.initializeSchema = initializeSchema;  
267 -  
268 - this.jedis = jedis;  
269 - this.embeddingModel = embeddingModel;  
270 - this.config = config;  
271 - this.filterExpressionConverter = new RedisFilterExpressionConverter(this.config.metadataFields);  
272 - }  
273 -  
274 - public JedisPooled getJedis() {  
275 - return this.jedis;  
276 - }  
277 -  
278 - @Override  
279 - public void add(List<Document> documents) {  
280 - try (Pipeline pipeline = this.jedis.pipelined()) {  
281 - for (Document document : documents) {  
282 - var embedding = this.embeddingModel.embed(document);  
283 - document.setEmbedding(embedding);  
284 -  
285 - var fields = new HashMap<String, Object>();  
286 - fields.put(this.config.embeddingFieldName, embedding);  
287 - fields.put(this.config.contentFieldName, document.getContent());  
288 - fields.putAll(document.getMetadata());  
289 - pipeline.jsonSetWithEscape(key(document.getId()), JSON_SET_PATH, fields);  
290 - }  
291 - List<Object> responses = pipeline.syncAndReturnAll();  
292 - Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_OK)).findAny();  
293 - if (errResponse.isPresent()) {  
294 - String message = MessageFormat.format("Could not add document: {0}", errResponse.get());  
295 - if (logger.isErrorEnabled()) {  
296 - logger.error(message);  
297 - }  
298 - throw new RuntimeException(message);  
299 - }  
300 - }  
301 - }  
302 -  
303 - private String key(String id) {  
304 - return this.config.prefix + id;  
305 - }  
306 -  
307 - @Override  
308 - public Optional<Boolean> delete(List<String> idList) {  
309 - try (Pipeline pipeline = this.jedis.pipelined()) {  
310 - for (String id : idList) {  
311 - pipeline.jsonDel(key(id));  
312 - }  
313 - List<Object> responses = pipeline.syncAndReturnAll();  
314 - Optional<Object> errResponse = responses.stream().filter(Predicate.not(RESPONSE_DEL_OK)).findAny();  
315 - if (errResponse.isPresent()) {  
316 - if (logger.isErrorEnabled()) {  
317 - logger.error("Could not delete document: {}", errResponse.get());  
318 - }  
319 - return Optional.of(false);  
320 - }  
321 - return Optional.of(true);  
322 - }  
323 - }  
324 -  
325 - @Override  
326 - public List<Document> similaritySearch(SearchRequest request) {  
327 -  
328 - Assert.isTrue(request.getTopK() > 0, "The number of documents to returned must be greater than zero");  
329 - Assert.isTrue(request.getSimilarityThreshold() >= 0 && request.getSimilarityThreshold() <= 1,  
330 - "The similarity score is bounded between 0 and 1; least to most similar respectively.");  
331 -  
332 - String filter = nativeExpressionFilter(request);  
333 -  
334 - String queryString = String.format(QUERY_FORMAT, filter, request.getTopK(), this.config.embeddingFieldName,  
335 - EMBEDDING_PARAM_NAME, DISTANCE_FIELD_NAME);  
336 -  
337 - List<String> returnFields = new ArrayList<>();  
338 - this.config.metadataFields.stream().map(MetadataField::name).forEach(returnFields::add);  
339 - returnFields.add(this.config.embeddingFieldName);  
340 - returnFields.add(this.config.contentFieldName);  
341 - returnFields.add(DISTANCE_FIELD_NAME);  
342 - var embedding = toFloatArray(this.embeddingModel.embed(request.getQuery()));  
343 - Query query = new Query(queryString).addParam(EMBEDDING_PARAM_NAME, RediSearchUtil.toByteArray(embedding))  
344 - .returnFields(returnFields.toArray(new String[0]))  
345 - .setSortBy(DISTANCE_FIELD_NAME, true)  
346 - .dialect(2);  
347 -  
348 - SearchResult result = this.jedis.ftSearch(this.config.indexName, query);  
349 - return result.getDocuments()  
350 - .stream()  
351 - .filter(d -> similarityScore(d) >= request.getSimilarityThreshold())  
352 - .map(this::toDocument)  
353 - .toList();  
354 - }  
355 -  
356 - private Document toDocument(redis.clients.jedis.search.Document doc) {  
357 - var id = doc.getId().substring(this.config.prefix.length());  
358 - var content = doc.hasProperty(this.config.contentFieldName) ? doc.getString(this.config.contentFieldName)  
359 - : null;  
360 - Map<String, Object> metadata = this.config.metadataFields.stream()  
361 - .map(MetadataField::name)  
362 - .filter(doc::hasProperty)  
363 - .collect(Collectors.toMap(Function.identity(), doc::getString));  
364 - metadata.put(DISTANCE_FIELD_NAME, 1 - similarityScore(doc));  
365 - return new Document(id, content, metadata);  
366 - }  
367 -  
368 - private float similarityScore(redis.clients.jedis.search.Document doc) {  
369 - return (2 - Float.parseFloat(doc.getString(DISTANCE_FIELD_NAME))) / 2;  
370 - }  
371 -  
372 - private String nativeExpressionFilter(SearchRequest request) {  
373 - if (request.getFilterExpression() == null) {  
374 - return "*";  
375 - }  
376 - return "(" + this.filterExpressionConverter.convertExpression(request.getFilterExpression()) + ")";  
377 - }  
378 -  
379 - @Override  
380 - public void afterPropertiesSet() {  
381 -  
382 - if (!this.initializeSchema) {  
383 - return;  
384 - }  
385 -  
386 - // If index already exists don't do anything  
387 - if (this.jedis.ftList().contains(this.config.indexName)) {  
388 - return;  
389 - }  
390 -  
391 - String response = this.jedis.ftCreate(this.config.indexName,  
392 - FTCreateParams.createParams().on(IndexDataType.JSON).addPrefix(this.config.prefix), schemaFields());  
393 - if (!RESPONSE_OK.test(response)) {  
394 - String message = MessageFormat.format("Could not create index: {0}", response);  
395 - throw new RuntimeException(message);  
396 - }  
397 - }  
398 -  
399 - private Iterable<SchemaField> schemaFields() {  
400 - Map<String, Object> vectorAttrs = new HashMap<>();  
401 - vectorAttrs.put("DIM", this.embeddingModel.dimensions());  
402 - vectorAttrs.put("DISTANCE_METRIC", DEFAULT_DISTANCE_METRIC);  
403 - vectorAttrs.put("TYPE", VECTOR_TYPE_FLOAT32);  
404 - List<SchemaField> fields = new ArrayList<>();  
405 - fields.add(TextField.of(jsonPath(this.config.contentFieldName)).as(this.config.contentFieldName).weight(1.0));  
406 - fields.add(VectorField.builder()  
407 - .fieldName(jsonPath(this.config.embeddingFieldName))  
408 - .algorithm(vectorAlgorithm())  
409 - .attributes(vectorAttrs)  
410 - .as(this.config.embeddingFieldName)  
411 - .build());  
412 -  
413 - if (!CollectionUtils.isEmpty(this.config.metadataFields)) {  
414 - for (MetadataField field : this.config.metadataFields) {  
415 - fields.add(schemaField(field));  
416 - }  
417 - }  
418 - return fields;  
419 - }  
420 -  
421 - private SchemaField schemaField(MetadataField field) {  
422 - String fieldName = jsonPath(field.name);  
423 - switch (field.fieldType) {  
424 - case NUMERIC:  
425 - return NumericField.of(fieldName).as(field.name);  
426 - case TAG:  
427 - return TagField.of(fieldName).as(field.name);  
428 - case TEXT:  
429 - return TextField.of(fieldName).as(field.name);  
430 - default:  
431 - throw new IllegalArgumentException(  
432 - MessageFormat.format("Field {0} has unsupported type {1}", field.name, field.fieldType));  
433 - }  
434 - }  
435 -  
436 - private VectorAlgorithm vectorAlgorithm() {  
437 - if (config.vectorAlgorithm == Algorithm.HSNW) {  
438 - return VectorAlgorithm.HNSW;  
439 - }  
440 - return VectorAlgorithm.FLAT;  
441 - }  
442 -  
443 - private String jsonPath(String field) {  
444 - return JSON_PATH_PREFIX + field;  
445 - }  
446 -  
447 - private static float[] toFloatArray(List<Double> embeddingDouble) {  
448 - float[] embeddingFloat = new float[embeddingDouble.size()];  
449 - int i = 0;  
450 - for (Double d : embeddingDouble) {  
451 - embeddingFloat[i++] = d.floatValue();  
452 - }  
453 - return embeddingFloat;  
454 - }  
455 -  
456 -}  
457 \ No newline at end of file 0 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/AzureOpenAIChatModelTests.java
1 package cn.iocoder.yudao.framework.ai.chat; 1 package cn.iocoder.yudao.framework.ai.chat;
2 2
3 -import com.azure.ai.openai.OpenAIClient;  
4 import com.azure.ai.openai.OpenAIClientBuilder; 3 import com.azure.ai.openai.OpenAIClientBuilder;
5 import com.azure.core.credential.AzureKeyCredential; 4 import com.azure.core.credential.AzureKeyCredential;
6 import com.azure.core.util.ClientOptions; 5 import com.azure.core.util.ClientOptions;
@@ -27,13 +26,13 @@ import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatP @@ -27,13 +26,13 @@ import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatP
27 */ 26 */
28 public class AzureOpenAIChatModelTests { 27 public class AzureOpenAIChatModelTests {
29 28
30 - private final OpenAIClient openAiApi = (new OpenAIClientBuilder()) 29 + // TODO @芋艿:晚点在调整
  30 + private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
31 .endpoint("https://eastusprejade.openai.azure.com") 31 .endpoint("https://eastusprejade.openai.azure.com")
32 .credential(new AzureKeyCredential("xxx")) 32 .credential(new AzureKeyCredential("xxx"))
33 - .clientOptions((new ClientOptions()).setApplicationId("spring-ai"))  
34 - .buildClient(); 33 + .clientOptions((new ClientOptions()).setApplicationId("spring-ai"));
35 private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi, 34 private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
36 - AzureOpenAiChatOptions.builder().withDeploymentName(DEFAULT_DEPLOYMENT_NAME).build()); 35 + AzureOpenAiChatOptions.builder().deploymentName(DEFAULT_DEPLOYMENT_NAME).build());
37 36
38 @Test 37 @Test
39 @Disabled 38 @Disabled
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/LlamaChatModelTests.java
1 package cn.iocoder.yudao.framework.ai.chat; 1 package cn.iocoder.yudao.framework.ai.chat;
2 2
3 -import org.junit.jupiter.api.Disabled;  
4 -import org.junit.jupiter.api.Test;  
5 -import org.springframework.ai.chat.messages.Message;  
6 -import org.springframework.ai.chat.messages.SystemMessage;  
7 -import org.springframework.ai.chat.messages.UserMessage;  
8 -import org.springframework.ai.chat.model.ChatResponse;  
9 -import org.springframework.ai.chat.prompt.Prompt;  
10 import org.springframework.ai.ollama.OllamaChatModel; 3 import org.springframework.ai.ollama.OllamaChatModel;
11 -import org.springframework.ai.ollama.api.OllamaApi;  
12 -import org.springframework.ai.ollama.api.OllamaModel;  
13 -import org.springframework.ai.ollama.api.OllamaOptions;  
14 -import reactor.core.publisher.Flux;  
15 -  
16 -import java.util.ArrayList;  
17 -import java.util.List;  
18 4
19 /** 5 /**
20 * {@link OllamaChatModel} 集成测试 6 * {@link OllamaChatModel} 集成测试
@@ -23,41 +9,41 @@ import java.util.List; @@ -23,41 +9,41 @@ import java.util.List;
23 */ 9 */
24 public class LlamaChatModelTests { 10 public class LlamaChatModelTests {
25 11
26 - private final OllamaApi ollamaApi = new OllamaApi(  
27 - "http://127.0.0.1:11434");  
28 - private final OllamaChatModel chatModel = new OllamaChatModel(ollamaApi,  
29 - OllamaOptions.create().withModel(OllamaModel.LLAMA3.getModelName()));  
30 -  
31 - @Test  
32 - @Disabled  
33 - public void testCall() {  
34 - // 准备参数  
35 - List<Message> messages = new ArrayList<>();  
36 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
37 - messages.add(new UserMessage("1 + 1 = ?"));  
38 -  
39 - // 调用  
40 - ChatResponse response = chatModel.call(new Prompt(messages));  
41 - // 打印结果  
42 - System.out.println(response);  
43 - System.out.println(response.getResult().getOutput());  
44 - }  
45 -  
46 - @Test  
47 - @Disabled  
48 - public void testStream() {  
49 - // 准备参数  
50 - List<Message> messages = new ArrayList<>();  
51 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
52 - messages.add(new UserMessage("1 + 1 = ?"));  
53 -  
54 - // 调用  
55 - Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));  
56 - // 打印结果  
57 - flux.doOnNext(response -> {  
58 -// System.out.println(response);  
59 - System.out.println(response.getResult().getOutput());  
60 - }).then().block();  
61 - } 12 +// private final OllamaApi ollamaApi = new OllamaApi(
  13 +// "http://127.0.0.1:11434");
  14 +// private final OllamaChatModel chatModel = new OllamaChatModel(ollamaApi,
  15 +// OllamaOptions.create().withModel(OllamaModel.LLAMA3.getModelName()));
  16 +//
  17 +// @Test
  18 +// @Disabled
  19 +// public void testCall() {
  20 +// // 准备参数
  21 +// List<Message> messages = new ArrayList<>();
  22 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  23 +// messages.add(new UserMessage("1 + 1 = ?"));
  24 +//
  25 +// // 调用
  26 +// ChatResponse response = chatModel.call(new Prompt(messages));
  27 +// // 打印结果
  28 +// System.out.println(response);
  29 +// System.out.println(response.getResult().getOutput());
  30 +// }
  31 +//
  32 +// @Test
  33 +// @Disabled
  34 +// public void testStream() {
  35 +// // 准备参数
  36 +// List<Message> messages = new ArrayList<>();
  37 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  38 +// messages.add(new UserMessage("1 + 1 = ?"));
  39 +//
  40 +// // 调用
  41 +// Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  42 +// // 打印结果
  43 +// flux.doOnNext(response -> {
  44 +//// System.out.println(response);
  45 +// System.out.println(response.getResult().getOutput());
  46 +// }).then().block();
  47 +// }
62 48
63 } 49 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java
@@ -26,7 +26,7 @@ public class OpenAIChatModelTests { @@ -26,7 +26,7 @@ public class OpenAIChatModelTests {
26 "https://api.holdai.top", 26 "https://api.holdai.top",
27 "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf"); 27 "sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf");
28 private final OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi, 28 private final OpenAiChatModel chatModel = new OpenAiChatModel(openAiApi,
29 - OpenAiChatOptions.builder().withModel(OpenAiApi.ChatModel.GPT_4_O).build()); 29 + OpenAiChatOptions.builder().model(OpenAiApi.ChatModel.GPT_4_O).build());
30 30
31 @Test 31 @Test
32 @Disabled 32 @Disabled
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java
@@ -25,7 +25,7 @@ public class YiYanChatModelTests { @@ -25,7 +25,7 @@ public class YiYanChatModelTests {
25 "qS8k8dYr2nXunagK4SSU8Xjj", 25 "qS8k8dYr2nXunagK4SSU8Xjj",
26 "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"); 26 "pHGbx51ql2f0hOyabQvSZezahVC3hh3e");
27 private final QianFanChatModel chatModel = new QianFanChatModel(qianFanApi, 27 private final QianFanChatModel chatModel = new QianFanChatModel(qianFanApi,
28 - QianFanChatOptions.builder().withModel(QianFanApi.ChatModel.ERNIE_Tiny_8K.getValue()).build() 28 + QianFanChatOptions.builder().model(QianFanApi.ChatModel.ERNIE_Tiny_8K.getValue()).build()
29 ); 29 );
30 30
31 @Test 31 @Test
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/ZhiPuAiChatModelTests.java
@@ -24,7 +24,7 @@ public class ZhiPuAiChatModelTests { @@ -24,7 +24,7 @@ public class ZhiPuAiChatModelTests {
24 24
25 private final ZhiPuAiApi zhiPuAiApi = new ZhiPuAiApi("32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs"); 25 private final ZhiPuAiApi zhiPuAiApi = new ZhiPuAiApi("32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs");
26 private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(zhiPuAiApi, 26 private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(zhiPuAiApi,
27 - ZhiPuAiChatOptions.builder().withModel(ZhiPuAiApi.ChatModel.GLM_4.getModelName()).build()); 27 + ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.ChatModel.GLM_4.getName()).build());
28 28
29 @Test 29 @Test
30 @Disabled 30 @Disabled
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/QianFanImageTests.java
@@ -25,9 +25,9 @@ public class QianFanImageTests { @@ -25,9 +25,9 @@ public class QianFanImageTests {
25 // 准备参数 25 // 准备参数
26 // 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576 26 // 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576
27 QianFanImageOptions imageOptions = QianFanImageOptions.builder() 27 QianFanImageOptions imageOptions = QianFanImageOptions.builder()
28 - .withModel(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())  
29 - .withWidth(1024).withHeight(1024)  
30 - .withN(1) 28 + .model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())
  29 + .width(1024).height(1024)
  30 + .N(1)
31 .build(); 31 .build();
32 ImagePrompt prompt = new ImagePrompt("good", imageOptions); 32 ImagePrompt prompt = new ImagePrompt("good", imageOptions);
33 33
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/TongYiImagesModelTest.java
1 package cn.iocoder.yudao.framework.ai.image; 1 package cn.iocoder.yudao.framework.ai.image;
2 2
3 -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel; 3 +import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
  4 +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
  5 +import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
4 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; 6 import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
5 -import com.alibaba.dashscope.utils.Constants;  
6 import org.junit.jupiter.api.Disabled; 7 import org.junit.jupiter.api.Disabled;
7 import org.junit.jupiter.api.Test; 8 import org.junit.jupiter.api.Test;
8 import org.springframework.ai.image.ImageOptions; 9 import org.springframework.ai.image.ImageOptions;
9 import org.springframework.ai.image.ImagePrompt; 10 import org.springframework.ai.image.ImagePrompt;
10 import org.springframework.ai.image.ImageResponse; 11 import org.springframework.ai.image.ImageResponse;
11 -import org.springframework.ai.openai.OpenAiImageOptions;  
12 12
13 /** 13 /**
14 - * {@link com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel} 集成测试类 14 + * {@link DashScopeImageModel} 集成测试类
15 * 15 *
16 * @author fansili 16 * @author fansili
17 */ 17 */
18 public class TongYiImagesModelTest { 18 public class TongYiImagesModelTest {
19 19
20 - private final ImageSynthesis imageApi = new ImageSynthesis();  
21 - private final TongYiImagesModel imageModel = new TongYiImagesModel(imageApi);  
22 -  
23 - static {  
24 - Constants.apiKey = "sk-Zsd81gZYg7";  
25 - } 20 + private final DashScopeImageModel imageModel = new DashScopeImageModel(
  21 + new DashScopeImageApi("sk-7d903764249848cfa912733146da12d1"));
26 22
27 @Test 23 @Test
28 @Disabled 24 @Disabled
29 public void imageCallTest() { 25 public void imageCallTest() {
30 // 准备参数 26 // 准备参数
31 - ImageOptions options = OpenAiImageOptions.builder() 27 + ImageOptions options = DashScopeImageOptions.builder()
32 .withModel(ImageSynthesis.Models.WANX_V1) 28 .withModel(ImageSynthesis.Models.WANX_V1)
33 .withHeight(256).withWidth(256) 29 .withHeight(256).withWidth(256)
34 .build(); 30 .build();
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/ZhiPuAiImageModelTests.java
@@ -22,7 +22,7 @@ public class ZhiPuAiImageModelTests { @@ -22,7 +22,7 @@ public class ZhiPuAiImageModelTests {
22 public void testCall() { 22 public void testCall() {
23 // 准备参数 23 // 准备参数
24 ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder() 24 ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder()
25 - .withModel(ZhiPuAiImageApi.ImageModel.CogView_3.getValue()) 25 + .model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue())
26 .build(); 26 .build();
27 ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions); 27 ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);
28 28