Commit 9c6cff33ad7331762bab918d04666e462b4093f3

Authored by YunaiV
1 parent 0cb951f3

feat(ai): 优化大模型兼容接入

Showing 33 changed files with 1169 additions and 557 deletions
yudao-module-ai/pom.xml
@@ -19,12 +19,24 @@ @@ -19,12 +19,24 @@
19 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno 19 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
20 </description> 20 </description>
21 <properties> 21 <properties>
22 - <spring-ai.version>1.1.5</spring-ai.version> 22 + <spring-ai.version>1.1.8</spring-ai.version>
23 <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> 23 <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
24 <alibaba-ai.version>1.1.2.2</alibaba-ai.version> 24 <alibaba-ai.version>1.1.2.2</alibaba-ai.version>
25 <tinyflow.version>1.2.6</tinyflow.version> 25 <tinyflow.version>1.2.6</tinyflow.version>
26 </properties> 26 </properties>
27 27
  28 + <dependencyManagement>
  29 + <dependencies>
  30 + <dependency>
  31 + <groupId>org.springframework.ai</groupId>
  32 + <artifactId>spring-ai-bom</artifactId>
  33 + <version>${spring-ai.version}</version>
  34 + <type>pom</type>
  35 + <scope>import</scope>
  36 + </dependency>
  37 + </dependencies>
  38 + </dependencyManagement>
  39 +
28 <dependencies> 40 <dependencies>
29 <dependency> 41 <dependency>
30 <groupId>cn.iocoder.boot</groupId> 42 <groupId>cn.iocoder.boot</groupId>
@@ -112,37 +124,11 @@ @@ -112,37 +124,11 @@
112 <version>${spring-ai.version}</version> 124 <version>${spring-ai.version}</version>
113 </dependency> 125 </dependency>
114 <dependency> 126 <dependency>
115 - <!-- 智谱 GLM -->  
116 - <groupId>org.springframework.ai</groupId>  
117 - <artifactId>spring-ai-starter-model-zhipuai</artifactId>  
118 - <version>${spring-ai.version}</version>  
119 - </dependency>  
120 - <dependency>  
121 - <groupId>org.springframework.ai</groupId>  
122 - <artifactId>spring-ai-starter-model-minimax</artifactId>  
123 - <version>${spring-ai.version}</version>  
124 - </dependency>  
125 -  
126 - <dependency>  
127 <!-- 通义千问 --> 127 <!-- 通义千问 -->
128 <groupId>com.alibaba.cloud.ai</groupId> 128 <groupId>com.alibaba.cloud.ai</groupId>
129 <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> 129 <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
130 <version>${alibaba-ai.version}</version> 130 <version>${alibaba-ai.version}</version>
131 </dependency> 131 </dependency>
132 -  
133 - <dependency>  
134 - <!-- 文心一言 -->  
135 - <groupId>org.springaicommunity</groupId>  
136 - <artifactId>qianfan-spring-boot-starter</artifactId>  
137 - <version>1.0.0</version>  
138 - </dependency>  
139 - <dependency>  
140 - <!-- 月之暗面 -->  
141 - <groupId>org.springaicommunity</groupId>  
142 - <artifactId>moonshot-spring-boot-starter</artifactId>  
143 - <version>1.0.0</version>  
144 - </dependency>  
145 -  
146 <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> 132 <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
147 <dependency> 133 <dependency>
148 <!-- Qdrant:https://qdrant.tech/ --> 134 <!-- Qdrant:https://qdrant.tech/ -->
@@ -260,4 +246,4 @@ @@ -260,4 +246,4 @@
260 </dependency> 246 </dependency>
261 </dependencies> 247 </dependencies>
262 248
263 -</project>  
264 \ No newline at end of file 249 \ No newline at end of file
  250 +</project>
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -12,6 +12,7 @@ public interface ErrorCodeConstants { @@ -12,6 +12,7 @@ public interface ErrorCodeConstants {
12 // ========== API 密钥 1-040-000-000 ========== 12 // ========== API 密钥 1-040-000-000 ==========
13 ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); 13 ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
14 ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!"); 14 ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
  15 + ErrorCode API_CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_040_000_002, "AI 配置({})无法解析,请检查环境变量或配置项");
