Commit 7ef73b7d0992486c380c49e7f214f48ebf0f29d4
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
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 |