Commit 46726fe67c0feddcdebb6b87c1b855e1fd30d7dd
1 parent
875cfa10
【代码新增】AI:适配字节豆包 doubao、deepseek
Showing
8 changed files
with
186 additions
and
2 deletions
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 |