【代码新增】AI:适配字节豆包 doubao、deepseek
This commit is contained in:
parent
875cfa10c6
commit
46726fe67c
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ public enum AiPlatformEnum {
|
|||
DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek
|
||||
ZHI_PU("ZhiPu", "智谱"), // 智谱 AI
|
||||
XING_HUO("XingHuo", "星火"), // 讯飞
|
||||
DOU_BAO("DouBao", "豆包"), // 字节
|
||||
|
||||
// ========== 国外平台 ==========
|
||||
|
||||
|
|
|
@ -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 方法
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue