From 59c853b54d3f5ca4f32e0ca82114b777850b7ed1 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 6 Jul 2024 17:37:17 +0800 Subject: [PATCH] 【代码优化】AI:完善 DeepSeekChatTests、XingHuoChatModelTests 单测,方便大家快速体验 --- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java | 28 ++++++++++++++-------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java | 8 ++++---- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java | 49 ++++++++++++++++++++++++++----------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatClient.java | 165 --------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java | 162 ------------------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatModelTests.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatTests.java | 50 -------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java | 4 ++-- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java | 52 ---------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatModelTests.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java | 61 ------------------------------------------------------------- yudao-server/src/main/resources/application.yaml | 10 +++++++--- 15 files changed, 543 insertions(+), 536 deletions(-) delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatModelTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatModelTests.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index 128d9d9..05a3172 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.framework.ai.config; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatClient; +import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; import lombok.extern.slf4j.Slf4j; @@ -35,29 +35,29 @@ public class YudaoAiAutoConfiguration { // ========== 各种 AI Client 创建 ========== @Bean - @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") - public XingHuoChatClient xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { - YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); - XingHuoChatOptions options = XingHuoChatOptions.builder() + @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") + public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) { + YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek(); + DeepSeekChatOptions options = DeepSeekChatOptions.builder() .model(properties.getModel()) .temperature(properties.getTemperature()) .maxTokens(properties.getMaxTokens()) - .topK(properties.getTopK()) + .topP(properties.getTopP()) .build(); - return new XingHuoChatClient(properties.getAppKey(), properties.getSecretKey(), options); + return new DeepSeekChatModel(properties.getApiKey(), options); } @Bean - @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") - public DeepSeekChatClient deepSeekChatClient(YudaoAiProperties yudaoAiProperties) { - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek(); - DeepSeekChatOptions options = DeepSeekChatOptions.builder() + @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") + public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { + YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); + XingHuoChatOptions options = XingHuoChatOptions.builder() .model(properties.getModel()) .temperature(properties.getTemperature()) .maxTokens(properties.getMaxTokens()) - .topP(properties.getTopP()) + .topK(properties.getTopK()) .build(); - return new DeepSeekChatClient(properties.getApiKey(), options); + return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options); } @Bean diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index f235f55..82c74b0 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -14,14 +14,14 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class YudaoAiProperties { /** - * 讯飞星火 + * DeepSeek */ - private XingHuoProperties xinghuo; + private DeepSeekProperties deepSeek; /** - * DeepSeek + * 讯飞星火 */ - private DeepSeekProperties deepSeek; + private XingHuoProperties xinghuo; /** * Midjourney 绘图 diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java index da4c5e4..0a9f0e4 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java @@ -9,10 +9,10 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatClient; +import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; @@ -59,10 +59,10 @@ public class AiModelFactoryImpl implements AiModelFactory { return buildTongYiChatModel(apiKey); case YI_YAN: return buildYiYanChatModel(apiKey); - case XING_HUO: - return buildXingHuoChatClient(apiKey); case DEEP_SEEK: - return buildDeepSeekChatClient(apiKey); + return buildDeepSeekChatModel(apiKey); + case XING_HUO: + return buildXingHuoChatModel(apiKey); case OPENAI: return buildOpenAiChatModel(apiKey, url); case OLLAMA: @@ -82,7 +82,7 @@ public class AiModelFactoryImpl implements AiModelFactory { case YI_YAN: return SpringUtil.getBean(QianFanChatModel.class); case XING_HUO: - return SpringUtil.getBean(XingHuoChatClient.class); + return SpringUtil.getBean(XingHuoChatModel.class); case OPENAI: return SpringUtil.getBean(OpenAiChatModel.class); case OLLAMA: @@ -112,7 +112,7 @@ public class AiModelFactoryImpl implements AiModelFactory { case OPENAI: return buildOpenAiImageModel(apiKey, url); case STABLE_DIFFUSION: - return buildStabilityAiImageClient(apiKey, url); + return buildStabilityAiImageModel(apiKey, url); default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); } @@ -169,6 +169,24 @@ public class AiModelFactoryImpl implements AiModelFactory { } /** + * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)} + */ + private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { + return new DeepSeekChatModel(apiKey); + } + + /** + * 可参考 {@link YudaoAiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} + */ + private static XingHuoChatModel buildXingHuoChatModel(String key) { + List keys = StrUtil.split(key, '|'); + Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式"); + String appKey = keys.get(1); + String secretKey = keys.get(2); + return new XingHuoChatModel(appKey, secretKey); + } + + /** * 可参考 {@link OpenAiAutoConfiguration} */ private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { @@ -194,22 +212,7 @@ public class AiModelFactoryImpl implements AiModelFactory { return new OllamaChatModel(ollamaApi); } - /** - * 可参考 {@link YudaoAiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} - */ - private static XingHuoChatClient buildXingHuoChatClient(String key) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式"); - String appKey = keys.get(1); - String secretKey = keys.get(2); - return new XingHuoChatClient(appKey, secretKey); - } - - private static DeepSeekChatClient buildDeepSeekChatClient(String apiKey) { - return new DeepSeekChatClient(apiKey); - } - - private StabilityAiImageModel buildStabilityAiImageClient(String apiKey, String url) { + private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL); StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url); return new StabilityAiImageModel(stabilityAiApi); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatClient.java deleted file mode 100644 index 59a28aa..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatClient.java +++ /dev/null @@ -1,165 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.deepseek; - -import cn.hutool.core.lang.Assert; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions.MODEL_DEFAULT; - -/** - * DeepSeek {@link ChatModel} 实现类 - * - * @author fansili - */ -@Slf4j -public class DeepSeekChatClient implements ChatModel { - - private static final String BASE_URL = "https://api.deepseek.com"; - - private final DeepSeekChatOptions defaultOptions; - private final RetryTemplate retryTemplate; - - /** - * DeepSeek 兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 - * - * 不过要注意,DeepSeek 没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 - */ - private final OpenAiApi openAiApi; - - public DeepSeekChatClient(String apiKey) { - this(apiKey, DeepSeekChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); - } - - public DeepSeekChatClient(String apiKey, DeepSeekChatOptions options) { - this(apiKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); - } - - public DeepSeekChatClient(String apiKey, DeepSeekChatOptions options, RetryTemplate retryTemplate) { - Assert.notEmpty(apiKey, "apiKey 不能为空"); - Assert.notNull(options, "options 不能为空"); - Assert.notNull(retryTemplate, "retryTemplate 不能为空"); - this.openAiApi = new OpenAiApi(BASE_URL, apiKey); - this.defaultOptions = options; - this.retryTemplate = retryTemplate; - } - - @Override - public ChatResponse call(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); - return this.retryTemplate.execute(ctx -> { - // 1.1 发起调用 - ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request); - // 1.2 校验结果 - OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); - if (chatCompletion == null) { - log.warn("No chat completion returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - List choices = chatCompletion.choices(); - if (choices == null) { - log.warn("No choices returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - - // 2. 转换 ChatResponse 返回 - List generations = choices.stream().map(choice -> { - Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); - if (choice.finishReason() != null) { - generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations, - OpenAiChatResponseMetadata.from(completionEntity.getBody())); - }); - } - - private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { - Map map = new HashMap<>(); - OpenAiApi.ChatCompletionMessage message = choice.message(); - if (message.role() != null) { - map.put("role", message.role().name()); - } - if (choice.finishReason() != null) { - map.put("finishReason", choice.finishReason().name()); - } - map.put("id", id); - return map; - } - - @Override - public Flux stream(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); - return this.retryTemplate.execute(ctx -> { - // 1. 发起调用 - Flux response = this.openAiApi.chatCompletionStream(request); - return response.map(chatCompletion -> { - String id = chatCompletion.id(); - // 2. 转换 ChatResponse 返回 - List generations = chatCompletion.choices().stream().map(choice -> { - String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); - String role = (choice.delta().role() != null ? choice.delta().role().name() : ""); - if (choice.finishReason() == OpenAiApi.ChatCompletionFinishReason.STOP) { - // 兜底处理 DeepSeek 返回 STOP 时,role 为空的情况 - role = OpenAiApi.ChatCompletionMessage.Role.ASSISTANT.name(); - } - Generation generation = new Generation(choice.delta().content(), - Map.of("id", id, "role", role, "finishReason", finish)); - if (choice.finishReason() != null) { - generation = generation.withGenerationMetadata( - ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations); - }); - }); - } - - OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { - // 1. 构建 ChatCompletionMessage 对象 - List chatCompletionMessages = prompt.getInstructions().stream().map(m -> - new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); - OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); - - // 2.1 补充 prompt 内置的 options - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, OpenAiChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); - } else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } - // 2.2 补充默认 options - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); - } - return request; - } - - @Override - public ChatOptions getDefaultOptions() { - return DeepSeekChatOptions.fromOptions(defaultOptions); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java new file mode 100644 index 0000000..1437404 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.framework.ai.core.model.deepseek; + +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions.MODEL_DEFAULT; + +/** + * DeepSeek {@link ChatModel} 实现类 + * + * @author fansili + */ +@Slf4j +public class DeepSeekChatModel implements ChatModel { + + private static final String BASE_URL = "https://api.deepseek.com"; + + private final DeepSeekChatOptions defaultOptions; + private final RetryTemplate retryTemplate; + + /** + * DeepSeek 兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 + * + * 不过要注意,DeepSeek 没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 + */ + private final OpenAiApi openAiApi; + + public DeepSeekChatModel(String apiKey) { + this(apiKey, DeepSeekChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); + } + + public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options) { + this(apiKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options, RetryTemplate retryTemplate) { + Assert.notEmpty(apiKey, "apiKey 不能为空"); + Assert.notNull(options, "options 不能为空"); + Assert.notNull(retryTemplate, "retryTemplate 不能为空"); + this.openAiApi = new OpenAiApi(BASE_URL, apiKey); + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + } + + @Override + public ChatResponse call(Prompt prompt) { + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); + return this.retryTemplate.execute(ctx -> { + // 1.1 发起调用 + ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request); + // 1.2 校验结果 + OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); + if (chatCompletion == null) { + log.warn("No chat completion returned for prompt: {}", prompt); + return new ChatResponse(List.of()); + } + List choices = chatCompletion.choices(); + if (choices == null) { + log.warn("No choices returned for prompt: {}", prompt); + return new ChatResponse(List.of()); + } + + // 2. 转换 ChatResponse 返回 + List generations = choices.stream().map(choice -> { + Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); + if (choice.finishReason() != null) { + generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); + } + return generation; + }).toList(); + return new ChatResponse(generations, + OpenAiChatResponseMetadata.from(completionEntity.getBody())); + }); + } + + private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { + Map map = new HashMap<>(); + OpenAiApi.ChatCompletionMessage message = choice.message(); + if (message.role() != null) { + map.put("role", message.role().name()); + } + if (choice.finishReason() != null) { + map.put("finishReason", choice.finishReason().name()); + } + map.put("id", id); + return map; + } + + @Override + public Flux stream(Prompt prompt) { + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); + return this.retryTemplate.execute(ctx -> { + // 1. 发起调用 + Flux response = this.openAiApi.chatCompletionStream(request); + return response.map(chatCompletion -> { + String id = chatCompletion.id(); + // 2. 转换 ChatResponse 返回 + List generations = chatCompletion.choices().stream().map(choice -> { + String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); + String role = (choice.delta().role() != null ? choice.delta().role().name() : ""); + if (choice.finishReason() == OpenAiApi.ChatCompletionFinishReason.STOP) { + // 兜底处理 DeepSeek 返回 STOP 时,role 为空的情况 + role = OpenAiApi.ChatCompletionMessage.Role.ASSISTANT.name(); + } + Generation generation = new Generation(choice.delta().content(), + Map.of("id", id, "role", role, "finishReason", finish)); + if (choice.finishReason() != null) { + generation = generation.withGenerationMetadata( + ChatGenerationMetadata.from(choice.finishReason().name(), null)); + } + return generation; + }).toList(); + return new ChatResponse(generations); + }); + }); + } + + OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { + // 1. 构建 ChatCompletionMessage 对象 + List chatCompletionMessages = prompt.getInstructions().stream().map(m -> + new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); + OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); + + // 2.1 补充 prompt 内置的 options + if (prompt.getOptions() != null) { + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { + OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, + ChatOptions.class, OpenAiChatOptions.class); + request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); + } else { + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " + + prompt.getOptions().getClass().getSimpleName()); + } + } + // 2.2 补充默认 options + if (this.defaultOptions != null) { + request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); + } + return request; + } + + @Override + public ChatOptions getDefaultOptions() { + return DeepSeekChatOptions.fromOptions(defaultOptions); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java deleted file mode 100644 index 52b58c6..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java +++ /dev/null @@ -1,162 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.xinghuo; - -import cn.hutool.core.lang.Assert; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions.MODEL_DEFAULT; - -/** - * 讯飞星火 {@link ChatModel} 实现类 - * - * @author fansili - */ -@Slf4j -public class XingHuoChatClient implements ChatModel { - - private static final String BASE_URL = "https://spark-api-open.xf-yun.com"; - - private final XingHuoChatOptions defaultOptions; - private final RetryTemplate retryTemplate; - - /** - * 星火兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 - * - * 不过要注意,星火没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 - */ - private final OpenAiApi openAiApi; - - public XingHuoChatClient(String apiKey, String secretKey) { - this(apiKey, secretKey, - XingHuoChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); - } - - public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options) { - this(apiKey, secretKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); - } - - public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options, RetryTemplate retryTemplate) { - Assert.notEmpty(apiKey, "apiKey 不能为空"); - Assert.notEmpty(secretKey, "secretKey 不能为空"); - Assert.notNull(options, "options 不能为空"); - Assert.notNull(retryTemplate, "retryTemplate 不能为空"); - this.openAiApi = new OpenAiApi(BASE_URL, apiKey + ":" + secretKey); - this.defaultOptions = options; - this.retryTemplate = retryTemplate; - } - - @Override - public ChatResponse call(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); - return this.retryTemplate.execute(ctx -> { - // 1.1 发起调用 - ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request); - // 1.2 校验结果 - OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); - if (chatCompletion == null) { - log.warn("No chat completion returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - List choices = chatCompletion.choices(); - if (choices == null) { - log.warn("No choices returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - - // 2. 转换 ChatResponse 返回 - List generations = choices.stream().map(choice -> { - Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); - if (choice.finishReason() != null) { - generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations, - OpenAiChatResponseMetadata.from(completionEntity.getBody())); - }); - } - - private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { - Map map = new HashMap<>(); - OpenAiApi.ChatCompletionMessage message = choice.message(); - if (message.role() != null) { - map.put("role", message.role().name()); - } - if (choice.finishReason() != null) { - map.put("finishReason", choice.finishReason().name()); - } - map.put("id", id); - return map; - } - - @Override - public Flux stream(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); - return this.retryTemplate.execute(ctx -> { - // 1. 发起调用 - Flux response = this.openAiApi.chatCompletionStream(request); - return response.map(chatCompletion -> { - String id = chatCompletion.id(); - // 2. 转换 ChatResponse 返回 - List generations = chatCompletion.choices().stream().map(choice -> { - String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); - Generation generation = new Generation(choice.delta().content(), - Map.of("id", id, "role", choice.delta().role().name(), "finishReason", finish)); - if (choice.finishReason() != null) { - generation = generation.withGenerationMetadata( - ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations); - }); - }); - } - - OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { - // 1. 构建 ChatCompletionMessage 对象 - List chatCompletionMessages = prompt.getInstructions().stream().map(m -> - new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); - OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); - - // 2.1 补充 prompt 内置的 options - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, OpenAiChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); - } else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } - // 2.2 补充默认 options - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); - } - return request; - } - - @Override - public ChatOptions getDefaultOptions() { - return XingHuoChatOptions.fromOptions(defaultOptions); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java new file mode 100644 index 0000000..60284bf --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java @@ -0,0 +1,162 @@ +package cn.iocoder.yudao.framework.ai.core.model.xinghuo; + +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions.MODEL_DEFAULT; + +/** + * 讯飞星火 {@link ChatModel} 实现类 + * + * @author fansili + */ +@Slf4j +public class XingHuoChatModel implements ChatModel { + + private static final String BASE_URL = "https://spark-api-open.xf-yun.com"; + + private final XingHuoChatOptions defaultOptions; + private final RetryTemplate retryTemplate; + + /** + * 星火兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 + * + * 不过要注意,星火没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 + */ + private final OpenAiApi openAiApi; + + public XingHuoChatModel(String apiKey, String secretKey) { + this(apiKey, secretKey, + XingHuoChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); + } + + public XingHuoChatModel(String apiKey, String secretKey, XingHuoChatOptions options) { + this(apiKey, secretKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + public XingHuoChatModel(String apiKey, String secretKey, XingHuoChatOptions options, RetryTemplate retryTemplate) { + Assert.notEmpty(apiKey, "apiKey 不能为空"); + Assert.notEmpty(secretKey, "secretKey 不能为空"); + Assert.notNull(options, "options 不能为空"); + Assert.notNull(retryTemplate, "retryTemplate 不能为空"); + this.openAiApi = new OpenAiApi(BASE_URL, apiKey + ":" + secretKey); + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + } + + @Override + public ChatResponse call(Prompt prompt) { + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); + return this.retryTemplate.execute(ctx -> { + // 1.1 发起调用 + ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request); + // 1.2 校验结果 + OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); + if (chatCompletion == null) { + log.warn("No chat completion returned for prompt: {}", prompt); + return new ChatResponse(List.of()); + } + List choices = chatCompletion.choices(); + if (choices == null) { + log.warn("No choices returned for prompt: {}", prompt); + return new ChatResponse(List.of()); + } + + // 2. 转换 ChatResponse 返回 + List generations = choices.stream().map(choice -> { + Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); + if (choice.finishReason() != null) { + generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); + } + return generation; + }).toList(); + return new ChatResponse(generations, + OpenAiChatResponseMetadata.from(completionEntity.getBody())); + }); + } + + private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { + Map map = new HashMap<>(); + OpenAiApi.ChatCompletionMessage message = choice.message(); + if (message.role() != null) { + map.put("role", message.role().name()); + } + if (choice.finishReason() != null) { + map.put("finishReason", choice.finishReason().name()); + } + map.put("id", id); + return map; + } + + @Override + public Flux stream(Prompt prompt) { + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); + return this.retryTemplate.execute(ctx -> { + // 1. 发起调用 + Flux response = this.openAiApi.chatCompletionStream(request); + return response.map(chatCompletion -> { + String id = chatCompletion.id(); + // 2. 转换 ChatResponse 返回 + List generations = chatCompletion.choices().stream().map(choice -> { + String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); + Generation generation = new Generation(choice.delta().content(), + Map.of("id", id, "role", choice.delta().role().name(), "finishReason", finish)); + if (choice.finishReason() != null) { + generation = generation.withGenerationMetadata( + ChatGenerationMetadata.from(choice.finishReason().name(), null)); + } + return generation; + }).toList(); + return new ChatResponse(generations); + }); + }); + } + + OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { + // 1. 构建 ChatCompletionMessage 对象 + List chatCompletionMessages = prompt.getInstructions().stream().map(m -> + new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); + OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); + + // 2.1 补充 prompt 内置的 options + if (prompt.getOptions() != null) { + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { + OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, + ChatOptions.class, OpenAiChatOptions.class); + request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); + } else { + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " + + prompt.getOptions().getClass().getSimpleName()); + } + } + // 2.2 补充默认 options + if (this.defaultOptions != null) { + request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); + } + return request; + } + + @Override + public ChatOptions getDefaultOptions() { + return XingHuoChatOptions.fromOptions(defaultOptions); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatModelTests.java new file mode 100644 index 0000000..8eb54b2 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatModelTests.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.framework.ai.chat; + +import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link DeepSeekChatModel} 集成测试 + * + * @author 芋道源码 + */ +public class DeepSeekChatModelTests { + + private final DeepSeekChatModel chatModel = new DeepSeekChatModel("sk-e94db327cc7d457d99a8de8810fc6b12"); + + @Test + public void testCall() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + ChatResponse response = chatModel.call(new Prompt(messages)); + // 打印结果 + System.out.println(response); + } + + @Test + public void testStream() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + Flux flux = chatModel.stream(new Prompt(messages)); + // 打印结果 + flux.doOnNext(System.out::println).then().block(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatTests.java deleted file mode 100644 index 32e0f5d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DeepSeekChatTests.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat; - -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatClient; -import org.junit.jupiter.api.Test; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import reactor.core.publisher.Flux; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@link DeepSeekChatClient} 集成测试 - * - * @author 芋道源码 - */ -public class DeepSeekChatTests { - - private final DeepSeekChatClient chatModel = new DeepSeekChatClient("sk-e94db327cc7d457d99a8de8810fc6b12"); - - @Test - public void testCall() { - // 准备参数 - List messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - ChatResponse response = chatModel.call(new Prompt(messages)); - // 打印结果 - System.out.println(response); - } - - @Test - public void testStream() { - // 准备参数 - List messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - Flux flux = chatModel.stream(new Prompt(messages)); - // 打印结果 - flux.doOnNext(System.out::println).then().block(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java index 77a77e3..0d956e5 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/OpenAIChatModelTests.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.framework.ai.chat; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.ai.chat.messages.Message; @@ -17,7 +17,7 @@ import java.util.ArrayList; import java.util.List; /** - * {@link XingHuoChatClient} 集成测试 + * {@link XingHuoChatModel} 集成测试 * * @author 芋道源码 */ diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java deleted file mode 100644 index 7cd7d0b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat; - -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; -import org.junit.jupiter.api.Test; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import reactor.core.publisher.Flux; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@link XingHuoChatClient} 集成测试 - * - * @author fansili - */ -public class XingHuoChatClientTests { - - private final XingHuoChatClient chatModel = new XingHuoChatClient( - "cb6415c19d6162cda07b47316fcb0416", - "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"); - - @Test - public void testCall() { - // 准备参数 - List messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - ChatResponse response = chatModel.call(new Prompt(messages)); - // 打印结果 - System.out.println(response); - } - - @Test - public void testStream() { - // 准备参数 - List messages = new ArrayList<>(); - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - Flux flux = chatModel.stream(new Prompt(messages)); - // 打印结果 - flux.doOnNext(System.out::println).then().block(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatModelTests.java new file mode 100644 index 0000000..9e7dfd5 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatModelTests.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.framework.ai.chat; + +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link XingHuoChatModel} 集成测试 + * + * @author fansili + */ +public class XingHuoChatModelTests { + + private final XingHuoChatModel chatModel = new XingHuoChatModel( + "cb6415c19d6162cda07b47316fcb0416", + "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"); + + @Test + public void testCall() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + ChatResponse response = chatModel.call(new Prompt(messages)); + // 打印结果 + System.out.println(response); + } + + @Test + public void testStream() { + // 准备参数 + List messages = new ArrayList<>(); + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + Flux flux = chatModel.stream(new Prompt(messages)); + // 打印结果 + flux.doOnNext(System.out::println).then().block(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java new file mode 100644 index 0000000..d10a046 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatModelTests.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.framework.ai.chat; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.qianfan.QianFanChatModel; +import org.springframework.ai.qianfan.QianFanChatOptions; +import org.springframework.ai.qianfan.api.QianFanApi; +import reactor.core.publisher.Flux; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link QianFanChatModel} 的集成测试 + * + * @author fansili + */ +public class YiYanChatModelTests { + + private final QianFanApi qianFanApi = new QianFanApi( + "qS8k8dYr2nXunagK4SSU8Xjj", + "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"); + private final QianFanChatModel chatModel = new QianFanChatModel(qianFanApi, + QianFanChatOptions.builder().withModel(QianFanApi.ChatModel.ERNIE_Tiny_8K.getValue()).build() + ); + + @Test + @Disabled + public void testCall() { + // 准备参数 + List messages = new ArrayList<>(); + // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + ChatResponse response = chatModel.call(new Prompt(messages)); + // 打印结果 + System.out.println(response); + } + + @Test + @Disabled + public void testStream() { + // 准备参数 + List messages = new ArrayList<>(); + // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); + messages.add(new UserMessage("1 + 1 = ?")); + + // 调用 + Flux flux = chatModel.stream(new Prompt(messages)); + // 打印结果 + flux.doOnNext(System.out::println).then().block(); + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java deleted file mode 100644 index 14031a1..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chat; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanChatOptions; -import org.springframework.ai.qianfan.api.QianFanApi; -import reactor.core.publisher.Flux; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@link QianFanChatModel} 的集成测试 - * - * @author fansili - */ -public class YiYanChatTests { - - private final QianFanApi qianFanApi = new QianFanApi( - "qS8k8dYr2nXunagK4SSU8Xjj", - "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"); - private final QianFanChatModel chatModel = new QianFanChatModel(qianFanApi, - QianFanChatOptions.builder().withModel(QianFanApi.ChatModel.ERNIE_Tiny_8K.getValue()).build() - ); - - @Test - @Disabled - public void testCall() { - // 准备参数 - List messages = new ArrayList<>(); - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - ChatResponse response = chatModel.call(new Prompt(messages)); - // 打印结果 - System.out.println(response); - } - - @Test - @Disabled - public void testStream() { - // 准备参数 - List messages = new ArrayList<>(); - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); - messages.add(new UserMessage("1 + 1 = ?")); - - // 调用 - Flux flux = chatModel.stream(new Prompt(messages)); - // 打印结果 - flux.doOnNext(System.out::println).then().block(); - } - -} diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 8c325b8..9cc5f0e 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -153,6 +153,9 @@ spring: spring: ai: + qianfan: # 文心一言 + api-key: x0cuLZ7XsaTCU08vuJWO87Lg + secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK ollama: base-url: http://127.0.0.1:11434 chat: @@ -162,9 +165,6 @@ spring: base-url: https://api.gptsapi.net stabilityai: api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx - qianfan: # 文心一言 - api-key: x0cuLZ7XsaTCU08vuJWO87Lg - secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK cloud: ai: tongyi: # 通义千问 @@ -173,6 +173,10 @@ spring: yudao: ai: + deep-seek: + enable: true + api-key: sk-e94db327cc7d457d99a8de8810fc6b12 + model: deepseek-chat xinghuo: enable: true appId: 13c8cca6 -- libgit2 0.21.4