Commit 46726fe67c0feddcdebb6b87c1b855e1fd30d7dd

Authored by YunaiV
1 parent 875cfa10

【代码新增】AI:适配字节豆包 doubao、deepseek

yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; 4 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
5 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; 5 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
6 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; 6 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
  7 +import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
7 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 8 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
8 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 9 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
9 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; 10 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
@@ -40,7 +41,7 @@ public class YudaoAiAutoConfiguration { @@ -40,7 +41,7 @@ public class YudaoAiAutoConfiguration {
40 @Bean 41 @Bean
41 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") 42 @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
42 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) { 43 public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
43 - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek(); 44 + YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
44 return buildDeepSeekChatModel(properties); 45 return buildDeepSeekChatModel(properties);
45 } 46 }
46 47
@@ -90,6 +91,32 @@ public class YudaoAiAutoConfiguration { @@ -90,6 +91,32 @@ public class YudaoAiAutoConfiguration {
90 } 91 }
91 92
92 @Bean 93 @Bean
  94 + @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
  95 + public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
  96 + YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
  97 + return buildDouBaoChatClient(properties);
  98 + }
  99 +
  100 + public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
  101 + if (StrUtil.isEmpty(properties.getModel())) {
  102 + properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
  103 + }
  104 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  105 + .openAiApi(OpenAiApi.builder()
  106 + .baseUrl(DouBaoChatModel.BASE_URL)
  107 + .apiKey(properties.getApiKey())
  108 + .build())
  109 + .defaultOptions(OpenAiChatOptions.builder()
  110 + .model(properties.getModel())
  111 + .temperature(properties.getTemperature())
  112 + .maxTokens(properties.getMaxTokens())
  113 + .topP(properties.getTopP())
  114 + .build())
  115 + .build();
  116 + return new DouBaoChatModel(openAiChatModel);
  117 + }
  118 +
  119 + @Bean
93 @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") 120 @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
94 public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { 121 public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
95 YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney(); 122 YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney();
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
@@ -16,11 +16,19 @@ public class YudaoAiProperties { @@ -16,11 +16,19 @@ public class YudaoAiProperties {
16 /** 16 /**
17 * DeepSeek 17 * DeepSeek
18 */ 18 */
19 - private DeepSeekProperties deepSeek; 19 + @SuppressWarnings("SpellCheckingInspection")
  20 + private DeepSeekProperties deepseek;
  21 +
  22 + /**
  23 + * 字节豆包
  24 + */
  25 + @SuppressWarnings("SpellCheckingInspection")
  26 + private DouBaoProperties doubao;
20 27
21 /** 28 /**
22 * 讯飞星火 29 * 讯飞星火
23 */ 30 */
  31 + @SuppressWarnings("SpellCheckingInspection")
24 private XingHuoProperties xinghuo; 32 private XingHuoProperties xinghuo;
25 33
26 /** 34 /**
@@ -31,6 +39,7 @@ public class YudaoAiProperties { @@ -31,6 +39,7 @@ public class YudaoAiProperties {
31 /** 39 /**
32 * Suno 音乐 40 * Suno 音乐
33 */ 41 */
  42 + @SuppressWarnings("SpellCheckingInspection")
34 private SunoProperties suno; 43 private SunoProperties suno;
35 44
36 @Data 45 @Data
@@ -62,6 +71,19 @@ public class YudaoAiProperties { @@ -62,6 +71,19 @@ public class YudaoAiProperties {
62 } 71 }
63 72
64 @Data 73 @Data
  74 + public static class DouBaoProperties {
  75 +
  76 + private String enable;
  77 + private String apiKey;
  78 +
  79 + private String model;
  80 + private Double temperature;
  81 + private Integer maxTokens;
  82 + private Double topP;
  83 +
  84 + }
  85 +
  86 + @Data
65 public static class MidjourneyProperties { 87 public static class MidjourneyProperties {
66 88
67 private String enable; 89 private String enable;
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
@@ -19,6 +19,7 @@ public enum AiPlatformEnum { @@ -19,6 +19,7 @@ public enum AiPlatformEnum {
19 DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek 19 DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek
20 ZHI_PU("ZhiPu", "智谱"), // 智谱 AI 20 ZHI_PU("ZhiPu", "智谱"), // 智谱 AI
21 XING_HUO("XingHuo", "星火"), // 讯飞 21 XING_HUO("XingHuo", "星火"), // 讯飞
  22 + DOU_BAO("DouBao", "豆包"), // 字节
22 23
23 // ========== 国外平台 ========== 24 // ========== 国外平台 ==========
24 25
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration;
10 import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; 10 import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties;
11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 11 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; 12 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
  13 +import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
13 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 14 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
14 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 15 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
15 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; 16 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
@@ -74,6 +75,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -74,6 +75,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
74 return buildYiYanChatModel(apiKey); 75 return buildYiYanChatModel(apiKey);
75 case DEEP_SEEK: 76 case DEEP_SEEK:
76 return buildDeepSeekChatModel(apiKey); 77 return buildDeepSeekChatModel(apiKey);
  78 + case DOU_BAO:
  79 + return buildDouBaoChatModel(apiKey);
77 case ZHI_PU: 80 case ZHI_PU:
78 return buildZhiPuChatModel(apiKey, url); 81 return buildZhiPuChatModel(apiKey, url);
79 case XING_HUO: 82 case XING_HUO:
@@ -100,6 +103,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -100,6 +103,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
100 return SpringUtil.getBean(QianFanChatModel.class); 103 return SpringUtil.getBean(QianFanChatModel.class);
101 case DEEP_SEEK: 104 case DEEP_SEEK:
102 return SpringUtil.getBean(DeepSeekChatModel.class); 105 return SpringUtil.getBean(DeepSeekChatModel.class);
  106 + case DOU_BAO:
  107 + return SpringUtil.getBean(DouBaoChatModel.class);
103 case ZHI_PU: 108 case ZHI_PU:
104 return SpringUtil.getBean(ZhiPuAiChatModel.class); 109 return SpringUtil.getBean(ZhiPuAiChatModel.class);
105 case XING_HUO: 110 case XING_HUO:
@@ -263,6 +268,15 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -263,6 +268,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
263 } 268 }
264 269
265 /** 270 /**
  271 + * 可参考 {@link YudaoAiAutoConfiguration#douBaoChatClient(YudaoAiProperties)}
  272 + */
  273 + private ChatModel buildDouBaoChatModel(String apiKey) {
  274 + YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties()
  275 + .setApiKey(apiKey);
  276 + return new YudaoAiAutoConfiguration().buildDouBaoChatClient(properties);
  277 + }
  278 +
  279 + /**
266 * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法 280 * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
267 */ 281 */
268 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { 282 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/doubao/DouBaoChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.model.doubao;
  2 +
  3 +import lombok.RequiredArgsConstructor;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.ai.chat.model.ChatModel;
  6 +import org.springframework.ai.chat.model.ChatResponse;
  7 +import org.springframework.ai.chat.prompt.ChatOptions;
  8 +import org.springframework.ai.chat.prompt.Prompt;
  9 +import org.springframework.ai.openai.OpenAiChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * 字节豆包 {@link ChatModel} 实现类
  14 + *
  15 + * @author fansili
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class DouBaoChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
  22 +
  23 + public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115";
  24 +
  25 + /**
  26 + * 兼容 OpenAI 接口,进行复用
  27 + */
  28 + private final OpenAiChatModel openAiChatModel;
  29 +
  30 + @Override
  31 + public ChatResponse call(Prompt prompt) {
  32 + return openAiChatModel.call(prompt);
  33 + }
  34 +
  35 + @Override
  36 + public Flux<ChatResponse> stream(Prompt prompt) {
  37 + return openAiChatModel.stream(prompt);
  38 + }
  39 +
  40 + @Override
  41 + public ChatOptions getDefaultOptions() {
  42 + return openAiChatModel.getDefaultOptions();
  43 + }
  44 +
  45 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
@@ -30,6 +30,7 @@ public class AiUtils { @@ -30,6 +30,7 @@ public class AiUtils {
30 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); 30 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
31 case OPENAI: 31 case OPENAI:
32 case DEEP_SEEK: // 复用 OpenAI 客户端 32 case DEEP_SEEK: // 复用 OpenAI 客户端
  33 + case DOU_BAO: // 复用 OpenAI 客户端
33 case XING_HUO: // 复用 OpenAI 客户端 34 case XING_HUO: // 复用 OpenAI 客户端
34 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); 35 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
35 case AZURE_OPENAI: 36 case AZURE_OPENAI:
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DouBaoChatModelTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
  4 +import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
  5 +import org.junit.jupiter.api.Disabled;
  6 +import org.junit.jupiter.api.Test;
  7 +import org.springframework.ai.chat.messages.Message;
  8 +import org.springframework.ai.chat.messages.SystemMessage;
  9 +import org.springframework.ai.chat.messages.UserMessage;
  10 +import org.springframework.ai.chat.model.ChatResponse;
  11 +import org.springframework.ai.chat.prompt.Prompt;
  12 +import org.springframework.ai.openai.OpenAiChatModel;
  13 +import org.springframework.ai.openai.OpenAiChatOptions;
  14 +import org.springframework.ai.openai.api.OpenAiApi;
  15 +import reactor.core.publisher.Flux;
  16 +
  17 +import java.util.ArrayList;
  18 +import java.util.List;
  19 +
  20 +/**
  21 + * {@link DeepSeekChatModel} 集成测试
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +public class DouBaoChatModelTests {
  26 +
  27 + private static final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  28 + .openAiApi(OpenAiApi.builder()
  29 + .baseUrl(DouBaoChatModel.BASE_URL)
  30 + .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
  31 + .build())
  32 + .defaultOptions(OpenAiChatOptions.builder()
  33 + .model("doubao-1-5-lite-32k-250115") // 模型(doubao)
  34 +// .model("deepseek-r1-250120") // 模型(deepseek)
  35 + .temperature(0.7)
  36 + .build())
  37 + .build();
  38 +
  39 + private final DouBaoChatModel chatModel = new DouBaoChatModel(openAiChatModel);
  40 +
  41 + @Test
  42 + @Disabled
  43 + public void testCall() {
  44 + // 准备参数
  45 + List<Message> messages = new ArrayList<>();
  46 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  47 + messages.add(new UserMessage("1 + 1 = ?"));
  48 +
  49 + // 调用
  50 + ChatResponse response = chatModel.call(new Prompt(messages));
  51 + // 打印结果
  52 + System.out.println(response);
  53 + }
  54 +
  55 + // TODO @芋艿:因为使用的是 v1 api,导致 deepseek-r1-250120 不返回 think 过程,后续需要优化
  56 + @Test
  57 + @Disabled
  58 + public void testStream() {
  59 + // 准备参数
  60 + List<Message> messages = new ArrayList<>();
  61 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  62 + messages.add(new UserMessage("1 + 1 = ?"));
  63 +
  64 + // 调用
  65 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  66 + // 打印结果
  67 + flux.doOnNext(System.out::println).then().block();
  68 + }
  69 +
  70 +}
yudao-server/src/main/resources/application.yaml
@@ -184,6 +184,10 @@ yudao: @@ -184,6 +184,10 @@ yudao:
184 enable: true 184 enable: true
185 api-key: sk-e94db327cc7d457d99a8de8810fc6b12 185 api-key: sk-e94db327cc7d457d99a8de8810fc6b12
186 model: deepseek-chat 186 model: deepseek-chat
  187 + doubao: # 字节豆包
  188 + enable: true
  189 + api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
  190 + model: doubao-1-5-lite-32k-250115
187 xinghuo: # 讯飞星火 191 xinghuo: # 讯飞星火
188 enable: true 192 enable: true
189 appKey: 75b161ed2aef4719b275d6e7f2a4d4cd 193 appKey: 75b161ed2aef4719b275d6e7f2a4d4cd