15 16
16 // ========== API 模型 1-040-001-000 ========== 17 // ========== API 模型 1-040-001-000 ==========
17 ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!"); 18 ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java
@@ -28,6 +28,7 @@ public enum AiPlatformEnum implements ArrayValuable&lt;String&gt; { @@ -28,6 +28,7 @@ public enum AiPlatformEnum implements ArrayValuable&lt;String&gt; {
28 MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 28 MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技
29 MOONSHOT("Moonshot", "月之暗面"), // KIMI 29 MOONSHOT("Moonshot", "月之暗面"), // KIMI
30 BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能 30 BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能
  31 + STEP_FUN("StepFun", "阶跃星辰"), // 阶跃星辰
31 32
32 // ========== 国外平台 ========== 33 // ========== 国外平台 ==========
33 34
@@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable&lt;String&gt; { @@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable&lt;String&gt; {
40 STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI 41 STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI
41 MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney 42 MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney
42 SUNO("Suno", "Suno"), // Suno AI 43 SUNO("Suno", "Suno"), // Suno AI
43 - GROK("Grok","Grok"), // Grok 44 + GROK("Grok", "Grok"), // Grok
44 45
45 ; 46 ;
46 47
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
@@ -10,10 +10,15 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel @@ -10,10 +10,15 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel
10 import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; 10 import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
11 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; 11 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
12 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; 12 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
  13 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
  14 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
13 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; 15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; 16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
  17 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; 18 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; 19 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
  20 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
  21 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
17 import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; 22 import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
18 import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; 23 import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
19 import cn.iocoder.yudao.module.ai.tool.method.PersonService; 24 import cn.iocoder.yudao.module.ai.tool.method.PersonService;
@@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean; @@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean;
44 import org.springframework.context.annotation.Configuration; 49 import org.springframework.context.annotation.Configuration;
45 50
46 import java.util.List; 51 import java.util.List;
47 -import java.util.Optional;  
48 52
49 /** 53 /**
50 * 芋道 AI 自动配置 54 * 芋道 AI 自动配置
@@ -113,13 +117,13 @@ public class AiAutoConfiguration { @@ -113,13 +117,13 @@ public class AiAutoConfiguration {
113 if (StrUtil.isEmpty(properties.getModel())) { 117 if (StrUtil.isEmpty(properties.getModel())) {
114 properties.setModel(DouBaoChatModel.MODEL_DEFAULT); 118 properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
115 } 119 }
116 - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
117 - .openAiApi(OpenAiApi.builder() 120 + DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
  121 + .deepSeekApi(DeepSeekApi.builder()
118 .baseUrl(DouBaoChatModel.BASE_URL) 122 .baseUrl(DouBaoChatModel.BASE_URL)
119 .completionsPath(DouBaoChatModel.COMPLETE_PATH) 123 .completionsPath(DouBaoChatModel.COMPLETE_PATH)
120 .apiKey(properties.getApiKey()) 124 .apiKey(properties.getApiKey())
121 .build()) 125 .build())
122 - .defaultOptions(OpenAiChatOptions.builder() 126 + .defaultOptions(DeepSeekChatOptions.builder()
123 .model(properties.getModel()) 127 .model(properties.getModel())
124 .temperature(properties.getTemperature()) 128 .temperature(properties.getTemperature())
125 .maxTokens(properties.getMaxTokens()) 129 .maxTokens(properties.getMaxTokens())
@@ -168,11 +172,8 @@ public class AiAutoConfiguration { @@ -168,11 +172,8 @@ public class AiAutoConfiguration {
168 if (StrUtil.isEmpty(properties.getModel())) { 172 if (StrUtil.isEmpty(properties.getModel())) {
169 properties.setModel(HunYuanChatModel.MODEL_DEFAULT); 173 properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
170 } 174 }
171 - // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL  
172 if (StrUtil.isEmpty(properties.getBaseUrl())) { 175 if (StrUtil.isEmpty(properties.getBaseUrl())) {
173 - properties.setBaseUrl(  
174 - StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL  
175 - : HunYuanChatModel.BASE_URL); 176 + properties.setBaseUrl(HunYuanChatModel.BASE_URL);
176 } 177 }
177 // 创建 DeepSeekChatModel、HunYuanChatModel 对象 178 // 创建 DeepSeekChatModel、HunYuanChatModel 对象
178 DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() 179 DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
@@ -203,25 +204,15 @@ public class AiAutoConfiguration { @@ -203,25 +204,15 @@ public class AiAutoConfiguration {
203 if (StrUtil.isEmpty(properties.getModel())) { 204 if (StrUtil.isEmpty(properties.getModel())) {
204 properties.setModel(XingHuoChatModel.MODEL_DEFAULT); 205 properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
205 } 206 }
206 - OpenAiApi.Builder builder = OpenAiApi.builder()  
207 - .baseUrl(XingHuoChatModel.BASE_URL_V1)  
208 - .apiKey(properties.getAppKey() + ":" + properties.getSecretKey());  
209 - if ("x1".equals(properties.getModel())) {  
210 - builder.baseUrl(XingHuoChatModel.BASE_URL_V2)  
211 - .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2);  
212 - }  
213 - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
214 - .openAiApi(builder.build())  
215 - .defaultOptions(OpenAiChatOptions.builder() 207 + return XingHuoChatModel.builder()
  208 + .apiKey(properties.getApiKey())
  209 + .options(DeepSeekChatOptions.builder()
216 .model(properties.getModel()) 210 .model(properties.getModel())
217 .temperature(properties.getTemperature()) 211 .temperature(properties.getTemperature())
218 .maxTokens(properties.getMaxTokens()) 212 .maxTokens(properties.getMaxTokens())
219 .topP(properties.getTopP()) 213 .topP(properties.getTopP())
220 .build()) 214 .build())
221 - // TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!!  
222 - .toolCallingManager(getToolCallingManager())  
223 .build(); 215 .build();
224 - return new XingHuoChatModel(openAiChatModel);  
225 } 216 }
226 217
227 @Bean 218 @Bean
@@ -235,12 +226,13 @@ public class AiAutoConfiguration { @@ -235,12 +226,13 @@ public class AiAutoConfiguration {
235 if (StrUtil.isEmpty(properties.getModel())) { 226 if (StrUtil.isEmpty(properties.getModel())) {
236 properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); 227 properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
237 } 228 }
238 - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
239 - .openAiApi(OpenAiApi.builder() 229 + DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
  230 + .deepSeekApi(DeepSeekApi.builder()
240 .baseUrl(BaiChuanChatModel.BASE_URL) 231 .baseUrl(BaiChuanChatModel.BASE_URL)
241 .apiKey(properties.getApiKey()) 232 .apiKey(properties.getApiKey())
  233 + .completionsPath(BaiChuanChatModel.COMPLETE_PATH)
242 .build()) 234 .build())
243 - .defaultOptions(OpenAiChatOptions.builder() 235 + .defaultOptions(DeepSeekChatOptions.builder()
244 .model(properties.getModel()) 236 .model(properties.getModel())
245 .temperature(properties.getTemperature()) 237 .temperature(properties.getTemperature())
246 .maxTokens(properties.getMaxTokens()) 238 .maxTokens(properties.getMaxTokens())
@@ -248,7 +240,123 @@ public class AiAutoConfiguration { @@ -248,7 +240,123 @@ public class AiAutoConfiguration {
248 .build()) 240 .build())
249 .toolCallingManager(getToolCallingManager()) 241 .toolCallingManager(getToolCallingManager())
250 .build(); 242 .build();
251 - return new BaiChuanChatModel(openAiChatModel); 243 + return new BaiChuanChatModel(deepSeekChatModel);
  244 + }
  245 +
  246 + @Bean
  247 + @ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true")
  248 + public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) {
  249 + YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan();
  250 + return buildYiYanChatClient(properties);
  251 + }
  252 +
  253 + public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) {
  254 + if (StrUtil.isEmpty(properties.getModel())) {
  255 + properties.setModel(YiYanChatModel.MODEL_DEFAULT);
  256 + }
  257 + return new YiYanChatModel(buildDeepSeekCompatibleChatModel(
  258 + StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL),
  259 + null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
  260 + properties.getMaxTokens(), properties.getTopP()));
  261 + }
  262 +
  263 + @Bean
  264 + @ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true")
  265 + public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) {
  266 + YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu();
  267 + return buildZhiPuChatClient(properties);
  268 + }
  269 +
  270 + public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) {
  271 + if (StrUtil.isEmpty(properties.getModel())) {
  272 + properties.setModel(ZhiPuChatModel.MODEL_DEFAULT);
  273 + }
  274 + DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
  275 + .deepSeekApi(DeepSeekApi.builder()
  276 + .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL))
  277 + .apiKey(properties.getApiKey())
  278 + .build())
  279 + .defaultOptions(DeepSeekChatOptions.builder()
  280 + .model(properties.getModel())
  281 + .temperature(properties.getTemperature())
  282 + .maxTokens(properties.getMaxTokens())
  283 + .topP(properties.getTopP())
  284 + .build())
  285 + .build();
  286 + return new ZhiPuChatModel(deepSeekChatModel);
  287 + }
  288 +
  289 + @Bean
  290 + @ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true")
  291 + public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) {
  292 + YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax();
  293 + return buildMiniMaxChatClient(properties);
  294 + }
  295 +
  296 + public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) {
  297 + if (StrUtil.isEmpty(properties.getModel())) {
  298 + properties.setModel(MiniMaxChatModel.MODEL_DEFAULT);
  299 + }
  300 + return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel(
  301 + StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL),
  302 + null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
  303 + properties.getMaxTokens(), properties.getTopP()));
  304 + }
  305 +
  306 + @Bean
  307 + @ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true")
  308 + public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) {
  309 + YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot();
  310 + return buildMoonshotChatClient(properties);
  311 + }
  312 +
  313 + public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) {
  314 + if (StrUtil.isEmpty(properties.getModel())) {
  315 + properties.setModel(MoonshotChatModel.MODEL_DEFAULT);
  316 + }
  317 + return new MoonshotChatModel(buildDeepSeekCompatibleChatModel(
  318 + StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL),
  319 + MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
  320 + properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
  321 + }
  322 +
  323 + @Bean
  324 + @ConditionalOnProperty(value = "yudao.ai.stepfun.enable", havingValue = "true")
  325 + public StepFunChatModel stepFunChatClient(YudaoAiProperties yudaoAiProperties) {
  326 + YudaoAiProperties.StepFun properties = yudaoAiProperties.getStepfun();
  327 + return buildStepFunChatClient(properties);
  328 + }
  329 +
  330 + public StepFunChatModel buildStepFunChatClient(YudaoAiProperties.StepFun properties) {
  331 + if (StrUtil.isEmpty(properties.getModel())) {
  332 + properties.setModel(StepFunChatModel.MODEL_DEFAULT);
  333 + }
  334 + return new StepFunChatModel(buildDeepSeekCompatibleChatModel(
  335 + StrUtil.blankToDefault(properties.getBaseUrl(), StepFunChatModel.BASE_URL),
  336 + StepFunChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
  337 + properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
  338 + }
  339 +
  340 + private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath,
  341 + String apiKey, String model,
  342 + Double temperature, Integer maxTokens,
  343 + Double topP) {
  344 + DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder()
  345 + .baseUrl(baseUrl)
  346 + .apiKey(apiKey);
  347 + if (StrUtil.isNotEmpty(completionsPath)) {
  348 + apiBuilder.completionsPath(completionsPath);
  349 + }
  350 + return DeepSeekChatModel.builder()
  351 + .deepSeekApi(apiBuilder.build())
  352 + .defaultOptions(DeepSeekChatOptions.builder()
  353 + .model(model)
  354 + .temperature(temperature)
  355 + .maxTokens(maxTokens)
  356 + .topP(topP)
  357 + .build())
  358 + .toolCallingManager(getToolCallingManager())
  359 + .build();
252 } 360 }
253 361
254 @Bean 362 @Bean
@@ -264,14 +372,20 @@ public class AiAutoConfiguration { @@ -264,14 +372,20 @@ public class AiAutoConfiguration {
264 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); 372 return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
265 } 373 }
266 374
267 - public ChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) { 375 + @Bean
  376 + @ConditionalOnProperty(value = "yudao.ai.grok.enable", havingValue = "true")
  377 + public GrokChatModel grokChatClient(YudaoAiProperties yudaoAiProperties) {
  378 + YudaoAiProperties.Grok properties = yudaoAiProperties.getGrok();
  379 + return buildGrokChatClient(properties);
  380 + }
  381 +
  382 + public GrokChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) {
268 if (StrUtil.isEmpty(properties.getModel())) { 383 if (StrUtil.isEmpty(properties.getModel())) {
269 properties.setModel(GrokChatModel.MODEL_DEFAULT); 384 properties.setModel(GrokChatModel.MODEL_DEFAULT);
270 } 385 }
271 OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() 386 OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
272 .openAiApi(OpenAiApi.builder() 387 .openAiApi(OpenAiApi.builder()
273 - .baseUrl(Optional.ofNullable(properties.getBaseUrl())  
274 - .orElse(GrokChatModel.BASE_URL)) 388 + .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL))
275 .completionsPath(GrokChatModel.COMPLETE_PATH) 389 .completionsPath(GrokChatModel.COMPLETE_PATH)
276 .apiKey(properties.getApiKey()) 390 .apiKey(properties.getApiKey())
277 .build()) 391 .build())
@@ -283,7 +397,7 @@ public class AiAutoConfiguration { @@ -283,7 +397,7 @@ public class AiAutoConfiguration {
283 .build()) 397 .build())
284 .toolCallingManager(getToolCallingManager()) 398 .toolCallingManager(getToolCallingManager())
285 .build(); 399 .build();
286 - return new DouBaoChatModel(openAiChatModel); 400 + return new GrokChatModel(openAiChatModel);
287 } 401 }
288 402
289 // ========== RAG 相关 ========== 403 // ========== RAG 相关 ==========
@@ -320,4 +434,4 @@ public class AiAutoConfiguration { @@ -320,4 +434,4 @@ public class AiAutoConfiguration {
320 return List.of(ToolCallbacks.from(personService)); 434 return List.of(ToolCallbacks.from(personService));
321 } 435 }
322 436
323 -}  
324 \ No newline at end of file 437 \ No newline at end of file
  438 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
@@ -44,6 +44,36 @@ public class YudaoAiProperties { @@ -44,6 +44,36 @@ public class YudaoAiProperties {
44 private BaiChuan baichuan; 44 private BaiChuan baichuan;
45 45
46 /** 46 /**
  47 + * 文心一言
  48 + */
  49 + private YiYan yiyan;
  50 +
  51 + /**
  52 + * 智谱
  53 + */
  54 + private ZhiPu zhipu;
  55 +
  56 + /**
  57 + * MiniMax
  58 + */
  59 + private MiniMax minimax;
  60 +
  61 + /**
  62 + * 月之暗面
  63 + */
  64 + private Moonshot moonshot;
  65 +
  66 + /**
  67 + * 阶跃星辰
  68 + */
  69 + private StepFun stepfun;
  70 +
  71 + /**
  72 + * Grok
  73 + */
  74 + private Grok grok;
  75 +
  76 + /**
47 * Midjourney 绘图 77 * Midjourney 绘图
48 */ 78 */
49 private Midjourney midjourney; 79 private Midjourney midjourney;
@@ -116,9 +146,7 @@ public class YudaoAiProperties { @@ -116,9 +146,7 @@ public class YudaoAiProperties {
116 public static class XingHuo { 146 public static class XingHuo {
117 147
118 private String enable; 148 private String enable;
119 - private String appId;  
120 - private String appKey;  
121 - private String secretKey; 149 + private String apiKey;
122 150
123 private String model; 151 private String model;
124 private Double temperature; 152 private Double temperature;
@@ -141,6 +169,76 @@ public class YudaoAiProperties { @@ -141,6 +169,76 @@ public class YudaoAiProperties {
141 } 169 }
142 170
143 @Data 171 @Data
  172 + public static class YiYan {
  173 +
  174 + private String enable;
  175 + private String baseUrl;
  176 + private String apiKey;
  177 +
  178 + private String model;
  179 + private Double temperature;
  180 + private Integer maxTokens;
  181 + private Double topP;
  182 +
  183 + }
  184 +
  185 + @Data
  186 + public static class ZhiPu {
  187 +
  188 + private String enable;
  189 + private String baseUrl;
  190 + private String apiKey;
  191 +
  192 + private String model;
  193 + private Double temperature;
  194 + private Integer maxTokens;
  195 + private Double topP;
  196 +
  197 + }
  198 +
  199 + @Data
  200 + public static class MiniMax {
  201 +
  202 + private String enable;
  203 + private String baseUrl;
  204 + private String apiKey;
  205 +
  206 + private String model;
  207 + private Double temperature;
  208 + private Integer maxTokens;
  209 + private Double topP;
  210 +
  211 + }
  212 +
  213 + @Data
  214 + public static class Moonshot {
  215 +
  216 + private String enable;
  217 + private String baseUrl;
  218 + private String apiKey;
  219 +
  220 + private String model;
  221 + private Double temperature;
  222 + private Integer maxTokens;
  223 + private Double topP;
  224 +
  225 + }
  226 +
  227 + @Data
  228 + public static class StepFun {
  229 +
  230 + private String enable;
  231 + private String apiKey;
  232 + private String baseUrl;
  233 +
  234 + private String model;
  235 + private Double temperature;
  236 + private Integer maxTokens;
  237 + private Double topP;
  238 +
  239 + }
  240 +
  241 + @Data
144 public static class Midjourney { 242 public static class Midjourney {
145 243
146 private String enable; 244 private String enable;
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model;
2 2
3 import cn.hutool.core.io.FileUtil; 3 import cn.hutool.core.io.FileUtil;
4 -import cn.hutool.core.lang.Assert;  
5 import cn.hutool.core.lang.Singleton; 4 import cn.hutool.core.lang.Singleton;
6 import cn.hutool.core.lang.func.Func0; 5 import cn.hutool.core.lang.func.Func0;
7 import cn.hutool.core.util.ArrayUtil; 6 import cn.hutool.core.util.ArrayUtil;
@@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; @@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; 14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; 15 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; 16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
  17 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
18 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; 18 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
19 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; 19 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
  20 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
  21 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
20 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; 22 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
21 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; 23 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
22 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; 24 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
23 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; 25 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
  26 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
24 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; 27 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
25 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; 28 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
  29 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
  30 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
  31 +import cn.iocoder.yudao.module.ai.util.AiUtils;
26 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; 32 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
27 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; 33 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
28 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; 34 import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
@@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient; @@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient;
40 import io.qdrant.client.QdrantClient; 46 import io.qdrant.client.QdrantClient;
41 import io.qdrant.client.QdrantGrpcClient; 47 import io.qdrant.client.QdrantGrpcClient;
42 import lombok.SneakyThrows; 48 import lombok.SneakyThrows;
43 -import org.springaicommunity.moonshot.MoonshotChatModel;  
44 -import org.springaicommunity.moonshot.MoonshotChatOptions;  
45 -import org.springaicommunity.moonshot.api.MoonshotApi;  
46 -import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;  
47 -import org.springaicommunity.qianfan.QianFanChatModel;  
48 -import org.springaicommunity.qianfan.QianFanEmbeddingModel;  
49 -import org.springaicommunity.qianfan.QianFanEmbeddingOptions;  
50 -import org.springaicommunity.qianfan.QianFanImageModel;  
51 -import org.springaicommunity.qianfan.api.QianFanApi;  
52 -import org.springaicommunity.qianfan.api.QianFanImageApi;  
53 -import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;  
54 -import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;  
55 import org.springframework.ai.azure.openai.AzureOpenAiChatModel; 49 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
56 import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; 50 import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
57 import org.springframework.ai.chat.model.ChatModel; 51 import org.springframework.ai.chat.model.ChatModel;
@@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy; @@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy;
63 import org.springframework.ai.embedding.EmbeddingModel; 57 import org.springframework.ai.embedding.EmbeddingModel;
64 import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; 58 import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
65 import org.springframework.ai.image.ImageModel; 59 import org.springframework.ai.image.ImageModel;
66 -import org.springframework.ai.minimax.MiniMaxChatModel;  
67 -import org.springframework.ai.minimax.MiniMaxChatOptions;  
68 -import org.springframework.ai.minimax.MiniMaxEmbeddingModel;  
69 -import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;  
70 -import org.springframework.ai.minimax.api.MiniMaxApi;  
71 import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; 60 import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
72 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; 61 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
73 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; 62 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration;
74 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; 63 import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
75 import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration; 64 import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration;
76 -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration;  
77 -import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration;  
78 import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; 65 import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
79 import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; 66 import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
80 import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; 67 import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
81 import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; 68 import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
82 import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; 69 import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration;
83 import org.springframework.ai.model.tool.ToolCallingManager; 70 import org.springframework.ai.model.tool.ToolCallingManager;
84 -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;  
85 -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;  
86 -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;  
87 import org.springframework.ai.ollama.OllamaChatModel; 71 import org.springframework.ai.ollama.OllamaChatModel;
88 import org.springframework.ai.ollama.OllamaEmbeddingModel; 72 import org.springframework.ai.ollama.OllamaEmbeddingModel;
89 import org.springframework.ai.ollama.api.OllamaApi; 73 import org.springframework.ai.ollama.api.OllamaApi;
@@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore @@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore
114 import org.springframework.ai.vectorstore.redis.RedisVectorStore; 98 import org.springframework.ai.vectorstore.redis.RedisVectorStore;
115 import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; 99 import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration;
116 import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; 100 import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
117 -import org.springframework.ai.zhipuai.*;  
118 -import org.springframework.ai.zhipuai.api.ZhiPuAiApi;  
119 -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;  
120 import org.springframework.beans.BeansException; 101 import org.springframework.beans.BeansException;
121 import org.springframework.beans.factory.ObjectProvider; 102 import org.springframework.beans.factory.ObjectProvider;
122 import org.springframework.boot.autoconfigure.data.redis.RedisProperties; 103 import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
123 -import org.springframework.web.client.RestClient;  
124 import redis.clients.jedis.JedisPooled; 104 import redis.clients.jedis.JedisPooled;
125 105
126 import java.io.File; 106 import java.io.File;
@@ -131,7 +111,6 @@ import java.util.Timer; @@ -131,7 +111,6 @@ import java.util.Timer;
131 import java.util.TimerTask; 111 import java.util.TimerTask;
132 112
133 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 113 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
134 -import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;  
135 114
136 /** 115 /**
137 * AI Model 模型工厂的实现类 116 * AI Model 模型工厂的实现类
@@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; @@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
141 public class AiModelFactoryImpl implements AiModelFactory { 120 public class AiModelFactoryImpl implements AiModelFactory {
142 121
143 @Override 122 @Override
144 - public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { 123 + public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
  124 + final String apiKey = resolveSpringPlaceholders(rawApiKey);
  125 + final String url = resolveSpringPlaceholders(rawUrl);
145 String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); 126 String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
146 return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { 127 return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
147 // noinspection EnhancedSwitchMigration 128 // noinspection EnhancedSwitchMigration
@@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
164 return buildMiniMaxChatModel(apiKey, url); 145 return buildMiniMaxChatModel(apiKey, url);
165 case MOONSHOT: 146 case MOONSHOT:
166 return buildMoonshotChatModel(apiKey, url); 147 return buildMoonshotChatModel(apiKey, url);
  148 + case STEP_FUN:
  149 + return buildStepFunChatModel(apiKey, url);
167 case XING_HUO: 150 case XING_HUO:
168 return buildXingHuoChatModel(apiKey); 151 return buildXingHuoChatModel(apiKey);
169 case BAI_CHUAN: 152 case BAI_CHUAN:
@@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
179 case OLLAMA: 162 case OLLAMA:
180 return buildOllamaChatModel(url); 163 return buildOllamaChatModel(url);
181 case GROK: 164 case GROK:
182 - return buildGrokChatModel(apiKey,url); 165 + return buildGrokChatModel(apiKey, url);
183 default: 166 default:
184 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 167 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
185 } 168 }
@@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
193 case TONG_YI: 176 case TONG_YI:
194 return SpringUtil.getBean(DashScopeChatModel.class); 177 return SpringUtil.getBean(DashScopeChatModel.class);
195 case YI_YAN: 178 case YI_YAN:
196 - return SpringUtil.getBean(QianFanChatModel.class); 179 + return SpringUtil.getBean(YiYanChatModel.class);
197 case DEEP_SEEK: 180 case DEEP_SEEK:
198 return SpringUtil.getBean(DeepSeekChatModel.class); 181 return SpringUtil.getBean(DeepSeekChatModel.class);
199 case DOU_BAO: 182 case DOU_BAO:
@@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory {
203 case SILICON_FLOW: 186 case SILICON_FLOW:
204 return SpringUtil.getBean(SiliconFlowChatModel.class); 187 return SpringUtil.getBean(SiliconFlowChatModel.class);
205 case ZHI_PU: 188 case ZHI_PU:
206 - return SpringUtil.getBean(ZhiPuAiChatModel.class); 189 + return SpringUtil.getBean(ZhiPuChatModel.class);
207 case MINI_MAX: 190 case MINI_MAX:
208 return SpringUtil.getBean(MiniMaxChatModel.class); 191 return SpringUtil.getBean(MiniMaxChatModel.class);
209 case MOONSHOT: 192 case MOONSHOT:
210 return SpringUtil.getBean(MoonshotChatModel.class); 193 return SpringUtil.getBean(MoonshotChatModel.class);
  194 + case STEP_FUN:
  195 + return SpringUtil.getBean(StepFunChatModel.class);
211 case XING_HUO: 196 case XING_HUO:
212 return SpringUtil.getBean(XingHuoChatModel.class); 197 return SpringUtil.getBean(XingHuoChatModel.class);
213 case BAI_CHUAN: 198 case BAI_CHUAN:
@@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory {
222 return SpringUtil.getBean(GeminiChatModel.class); 207 return SpringUtil.getBean(GeminiChatModel.class);
223 case OLLAMA: 208 case OLLAMA:
224 return SpringUtil.getBean(OllamaChatModel.class); 209 return SpringUtil.getBean(OllamaChatModel.class);
  210 + case GROK:
  211 + return SpringUtil.getBean(GrokChatModel.class);
225 default: 212 default:
226 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 213 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
227 } 214 }
@@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
233 switch (platform) { 220 switch (platform) {
234 case TONG_YI: 221 case TONG_YI:
235 return SpringUtil.getBean(DashScopeImageModel.class); 222 return SpringUtil.getBean(DashScopeImageModel.class);
236 - case YI_YAN:  
237 - return SpringUtil.getBean(QianFanImageModel.class);  
238 - case ZHI_PU:  
239 - return SpringUtil.getBean(ZhiPuAiImageModel.class);  
240 case SILICON_FLOW: 223 case SILICON_FLOW:
241 return SpringUtil.getBean(SiliconFlowImageModel.class); 224 return SpringUtil.getBean(SiliconFlowImageModel.class);
242 case OPENAI: 225 case OPENAI:
@@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory {
249 } 232 }
250 233
251 @Override 234 @Override
252 - public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { 235 + public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
  236 + String apiKey = resolveSpringPlaceholders(rawApiKey);
  237 + String url = resolveSpringPlaceholders(rawUrl);
253 // noinspection EnhancedSwitchMigration 238 // noinspection EnhancedSwitchMigration
254 switch (platform) { 239 switch (platform) {
255 case TONG_YI: 240 case TONG_YI:
256 return buildTongYiImagesModel(apiKey); 241 return buildTongYiImagesModel(apiKey);
257 - case YI_YAN:  
258 - return buildQianFanImageModel(apiKey);  
259 - case ZHI_PU:  
260 - return buildZhiPuAiImageModel(apiKey, url);  
261 case OPENAI: 242 case OPENAI:
262 return buildOpenAiImageModel(apiKey, url); 243 return buildOpenAiImageModel(apiKey, url);
263 case SILICON_FLOW: 244 case SILICON_FLOW:
264 - return buildSiliconFlowImageModel(apiKey,url); 245 + return buildSiliconFlowImageModel(apiKey, url);
265 case STABLE_DIFFUSION: 246 case STABLE_DIFFUSION:
266 return buildStabilityAiImageModel(apiKey, url); 247 return buildStabilityAiImageModel(apiKey, url);
267 default: 248 default:
@@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
270 } 251 }
271 252
272 @Override 253 @Override
273 - public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) {  
274 - String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,  
275 - url); 254 + public MidjourneyApi getOrCreateMidjourneyApi(String rawApiKey, String rawUrl) {
  255 + final String apiKey = resolveSpringPlaceholders(rawApiKey);
  256 + final String url = resolveSpringPlaceholders(rawUrl);
  257 + String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(),
  258 + apiKey, url);
276 return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { 259 return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> {
277 YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) 260 YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class)
278 .getMidjourney(); 261 .getMidjourney();
@@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory {
281 } 264 }
282 265
283 @Override 266 @Override
284 - public SunoApi getOrCreateSunoApi(String apiKey, String url) { 267 + public SunoApi getOrCreateSunoApi(String rawApiKey, String rawUrl) {
  268 + final String apiKey = resolveSpringPlaceholders(rawApiKey);
  269 + final String url = resolveSpringPlaceholders(rawUrl);
285 String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); 270 String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url);
286 return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); 271 return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url));
287 } 272 }
288 273
289 @Override 274 @Override
290 @SuppressWarnings("EnhancedSwitchMigration") 275 @SuppressWarnings("EnhancedSwitchMigration")
291 - public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) { 276 + public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String rawApiKey, String rawUrl, String model) {
  277 + final String apiKey = resolveSpringPlaceholders(rawApiKey);
  278 + final String url = resolveSpringPlaceholders(rawUrl);
292 String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); 279 String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model);
293 return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { 280 return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
294 switch (platform) { 281 switch (platform) {
295 case TONG_YI: 282 case TONG_YI:
296 return buildTongYiEmbeddingModel(apiKey, model); 283 return buildTongYiEmbeddingModel(apiKey, model);
297 - case YI_YAN:  
298 - return buildYiYanEmbeddingModel(apiKey, model);  
299 - case ZHI_PU:  
300 - return buildZhiPuEmbeddingModel(apiKey, url, model);  
301 - case MINI_MAX:  
302 - return buildMiniMaxEmbeddingModel(apiKey, url, model);  
303 case OPENAI: 284 case OPENAI:
304 return buildOpenAiEmbeddingModel(apiKey, url, model); 285 return buildOpenAiEmbeddingModel(apiKey, url, model);
305 case AZURE_OPENAI: 286 case AZURE_OPENAI:
@@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
341 return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); 322 return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
342 } 323 }
343 324
  325 + private static String resolveSpringPlaceholders(String value) {
  326 + // yml 配置的占位符由 Spring 自动解析;DB 里保存的 ${xxx} 需要在这里手动解析。
  327 + return AiUtils.resolveSpringPlaceholders(value);
  328 + }
  329 +
344 // ========== 各种创建 spring-ai 客户端的方法 ========== 330 // ========== 各种创建 spring-ai 客户端的方法 ==========
345 331
346 /** 332 /**
@@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
367 .build(); 353 .build();
368 } 354 }
369 355
370 - /**  
371 - * 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法  
372 - */  
373 - private static QianFanChatModel buildYiYanChatModel(String key) {  
374 - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6  
375 - List<String> keys = StrUtil.split(key, '|');  
376 - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");  
377 - String appKey = keys.get(0);  
378 - String secretKey = keys.get(1);  
379 - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);  
380 - return new QianFanChatModel(qianFanApi);  
381 - }  
382 -  
383 - /**  
384 - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法  
385 - */  
386 - private QianFanImageModel buildQianFanImageModel(String key) {  
387 - // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6  
388 - List<String> keys = StrUtil.split(key, '|');  
389 - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");  
390 - String appKey = keys.get(0);  
391 - String secretKey = keys.get(1);  
392 - QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);  
393 - return new QianFanImageModel(qianFanApi); 356 + private ChatModel buildYiYanChatModel(String apiKey) {
  357 + YudaoAiProperties.YiYan properties = new YudaoAiProperties.YiYan()
  358 + .setApiKey(apiKey);
  359 + return new AiAutoConfiguration().buildYiYanChatClient(properties);
394 } 360 }
395 361
396 /** 362 /**
@@ -435,62 +401,47 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -435,62 +401,47 @@ public class AiModelFactoryImpl implements AiModelFactory {
435 } 401 }
436 402
437 /** 403 /**
438 - * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 404 + * 可参考 {@link AiAutoConfiguration#zhiPuChatClient(YudaoAiProperties)}
439 */ 405 */
440 - private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {  
441 - ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);  
442 - if (StrUtil.isNotEmpty(url)) {  
443 - zhiPuAiApiBuilder.baseUrl(url);  
444 - }  
445 - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();  
446 - return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,  
447 - getObservationRegistry().getIfAvailable()); 406 + private ZhiPuChatModel buildZhiPuChatModel(String apiKey, String url) {
  407 + YudaoAiProperties.ZhiPu properties = new YudaoAiProperties.ZhiPu()
  408 + .setBaseUrl(url).setApiKey(apiKey);
  409 + return new AiAutoConfiguration().buildZhiPuChatClient(properties);
448 } 410 }
449 411
450 /** 412 /**
451 - * 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法 413 + * 可参考 {@link AiAutoConfiguration#miniMaxChatClient(YudaoAiProperties)}
452 */ 414 */
453 - private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {  
454 - ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)  
455 - : new ZhiPuAiImageApi(url, apiKey, RestClient.builder());  
456 - return new ZhiPuAiImageModel(zhiPuAiApi); 415 + private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
  416 + YudaoAiProperties.MiniMax properties = new YudaoAiProperties.MiniMax()
  417 + .setBaseUrl(url).setApiKey(apiKey);
  418 + return new AiAutoConfiguration().buildMiniMaxChatClient(properties);
457 } 419 }
458 420
459 /** 421 /**
460 - * 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法 422 + * 可参考 {@link AiAutoConfiguration#moonshotChatClient(YudaoAiProperties)}
461 */ 423 */
462 - private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {  
463 - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)  
464 - : new MiniMaxApi(url, apiKey);  
465 - MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();  
466 - return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE); 424 + private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
  425 + YudaoAiProperties.Moonshot properties = new YudaoAiProperties.Moonshot()
  426 + .setBaseUrl(url).setApiKey(apiKey);
  427 + return new AiAutoConfiguration().buildMoonshotChatClient(properties);
467 } 428 }
468 429
469 /** 430 /**
470 - * 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法 431 + * 可参考 {@link AiAutoConfiguration#stepFunChatClient(YudaoAiProperties)}
471 */ 432 */
472 - private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {  
473 - MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()  
474 - .apiKey(apiKey);  
475 - if (StrUtil.isNotEmpty(url)) {  
476 - moonshotApiBuilder.baseUrl(url);  
477 - }  
478 - MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();  
479 - return MoonshotChatModel.builder()  
480 - .moonshotApi(moonshotApiBuilder.build())  
481 - .defaultOptions(options)  
482 - .toolCallingManager(getToolCallingManager())  
483 - .build(); 433 + private StepFunChatModel buildStepFunChatModel(String apiKey, String url) {
  434 + YudaoAiProperties.StepFun properties = new YudaoAiProperties.StepFun()
  435 + .setBaseUrl(url).setApiKey(apiKey);
  436 + return new AiAutoConfiguration().buildStepFunChatClient(properties);
484 } 437 }
485 438
486 /** 439 /**
487 * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} 440 * 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)}
488 */ 441 */
489 - private static XingHuoChatModel buildXingHuoChatModel(String key) {  
490 - List<String> keys = StrUtil.split(key, '|');  
491 - Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); 442 + private static XingHuoChatModel buildXingHuoChatModel(String apiKey) {
492 YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() 443 YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo()
493 - .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); 444 + .setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT);
494 return new AiAutoConfiguration().buildXingHuoChatClient(properties); 445 return new AiAutoConfiguration().buildXingHuoChatClient(properties);
495 } 446 }
496 447
@@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
590 return new StabilityAiImageModel(stabilityAiApi); 541 return new StabilityAiImageModel(stabilityAiApi);
591 } 542 }
592 543
593 - private ChatModel buildGrokChatModel(String apiKey,String url) { 544 + private GrokChatModel buildGrokChatModel(String apiKey, String url) {
594 YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() 545 YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok()
595 .setBaseUrl(url) 546 .setBaseUrl(url)
596 .setApiKey(apiKey); 547 .setApiKey(apiKey);
@@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
608 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); 559 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
609 } 560 }
610 561
611 - /**  
612 - * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法  
613 - */  
614 - private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {  
615 - ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);  
616 - if (StrUtil.isNotEmpty(url)) {  
617 - zhiPuAiApiBuilder.baseUrl(url);  
618 - }  
619 - ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build();  
620 - return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions);  
621 - }  
622 -  
623 - /**  
624 - * 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法  
625 - */  
626 - private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {  
627 - MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)  
628 - : new MiniMaxApi(url, apiKey);  
629 - MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build();  
630 - return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions);  
631 - }  
632 -  
633 - /**  
634 - * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法  
635 - */  
636 - private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {  
637 - List<String> keys = StrUtil.split(key, '|');  
638 - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");  
639 - String appKey = keys.get(0);  
640 - String secretKey = keys.get(1);  
641 - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);  
642 - QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build();  
643 - return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions);  
644 - }  
645 -  
646 private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { 562 private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
647 OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); 563 OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
648 OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); 564 OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build();
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java
@@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel; @@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel;
6 import org.springframework.ai.chat.model.ChatResponse; 6 import org.springframework.ai.chat.model.ChatResponse;
7 import org.springframework.ai.chat.prompt.ChatOptions; 7 import org.springframework.ai.chat.prompt.ChatOptions;
8 import org.springframework.ai.chat.prompt.Prompt; 8 import org.springframework.ai.chat.prompt.Prompt;
9 -import org.springframework.ai.openai.OpenAiChatModel; 9 +import org.springframework.ai.deepseek.DeepSeekChatModel;
10 import reactor.core.publisher.Flux; 10 import reactor.core.publisher.Flux;
11 11
12 /** 12 /**
@@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel { @@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel {
20 20
21 public static final String BASE_URL = "https://api.baichuan-ai.com"; 21 public static final String BASE_URL = "https://api.baichuan-ai.com";
22 22
23 - public static final String MODEL_DEFAULT = "Baichuan4-Turbo"; 23 + public static final String COMPLETE_PATH = "/v1/chat/completions";
  24 +
  25 + public static final String MODEL_DEFAULT = "Baichuan-M3";
24 26
25 /** 27 /**
26 - * 兼容 OpenAI 接口,进行复用 28 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
27 */ 29 */
28 - private final OpenAiChatModel openAiChatModel; 30 + private final DeepSeekChatModel deepSeekChatModel;
29 31
30 @Override 32 @Override
31 public ChatResponse call(Prompt prompt) { 33 public ChatResponse call(Prompt prompt) {
32 - return openAiChatModel.call(prompt); 34 + return deepSeekChatModel.call(prompt);
33 } 35 }
34 36
35 @Override 37 @Override
36 public Flux<ChatResponse> stream(Prompt prompt) { 38 public Flux<ChatResponse> stream(Prompt prompt) {
37 - return openAiChatModel.stream(prompt); 39 + return deepSeekChatModel.stream(prompt);
38 } 40 }
39 41
40 @Override 42 @Override
41 public ChatOptions getDefaultOptions() { 43 public ChatOptions getDefaultOptions() {
42 - return openAiChatModel.getDefaultOptions(); 44 + return deepSeekChatModel.getDefaultOptions();
43 } 45 }
44 46
45 } 47 }
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java
@@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel { @@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel {
20 public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api"; 20 public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
21 public static final String COMPLETE_PATH = "/v3/chat/completions"; 21 public static final String COMPLETE_PATH = "/v3/chat/completions";
22 22
23 - public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115"; 23 + public static final String MODEL_DEFAULT = "doubao-seed-2-1-turbo-260628";
24 24
25 /** 25 /**
26 * 兼容 OpenAI 接口,进行复用 26 * 兼容 OpenAI 接口,进行复用
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java
@@ -19,7 +19,7 @@ public class GrokChatModel implements ChatModel { @@ -19,7 +19,7 @@ public class GrokChatModel implements ChatModel {
19 19
20 public static final String BASE_URL = "https://api.x.ai"; 20 public static final String BASE_URL = "https://api.x.ai";
21 public static final String COMPLETE_PATH = "/v1/chat/completions"; 21 public static final String COMPLETE_PATH = "/v1/chat/completions";
22 - public static final String MODEL_DEFAULT = "grok-4-fast-reasoning"; 22 + public static final String MODEL_DEFAULT = "grok-4.3";
23 23
24 /** 24 /**
25 * 兼容 OpenAI 接口,进行复用 25 * 兼容 OpenAI 接口,进行复用
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java
@@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt; @@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt;
9 import reactor.core.publisher.Flux; 9 import reactor.core.publisher.Flux;
10 10
11 /** 11 /**
12 - * 腾云混元 {@link ChatModel} 实现类 12 + * 腾讯混元 {@link ChatModel} 实现类
13 * 13 *
14 - * 1. 混元大模型:基于 <a href="https://cloud.tencent.com/document/product/1729/111007">知识引擎原子能力</a> 实现  
15 - * 2. 知识引擎原子能力:基于 <a href="https://cloud.tencent.com/document/product/1772/115969">知识引擎原子能力</a> 实现 14 + * 基于 <a href="https://cloud.tencent.com/document/product/1823/132252">TokenHub OpenAI 兼容接口</a> 实现
16 * 15 *
17 * @author fansili 16 * @author fansili
18 */ 17 */
@@ -20,14 +19,10 @@ import reactor.core.publisher.Flux; @@ -20,14 +19,10 @@ import reactor.core.publisher.Flux;
20 @RequiredArgsConstructor 19 @RequiredArgsConstructor
21 public class HunYuanChatModel implements ChatModel { 20 public class HunYuanChatModel implements ChatModel {
22 21
23 - public static final String BASE_URL = "https://api.hunyuan.cloud.tencent.com"; 22 + public static final String BASE_URL = "https://tokenhub.tencentmaas.com";
24 public static final String COMPLETE_PATH = "/v1/chat/completions"; 23 public static final String COMPLETE_PATH = "/v1/chat/completions";
25 24
26 - public static final String MODEL_DEFAULT = "hunyuan-turbo";  
27 -  
28 - public static final String DEEP_SEEK_BASE_URL = "https://api.lkeap.cloud.tencent.com";  
29 -  
30 - public static final String DEEP_SEEK_MODEL_DEFAULT = "deepseek-v3"; 25 + public static final String MODEL_DEFAULT = "hy3-preview";
31 26
32 /** 27 /**
33 * 兼容 OpenAI 接口,进行复用 28 * 兼容 OpenAI 接口,进行复用
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/minimax/MiniMaxChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax;
  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.deepseek.DeepSeekChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * MiniMax {@link ChatModel} 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class MiniMaxChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://api.minimaxi.com/v1";
  22 +
  23 + public static final String MODEL_DEFAULT = "MiniMax-M3";
  24 +
  25 + /**
  26 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
  27 + */
  28 + private final DeepSeekChatModel deepSeekChatModel;
  29 +
  30 + @Override
  31 + public ChatResponse call(Prompt prompt) {
  32 + return deepSeekChatModel.call(prompt);
  33 + }
  34 +
  35 + @Override
  36 + public Flux<ChatResponse> stream(Prompt prompt) {
  37 + return deepSeekChatModel.stream(prompt);
  38 + }
  39 +
  40 + @Override
  41 + public ChatOptions getDefaultOptions() {
  42 + return deepSeekChatModel.getDefaultOptions();
  43 + }
  44 +
  45 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/moonshot/MoonshotChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot;
  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.deepseek.DeepSeekChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * 月之暗面 {@link ChatModel} 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class MoonshotChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://api.moonshot.cn";
  22 +
  23 + public static final String COMPLETE_PATH = "/v1/chat/completions";
  24 +
  25 + public static final String MODEL_DEFAULT = "kimi-k2.6";
  26 +
  27 + /**
  28 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
  29 + */
  30 + private final DeepSeekChatModel deepSeekChatModel;
  31 +
  32 + @Override
  33 + public ChatResponse call(Prompt prompt) {
  34 + return deepSeekChatModel.call(prompt);
  35 + }
  36 +
  37 + @Override
  38 + public Flux<ChatResponse> stream(Prompt prompt) {
  39 + return deepSeekChatModel.stream(prompt);
  40 + }
  41 +
  42 + @Override
  43 + public ChatOptions getDefaultOptions() {
  44 + return deepSeekChatModel.getDefaultOptions();
  45 + }
  46 +
  47 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/stepfun/StepFunChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun;
  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.deepseek.DeepSeekChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * 阶跃星辰 {@link ChatModel} 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class StepFunChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://api.stepfun.com";
  22 +
  23 + public static final String COMPLETE_PATH = "/v1/chat/completions";
  24 +
  25 + public static final String MODEL_DEFAULT = "step-3.7-flash";
  26 +
  27 + /**
  28 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
  29 + */
  30 + private final DeepSeekChatModel deepSeekChatModel;
  31 +
  32 + @Override
  33 + public ChatResponse call(Prompt prompt) {
  34 + return deepSeekChatModel.call(prompt);
  35 + }
  36 +
  37 + @Override
  38 + public Flux<ChatResponse> stream(Prompt prompt) {
  39 + return deepSeekChatModel.stream(prompt);
  40 + }
  41 +
  42 + @Override
  43 + public ChatOptions getDefaultOptions() {
  44 + return deepSeekChatModel.getDefaultOptions();
  45 + }
  46 +
  47 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/xinghuo/XingHuoChatModel.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo;
2 2
3 -import lombok.RequiredArgsConstructor; 3 +import cn.hutool.core.util.StrUtil;
4 import lombok.extern.slf4j.Slf4j; 4 import lombok.extern.slf4j.Slf4j;
5 import org.springframework.ai.chat.model.ChatModel; 5 import org.springframework.ai.chat.model.ChatModel;
6 import org.springframework.ai.chat.model.ChatResponse; 6 import org.springframework.ai.chat.model.ChatResponse;
7 import org.springframework.ai.chat.prompt.ChatOptions; 7 import org.springframework.ai.chat.prompt.ChatOptions;
8 import org.springframework.ai.chat.prompt.Prompt; 8 import org.springframework.ai.chat.prompt.Prompt;
  9 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  10 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  11 +import org.springframework.ai.deepseek.api.DeepSeekApi;
9 import reactor.core.publisher.Flux; 12 import reactor.core.publisher.Flux;
10 13
11 /** 14 /**
@@ -14,37 +17,140 @@ import reactor.core.publisher.Flux; @@ -14,37 +17,140 @@ import reactor.core.publisher.Flux;
14 * @author fansili 17 * @author fansili
15 */ 18 */
16 @Slf4j 19 @Slf4j
17 -@RequiredArgsConstructor  
18 public class XingHuoChatModel implements ChatModel { 20 public class XingHuoChatModel implements ChatModel {
19 21
20 - public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com";  
21 -  
22 - public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com";  
23 - public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions"; 22 + /**
  23 + * 星火 X2
  24 + *
  25 + * @see <a href="https://spark-api-open.xf-yun.com/x2/chat/completions">接口地址</a>
  26 + */
  27 + public static final String MODEL_X2 = "x2";
24 28
25 /** 29 /**
26 - * 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite 30 + * 星火 X2 Flash
  31 + *
  32 + * @see <a href="https://spark-api-open.xf-yun.com/agent/v1/chat/completions">接口地址</a>
27 */ 33 */
28 - public static final String MODEL_DEFAULT = "4.0Ultra"; 34 + public static final String MODEL_X2_FLASH = "x2-flash";
  35 +
  36 + private static final String BASE_URL_X2 = "https://spark-api-open.xf-yun.com/x2";
  37 +
  38 + private static final String BASE_URL_X2_FLASH = "https://spark-api-open.xf-yun.com/agent/v1";
  39 +
  40 + public static final String MODEL_DEFAULT = MODEL_X2_FLASH;
  41 +
  42 + private static String getBaseUrl(String model) {
  43 + if (MODEL_X2_FLASH.equals(model)) {
  44 + return BASE_URL_X2_FLASH;
  45 + }
  46 + return BASE_URL_X2;
  47 + }
  48 +
  49 + private final String apiKey;
  50 +
  51 + private final DeepSeekChatOptions options;
29 52
30 /** 53 /**
31 - * v1 兼容 OpenAI 接口,进行复用 54 + * 兼容 OpenAI 接口,进行复用
32 */ 55 */
33 - private final ChatModel openAiChatModelV1; 56 + private final ChatModel chatModelX2;
  57 +
  58 + private final ChatModel chatModelX2Flash;
  59 +
  60 + private XingHuoChatModel(String apiKey, DeepSeekChatOptions options) {
  61 + this.apiKey = apiKey;
  62 + this.options = options;
  63 + this.chatModelX2 = buildChatModel(MODEL_X2);
  64 + this.chatModelX2Flash = buildChatModel(MODEL_X2_FLASH);
  65 + }
  66 +
  67 + public static Builder builder() {
  68 + return new Builder();
  69 + }
34 70
35 @Override 71 @Override
36 public ChatResponse call(Prompt prompt) { 72 public ChatResponse call(Prompt prompt) {
37 - return openAiChatModelV1.call(prompt); 73 + return getChatModel(prompt).call(buildApiPrompt(prompt));
38 } 74 }
39 75
40 @Override 76 @Override
41 public Flux<ChatResponse> stream(Prompt prompt) { 77 public Flux<ChatResponse> stream(Prompt prompt) {
42 - return openAiChatModelV1.stream(prompt); 78 + return getChatModel(prompt).stream(buildApiPrompt(prompt));
43 } 79 }
44 80
45 @Override 81 @Override
46 public ChatOptions getDefaultOptions() { 82 public ChatOptions getDefaultOptions() {
47 - return openAiChatModelV1.getDefaultOptions(); 83 + return options;
  84 + }
  85 +
  86 + private ChatModel getChatModel(Prompt prompt) {
  87 + String model = options.getModel();
  88 + ChatOptions options = prompt.getOptions();
  89 + if (options != null && isBusinessModel(options.getModel())) {
  90 + model = options.getModel();
  91 + }
  92 + return getChatModel(model);
  93 + }
  94 +
  95 + private ChatModel getChatModel(String model) {
  96 + if (MODEL_X2_FLASH.equals(model)) {
  97 + return chatModelX2Flash;
  98 + }
  99 + return chatModelX2;
  100 + }
  101 +
  102 + private ChatModel buildChatModel(String model) {
  103 + return DeepSeekChatModel.builder()
  104 + .deepSeekApi(DeepSeekApi.builder()
  105 + .baseUrl(getBaseUrl(model))
  106 + .apiKey(apiKey)
  107 + .build())
  108 + .defaultOptions(new DeepSeekChatOptions.Builder(options).model("spark-x").build())
  109 + .build();
  110 + }
  111 +
  112 + private static Prompt buildApiPrompt(Prompt prompt) {
  113 + ChatOptions options = prompt.getOptions();
  114 + if (options == null) {
  115 + return prompt;
  116 + }
  117 + if (!(options instanceof DeepSeekChatOptions)) {
  118 + return prompt;
  119 + }
  120 + return Prompt.builder()
  121 + .messages(prompt.getInstructions())
  122 + .chatOptions(new DeepSeekChatOptions.Builder((DeepSeekChatOptions) options).model("spark-x").build())
  123 + .build();
  124 + }
  125 +
  126 + private static boolean isBusinessModel(String model) {
  127 + return MODEL_X2.equals(model) || MODEL_X2_FLASH.equals(model);
  128 + }
  129 +
  130 + public static final class Builder {
  131 +
  132 + private String apiKey;
  133 +
  134 + private DeepSeekChatOptions options;
  135 +
  136 + public Builder apiKey(String apiKey) {
  137 + this.apiKey = apiKey;
  138 + return this;
  139 + }
  140 +
  141 + public Builder options(DeepSeekChatOptions options) {
  142 + this.options = options;
  143 + return this;
  144 + }
  145 +
  146 + public XingHuoChatModel build() {
  147 + DeepSeekChatOptions options = this.options != null ? this.options : DeepSeekChatOptions.builder().build();
  148 + if (StrUtil.isEmpty(options.getModel())) {
  149 + options = new DeepSeekChatOptions.Builder(options).model(MODEL_DEFAULT).build();
  150 + }
  151 + return new XingHuoChatModel(apiKey, options);
  152 + }
  153 +
48 } 154 }
49 155
50 } 156 }
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/yiyan/YiYanChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan;
  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.deepseek.DeepSeekChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * 文心一言 {@link ChatModel} 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class YiYanChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://qianfan.baidubce.com/v2";
  22 +
  23 + public static final String MODEL_DEFAULT = "ernie-5.1";
  24 +
  25 + /**
  26 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
  27 + */
  28 + private final DeepSeekChatModel deepSeekChatModel;
  29 +
  30 + @Override
  31 + public ChatResponse call(Prompt prompt) {
  32 + return deepSeekChatModel.call(prompt);
  33 + }
  34 +
  35 + @Override
  36 + public Flux<ChatResponse> stream(Prompt prompt) {
  37 + return deepSeekChatModel.stream(prompt);
  38 + }
  39 +
  40 + @Override
  41 + public ChatOptions getDefaultOptions() {
  42 + return deepSeekChatModel.getDefaultOptions();
  43 + }
  44 +
  45 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/zhipu/ZhiPuChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu;
  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.deepseek.DeepSeekChatModel;
  10 +import reactor.core.publisher.Flux;
  11 +
  12 +/**
  13 + * 智谱 {@link ChatModel} 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@RequiredArgsConstructor
  19 +public class ZhiPuChatModel implements ChatModel {
  20 +
  21 + public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
  22 +
  23 + public static final String MODEL_DEFAULT = "glm-5.2";
  24 +
  25 + /**
  26 + * 兼容 OpenAI 接口,复用 DeepSeek 客户端
  27 + */
  28 + private final DeepSeekChatModel deepSeekChatModel;
  29 +
  30 + @Override
  31 + public ChatResponse call(Prompt prompt) {
  32 + return deepSeekChatModel.call(prompt);
  33 + }
  34 +
  35 + @Override
  36 + public Flux<ChatResponse> stream(Prompt prompt) {
  37 + return deepSeekChatModel.stream(prompt);
  38 + }
  39 +
  40 + @Override
  41 + public ChatOptions getDefaultOptions() {
  42 + return deepSeekChatModel.getDefaultOptions();
  43 + }
  44 +
  45 +}
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi; @@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
29 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; 29 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
30 import jakarta.annotation.Resource; 30 import jakarta.annotation.Resource;
31 import lombok.extern.slf4j.Slf4j; 31 import lombok.extern.slf4j.Slf4j;
32 -import org.springaicommunity.qianfan.QianFanImageOptions;  
33 import org.springframework.ai.image.ImageModel; 32 import org.springframework.ai.image.ImageModel;
34 import org.springframework.ai.image.ImageOptions; 33 import org.springframework.ai.image.ImageOptions;
35 import org.springframework.ai.image.ImagePrompt; 34 import org.springframework.ai.image.ImagePrompt;
36 import org.springframework.ai.image.ImageResponse; 35 import org.springframework.ai.image.ImageResponse;
37 import org.springframework.ai.openai.OpenAiImageOptions; 36 import org.springframework.ai.openai.OpenAiImageOptions;
38 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; 37 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
39 -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;  
40 import org.springframework.scheduling.annotation.Async; 38 import org.springframework.scheduling.annotation.Async;
41 import org.springframework.stereotype.Service; 39 import org.springframework.stereotype.Service;
42 import org.springframework.transaction.annotation.Transactional; 40 import org.springframework.transaction.annotation.Transactional;
@@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService { @@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService {
168 .model(model.getModel()).n(1) 166 .model(model.getModel()).n(1)
169 .height(draw.getHeight()).width(draw.getWidth()) 167 .height(draw.getHeight()).width(draw.getWidth())
170 .build(); 168 .build();
171 - } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {  
172 - return QianFanImageOptions.builder()  
173 - .model(model.getModel()).N(1)  
174 - .height(draw.getHeight()).width(draw.getWidth())  
175 - .build();  
176 - } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {  
177 - return ZhiPuAiImageOptions.builder()  
178 - .model(model.getModel())  
179 - .build();  
180 } 169 }
181 throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform()); 170 throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
182 } 171 }
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
@@ -8,8 +8,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; @@ -8,8 +8,7 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
8 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 8 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
9 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; 9 import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
10 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; 10 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
11 -import org.springaicommunity.moonshot.MoonshotChatOptions;  
12 -import org.springaicommunity.qianfan.QianFanChatOptions; 11 +import cn.hutool.extra.spring.SpringUtil;
13 import org.springframework.ai.anthropic.AnthropicChatOptions; 12 import org.springframework.ai.anthropic.AnthropicChatOptions;
14 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; 13 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
15 import org.springframework.ai.chat.messages.*; 14 import org.springframework.ai.chat.messages.*;
@@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse; @@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse;
17 import org.springframework.ai.chat.prompt.ChatOptions; 16 import org.springframework.ai.chat.prompt.ChatOptions;
18 import org.springframework.ai.deepseek.DeepSeekAssistantMessage; 17 import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
19 import org.springframework.ai.deepseek.DeepSeekChatOptions; 18 import org.springframework.ai.deepseek.DeepSeekChatOptions;
20 -import org.springframework.ai.minimax.MiniMaxChatOptions;  
21 import org.springframework.ai.ollama.api.OllamaChatOptions; 19 import org.springframework.ai.ollama.api.OllamaChatOptions;
22 import org.springframework.ai.openai.OpenAiChatOptions; 20 import org.springframework.ai.openai.OpenAiChatOptions;
23 import org.springframework.ai.tool.ToolCallback; 21 import org.springframework.ai.tool.ToolCallback;
24 -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; 22 +import org.springframework.core.env.Environment;
25 23
26 import java.util.*; 24 import java.util.*;
27 25
  26 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  27 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_CONFIG_PLACEHOLDER_NOT_RESOLVED;
  28 +
