Commit 9d149f414747278cab75b3e12038bafb7dab3d58

Authored by YunaiV
1 parent 96a74315

feat:【ai 大模型】增加 gemini 接入

yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
... ... @@ -34,6 +34,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> {
34 34 OPENAI("OpenAI", "OpenAI"), // OpenAI 官方
35 35 AZURE_OPENAI("AzureOpenAI", "AzureOpenAI"), // OpenAI 微软
36 36 ANTHROPIC("Anthropic", "Anthropic"), // Anthropic Claude
  37 + GEMINI("Gemini", "Gemini"), // 谷歌 Gemini
37 38 OLLAMA("Ollama", "Ollama"),
38 39  
39 40 STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
6 6 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
7 7 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
8 8 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
  9 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
9 10 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
10 11 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
11 12 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
... ... @@ -52,6 +53,34 @@ public class AiAutoConfiguration {
52 53 // ========== 各种 AI Client 创建 ==========
53 54  
54 55 @Bean
  56 + @ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true")
  57 + public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) {
  58 + YudaoAiProperties.GeminiProperties properties = yudaoAiProperties.getGemini();
  59 + return buildGeminiChatClient(properties);
  60 + }
  61 +
  62 + public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.GeminiProperties properties) {
  63 + if (StrUtil.isEmpty(properties.getModel())) {
  64 + properties.setModel(GeminiChatModel.MODEL_DEFAULT);
  65 + }
  66 + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  67 + .openAiApi(OpenAiApi.builder()
  68 + .baseUrl(GeminiChatModel.BASE_URL)
  69 + .completionsPath(GeminiChatModel.COMPLETE_PATH)
  70 + .apiKey(properties.getApiKey())
  71 + .build())
  72 + .defaultOptions(OpenAiChatOptions.builder()
  73 + .model(properties.getModel())
  74 + .temperature(properties.getTemperature())
  75 + .maxTokens(properties.getMaxTokens())
  76 + .topP(properties.getTopP())
  77 + .build())
  78 + .toolCallingManager(getToolCallingManager())
  79 + .build();
  80 + return new GeminiChatModel(openAiChatModel);
  81 + }
  82 +
  83 + @Bean
55 84 @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
56 85 public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
57 86 YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
... ... @@ -150,17 +179,22 @@ public class AiAutoConfiguration {
150 179 if (StrUtil.isEmpty(properties.getModel())) {
151 180 properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
152 181 }
  182 + OpenAiApi.Builder builder = OpenAiApi.builder()
  183 + .baseUrl(XingHuoChatModel.BASE_URL_V1)
  184 + .apiKey(properties.getAppKey() + ":" + properties.getSecretKey());
  185 + if ("x1".equals(properties.getModel())) {
  186 + builder.baseUrl(XingHuoChatModel.BASE_URL_V2)
  187 + .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2);
  188 + }
153 189 OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
154   - .openAiApi(OpenAiApi.builder()
155   - .baseUrl(XingHuoChatModel.BASE_URL)
156   - .apiKey(properties.getAppKey() + ":" + properties.getSecretKey())
157   - .build())
  190 + .openAiApi(builder.build())
158 191 .defaultOptions(OpenAiChatOptions.builder()
159 192 .model(properties.getModel())
160 193 .temperature(properties.getTemperature())
161 194 .maxTokens(properties.getMaxTokens())
162 195 .topP(properties.getTopP())
163 196 .build())
  197 + // TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!!
