浏览代码

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

YunaiV 5 月之前
父节点
当前提交
46726fe67c

+ 28 - 1
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;
 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory;
 import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl;
 import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
+import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
@@ -40,7 +41,7 @@ public class YudaoAiAutoConfiguration {
     @Bean
     @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
     public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
-        YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek();
+        YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
         return buildDeepSeekChatModel(properties);
     }
 
@@ -89,6 +90,32 @@ public class YudaoAiAutoConfiguration {
         return new XingHuoChatModel(openAiChatModel);
     }
 
+    @Bean
+    @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
+    public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
+        YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
+        return buildDouBaoChatClient(properties);
+    }
+
+    public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
+        if (StrUtil.isEmpty(properties.getModel())) {
+            properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
+        }
+        OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+                .openAiApi(OpenAiApi.builder()
+                        .baseUrl(DouBaoChatModel.BASE_URL)
+                        .apiKey(properties.getApiKey())
+                        .build())
+                .defaultOptions(OpenAiChatOptions.builder()
+                        .model(properties.getModel())
+                        .temperature(properties.getTemperature())
+                        .maxTokens(properties.getMaxTokens())
+                        .topP(properties.getTopP())
+                        .build())
+                .build();
+        return new DouBaoChatModel(openAiChatModel);
+    }
+
     @Bean
     @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
     public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {

+ 23 - 1
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 {
     /**
      * DeepSeek
      */
-    private DeepSeekProperties deepSeek;
+    @SuppressWarnings("SpellCheckingInspection")
+    private DeepSeekProperties deepseek;
+
+    /**
+     * 字节豆包
+     */
+    @SuppressWarnings("SpellCheckingInspection")
+    private DouBaoProperties doubao;
 
     /**
      * 讯飞星火
      */
+    @SuppressWarnings("SpellCheckingInspection")
     private XingHuoProperties xinghuo;
 
     /**
@@ -31,6 +39,7 @@ public class YudaoAiProperties {
     /**
      * Suno 音乐
      */
+    @SuppressWarnings("SpellCheckingInspection")
     private SunoProperties suno;
 
     @Data
@@ -61,6 +70,19 @@ public class YudaoAiProperties {
 
     }
 
+    @Data
+    public static class DouBaoProperties {
+
+        private String enable;
+        private String apiKey;
+
+        private String model;
+        private Double temperature;
+        private Integer maxTokens;
+        private Double topP;
+
+    }
+
     @Data
     public static class MidjourneyProperties {
 

+ 1 - 0
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 {
     DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek
     ZHI_PU("ZhiPu", "智谱"), // 智谱 AI
     XING_HUO("XingHuo", "星火"), // 讯飞
+    DOU_BAO("DouBao", "豆包"), // 字节
 
     // ========== 国外平台 ==========
 

+ 14 - 0
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;
 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.DeepSeekChatModel;
+import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
@@ -74,6 +75,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
                     return buildYiYanChatModel(apiKey);
                 case DEEP_SEEK:
                     return buildDeepSeekChatModel(apiKey);
+                case DOU_BAO:
+                    return buildDouBaoChatModel(apiKey);
                 case ZHI_PU:
                     return buildZhiPuChatModel(apiKey, url);
                 case XING_HUO:
@@ -100,6 +103,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
                 return SpringUtil.getBean(QianFanChatModel.class);
             case DEEP_SEEK:
                 return SpringUtil.getBean(DeepSeekChatModel.class);
+            case DOU_BAO:
+                return SpringUtil.getBean(DouBaoChatModel.class);
             case ZHI_PU:
                 return SpringUtil.getBean(ZhiPuAiChatModel.class);
             case XING_HUO:
@@ -262,6 +267,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
         return new YudaoAiAutoConfiguration().buildDeepSeekChatModel(properties);
     }
 
+    /**
+     * 可参考 {@link YudaoAiAutoConfiguration#douBaoChatClient(YudaoAiProperties)}
+     */
+    private ChatModel buildDouBaoChatModel(String apiKey) {
+        YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties()
+               .setApiKey(apiKey);
+        return new YudaoAiAutoConfiguration().buildDouBaoChatClient(properties);
+    }
+
     /**
      * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
      */

+ 45 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/doubao/DouBaoChatModel.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.framework.ai.core.model.doubao;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatModel;
+import reactor.core.publisher.Flux;
+
+/**
+ * 字节豆包 {@link ChatModel} 实现类
+ *
+ * @author fansili
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class DouBaoChatModel implements ChatModel {
+
+    public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
+
+    public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115";
+
+    /**
+     * 兼容 OpenAI 接口,进行复用
+     */
+    private final OpenAiChatModel openAiChatModel;
+
+    @Override
+    public ChatResponse call(Prompt prompt) {
+        return openAiChatModel.call(prompt);
+    }
+
+    @Override
+    public Flux<ChatResponse> stream(Prompt prompt) {
+        return openAiChatModel.stream(prompt);
+    }
+
+    @Override
+    public ChatOptions getDefaultOptions() {
+        return openAiChatModel.getDefaultOptions();
+    }
+
+}

+ 1 - 0
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 {
                 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
             case OPENAI:
             case DEEP_SEEK: // 复用 OpenAI 客户端
+            case DOU_BAO: // 复用 OpenAI 客户端
             case XING_HUO: // 复用 OpenAI 客户端
                 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
             case AZURE_OPENAI:

+ 70 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/DouBaoChatModelTests.java

@@ -0,0 +1,70 @@
+package cn.iocoder.yudao.framework.ai.chat;
+
+import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel;
+import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
+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.SystemMessage;
+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.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import reactor.core.publisher.Flux;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link DeepSeekChatModel} 集成测试
+ *
+ * @author 芋道源码
+ */
+public class DouBaoChatModelTests {
+
+    private static final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+            .openAiApi(OpenAiApi.builder()
+                    .baseUrl(DouBaoChatModel.BASE_URL)
+                    .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
+                    .build())
+            .defaultOptions(OpenAiChatOptions.builder()
+                    .model("doubao-1-5-lite-32k-250115") // 模型(doubao)
+//                    .model("deepseek-r1-250120") // 模型(deepseek)
+                    .temperature(0.7)
+                    .build())
+            .build();
+
+    private final DouBaoChatModel chatModel = new DouBaoChatModel(openAiChatModel);
+
+    @Test
+    @Disabled
+    public void testCall() {
+        // 准备参数
+        List<Message> messages = new ArrayList<>();
+        messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
+        messages.add(new UserMessage("1 + 1 = ?"));
+
+        // 调用
+        ChatResponse response = chatModel.call(new Prompt(messages));
+        // 打印结果
+        System.out.println(response);
+    }
+
+    // TODO @芋艿:因为使用的是 v1 api,导致 deepseek-r1-250120 不返回 think 过程,后续需要优化
+    @Test
+    @Disabled
+    public void testStream() {
+        // 准备参数
+        List<Message> messages = new ArrayList<>();
+        messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
+        messages.add(new UserMessage("1 + 1 = ?"));
+
+        // 调用
+        Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
+        // 打印结果
+        flux.doOnNext(System.out::println).then().block();
+    }
+
+}

+ 4 - 0
yudao-server/src/main/resources/application.yaml

@@ -184,6 +184,10 @@ yudao:
       enable: true
       api-key: sk-e94db327cc7d457d99a8de8810fc6b12
       model: deepseek-chat
+    doubao: # 字节豆包
+      enable: true
+      api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
+      model: doubao-1-5-lite-32k-250115
     xinghuo: # 讯飞星火
       enable: true
       appKey: 75b161ed2aef4719b275d6e7f2a4d4cd