28 /** 29 /**
29 * Spring AI 工具类 30 * Spring AI 工具类
30 * 31 *
@@ -36,13 +37,42 @@ public class AiUtils { @@ -36,13 +37,42 @@ public class AiUtils {
36 public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID"; 37 public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
37 38
38 /** 39 /**
  40 + * 解析 DB 等动态配置里的 Spring 占位符,例如 ${OPENAI_API_KEY}
  41 + *
  42 + * @param value 待解析的配置值
  43 + * @return 解析后的配置值
  44 + */
  45 + public static String resolveSpringPlaceholders(String value) {
  46 + if (StrUtil.isBlank(value) || !StrUtil.contains(value, "${")) {
  47 + return value;
  48 + }
  49 + try {
  50 + return SpringUtil.getBean(Environment.class).resolveRequiredPlaceholders(value);
  51 + } catch (IllegalArgumentException ex) {
  52 + throw exception(API_CONFIG_PLACEHOLDER_NOT_RESOLVED, value);
  53 + }
  54 + }
  55 +
  56 + /**
  57 + * 校验 API Key,避免集成测试使用默认占位值发起调用。
  58 + *
  59 + * @param apiKey API Key
  60 + */
  61 + public static void validateApiKey(String apiKey) {
  62 + if (StrUtil.isBlank(apiKey) || "sk-xxxx".equals(apiKey)) {
  63 + throw new IllegalStateException("apiKey 不能为空");
  64 + }
  65 + }
  66 +
  67 + /**
39 * 通义千问支持多模态的模型 68 * 通义千问支持多模态的模型
40 * 69 *
41 * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a> 70 * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a>
42 * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a> 71 * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a>
43 */ 72 */
44 public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet( 73 public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
45 - // qwen3.5 / 3.6 系列(统一多模态主干) 74 + // qwen3.5 / 3.6 / 3.7 系列(统一多模态主干)
  75 + "qwen3.7-max", "qwen3.7-plus", "qwen3.7-flash",
46 "qwen3.6-plus", "qwen3.6-flash", 76 "qwen3.6-plus", "qwen3.6-flash",
47 "qwen3.5-plus", "qwen3.5-flash", 77 "qwen3.5-plus", "qwen3.5-flash",
48 // qwen-vl 视觉理解 78 // qwen-vl 视觉理解
@@ -72,27 +102,21 @@ public class AiUtils { @@ -72,27 +102,21 @@ public class AiUtils {
72 .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 102 .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
73 .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 103 .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
74 .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); 104 .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
75 - case YI_YAN:  
76 - return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();  
77 case DEEP_SEEK: 105 case DEEP_SEEK:
78 case DOU_BAO: // 复用 DeepSeek 客户端 106 case DOU_BAO: // 复用 DeepSeek 客户端
79 case HUN_YUAN: // 复用 DeepSeek 客户端 107 case HUN_YUAN: // 复用 DeepSeek 客户端
80 case SILICON_FLOW: // 复用 DeepSeek 客户端 108 case SILICON_FLOW: // 复用 DeepSeek 客户端
  109 + case YI_YAN: // 复用 DeepSeek 客户端
  110 + case ZHI_PU: // 复用 DeepSeek 客户端
81 case XING_HUO: // 复用 DeepSeek 客户端 111 case XING_HUO: // 复用 DeepSeek 客户端
  112 + case MINI_MAX: // 复用 DeepSeek 客户端
  113 + case MOONSHOT: // 复用 DeepSeek 客户端
  114 + case BAI_CHUAN: // 复用 DeepSeek 客户端
  115 + case STEP_FUN: // 复用 DeepSeek 客户端
82 return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 116 return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
83 .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); 117 .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
84 - case ZHI_PU:  
85 - return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)  
86 - .toolCallbacks(toolCallbacks).toolContext(toolContext).build();  
87 - case MINI_MAX:  
88 - return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)  
89 - .toolCallbacks(toolCallbacks).toolContext(toolContext).build();  
90 - case MOONSHOT:  
91 - return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)  
92 - .toolCallbacks(toolCallbacks).toolContext(toolContext).build();  
93 case OPENAI: 118 case OPENAI:
94 case GEMINI: // 复用 OpenAI 客户端 119 case GEMINI: // 复用 OpenAI 客户端
95 - case BAI_CHUAN: // 复用 OpenAI 客户端  
96 case GROK: // 复用 OpenAI 客户端 120 case GROK: // 复用 OpenAI 客户端
97 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 121 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
98 .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); 122 .toolCallbacks(toolCallbacks).toolContext(toolContext).build();
@@ -159,4 +183,4 @@ public class AiUtils { @@ -159,4 +183,4 @@ public class AiUtils {
159 return MapUtil.getStr(output.getMetadata(), "reasoningContent"); 183 return MapUtil.getStr(output.getMetadata(), "reasoningContent");
160 } 184 }
161 185
162 -}  
163 \ No newline at end of file 186 \ No newline at end of file
  187 +}
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; 4 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
4 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
@@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; @@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage;
8 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
9 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
10 import org.springframework.ai.chat.prompt.Prompt; 11 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; 12 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  13 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  14 +import org.springframework.ai.deepseek.api.DeepSeekApi;
14 import reactor.core.publisher.Flux; 15 import reactor.core.publisher.Flux;
15 16
16 import java.util.ArrayList; 17 import java.util.ArrayList;
17 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
18 22
19 /** 23 /**
20 * {@link BaiChuanChatModel} 集成测试 24 * {@link BaiChuanChatModel} 集成测试
@@ -23,22 +27,27 @@ import java.util.List; @@ -23,22 +27,27 @@ import java.util.List;
23 */ 27 */
24 public class BaiChuanChatModelTests { 28 public class BaiChuanChatModelTests {
25 29
26 - private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
27 - .openAiApi(OpenAiApi.builder() 30 + private static final String API_KEY = SystemUtil.get("BAICHUAN_API_KEY",
  31 + "sk-xxxx"); // 按需改成你的百川 API Key
  32 + private static final String MODEL = SystemUtil.get("BAICHUAN_MODEL",
  33 + BaiChuanChatModel.MODEL_DEFAULT);
  34 +
  35 + private final BaiChuanChatModel chatModel = new BaiChuanChatModel(DeepSeekChatModel.builder()
  36 + .deepSeekApi(DeepSeekApi.builder()
28 .baseUrl(BaiChuanChatModel.BASE_URL) 37 .baseUrl(BaiChuanChatModel.BASE_URL)
29 - .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey 38 + .completionsPath(BaiChuanChatModel.COMPLETE_PATH)
  39 + .apiKey(API_KEY)
30 .build()) 40 .build())
31 - .defaultOptions(OpenAiChatOptions.builder()  
32 - .model("Baichuan4-Turbo") // 模型(https://platform.baichuan-ai.com/docs/api) 41 + .defaultOptions(DeepSeekChatOptions.builder()
  42 + .model(MODEL)
33 .temperature(0.7) 43 .temperature(0.7)
34 .build()) 44 .build())
35 - .build();  
36 -  
37 - private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel); 45 + .build());
38 46
39 @Test 47 @Test
40 @Disabled 48 @Disabled
41 public void testCall() { 49 public void testCall() {
  50 + validateApiKey(API_KEY);
42 // 准备参数 51 // 准备参数
43 List<Message> messages = new ArrayList<>(); 52 List<Message> messages = new ArrayList<>();
44 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 53 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -48,11 +57,13 @@ public class BaiChuanChatModelTests { @@ -48,11 +57,13 @@ public class BaiChuanChatModelTests {
48 ChatResponse response = chatModel.call(new Prompt(messages)); 57 ChatResponse response = chatModel.call(new Prompt(messages));
49 // 打印结果 58 // 打印结果
50 System.out.println(response); 59 System.out.println(response);
  60 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
51 } 61 }
52 62
53 @Test 63 @Test
54 @Disabled 64 @Disabled
55 public void testStream() { 65 public void testStream() {
  66 + validateApiKey(API_KEY);
56 // 准备参数 67 // 准备参数
57 List<Message> messages = new ArrayList<>(); 68 List<Message> messages = new ArrayList<>();
58 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 69 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -61,7 +72,10 @@ public class BaiChuanChatModelTests { @@ -61,7 +72,10 @@ public class BaiChuanChatModelTests {
61 // 调用 72 // 调用
62 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); 73 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
63 // 打印结果 74 // 打印结果
64 - flux.doOnNext(System.out::println).then().block(); 75 + flux.doOnNext(response -> {
  76 +// System.out.println(response);
  77 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  78 + }).then().block();
65 } 79 }
66 80
67 } 81 }
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import org.junit.jupiter.api.Disabled; 4 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 5 import org.junit.jupiter.api.Test;
5 import org.springframework.ai.chat.messages.Message; 6 import org.springframework.ai.chat.messages.Message;
@@ -14,6 +15,9 @@ import reactor.core.publisher.Flux; @@ -14,6 +15,9 @@ import reactor.core.publisher.Flux;
14 15
15 import java.util.ArrayList; 16 import java.util.ArrayList;
16 import java.util.List; 17 import java.util.List;
  18 +import java.util.Objects;
  19 +
  20 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
17 21
18 /** 22 /**
19 * {@link DeepSeekChatModel} 集成测试 23 * {@link DeepSeekChatModel} 集成测试
@@ -22,12 +26,17 @@ import java.util.List; @@ -22,12 +26,17 @@ import java.util.List;
22 */ 26 */
23 public class DeepSeekChatModelTests { 27 public class DeepSeekChatModelTests {
24 28
  29 + private static final String API_KEY = SystemUtil.get("DEEPSEEK_API_KEY",
  30 + "sk-xxxx");
  31 + private static final String MODEL = SystemUtil.get("DEEPSEEK_MODEL",
  32 + "deepseek-v4-flash");
  33 +
25 private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder() 34 private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
26 .deepSeekApi(DeepSeekApi.builder() 35 .deepSeekApi(DeepSeekApi.builder()
27 - .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey 36 + .apiKey(API_KEY) // apiKey
28 .build()) 37 .build())
29 .defaultOptions(DeepSeekChatOptions.builder() 38 .defaultOptions(DeepSeekChatOptions.builder()
30 - .model("deepseek-chat") // 模型 39 + .model(MODEL) // 模型
31 .temperature(0.7) 40 .temperature(0.7)
32 .build()) 41 .build())
33 .build(); 42 .build();
@@ -35,6 +44,7 @@ public class DeepSeekChatModelTests { @@ -35,6 +44,7 @@ public class DeepSeekChatModelTests {
35 @Test 44 @Test
36 @Disabled 45 @Disabled
37 public void testCall() { 46 public void testCall() {
  47 + validateApiKey(API_KEY);
38 // 准备参数 48 // 准备参数
39 List<Message> messages = new ArrayList<>(); 49 List<Message> messages = new ArrayList<>();
40 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 50 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -43,12 +53,13 @@ public class DeepSeekChatModelTests { @@ -43,12 +53,13 @@ public class DeepSeekChatModelTests {
43 // 调用 53 // 调用
44 ChatResponse response = chatModel.call(new Prompt(messages)); 54 ChatResponse response = chatModel.call(new Prompt(messages));
45 // 打印结果 55 // 打印结果
46 - System.out.println(response); 56 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
47 } 57 }
48 58
49 @Test 59 @Test
50 @Disabled 60 @Disabled
51 public void testStream() { 61 public void testStream() {
  62 + validateApiKey(API_KEY);
52 // 准备参数 63 // 准备参数
53 List<Message> messages = new ArrayList<>(); 64 List<Message> messages = new ArrayList<>();
54 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 65 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -63,11 +74,12 @@ public class DeepSeekChatModelTests { @@ -63,11 +74,12 @@ public class DeepSeekChatModelTests {
63 @Test 74 @Test
64 @Disabled 75 @Disabled
65 public void testStream_thinking() { 76 public void testStream_thinking() {
  77 + validateApiKey(API_KEY);
66 // 准备参数 78 // 准备参数
67 List<Message> messages = new ArrayList<>(); 79 List<Message> messages = new ArrayList<>();
68 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 80 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
69 DeepSeekChatOptions options = DeepSeekChatOptions.builder() 81 DeepSeekChatOptions options = DeepSeekChatOptions.builder()
70 - .model("deepseek-reasoner") 82 + .model(MODEL)
71 .build(); 83 .build();
72 84
73 // 调用 85 // 调用
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DouBaoChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; 4 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
4 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
@@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
15 16
16 import java.util.ArrayList; 17 import java.util.ArrayList;
17 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
18 22
19 /** 23 /**
20 * {@link DouBaoChatModel} 集成测试 24 * {@link DouBaoChatModel} 集成测试
@@ -23,19 +27,19 @@ import java.util.List; @@ -23,19 +27,19 @@ import java.util.List;
23 */ 27 */
24 public class DouBaoChatModelTests { 28 public class DouBaoChatModelTests {
25 29
26 - /**  
27 - * 相比 OpenAIChatModel 来说,DeepSeekChatModel 可以兼容豆包的 thinking 能力!  
28 - */ 30 + private static final String API_KEY = SystemUtil.get("DOUBAO_API_KEY",
  31 + "sk-xxxx"); // 按需改成你的豆包 API Key
  32 + private static final String MODEL = SystemUtil.get("DOUBAO_MODEL",
  33 + DouBaoChatModel.MODEL_DEFAULT);
  34 +
29 private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() 35 private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
30 .deepSeekApi(DeepSeekApi.builder() 36 .deepSeekApi(DeepSeekApi.builder()
31 .baseUrl(DouBaoChatModel.BASE_URL) 37 .baseUrl(DouBaoChatModel.BASE_URL)
32 .completionsPath(DouBaoChatModel.COMPLETE_PATH) 38 .completionsPath(DouBaoChatModel.COMPLETE_PATH)
33 - .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey 39 + .apiKey(API_KEY)
34 .build()) 40 .build())
35 .defaultOptions(DeepSeekChatOptions.builder() 41 .defaultOptions(DeepSeekChatOptions.builder()
36 - .model("doubao-1-5-lite-32k-250115") // 模型(doubao)  
37 -// .model("doubao-seed-1-6-thinking-250715") // 模型(doubao)  
38 -// .model("deepseek-r1-250120") // 模型(deepseek) 42 + .model(MODEL)
39 .temperature(0.7) 43 .temperature(0.7)
40 .build()) 44 .build())
41 .build(); 45 .build();
@@ -45,6 +49,7 @@ public class DouBaoChatModelTests { @@ -45,6 +49,7 @@ public class DouBaoChatModelTests {
45 @Test 49 @Test
46 @Disabled 50 @Disabled
47 public void testCall() { 51 public void testCall() {
  52 + validateApiKey(API_KEY);
48 // 准备参数 53 // 准备参数
49 List<Message> messages = new ArrayList<>(); 54 List<Message> messages = new ArrayList<>();
50 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 55 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -54,11 +59,13 @@ public class DouBaoChatModelTests { @@ -54,11 +59,13 @@ public class DouBaoChatModelTests {
54 ChatResponse response = chatModel.call(new Prompt(messages)); 59 ChatResponse response = chatModel.call(new Prompt(messages));
55 // 打印结果 60 // 打印结果
56 System.out.println(response); 61 System.out.println(response);
  62 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
57 } 63 }
58 64
59 @Test 65 @Test
60 @Disabled 66 @Disabled
61 public void testStream() { 67 public void testStream() {
  68 + validateApiKey(API_KEY);
62 // 准备参数 69 // 准备参数
63 List<Message> messages = new ArrayList<>(); 70 List<Message> messages = new ArrayList<>();
64 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 71 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -67,25 +74,26 @@ public class DouBaoChatModelTests { @@ -67,25 +74,26 @@ public class DouBaoChatModelTests {
67 // 调用 74 // 调用
68 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); 75 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
69 // 打印结果 76 // 打印结果
70 - flux.doOnNext(System.out::println).then().block(); 77 + flux.doOnNext(response -> {
  78 +// System.out.println(response);
  79 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  80 + }).then().block();
71 } 81 }
72 82
73 @Test 83 @Test
74 @Disabled 84 @Disabled
75 public void testStream_thinking() { 85 public void testStream_thinking() {
  86 + validateApiKey(API_KEY);
76 // 准备参数 87 // 准备参数
77 List<Message> messages = new ArrayList<>(); 88 List<Message> messages = new ArrayList<>();
78 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 89 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
79 - DeepSeekChatOptions options = DeepSeekChatOptions.builder()  
80 - .model("doubao-seed-1-6-thinking-250715")  
81 - .build();  
82 90
83 // 调用 91 // 调用
84 - Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options)); 92 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
85 // 打印结果 93 // 打印结果
86 flux.doOnNext(response -> { 94 flux.doOnNext(response -> {
87 // System.out.println(response); 95 // System.out.println(response);
88 - System.out.println(response.getResult().getOutput()); 96 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
89 }).then().block(); 97 }).then().block();
90 } 98 }
91 99
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/GrokChatModelTests.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
  2 +
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
  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 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
  22 +
  23 +/**
  24 + * {@link GrokChatModel} 集成测试
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +public class GrokChatModelTests {
  29 +
  30 + private static final String API_KEY = SystemUtil.get("GROK_API_KEY",
  31 + "sk-xxxx");
  32 + private static final String MODEL = SystemUtil.get("GROK_MODEL",
  33 + GrokChatModel.MODEL_DEFAULT);
  34 +
  35 + private final GrokChatModel chatModel = new GrokChatModel(OpenAiChatModel.builder()
  36 + .openAiApi(OpenAiApi.builder()
  37 + .baseUrl(GrokChatModel.BASE_URL)
  38 + .completionsPath(GrokChatModel.COMPLETE_PATH)
  39 + .apiKey(API_KEY)
  40 + .build())
  41 + .defaultOptions(OpenAiChatOptions.builder()
  42 + .model(MODEL)
  43 + .temperature(0.7)
  44 + .build())
  45 + .build());
  46 +
  47 + @Test
  48 + @Disabled
  49 + public void testCall() {
  50 + validateApiKey(API_KEY);
  51 + // 准备参数
  52 + List<Message> messages = new ArrayList<>();
  53 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  54 + messages.add(new UserMessage("1 + 1 = ?"));
  55 +
  56 + // 调用
  57 + ChatResponse response = chatModel.call(new Prompt(messages));
  58 + // 打印结果
  59 + System.out.println(response);
  60 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  61 + }
  62 +
  63 + @Test
  64 + @Disabled
  65 + public void testStream() {
  66 + validateApiKey(API_KEY);
  67 + // 准备参数
  68 + List<Message> messages = new ArrayList<>();
  69 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  70 + messages.add(new UserMessage("1 + 1 = ?"));
  71 +
  72 + // 调用
  73 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  74 + // 打印结果
  75 + flux.doOnNext(response -> {
  76 +// System.out.println(response);
  77 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  78 + }).then().block();
  79 + }
  80 +
  81 +}
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/HunYuanChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; 4 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
4 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
@@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
15 16
16 import java.util.ArrayList; 17 import java.util.ArrayList;
17 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
18 22
19 /** 23 /**
20 * {@link HunYuanChatModel} 集成测试 24 * {@link HunYuanChatModel} 集成测试
@@ -23,14 +27,19 @@ import java.util.List; @@ -23,14 +27,19 @@ import java.util.List;
23 */ 27 */
24 public class HunYuanChatModelTests { 28 public class HunYuanChatModelTests {
25 29
  30 + private static final String API_KEY = SystemUtil.get("HUNYUAN_API_KEY",
  31 + "sk-xxxx");
  32 + private static final String MODEL = SystemUtil.get("HUNYUAN_MODEL",
  33 + HunYuanChatModel.MODEL_DEFAULT);
  34 +
26 private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() 35 private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
27 .deepSeekApi(DeepSeekApi.builder() 36 .deepSeekApi(DeepSeekApi.builder()
28 .baseUrl(HunYuanChatModel.BASE_URL) 37 .baseUrl(HunYuanChatModel.BASE_URL)
29 .completionsPath(HunYuanChatModel.COMPLETE_PATH) 38 .completionsPath(HunYuanChatModel.COMPLETE_PATH)
30 - .apiKey("sk-abc") // apiKey 39 + .apiKey(API_KEY) // apiKey
31 .build()) 40 .build())
32 .defaultOptions(DeepSeekChatOptions.builder() 41 .defaultOptions(DeepSeekChatOptions.builder()
33 - .model(HunYuanChatModel.MODEL_DEFAULT) // 模型 42 + .model(MODEL) // 模型
34 .temperature(0.7) 43 .temperature(0.7)
35 .build()) 44 .build())
36 .build(); 45 .build();
@@ -40,6 +49,7 @@ public class HunYuanChatModelTests { @@ -40,6 +49,7 @@ public class HunYuanChatModelTests {
40 @Test 49 @Test
41 @Disabled 50 @Disabled
42 public void testCall() { 51 public void testCall() {
  52 + validateApiKey(API_KEY);
43 // 准备参数 53 // 准备参数
44 List<Message> messages = new ArrayList<>(); 54 List<Message> messages = new ArrayList<>();
45 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 55 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -48,12 +58,13 @@ public class HunYuanChatModelTests { @@ -48,12 +58,13 @@ public class HunYuanChatModelTests {
48 // 调用 58 // 调用
49 ChatResponse response = chatModel.call(new Prompt(messages)); 59 ChatResponse response = chatModel.call(new Prompt(messages));
50 // 打印结果 60 // 打印结果
51 - System.out.println(response); 61 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
52 } 62 }
53 63
54 @Test 64 @Test
55 @Disabled 65 @Disabled
56 public void testStream() { 66 public void testStream() {
  67 + validateApiKey(API_KEY);
57 // 准备参数 68 // 准备参数
58 List<Message> messages = new ArrayList<>(); 69 List<Message> messages = new ArrayList<>();
59 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 70 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -68,12 +79,12 @@ public class HunYuanChatModelTests { @@ -68,12 +79,12 @@ public class HunYuanChatModelTests {
68 @Test 79 @Test
69 @Disabled 80 @Disabled
70 public void testStream_thinking() { 81 public void testStream_thinking() {
  82 + validateApiKey(API_KEY);
71 // 准备参数 83 // 准备参数
72 List<Message> messages = new ArrayList<>(); 84 List<Message> messages = new ArrayList<>();
73 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 85 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
74 DeepSeekChatOptions options = DeepSeekChatOptions.builder() 86 DeepSeekChatOptions options = DeepSeekChatOptions.builder()
75 - .model("hunyuan-a13b")  
76 -// .model("hunyuan-turbos-latest") 87 + .model(MODEL)
77 .build(); 88 .build();
78 89
79 // 调用 90 // 调用
@@ -85,66 +96,4 @@ public class HunYuanChatModelTests { @@ -85,66 +96,4 @@ public class HunYuanChatModelTests {
85 }).then().block(); 96 }).then().block();
86 } 97 }
87 98
88 - private final DeepSeekChatModel deepSeekOpenAiChatModel = DeepSeekChatModel.builder()  
89 - .deepSeekApi(DeepSeekApi.builder()  
90 - .baseUrl(HunYuanChatModel.DEEP_SEEK_BASE_URL)  
91 - .completionsPath(HunYuanChatModel.COMPLETE_PATH)  
92 - .apiKey("sk-abc") // apiKey  
93 - .build())  
94 - .defaultOptions(DeepSeekChatOptions.builder()  
95 -// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3")  
96 - .model("deepseek-r1") // 模型("deepseek-r1")  
97 - .temperature(0.7)  
98 - .build())  
99 - .build();  
100 -  
101 - private final HunYuanChatModel deepSeekChatModel = new HunYuanChatModel(deepSeekOpenAiChatModel);  
102 -  
103 - @Test  
104 - @Disabled  
105 - public void testCall_deepseek() {  
106 - // 准备参数  
107 - List<Message> messages = new ArrayList<>();  
108 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
109 - messages.add(new UserMessage("1 + 1 = ?"));  
110 -  
111 - // 调用  
112 - ChatResponse response = deepSeekChatModel.call(new Prompt(messages));  
113 - // 打印结果  
114 - System.out.println(response);  
115 - }  
116 -  
117 - @Test  
118 - @Disabled  
119 - public void testStream_deepseek() {  
120 - // 准备参数  
121 - List<Message> messages = new ArrayList<>();  
122 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
123 - messages.add(new UserMessage("1 + 1 = ?"));  
124 -  
125 - // 调用  
126 - Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages));  
127 - // 打印结果  
128 - flux.doOnNext(System.out::println).then().block();  
129 - }  
130 -  
131 - @Test  
132 - @Disabled  
133 - public void testStream_deepseek_thinking() {  
134 - // 准备参数  
135 - List<Message> messages = new ArrayList<>();  
136 - messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));  
137 - DeepSeekChatOptions options = DeepSeekChatOptions.builder()  
138 - .model("deepseek-r1")  
139 - .build();  
140 -  
141 - // 调用  
142 - Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages, options));  
143 - // 打印结果  
144 - flux.doOnNext(response -> {  
145 -// System.out.println(response);  
146 - System.out.println(response.getResult().getOutput());  
147 - }).then().block();  
148 - }  
149 -  
150 } 99 }
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MiniMaxChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
3 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
5 import org.springframework.ai.chat.messages.Message; 7 import org.springframework.ai.chat.messages.Message;
@@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; @@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage;
7 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
8 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
9 import org.springframework.ai.chat.prompt.Prompt; 11 import org.springframework.ai.chat.prompt.Prompt;
10 -import org.springframework.ai.minimax.MiniMaxChatModel;  
11 -import org.springframework.ai.minimax.MiniMaxChatOptions;  
12 -import org.springframework.ai.minimax.api.MiniMaxApi; 12 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  13 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  14 +import org.springframework.ai.deepseek.api.DeepSeekApi;
13 import reactor.core.publisher.Flux; 15 import reactor.core.publisher.Flux;
14 16
15 import java.util.ArrayList; 17 import java.util.ArrayList;
16 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
17 22
18 /** 23 /**
19 * {@link MiniMaxChatModel} 的集成测试 24 * {@link MiniMaxChatModel} 的集成测试
@@ -22,14 +27,24 @@ import java.util.List; @@ -22,14 +27,24 @@ import java.util.List;
22 */ 27 */
23 public class MiniMaxChatModelTests { 28 public class MiniMaxChatModelTests {
24 29
25 - private final MiniMaxChatModel chatModel = new MiniMaxChatModel(  
26 - new MiniMaxApi("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJHcm91cE5hbWUiOiLnjovmlofmlowiLCJVc2VyTmFtZSI6IueOi-aWh-aWjCIsIkFjY291bnQiOiIiLCJTdWJqZWN0SUQiOiIxODk3Mjg3MjQ5NDU2ODA4MzQ2IiwiUGhvbmUiOiIxNTYwMTY5MTM5OSIsIkdyb3VwSUQiOiIxODk3Mjg3MjQ5NDQ4NDE5NzM4IiwiUGFnZU5hbWUiOiIiLCJNYWlsIjoiIiwiQ3JlYXRlVGltZSI6IjIwMjUtMDMtMTEgMTI6NTI6MDIiLCJUb2tlblR5cGUiOjEsImlzcyI6Im1pbmltYXgifQ.aAuB7gWW_oA4IYhh-CF7c9MfWWxKN49B_HK-DYjXaDwwffhiG-H1571z1WQhp9QytWG-DqgLejneeSxkiq1wQIe3FsEP2wz4BmGBct31LehbJu8ehLxg_vg75Uod1nFAHbm5mZz6JSVLNIlSo87Xr3UtSzJhAXlapEkcqlA4YOzOpKrZ8l5_OJPTORTCmHWZYgJcRS-faNiH62ZnUEHUozesTFhubJHo5GfJCw_edlnmfSUocERV1BjWvenhZ9My-aYXNktcW9WaSj9l6gayV7A0Ium_PL55T9ln1PcI8gayiVUKJGJDoqNyF1AF9_aF9NOKtTnQzwNqnZdlTYH6hw"), // 密钥  
27 - MiniMaxChatOptions.builder()  
28 - .model(MiniMaxApi.ChatModel.ABAB_6_5_G_Chat.getValue()) // 模型  
29 - .build()); 30 + private static final String API_KEY = SystemUtil.get("MINIMAX_API_KEY",
  31 + "sk-xxxx"); // 按需改成你的 MiniMax API Key
  32 + private static final String MODEL = SystemUtil.get("MINIMAX_MODEL",
  33 + MiniMaxChatModel.MODEL_DEFAULT);
  34 +
  35 + private final MiniMaxChatModel chatModel = new MiniMaxChatModel(DeepSeekChatModel.builder()
  36 + .deepSeekApi(DeepSeekApi.builder()
  37 + .baseUrl(MiniMaxChatModel.BASE_URL)
  38 + .apiKey(API_KEY)
  39 + .build())
  40 + .defaultOptions(DeepSeekChatOptions.builder()
  41 + .model(MODEL)
  42 + .build())
  43 + .build());
30 @Test 44 @Test
31 @Disabled 45 @Disabled
32 public void testCall() { 46 public void testCall() {
  47 + validateApiKey(API_KEY);
33 // 准备参数 48 // 准备参数
34 List<Message> messages = new ArrayList<>(); 49 List<Message> messages = new ArrayList<>();
35 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 50 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -39,12 +54,13 @@ public class MiniMaxChatModelTests { @@ -39,12 +54,13 @@ public class MiniMaxChatModelTests {
39 ChatResponse response = chatModel.call(new Prompt(messages)); 54 ChatResponse response = chatModel.call(new Prompt(messages));
40 // 打印结果 55 // 打印结果
41 System.out.println(response); 56 System.out.println(response);
42 - System.out.println(response.getResult().getOutput()); 57 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
43 } 58 }
44 59
45 @Test 60 @Test
46 @Disabled 61 @Disabled
47 public void testStream() { 62 public void testStream() {
  63 + validateApiKey(API_KEY);
48 // 准备参数 64 // 准备参数
49 List<Message> messages = new ArrayList<>(); 65 List<Message> messages = new ArrayList<>();
50 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 66 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -55,19 +71,19 @@ public class MiniMaxChatModelTests { @@ -55,19 +71,19 @@ public class MiniMaxChatModelTests {
55 // 打印结果 71 // 打印结果
56 flux.doOnNext(response -> { 72 flux.doOnNext(response -> {
57 // System.out.println(response); 73 // System.out.println(response);
58 - System.out.println(response.getResult().getOutput()); 74 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
59 }).then().block(); 75 }).then().block();
60 } 76 }
61 77
62 - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复  
63 @Test 78 @Test
64 @Disabled 79 @Disabled
65 public void testStream_thinking() { 80 public void testStream_thinking() {
  81 + validateApiKey(API_KEY);
66 // 准备参数 82 // 准备参数
67 List<Message> messages = new ArrayList<>(); 83 List<Message> messages = new ArrayList<>();
68 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 84 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
69 - MiniMaxChatOptions options = MiniMaxChatOptions.builder()  
70 - .model("MiniMax-M1") 85 + DeepSeekChatOptions options = DeepSeekChatOptions.builder()
  86 + .model(MODEL)
71 .build(); 87 .build();
72 88
73 // 调用 89 // 调用
@@ -75,7 +91,7 @@ public class MiniMaxChatModelTests { @@ -75,7 +91,7 @@ public class MiniMaxChatModelTests {
75 // 打印结果 91 // 打印结果
76 flux.doOnNext(response -> { 92 flux.doOnNext(response -> {
77 // System.out.println(response); 93 // System.out.println(response);
78 - System.out.println(response.getResult().getOutput()); 94 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
79 }).then().block(); 95 }).then().block();
80 } 96 }
81 97
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
3 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
5 -import org.springaicommunity.moonshot.MoonshotChatModel;  
6 -import org.springaicommunity.moonshot.MoonshotChatOptions;  
7 -import org.springaicommunity.moonshot.api.MoonshotApi;  
8 import org.springframework.ai.chat.messages.Message; 7 import org.springframework.ai.chat.messages.Message;
9 import org.springframework.ai.chat.messages.SystemMessage; 8 import org.springframework.ai.chat.messages.SystemMessage;
10 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
11 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
12 import org.springframework.ai.chat.prompt.Prompt; 11 import org.springframework.ai.chat.prompt.Prompt;
  12 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  13 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  14 +import org.springframework.ai.deepseek.api.DeepSeekApi;
13 import reactor.core.publisher.Flux; 15 import reactor.core.publisher.Flux;
14 16
15 import java.util.ArrayList; 17 import java.util.ArrayList;
16 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
17 22
18 /** 23 /**
19 * {@link MoonshotChatModel} 的集成测试 24 * {@link MoonshotChatModel} 的集成测试
@@ -22,18 +27,27 @@ import java.util.List; @@ -22,18 +27,27 @@ import java.util.List;
22 */ 27 */
23 public class MoonshotChatModelTests { 28 public class MoonshotChatModelTests {
24 29
25 - private final MoonshotChatModel chatModel = MoonshotChatModel.builder()  
26 - .moonshotApi(MoonshotApi.builder()  
27 - .apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥 30 + private static final String API_KEY = SystemUtil.get("MOONSHOT_API_KEY",
  31 + "sk-xxxx"); // 按需改成你的 Moonshot API Key
  32 + private static final String MODEL = SystemUtil.get("MOONSHOT_MODEL",
  33 + MoonshotChatModel.MODEL_DEFAULT);
  34 +
  35 + private final MoonshotChatModel chatModel = new MoonshotChatModel(DeepSeekChatModel.builder()
  36 + .deepSeekApi(DeepSeekApi.builder()
  37 + .baseUrl(MoonshotChatModel.BASE_URL)
  38 + .completionsPath(MoonshotChatModel.COMPLETE_PATH)
  39 + .apiKey(API_KEY)
28 .build()) 40 .build())
29 - .defaultOptions(MoonshotChatOptions.builder()  
30 - .model("kimi-k2-0711-preview") // 模型 41 + .defaultOptions(DeepSeekChatOptions.builder()
  42 + .model(MODEL)
  43 + .temperature(1D)
31 .build()) 44 .build())
32 - .build(); 45 + .build());
33 46
34 @Test 47 @Test
35 @Disabled 48 @Disabled
36 public void testCall() { 49 public void testCall() {
  50 + validateApiKey(API_KEY);
37 // 准备参数 51 // 准备参数
38 List<Message> messages = new ArrayList<>(); 52 List<Message> messages = new ArrayList<>();
39 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 53 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -43,12 +57,13 @@ public class MoonshotChatModelTests { @@ -43,12 +57,13 @@ public class MoonshotChatModelTests {
43 ChatResponse response = chatModel.call(new Prompt(messages)); 57 ChatResponse response = chatModel.call(new Prompt(messages));
44 // 打印结果 58 // 打印结果
45 System.out.println(response); 59 System.out.println(response);
46 - System.out.println(response.getResult().getOutput()); 60 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
47 } 61 }
48 62
49 @Test 63 @Test
50 @Disabled 64 @Disabled
51 public void testStream() { 65 public void testStream() {
  66 + validateApiKey(API_KEY);
52 // 准备参数 67 // 准备参数
53 List<Message> messages = new ArrayList<>(); 68 List<Message> messages = new ArrayList<>();
54 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 69 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -59,20 +74,19 @@ public class MoonshotChatModelTests { @@ -59,20 +74,19 @@ public class MoonshotChatModelTests {
59 // 打印结果 74 // 打印结果
60 flux.doOnNext(response -> { 75 flux.doOnNext(response -> {
61 // System.out.println(response); 76 // System.out.println(response);
62 - System.out.println(response.getResult().getOutput()); 77 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
63 }).then().block(); 78 }).then().block();
64 } 79 }
65 80
66 - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复  
67 @Test 81 @Test
68 @Disabled 82 @Disabled
69 public void testStream_thinking() { 83 public void testStream_thinking() {
  84 + validateApiKey(API_KEY);
70 // 准备参数 85 // 准备参数
71 List<Message> messages = new ArrayList<>(); 86 List<Message> messages = new ArrayList<>();
72 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 87 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
73 - MoonshotChatOptions options = MoonshotChatOptions.builder()  
74 -// .model("kimi-k2-0711-preview")  
75 - .model("kimi-thinking-preview") 88 + DeepSeekChatOptions options = DeepSeekChatOptions.builder()
  89 + .model(MODEL)
76 .build(); 90 .build();
77 91
78 // 调用 92 // 调用
@@ -80,7 +94,7 @@ public class MoonshotChatModelTests { @@ -80,7 +94,7 @@ public class MoonshotChatModelTests {
80 // 打印结果 94 // 打印结果
81 flux.doOnNext(response -> { 95 flux.doOnNext(response -> {
82 // System.out.println(response); 96 // System.out.println(response);
83 - System.out.println(response.getResult().getOutput()); 97 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
84 }).then().block(); 98 }).then().block();
85 } 99 }
86 100
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/StepFunChatModelTests.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
  2 +
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
  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.deepseek.DeepSeekChatModel;
  13 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  14 +import org.springframework.ai.deepseek.api.DeepSeekApi;
  15 +import reactor.core.publisher.Flux;
  16 +
  17 +import java.util.ArrayList;
  18 +import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
  22 +
  23 +/**
  24 + * {@link StepFunChatModel} 集成测试
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +public class StepFunChatModelTests {
  29 +
  30 + private static final String API_KEY = SystemUtil.get("STEPFUN_API_KEY",
  31 + "sk-xxxx");
  32 + private static final String MODEL = SystemUtil.get("STEPFUN_MODEL",
  33 + StepFunChatModel.MODEL_DEFAULT);
  34 +
  35 + private final StepFunChatModel chatModel = new StepFunChatModel(DeepSeekChatModel.builder()
  36 + .deepSeekApi(DeepSeekApi.builder()
  37 + .baseUrl(StepFunChatModel.BASE_URL)
  38 + .completionsPath(StepFunChatModel.COMPLETE_PATH)
  39 + .apiKey(API_KEY)
  40 + .build())
  41 + .defaultOptions(DeepSeekChatOptions.builder()
  42 + .model(MODEL)
  43 + .temperature(0.7)
  44 + .build())
  45 + .build());
  46 +
  47 + @Test
  48 + @Disabled
  49 + public void testCall() {
  50 + validateApiKey(API_KEY);
  51 + // 准备参数
  52 + List<Message> messages = new ArrayList<>();
  53 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  54 + messages.add(new UserMessage("1 + 1 = ?"));
  55 +
  56 + // 调用
  57 + ChatResponse response = chatModel.call(new Prompt(messages));
  58 + // 打印结果
  59 + System.out.println(response);
  60 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  61 + }
  62 +
  63 + @Test
  64 + @Disabled
  65 + public void testStream() {
  66 + validateApiKey(API_KEY);
  67 + // 准备参数
  68 + List<Message> messages = new ArrayList<>();
  69 + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  70 + messages.add(new UserMessage("1 + 1 = ?"));
  71 +
  72 + // 调用
  73 + Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  74 + // 打印结果
  75 + flux.doOnNext(response -> {
  76 +// System.out.println(response);
  77 +// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  78 + System.out.println(response.getResult() != null ? response.getResult().getOutput() : null);
  79 + }).then().block();
  80 + }
  81 +
  82 +}
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/XingHuoChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; 4 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
4 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
@@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage; @@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage;
8 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
9 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
10 import org.springframework.ai.chat.prompt.Prompt; 11 import org.springframework.ai.chat.prompt.Prompt;
11 -import org.springframework.ai.deepseek.DeepSeekChatModel;  
12 import org.springframework.ai.deepseek.DeepSeekChatOptions; 12 import org.springframework.ai.deepseek.DeepSeekChatOptions;
13 -import org.springframework.ai.deepseek.api.DeepSeekApi;  
14 import reactor.core.publisher.Flux; 13 import reactor.core.publisher.Flux;
15 14
16 import java.util.ArrayList; 15 import java.util.ArrayList;
17 import java.util.List; 16 import java.util.List;
  17 +import java.util.Objects;
  18 +
  19 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
18 20
19 /** 21 /**
20 * {@link XingHuoChatModel} 集成测试 22 * {@link XingHuoChatModel} 集成测试
@@ -23,24 +25,23 @@ import java.util.List; @@ -23,24 +25,23 @@ import java.util.List;
23 */ 25 */
24 public class XingHuoChatModelTests { 26 public class XingHuoChatModelTests {
25 27
26 - private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()  
27 - .deepSeekApi(DeepSeekApi.builder()  
28 - .baseUrl(XingHuoChatModel.BASE_URL_V2)  
29 - .completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2)  
30 - .apiKey("75b161ed2aef4719b275d6e7f2a4d4cd:YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz") // appKey:secretKey  
31 - .build())  
32 - .defaultOptions(DeepSeekChatOptions.builder()  
33 -// .model("generalv3.5") // 模型  
34 - .model("x1") // 模型  
35 - .temperature(0.7)  
36 - .build())  
37 - .build(); 28 + private static final String API_KEY = SystemUtil.get("XINGHUO_API_KEY",
  29 + "sk-xxxx"); // 按需改成你的讯飞星火 API Key
  30 + private static final String MODEL = SystemUtil.get("XINGHUO_MODEL",
  31 + XingHuoChatModel.MODEL_DEFAULT);
38 32
39 - private final XingHuoChatModel chatModel = new XingHuoChatModel(openAiChatModel); 33 + private final XingHuoChatModel chatModel = XingHuoChatModel.builder()
  34 + .apiKey(API_KEY)
  35 + .options(DeepSeekChatOptions.builder()
  36 + .model(MODEL)
  37 + .temperature(0.7)
  38 + .build())
  39 + .build();
40 40
41 @Test 41 @Test
42 @Disabled 42 @Disabled
43 public void testCall() { 43 public void testCall() {
  44 + validateApiKey(API_KEY);
44 // 准备参数 45 // 准备参数
45 List<Message> messages = new ArrayList<>(); 46 List<Message> messages = new ArrayList<>();
46 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 47 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -55,6 +56,7 @@ public class XingHuoChatModelTests { @@ -55,6 +56,7 @@ public class XingHuoChatModelTests {
55 @Test 56 @Test
56 @Disabled 57 @Disabled
57 public void testStream() { 58 public void testStream() {
  59 + validateApiKey(API_KEY);
58 // 准备参数 60 // 准备参数
59 List<Message> messages = new ArrayList<>(); 61 List<Message> messages = new ArrayList<>();
60 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 62 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -69,11 +71,12 @@ public class XingHuoChatModelTests { @@ -69,11 +71,12 @@ public class XingHuoChatModelTests {
69 @Test 71 @Test
70 @Disabled 72 @Disabled
71 public void testStream_thinking() { 73 public void testStream_thinking() {
  74 + validateApiKey(API_KEY);
72 // 准备参数 75 // 准备参数
73 List<Message> messages = new ArrayList<>(); 76 List<Message> messages = new ArrayList<>();
74 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 77 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
75 DeepSeekChatOptions options = DeepSeekChatOptions.builder() 78 DeepSeekChatOptions options = DeepSeekChatOptions.builder()
76 - .model("x1") 79 + .model(MODEL)
77 .build(); 80 .build();
78 81
79 // 调用 82 // 调用
@@ -81,7 +84,7 @@ public class XingHuoChatModelTests { @@ -81,7 +84,7 @@ public class XingHuoChatModelTests {
81 // 打印结果 84 // 打印结果
82 flux.doOnNext(response -> { 85 flux.doOnNext(response -> {
83 // System.out.println(response); 86 // System.out.println(response);
84 - System.out.println(response.getResult().getOutput()); 87 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
85 }).then().block(); 88 }).then().block();
86 } 89 }
87 90
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
3 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
5 -import org.springaicommunity.qianfan.QianFanChatModel;  
6 -import org.springaicommunity.qianfan.QianFanChatOptions;  
7 -import org.springaicommunity.qianfan.api.QianFanApi;  
8 import org.springframework.ai.chat.messages.Message; 7 import org.springframework.ai.chat.messages.Message;
9 import org.springframework.ai.chat.messages.UserMessage; 8 import org.springframework.ai.chat.messages.UserMessage;
10 import org.springframework.ai.chat.model.ChatResponse; 9 import org.springframework.ai.chat.model.ChatResponse;
11 import org.springframework.ai.chat.prompt.Prompt; 10 import org.springframework.ai.chat.prompt.Prompt;
  11 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  12 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  13 +import org.springframework.ai.deepseek.api.DeepSeekApi;
12 import reactor.core.publisher.Flux; 14 import reactor.core.publisher.Flux;
13 15
14 import java.util.ArrayList; 16 import java.util.ArrayList;
15 import java.util.List; 17 import java.util.List;
  18 +import java.util.Objects;
  19 +
  20 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
16 21
17 -// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展  
18 /** 22 /**
19 - * {@link QianFanChatModel} 的集成测试 23 + * {@link YiYanChatModel} 的集成测试
20 * 24 *
21 * @author fansili 25 * @author fansili
22 */ 26 */
23 public class YiYanChatModelTests { 27 public class YiYanChatModelTests {
24 28
25 - private final QianFanChatModel chatModel = new QianFanChatModel(  
26 - new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥  
27 - QianFanChatOptions.builder()  
28 - .model("ERNIE-4.5-8K-Preview")  
29 - .build()  
30 - ); 29 + private static final String API_KEY = SystemUtil.get("YIYAN_API_KEY",
  30 + "sk-xxxx"); // 按需改成你的文心一言 API Key
  31 + private static final String MODEL = SystemUtil.get("YIYAN_MODEL",
  32 + YiYanChatModel.MODEL_DEFAULT);
  33 +
  34 + private final YiYanChatModel chatModel = new YiYanChatModel(DeepSeekChatModel.builder()
  35 + .deepSeekApi(DeepSeekApi.builder()
  36 + .baseUrl(YiYanChatModel.BASE_URL)
  37 + .apiKey(API_KEY)
  38 + .build())
  39 + .defaultOptions(DeepSeekChatOptions.builder()
  40 + .model(MODEL)
  41 + .build())
  42 + .build());
31 43
32 @Test 44 @Test
33 @Disabled 45 @Disabled
34 public void testCall() { 46 public void testCall() {
  47 + validateApiKey(API_KEY);
35 // 准备参数 48 // 准备参数
36 List<Message> messages = new ArrayList<>(); 49 List<Message> messages = new ArrayList<>();
37 - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!  
38 -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
39 messages.add(new UserMessage("1 + 1 = ?")); 50 messages.add(new UserMessage("1 + 1 = ?"));
40 51
41 // 调用 52 // 调用
42 ChatResponse response = chatModel.call(new Prompt(messages)); 53 ChatResponse response = chatModel.call(new Prompt(messages));
43 // 打印结果 54 // 打印结果
44 System.out.println(response); 55 System.out.println(response);
  56 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
45 } 57 }
46 58
47 @Test 59 @Test
48 @Disabled 60 @Disabled
49 public void testStream() { 61 public void testStream() {
  62 + validateApiKey(API_KEY);
50 // 准备参数 63 // 准备参数
51 List<Message> messages = new ArrayList<>(); 64 List<Message> messages = new ArrayList<>();
52 - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名!  
53 -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
54 messages.add(new UserMessage("1 + 1 = ?")); 65 messages.add(new UserMessage("1 + 1 = ?"));
55 66
56 // 调用 67 // 调用
57 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages)); 68 Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
58 // 打印结果 69 // 打印结果
59 - flux.doOnNext(System.out::println).then().block(); 70 + flux.doOnNext(response -> {
  71 +// System.out.println(response);
  72 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
  73 + }).then().block();
60 } 74 }
61 75
62 } 76 }
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/ZhiPuAiChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
3 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
5 import org.springframework.ai.chat.messages.Message; 7 import org.springframework.ai.chat.messages.Message;
@@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage; @@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage;
7 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
8 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
9 import org.springframework.ai.chat.prompt.Prompt; 11 import org.springframework.ai.chat.prompt.Prompt;
10 -import org.springframework.ai.zhipuai.ZhiPuAiChatModel;  
11 -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;  
12 -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; 12 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  13 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  14 +import org.springframework.ai.deepseek.api.DeepSeekApi;
13 import reactor.core.publisher.Flux; 15 import reactor.core.publisher.Flux;
14 16
15 import java.util.ArrayList; 17 import java.util.ArrayList;
16 import java.util.List; 18 import java.util.List;
  19 +import java.util.Objects;
  20 +
  21 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
17 22
18 /** 23 /**
19 - * {@link ZhiPuAiChatModel} 的集成测试 24 + * {@link ZhiPuChatModel} 的集成测试
20 * 25 *
21 * @author 芋道源码 26 * @author 芋道源码
22 */ 27 */
23 public class ZhiPuAiChatModelTests { 28 public class ZhiPuAiChatModelTests {
24 29
25 - private final ZhiPuAiChatModel chatModel = new ZhiPuAiChatModel(  
26 - ZhiPuAiApi.builder().apiKey("2f35fb6ca4ea41fab898729b7fac086c.6ESSfPcCkxaKEUlR").build(), // 密钥  
27 - ZhiPuAiChatOptions.builder()  
28 - .model(ZhiPuAiApi.ChatModel.GLM_4.getName()) // 模型 30 + private static final String API_KEY = SystemUtil.get("ZHIPU_API_KEY",
  31 + "sk-xxxx"); // 按需改成你的智谱 API Key
  32 + private static final String MODEL = SystemUtil.get("ZHIPU_MODEL",
  33 + ZhiPuChatModel.MODEL_DEFAULT);
  34 +
  35 + private final ZhiPuChatModel chatModel = new ZhiPuChatModel(DeepSeekChatModel.builder()
  36 + .deepSeekApi(DeepSeekApi.builder()
  37 + .baseUrl(ZhiPuChatModel.BASE_URL)
  38 + .apiKey(API_KEY)
  39 + .build())
  40 + .defaultOptions(DeepSeekChatOptions.builder()
  41 + .model(MODEL)
29 .build() 42 .build()
30 - ); 43 + ).build());
31 44
32 @Test 45 @Test
33 @Disabled 46 @Disabled
34 public void testCall() { 47 public void testCall() {
  48 + validateApiKey(API_KEY);
35 // 准备参数 49 // 准备参数
36 List<Message> messages = new ArrayList<>(); 50 List<Message> messages = new ArrayList<>();
37 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 51 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests { @@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests {
41 ChatResponse response = chatModel.call(new Prompt(messages)); 55 ChatResponse response = chatModel.call(new Prompt(messages));
42 // 打印结果 56 // 打印结果
43 System.out.println(response); 57 System.out.println(response);
44 - System.out.println(response.getResult().getOutput()); 58 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
45 } 59 }
46 60
47 @Test 61 @Test
48 @Disabled 62 @Disabled
49 public void testStream() { 63 public void testStream() {
  64 + validateApiKey(API_KEY);
50 // 准备参数 65 // 准备参数
51 List<Message> messages = new ArrayList<>(); 66 List<Message> messages = new ArrayList<>();
52 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 67 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
@@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests { @@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests {
57 // 打印结果 72 // 打印结果
58 flux.doOnNext(response -> { 73 flux.doOnNext(response -> {
59 // System.out.println(response); 74 // System.out.println(response);
60 - System.out.println(response.getResult().getOutput()); 75 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
61 }).then().block(); 76 }).then().block();
62 } 77 }
63 78
64 - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复  
65 @Test 79 @Test
66 @Disabled 80 @Disabled
67 public void testStream_thinking() { 81 public void testStream_thinking() {
  82 + validateApiKey(API_KEY);
68 // 准备参数 83 // 准备参数
69 List<Message> messages = new ArrayList<>(); 84 List<Message> messages = new ArrayList<>();
70 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); 85 messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
71 - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()  
72 - .model("GLM-4.5") 86 + DeepSeekChatOptions options = DeepSeekChatOptions.builder()
  87 + .model(MODEL)
73 .build(); 88 .build();
74 89
75 // 调用 90 // 调用
@@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests { @@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests {
77 // 打印结果 92 // 打印结果
78 flux.doOnNext(response -> { 93 flux.doOnNext(response -> {
79 // System.out.println(response); 94 // System.out.println(response);
80 - System.out.println(response.getResult().getOutput()); 95 + System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
81 }).then().block(); 96 }).then().block();
82 } 97 }
83 98
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
2 2
  3 +import cn.hutool.system.SystemUtil;
3 import org.junit.jupiter.api.Disabled; 4 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 5 import org.junit.jupiter.api.Test;
5 import org.springframework.ai.image.ImageOptions; 6 import org.springframework.ai.image.ImageOptions;
@@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel; @@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel;
9 import org.springframework.ai.openai.OpenAiImageOptions; 10 import org.springframework.ai.openai.OpenAiImageOptions;
10 import org.springframework.ai.openai.api.OpenAiImageApi; 11 import org.springframework.ai.openai.api.OpenAiImageApi;
11 12
  13 +import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
  14 +
12 /** 15 /**
13 * {@link OpenAiImageModel} 集成测试类 16 * {@link OpenAiImageModel} 集成测试类
14 * 17 *
@@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi; @@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi;
16 */ 19 */
17 public class OpenAiImageModelTests { 20 public class OpenAiImageModelTests {
18 21
  22 + private static final String API_KEY = SystemUtil.get("OPENAI_API_KEY", "sk-xxxx");
  23 +
19 private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() 24 private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
20 .baseUrl("https://api.holdai.top") // apiKey 25 .baseUrl("https://api.holdai.top") // apiKey
21 - .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") 26 + .apiKey(API_KEY)
22 .build()); 27 .build());
23 28
24 @Test 29 @Test
25 @Disabled 30 @Disabled
26 public void testCall() { 31 public void testCall() {
  32 + validateApiKey(API_KEY);
27 // 准备参数 33 // 准备参数
28 ImageOptions options = OpenAiImageOptions.builder() 34 ImageOptions options = OpenAiImageOptions.builder()
29 - .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜 35 + .model("gpt-image-1")
30 .height(256).width(256) 36 .height(256).width(256)
31 .build(); 37 .build();
32 ImagePrompt prompt = new ImagePrompt("中国长城!", options); 38 ImagePrompt prompt = new ImagePrompt("中国长城!", options);
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java deleted
1 -package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;  
2 -  
3 -import org.junit.jupiter.api.Disabled;  
4 -import org.junit.jupiter.api.Test;  
5 -import org.springaicommunity.qianfan.QianFanImageModel;  
6 -import org.springaicommunity.qianfan.QianFanImageOptions;  
7 -import org.springaicommunity.qianfan.api.QianFanImageApi;  
8 -import org.springframework.ai.image.ImagePrompt;  
9 -import org.springframework.ai.image.ImageResponse;  
10 -  
11 -import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;  
12 -  
13 -// TODO @芋艿:百度千帆 API 提供了 V2 版本,目前 Spring AI 不兼容,可关键 <https://github.com/spring-projects/spring-ai/issues/2179> 进展  
14 -  
15 -/**  
16 - * {@link QianFanImageModel} 集成测试类  
17 - */  
18 -public class QianFanImageTests {  
19 -  
20 - private final QianFanImageModel imageModel = new QianFanImageModel(  
21 - new QianFanImageApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e")); // 密钥  
22 -  
23 - @Test  
24 - @Disabled  
25 - public void testCall() {  
26 - // 准备参数  
27 - // 只支持 1024x1024、768x768、768x1024、1024x768、576x1024、1024x576  
28 - QianFanImageOptions imageOptions = QianFanImageOptions.builder()  
29 - .model(QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue())  
30 - .width(1024).height(1024)  
31 - .N(1)  
32 - .build();  
33 - ImagePrompt prompt = new ImagePrompt("good", imageOptions);  
34 -  
35 - // 方法调用  
36 - ImageResponse response = imageModel.call(prompt);  
37 - // 打印结果  
38 - String b64Json = response.getResult().getOutput().getB64Json();  
39 - System.out.println(response);  
40 - viewImage(b64Json);  
41 - }  
42 -  
43 -}  
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/ZhiPuAiImageModelTests.java deleted
1 -package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;  
2 -  
3 -import org.junit.jupiter.api.Disabled;  
4 -import org.junit.jupiter.api.Test;  
5 -import org.springframework.ai.image.ImagePrompt;  
6 -import org.springframework.ai.image.ImageResponse;  
7 -import org.springframework.ai.zhipuai.ZhiPuAiImageModel;  
8 -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;  
9 -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;  
10 -  
11 -/**  
12 - * {@link ZhiPuAiImageModel} 集成测试  
13 - */  
14 -public class ZhiPuAiImageModelTests {  
15 -  
16 - private final ZhiPuAiImageModel imageModel = new ZhiPuAiImageModel(  
17 - new ZhiPuAiImageApi("78d3228c1d9e5e342a3e1ab349e2dd7b.VXLoq5vrwK2ofboy") // 密钥  
18 - );  
19 -  
20 - @Test  
21 - @Disabled  
22 - public void testCall() {  
23 - // 准备参数  
24 - ZhiPuAiImageOptions imageOptions = ZhiPuAiImageOptions.builder()  
25 - .model(ZhiPuAiImageApi.ImageModel.CogView_3.getValue())  
26 - .build();  
27 - ImagePrompt prompt = new ImagePrompt("万里长城", imageOptions);  
28 -  
29 - // 方法调用  
30 - ImageResponse response = imageModel.call(prompt);  
31 - // 打印结果  
32 - System.out.println(response);  
33 - }  
34 -  
35 -}  
yudao-server/src/main/resources/application.yaml
@@ -164,36 +164,28 @@ spring: @@ -164,36 +164,28 @@ spring:
164 client: 164 client:
165 host: 127.0.0.1 165 host: 127.0.0.1
166 port: 19530 166 port: 19530
167 - qianfan: # 文心一言  
168 - api-key: x0cuLZ7XsaTCU08vuJWO87Lg  
169 - secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK  
170 - zhipuai: # 智谱 AI  
171 - api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs  
172 openai: # OpenAI 官方 167 openai: # OpenAI 官方
173 - api-key: sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17  
174 - base-url: https://api.gptsapi.net 168 + base-url: ${OPENAI_BASE_URL:https://api.openai.com}
  169 + api-key: ${OPENAI_API_KEY:sk-xxxx}
175 azure: # OpenAI 微软 170 azure: # OpenAI 微软
176 openai: 171 openai:
177 endpoint: https://eastusprejade.openai.azure.com 172 endpoint: https://eastusprejade.openai.azure.com
178 anthropic: # Anthropic Claude 173 anthropic: # Anthropic Claude
179 - api-key: sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942 174 + base-url: ${ANTHROPIC_BASE_URL:https://api.anthropic.com}
  175 + api-key: ${ANTHROPIC_API_KEY:sk-xxxx}
180 ollama: 176 ollama:
181 base-url: http://127.0.0.1:11434 177 base-url: http://127.0.0.1:11434
182 chat: 178 chat:
183 model: llama3 179 model: llama3
184 stabilityai: 180 stabilityai:
185 - api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx 181 + api-key: ${STABILITYAI_API_KEY:sk-xxxx}
186 dashscope: # 通义千问 182 dashscope: # 通义千问
187 - api-key: sk-47aa124781be4bfb95244cc62f6xxxx  
188 - minimax: # Minimax:https://www.minimaxi.com/  
189 - api-key: xxxx  
190 - moonshot: # 月之暗面(KIMI)  
191 - api-key: sk-abc 183 + api-key: ${DASHSCOPE_API_KEY:sk-xxxx}
192 deepseek: # DeepSeek 184 deepseek: # DeepSeek
193 - api-key: sk-e94db327cc7d457d99a8de8810fc6b12 185 + api-key: ${DEEPSEEK_API_KEY:sk-xxxx}
194 chat: 186 chat:
195 options: 187 options:
196 - model: deepseek-chat 188 + model: deepseek-v4-flash
197 model: 189 model:
198 rerank: false # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启 190 rerank: false # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启
199 mcp: 191 mcp:
@@ -218,34 +210,57 @@ yudao: @@ -218,34 +210,57 @@ yudao:
218 ai: 210 ai:
219 gemini: # 谷歌 Gemini 211 gemini: # 谷歌 Gemini
220 enable: true 212 enable: true
221 - api-key: AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ 213 + api-key: ${GEMINI_API_KEY:sk-xxxx}
222 model: gemini-2.5-flash 214 model: gemini-2.5-flash
223 doubao: # 字节豆包 215 doubao: # 字节豆包
224 enable: true 216 enable: true
225 - api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272  
226 - model: doubao-1-5-lite-32k-250115 217 + api-key: ${DOUBAO_API_KEY:sk-xxxx}
  218 + model: doubao-seed-2-1-turbo-260628
227 hunyuan: # 腾讯混元 219 hunyuan: # 腾讯混元
228 enable: true 220 enable: true
229 - api-key: sk-abc  
230 - model: hunyuan-turbo 221 + api-key: ${HUNYUAN_API_KEY:sk-xxxx}
  222 + model: hy3-preview
231 siliconflow: # 硅基流动 223 siliconflow: # 硅基流动
232 enable: true 224 enable: true
233 - api-key: sk-epsakfenqnyzoxhmbucsxlhkdqlcbnimslqoivkshalvdozz  
234 - model: deepseek-ai/DeepSeek-R1-Distill-Qwen-7B 225 + api-key: ${SILICONFLOW_API_KEY:sk-xxxx}
  226 + model: deepseek-ai/DeepSeek-V4-Pro
235 xinghuo: # 讯飞星火 227 xinghuo: # 讯飞星火
236 enable: true 228 enable: true
237 - appKey: 75b161ed2aef4719b275d6e7f2a4d4cd  
238 - secretKey: YWYxYWI2MTA4ODI2NGZlYTQyNjAzZTcz  
239 - model: x1 229 + api-key: ${XINGHUO_API_KEY:sk-xxxx}
  230 + model: x2-flash
240 baichuan: # 百川智能 231 baichuan: # 百川智能
241 enable: true 232 enable: true
242 - api-key: sk-abc  
243 - model: Baichuan4-Turbo 233 + api-key: ${BAICHUAN_API_KEY:sk-xxxx}
  234 + model: Baichuan-M3
  235 + yiyan: # 文心一言
  236 + enable: false
  237 + api-key: ${YIYAN_API_KEY:sk-xxxx}
  238 + model: ernie-5.1
  239 + zhipu: # 智谱 AI(GLM)
  240 + enable: false
  241 + api-key: ${ZHIPU_API_KEY:sk-xxxx}
  242 + model: glm-5.2
  243 + minimax: # MiniMax
  244 + enable: false
  245 + api-key: ${MINIMAX_API_KEY:sk-xxxx}
  246 + model: MiniMax-M3
  247 + moonshot: # 月之暗面(KIMI)
  248 + enable: false
  249 + api-key: ${MOONSHOT_API_KEY:sk-xxxx}
  250 + model: kimi-k2.6
  251 + stepfun: # 阶跃星辰
  252 + enable: false
  253 + api-key: ${STEPFUN_API_KEY:sk-xxxx}
  254 + model: step-3.7-flash
  255 + grok: # Grok
  256 + enable: false
  257 + api-key: ${GROK_API_KEY:sk-xxxx}
  258 + model: grok-4.3
244 midjourney: 259 midjourney:
245 enable: true 260 enable: true
246 # base-url: https://api.holdai.top/mj-relax/mj 261 # base-url: https://api.holdai.top/mj-relax/mj
247 base-url: https://api.holdai.top/mj 262 base-url: https://api.holdai.top/mj
248 - api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf 263 + api-key: ${MIDJOURNEY_API_KEY:sk-xxxx}
249 notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify 264 notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify
250 suno: 265 suno:
251 enable: true 266 enable: true
@@ -253,7 +268,7 @@ yudao: @@ -253,7 +268,7 @@ yudao:
253 base-url: http://127.0.0.1:3001 268 base-url: http://127.0.0.1:3001
254 web-search: 269 web-search:
255 enable: true 270 enable: true
256 - api-key: sk-40500e52840f4d24b956d0b1d80d9abe 271 + api-key: ${WEB_SEARCH_API_KEY:sk-xxxx}
257 272
258 --- #################### 芋道相关配置 #################### 273 --- #################### 芋道相关配置 ####################
259 274
@@ -351,4 +366,4 @@ yudao: @@ -351,4 +366,4 @@ yudao:
351 message-bus: 366 message-bus:
352 type: redis # 消息总线的类型 367 type: redis # 消息总线的类型
353 368
354 -debug: false  
355 \ No newline at end of file 369 \ No newline at end of file
  370 +debug: false