164 198 .toolCallingManager(getToolCallingManager())
165 199 .build();
166 200 return new XingHuoChatModel(openAiChatModel);
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
... ... @@ -14,6 +14,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
14 14 public class YudaoAiProperties {
15 15  
16 16 /**
  17 + * 谷歌 Gemini
  18 + */
  19 + private GeminiProperties gemini;
  20 +
  21 + /**
17 22 * 字节豆包
18 23 */
19 24 @SuppressWarnings("SpellCheckingInspection")
... ... @@ -55,6 +60,19 @@ public class YudaoAiProperties {
55 60 private SunoProperties suno;
56 61  
57 62 @Data
  63 + public static class GeminiProperties {
  64 +
  65 + private String enable;
  66 + private String apiKey;
  67 +
  68 + private String model;
  69 + private Double temperature;
  70 + private Integer maxTokens;
  71 + private Double topP;
  72 +
  73 + }
  74 +
  75 + @Data
58 76 public static class DouBaoProperties {
59 77  
60 78 private String enable;
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
... ... @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
14 14 import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
15 15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
16 16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
  17 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
17 18 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
18 19 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
19 20 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
... ... @@ -173,6 +174,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
173 174 return buildAzureOpenAiChatModel(apiKey, url);
174 175 case ANTHROPIC:
175 176 return buildAnthropicChatModel(apiKey, url);
  177 + case GEMINI:
  178 + return buildGeminiChatModel(apiKey);
176 179 case OLLAMA:
177 180 return buildOllamaChatModel(url);
178 181 default:
... ... @@ -213,6 +216,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
213 216 return SpringUtil.getBean(AzureOpenAiChatModel.class);
214 217 case ANTHROPIC:
215 218 return SpringUtil.getBean(AnthropicChatModel.class);
  219 + case GEMINI:
  220 + return SpringUtil.getBean(GeminiChatModel.class);
216 221 case OLLAMA:
217 222 return SpringUtil.getBean(OllamaChatModel.class);
218 223 default:
... ... @@ -535,6 +540,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
535 540 }
536 541  
537 542 /**
  543 + * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.GeminiProperties)}
  544 + */
  545 + private static GeminiChatModel buildGeminiChatModel(String apiKey) {
  546 + YudaoAiProperties.GeminiProperties properties = SpringUtil.getBean(YudaoAiProperties.class)
  547 + .getGemini().setApiKey(apiKey);
  548 + return new AiAutoConfiguration().buildGeminiChatClient(properties);
  549 + }
  550 +
  551 + /**
538 552 * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法
539 553 */
540 554 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/gemini/GeminiChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini;
  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 + * 谷歌 Gemini {@link ChatModel} 实现类,基于 Google AI Studio 提供的 <a href="https://ai.google.dev/gemini-api/docs/openai">OpenAI 兼容方案</a>
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class GeminiChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
  22 + public static final String COMPLETE_PATH = "/chat/completions";
  23 +
  24 + public static final String MODEL_DEFAULT = "gemini-2.5-flash";
  25 +
  26 + /**
  27 + * 兼容 OpenAI 接口,进行复用
  28 + */
  29 + private final OpenAiChatModel openAiChatModel;
  30 +
  31 + @Override
  32 + public ChatResponse call(Prompt prompt) {
  33 + return openAiChatModel.call(prompt);
  34 + }
  35 +
  36 + @Override
  37 + public Flux<ChatResponse> stream(Prompt prompt) {
  38 + return openAiChatModel.stream(prompt);
  39 + }
  40 +
  41 + @Override
  42 + public ChatOptions getDefaultOptions() {
  43 + return openAiChatModel.getDefaultOptions();
  44 + }
  45 +
  46 +}
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/xinghuo/XingHuoChatModel.java
... ... @@ -18,28 +18,34 @@ import reactor.core.publisher.Flux;
18 18 @RequiredArgsConstructor
19 19 public class XingHuoChatModel implements ChatModel {
20 20  
21   - public static final String BASE_URL = "https://spark-api-open.xf-yun.com";
  21 + public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com";
22 22  
23   - public static final String MODEL_DEFAULT = "generalv3.5";
  23 + public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com";
  24 + public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions";
24 25  
25 26 /**
26   - * 兼容 OpenAI 接口,进行复用
  27 + * 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite
27 28 */
28   - private final OpenAiChatModel openAiChatModel;
  29 + public static final String MODEL_DEFAULT = "4.0Ultra";
  30 +
  31 + /**
  32 + * v1 兼容 OpenAI 接口,进行复用
  33 + */
  34 + private final OpenAiChatModel openAiChatModelV1;
29 35  
30 36 @Override
31 37 public ChatResponse call(Prompt prompt) {
32   - return openAiChatModel.call(prompt);
  38 + return openAiChatModelV1.call(prompt);
33 39 }
34 40  
35 41 @Override
36 42 public Flux<ChatResponse> stream(Prompt prompt) {
37   - return openAiChatModel.stream(prompt);
  43 + return openAiChatModelV1.stream(prompt);
38 44 }
39 45  
40 46 @Override
41 47 public ChatOptions getDefaultOptions() {
42   - return openAiChatModel.getDefaultOptions();
  48 + return openAiChatModelV1.getDefaultOptions();
43 49 }
44 50  
45 51 }
... ...
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
... ... @@ -12,6 +12,7 @@ import org.springframework.ai.anthropic.AnthropicChatOptions;
12 12 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
13 13 import org.springframework.ai.chat.messages.*;
14 14 import org.springframework.ai.chat.prompt.ChatOptions;
  15 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
15 16 import org.springframework.ai.minimax.MiniMaxChatOptions;
16 17 import org.springframework.ai.ollama.api.OllamaOptions;
17 18 import org.springframework.ai.openai.OpenAiChatOptions;
... ... @@ -47,6 +48,9 @@ public class AiUtils {
47 48 .withToolNames(toolNames).withToolContext(toolContext).build();
48 49 case YI_YAN:
49 50 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
  51 + case DEEP_SEEK:
  52 + return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
  53 + .toolNames(toolNames).toolContext(toolContext).build();
50 54 case ZHI_PU:
51 55 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
52 56 .toolNames(toolNames).toolContext(toolContext).build();
... ... @@ -57,7 +61,7 @@ public class AiUtils {
57 61 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
58 62 .toolNames(toolNames).toolContext(toolContext).build();
59 63 case OPENAI:
60   - case DEEP_SEEK: // 复用 OpenAI 客户端
  64 + case GEMINI: // 复用 OpenAI 客户端
61 65 case DOU_BAO: // 复用 OpenAI 客户端
62 66 case HUN_YUAN: // 复用 OpenAI 客户端
63 67 case XING_HUO: // 复用 OpenAI 客户端
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GeminiChatModelTests.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
  2 +
  3 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
  4 +import org.junit.jupiter.api.Disabled;
  5 +import org.junit.jupiter.api.Test;
  6 +import org.springframework.ai.chat.messages.Message;
  7 +import org.springframework.ai.chat.messages.SystemMessage;
  8 +import org.springframework.ai.chat.messages.UserMessage;
  9 +import org.springframework.ai.chat.model.ChatResponse;
  10 +import org.springframework.ai.chat.prompt.Prompt;
  11 +import org.springframework.ai.openai.OpenAiChatModel;
  12 +import org.springframework.ai.openai.OpenAiChatOptions;
  13 +import org.springframework.ai.openai.api.OpenAiApi;
  14 +import reactor.core.publisher.Flux;
  15 +
  16 +import java.util.ArrayList;
  17 +import java.util.List;
  18 +
  19 +/**
  20 + * {@link GeminiChatModel} 集成测试
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +public class GeminiChatModelTests {
  25 +
  26 + private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
  27 + .openAiApi(OpenAiApi.builder()
  28 + .baseUrl(GeminiChatModel.BASE_URL)
  29 + .completionsPath(GeminiChatModel.COMPLETE_PATH)
  30 + .apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ")
  31 + .build())
  32 + .defaultOptions(OpenAiChatOptions.builder()
  33 + .model(GeminiChatModel.MODEL_DEFAULT) // 模型
  34 + .temperature(0.7)
  35 + .build())
  36 + .build();
  37 +
  38 + private final GeminiChatModel chatModel = new GeminiChatModel(openAiChatModel);
  39 +
  40 + @Test
  41 + @Disabled
  42 + public void testCall() {
  43 + // 准备参数
  44 + List<Message> messages = new ArrayList<>();
  45 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  46 + messages.add(new UserMessage("1 + 1 = ?"));
  47 +
  48 + // 调用
  49 + ChatResponse response = chatModel.call(new Prompt(messages));
  50 + // 打印结果
  51 + System.out.println(response);
  52 + }
  53 +
  54 + @Test
  55 + @Disabled
  56 + public void testStream() {
  57 + // 准备参数
  58 + List<Message> messages = new ArrayList<>();
  59 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  60 + messages.add(new UserMessage("1 + 1 = ?"));
  61 +
  62 + // 调用
  63 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  64 + // 打印结果
  65 + flux.doOnNext(System.out::println).then().block();
  66 + }
  67 +
  68 +}
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/XingHuoChatModelTests.java
... ... @@ -25,11 +25,13 @@ public class XingHuoChatModelTests {
25 25  
26 26 private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
27 27 .openAiApi(OpenAiApi.builder()
28   - .baseUrl(XingHuoChatModel.BASE_URL)
  28 + .baseUrl(XingHuoChatModel.BASE_URL_V2)
  29 + .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2)
29 30 .apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey
30 31 .build())
31 32 .defaultOptions(OpenAiChatOptions.builder()
32   - .model("generalv3.5") // 模型
  33 +// .model("generalv3.5") // 模型
  34 + .model("x1") // 模型
33 35 .temperature(0.7)
34 36 .build())
35 37 .build();
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -197,6 +197,10 @@ spring:
197 197  
198 198 yudao:
199 199 ai:
  200 + gemini: # 谷歌 Gemini
  201 + enable: true
  202 + api-key: AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ
  203 + model: gemini-2.5-flash
200 204 doubao: # 字节豆包
201 205 enable: true
202 206 api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
... ...