Commit 9c6cff33ad7331762bab918d04666e462b4093f3
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 | 19 | 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno |
| 20 | 20 | </description> |
| 21 | 21 | <properties> |
| 22 | - <spring-ai.version>1.1.5</spring-ai.version> | |
| 22 | + <spring-ai.version>1.1.8</spring-ai.version> | |
| 23 | 23 | <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba --> |
| 24 | 24 | <alibaba-ai.version>1.1.2.2</alibaba-ai.version> |
| 25 | 25 | <tinyflow.version>1.2.6</tinyflow.version> |
| 26 | 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 | 40 | <dependencies> |
| 29 | 41 | <dependency> |
| 30 | 42 | <groupId>cn.iocoder.boot</groupId> |
| ... | ... | @@ -112,37 +124,11 @@ |
| 112 | 124 | <version>${spring-ai.version}</version> |
| 113 | 125 | </dependency> |
| 114 | 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 | 128 | <groupId>com.alibaba.cloud.ai</groupId> |
| 129 | 129 | <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> |
| 130 | 130 | <version>${alibaba-ai.version}</version> |
| 131 | 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 | 132 | <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> |
| 147 | 133 | <dependency> |
| 148 | 134 | <!-- Qdrant:https://qdrant.tech/ --> |
| ... | ... | @@ -260,4 +246,4 @@ |
| 260 | 246 | </dependency> |
| 261 | 247 | </dependencies> |
| 262 | 248 | |
| 263 | -</project> | |
| 264 | 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 | 12 | // ========== API 密钥 1-040-000-000 ========== |
| 13 | 13 | ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); |
| 14 | 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 | 17 | // ========== API 模型 1-040-001-000 ========== |
| 17 | 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<String> { |
| 28 | 28 | MINI_MAX("MiniMax", "MiniMax"), // 稀宇科技 |
| 29 | 29 | MOONSHOT("Moonshot", "月之暗面"), // KIMI |
| 30 | 30 | BAI_CHUAN("BaiChuan", "百川智能"), // 百川智能 |
| 31 | + STEP_FUN("StepFun", "阶跃星辰"), // 阶跃星辰 | |
| 31 | 32 | |
| 32 | 33 | // ========== 国外平台 ========== |
| 33 | 34 | |
| ... | ... | @@ -40,7 +41,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> { |
| 40 | 41 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI |
| 41 | 42 | MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney |
| 42 | 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 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 12 | 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 | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 14 | 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 | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 16 | 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 | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; |
| 18 | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; |
| 19 | 24 | import cn.iocoder.yudao.module.ai.tool.method.PersonService; |
| ... | ... | @@ -44,7 +49,6 @@ import org.springframework.context.annotation.Bean; |
| 44 | 49 | import org.springframework.context.annotation.Configuration; |
| 45 | 50 | |
| 46 | 51 | import java.util.List; |
| 47 | -import java.util.Optional; | |
| 48 | 52 | |
| 49 | 53 | /** |
| 50 | 54 | * 芋道 AI 自动配置 |
| ... | ... | @@ -113,13 +117,13 @@ public class AiAutoConfiguration { |
| 113 | 117 | if (StrUtil.isEmpty(properties.getModel())) { |
| 114 | 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 | 122 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 119 | 123 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 120 | 124 | .apiKey(properties.getApiKey()) |
| 121 | 125 | .build()) |
| 122 | - .defaultOptions(OpenAiChatOptions.builder() | |
| 126 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 123 | 127 | .model(properties.getModel()) |
| 124 | 128 | .temperature(properties.getTemperature()) |
| 125 | 129 | .maxTokens(properties.getMaxTokens()) |
| ... | ... | @@ -168,11 +172,8 @@ public class AiAutoConfiguration { |
| 168 | 172 | if (StrUtil.isEmpty(properties.getModel())) { |
| 169 | 173 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); |
| 170 | 174 | } |
| 171 | - // 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL | |
| 172 | 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 | 178 | // 创建 DeepSeekChatModel、HunYuanChatModel 对象 |
| 178 | 179 | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| ... | ... | @@ -203,25 +204,15 @@ public class AiAutoConfiguration { |
| 203 | 204 | if (StrUtil.isEmpty(properties.getModel())) { |
| 204 | 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 | 210 | .model(properties.getModel()) |
| 217 | 211 | .temperature(properties.getTemperature()) |
| 218 | 212 | .maxTokens(properties.getMaxTokens()) |
| 219 | 213 | .topP(properties.getTopP()) |
| 220 | 214 | .build()) |
| 221 | - // TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!! | |
| 222 | - .toolCallingManager(getToolCallingManager()) | |
| 223 | 215 | .build(); |
| 224 | - return new XingHuoChatModel(openAiChatModel); | |
| 225 | 216 | } |
| 226 | 217 | |
| 227 | 218 | @Bean |
| ... | ... | @@ -235,12 +226,13 @@ public class AiAutoConfiguration { |
| 235 | 226 | if (StrUtil.isEmpty(properties.getModel())) { |
| 236 | 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 | 231 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 241 | 232 | .apiKey(properties.getApiKey()) |
| 233 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) | |
| 242 | 234 | .build()) |
| 243 | - .defaultOptions(OpenAiChatOptions.builder() | |
| 235 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 244 | 236 | .model(properties.getModel()) |
| 245 | 237 | .temperature(properties.getTemperature()) |
| 246 | 238 | .maxTokens(properties.getMaxTokens()) |
| ... | ... | @@ -248,7 +240,123 @@ public class AiAutoConfiguration { |
| 248 | 240 | .build()) |
| 249 | 241 | .toolCallingManager(getToolCallingManager()) |
| 250 | 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 | 362 | @Bean |
| ... | ... | @@ -264,14 +372,20 @@ public class AiAutoConfiguration { |
| 264 | 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 | 383 | if (StrUtil.isEmpty(properties.getModel())) { |
| 269 | 384 | properties.setModel(GrokChatModel.MODEL_DEFAULT); |
| 270 | 385 | } |
| 271 | 386 | OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() |
| 272 | 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 | 389 | .completionsPath(GrokChatModel.COMPLETE_PATH) |
| 276 | 390 | .apiKey(properties.getApiKey()) |
| 277 | 391 | .build()) |
| ... | ... | @@ -283,7 +397,7 @@ public class AiAutoConfiguration { |
| 283 | 397 | .build()) |
| 284 | 398 | .toolCallingManager(getToolCallingManager()) |
| 285 | 399 | .build(); |
| 286 | - return new DouBaoChatModel(openAiChatModel); | |
| 400 | + return new GrokChatModel(openAiChatModel); | |
| 287 | 401 | } |
| 288 | 402 | |
| 289 | 403 | // ========== RAG 相关 ========== |
| ... | ... | @@ -320,4 +434,4 @@ public class AiAutoConfiguration { |
| 320 | 434 | return List.of(ToolCallbacks.from(personService)); |
| 321 | 435 | } |
| 322 | 436 | |
| 323 | -} | |
| 324 | 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 | 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 | 77 | * Midjourney 绘图 |
| 48 | 78 | */ |
| 49 | 79 | private Midjourney midjourney; |
| ... | ... | @@ -116,9 +146,7 @@ public class YudaoAiProperties { |
| 116 | 146 | public static class XingHuo { |
| 117 | 147 | |
| 118 | 148 | private String enable; |
| 119 | - private String appId; | |
| 120 | - private String appKey; | |
| 121 | - private String secretKey; | |
| 149 | + private String apiKey; | |
| 122 | 150 | |
| 123 | 151 | private String model; |
| 124 | 152 | private Double temperature; |
| ... | ... | @@ -141,6 +169,76 @@ public class YudaoAiProperties { |
| 141 | 169 | } |
| 142 | 170 | |
| 143 | 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 | 242 | public static class Midjourney { |
| 145 | 243 | |
| 146 | 244 | private String enable; | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.io.FileUtil; |
| 4 | -import cn.hutool.core.lang.Assert; | |
| 5 | 4 | import cn.hutool.core.lang.Singleton; |
| 6 | 5 | import cn.hutool.core.lang.func.Func0; |
| 7 | 6 | import cn.hutool.core.util.ArrayUtil; |
| ... | ... | @@ -15,14 +14,21 @@ import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; |
| 15 | 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 16 | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 17 | 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 | 18 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 19 | 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 | 22 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| 21 | 23 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| 22 | 24 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi; |
| 23 | 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 | 27 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 25 | 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 | 32 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration; |
| 27 | 33 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration; |
| 28 | 34 | import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration; |
| ... | ... | @@ -40,18 +46,6 @@ import io.milvus.client.MilvusServiceClient; |
| 40 | 46 | import io.qdrant.client.QdrantClient; |
| 41 | 47 | import io.qdrant.client.QdrantGrpcClient; |
| 42 | 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 | 49 | import org.springframework.ai.azure.openai.AzureOpenAiChatModel; |
| 56 | 50 | import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; |
| 57 | 51 | import org.springframework.ai.chat.model.ChatModel; |
| ... | ... | @@ -63,27 +57,17 @@ import org.springframework.ai.embedding.BatchingStrategy; |
| 63 | 57 | import org.springframework.ai.embedding.EmbeddingModel; |
| 64 | 58 | import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; |
| 65 | 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 | 60 | import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; |
| 72 | 61 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; |
| 73 | 62 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration; |
| 74 | 63 | import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; |
| 75 | 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 | 65 | import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; |
| 79 | 66 | import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; |
| 80 | 67 | import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; |
| 81 | 68 | import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration; |
| 82 | 69 | import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration; |
| 83 | 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 | 71 | import org.springframework.ai.ollama.OllamaChatModel; |
| 88 | 72 | import org.springframework.ai.ollama.OllamaEmbeddingModel; |
| 89 | 73 | import org.springframework.ai.ollama.api.OllamaApi; |
| ... | ... | @@ -114,13 +98,9 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore |
| 114 | 98 | import org.springframework.ai.vectorstore.redis.RedisVectorStore; |
| 115 | 99 | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration; |
| 116 | 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 | 101 | import org.springframework.beans.BeansException; |
| 121 | 102 | import org.springframework.beans.factory.ObjectProvider; |
| 122 | 103 | import org.springframework.boot.autoconfigure.data.redis.RedisProperties; |
| 123 | -import org.springframework.web.client.RestClient; | |
| 124 | 104 | import redis.clients.jedis.JedisPooled; |
| 125 | 105 | |
| 126 | 106 | import java.io.File; |
| ... | ... | @@ -131,7 +111,6 @@ import java.util.Timer; |
| 131 | 111 | import java.util.TimerTask; |
| 132 | 112 | |
| 133 | 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 | 116 | * AI Model 模型工厂的实现类 |
| ... | ... | @@ -141,7 +120,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE; |
| 141 | 120 | public class AiModelFactoryImpl implements AiModelFactory { |
| 142 | 121 | |
| 143 | 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 | 126 | String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); |
| 146 | 127 | return Singleton.get(cacheKey, (Func0<ChatModel>) () -> { |
| 147 | 128 | // noinspection EnhancedSwitchMigration |
| ... | ... | @@ -164,6 +145,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 164 | 145 | return buildMiniMaxChatModel(apiKey, url); |
| 165 | 146 | case MOONSHOT: |
| 166 | 147 | return buildMoonshotChatModel(apiKey, url); |
| 148 | + case STEP_FUN: | |
| 149 | + return buildStepFunChatModel(apiKey, url); | |
| 167 | 150 | case XING_HUO: |
| 168 | 151 | return buildXingHuoChatModel(apiKey); |
| 169 | 152 | case BAI_CHUAN: |
| ... | ... | @@ -179,7 +162,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 179 | 162 | case OLLAMA: |
| 180 | 163 | return buildOllamaChatModel(url); |
| 181 | 164 | case GROK: |
| 182 | - return buildGrokChatModel(apiKey,url); | |
| 165 | + return buildGrokChatModel(apiKey, url); | |
| 183 | 166 | default: |
| 184 | 167 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 185 | 168 | } |
| ... | ... | @@ -193,7 +176,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 193 | 176 | case TONG_YI: |
| 194 | 177 | return SpringUtil.getBean(DashScopeChatModel.class); |
| 195 | 178 | case YI_YAN: |
| 196 | - return SpringUtil.getBean(QianFanChatModel.class); | |
| 179 | + return SpringUtil.getBean(YiYanChatModel.class); | |
| 197 | 180 | case DEEP_SEEK: |
| 198 | 181 | return SpringUtil.getBean(DeepSeekChatModel.class); |
| 199 | 182 | case DOU_BAO: |
| ... | ... | @@ -203,11 +186,13 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 203 | 186 | case SILICON_FLOW: |
| 204 | 187 | return SpringUtil.getBean(SiliconFlowChatModel.class); |
| 205 | 188 | case ZHI_PU: |
| 206 | - return SpringUtil.getBean(ZhiPuAiChatModel.class); | |
| 189 | + return SpringUtil.getBean(ZhiPuChatModel.class); | |
| 207 | 190 | case MINI_MAX: |
| 208 | 191 | return SpringUtil.getBean(MiniMaxChatModel.class); |
| 209 | 192 | case MOONSHOT: |
| 210 | 193 | return SpringUtil.getBean(MoonshotChatModel.class); |
| 194 | + case STEP_FUN: | |
| 195 | + return SpringUtil.getBean(StepFunChatModel.class); | |
| 211 | 196 | case XING_HUO: |
| 212 | 197 | return SpringUtil.getBean(XingHuoChatModel.class); |
| 213 | 198 | case BAI_CHUAN: |
| ... | ... | @@ -222,6 +207,8 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 222 | 207 | return SpringUtil.getBean(GeminiChatModel.class); |
| 223 | 208 | case OLLAMA: |
| 224 | 209 | return SpringUtil.getBean(OllamaChatModel.class); |
| 210 | + case GROK: | |
| 211 | + return SpringUtil.getBean(GrokChatModel.class); | |
| 225 | 212 | default: |
| 226 | 213 | throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); |
| 227 | 214 | } |
| ... | ... | @@ -233,10 +220,6 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 233 | 220 | switch (platform) { |
| 234 | 221 | case TONG_YI: |
| 235 | 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 | 223 | case SILICON_FLOW: |
| 241 | 224 | return SpringUtil.getBean(SiliconFlowImageModel.class); |
| 242 | 225 | case OPENAI: |
| ... | ... | @@ -249,19 +232,17 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 249 | 232 | } |
| 250 | 233 | |
| 251 | 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 | 238 | // noinspection EnhancedSwitchMigration |
| 254 | 239 | switch (platform) { |
| 255 | 240 | case TONG_YI: |
| 256 | 241 | return buildTongYiImagesModel(apiKey); |
| 257 | - case YI_YAN: | |
| 258 | - return buildQianFanImageModel(apiKey); | |
| 259 | - case ZHI_PU: | |
| 260 | - return buildZhiPuAiImageModel(apiKey, url); | |
| 261 | 242 | case OPENAI: |
| 262 | 243 | return buildOpenAiImageModel(apiKey, url); |
| 263 | 244 | case SILICON_FLOW: |
| 264 | - return buildSiliconFlowImageModel(apiKey,url); | |
| 245 | + return buildSiliconFlowImageModel(apiKey, url); | |
| 265 | 246 | case STABLE_DIFFUSION: |
| 266 | 247 | return buildStabilityAiImageModel(apiKey, url); |
| 267 | 248 | default: |
| ... | ... | @@ -270,9 +251,11 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 270 | 251 | } |
| 271 | 252 | |
| 272 | 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 | 259 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { |
| 277 | 260 | YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) |
| 278 | 261 | .getMidjourney(); |
| ... | ... | @@ -281,25 +264,23 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 281 | 264 | } |
| 282 | 265 | |
| 283 | 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 | 270 | String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); |
| 286 | 271 | return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url)); |
| 287 | 272 | } |
| 288 | 273 | |
| 289 | 274 | @Override |
| 290 | 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 | 279 | String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model); |
| 293 | 280 | return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> { |
| 294 | 281 | switch (platform) { |
| 295 | 282 | case TONG_YI: |
| 296 | 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 | 284 | case OPENAI: |
| 304 | 285 | return buildOpenAiEmbeddingModel(apiKey, url, model); |
| 305 | 286 | case AZURE_OPENAI: |
| ... | ... | @@ -341,6 +322,11 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 341 | 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 | 330 | // ========== 各种创建 spring-ai 客户端的方法 ========== |
| 345 | 331 | |
| 346 | 332 | /** |
| ... | ... | @@ -367,30 +353,10 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 367 | 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 | 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 | 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 | 443 | YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() |
| 493 | - .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); | |
| 444 | + .setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT); | |
| 494 | 445 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); |
| 495 | 446 | } |
| 496 | 447 | |
| ... | ... | @@ -590,7 +541,7 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 590 | 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 | 545 | YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() |
| 595 | 546 | .setBaseUrl(url) |
| 596 | 547 | .setApiKey(apiKey); |
| ... | ... | @@ -608,41 +559,6 @@ public class AiModelFactoryImpl implements AiModelFactory { |
| 608 | 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 | 562 | private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { |
| 647 | 563 | OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); |
| 648 | 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 | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | 8 | import org.springframework.ai.chat.prompt.Prompt; |
| 9 | -import org.springframework.ai.openai.OpenAiChatModel; | |
| 9 | +import org.springframework.ai.deepseek.DeepSeekChatModel; | |
| 10 | 10 | import reactor.core.publisher.Flux; |
| 11 | 11 | |
| 12 | 12 | /** |
| ... | ... | @@ -20,26 +20,28 @@ public class BaiChuanChatModel implements ChatModel { |
| 20 | 20 | |
| 21 | 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 | 32 | @Override |
| 31 | 33 | public ChatResponse call(Prompt prompt) { |
| 32 | - return openAiChatModel.call(prompt); | |
| 34 | + return deepSeekChatModel.call(prompt); | |
| 33 | 35 | } |
| 34 | 36 | |
| 35 | 37 | @Override |
| 36 | 38 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 37 | - return openAiChatModel.stream(prompt); | |
| 39 | + return deepSeekChatModel.stream(prompt); | |
| 38 | 40 | } |
| 39 | 41 | |
| 40 | 42 | @Override |
| 41 | 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 | 20 | public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api"; |
| 21 | 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 | 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 | 19 | |
| 20 | 20 | public static final String BASE_URL = "https://api.x.ai"; |
| 21 | 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 | 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 | 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 | 16 | * @author fansili |
| 18 | 17 | */ |
| ... | ... | @@ -20,14 +19,10 @@ import reactor.core.publisher.Flux; |
| 20 | 19 | @RequiredArgsConstructor |
| 21 | 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 | 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 | 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 | 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 | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | 5 | import org.springframework.ai.chat.model.ChatModel; |
| 6 | 6 | import org.springframework.ai.chat.model.ChatResponse; |
| 7 | 7 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 8 | 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 | 12 | import reactor.core.publisher.Flux; |
| 10 | 13 | |
| 11 | 14 | /** |
| ... | ... | @@ -14,37 +17,140 @@ import reactor.core.publisher.Flux; |
| 14 | 17 | * @author fansili |
| 15 | 18 | */ |
| 16 | 19 | @Slf4j |
| 17 | -@RequiredArgsConstructor | |
| 18 | 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 | 71 | @Override |
| 36 | 72 | public ChatResponse call(Prompt prompt) { |
| 37 | - return openAiChatModelV1.call(prompt); | |
| 73 | + return getChatModel(prompt).call(buildApiPrompt(prompt)); | |
| 38 | 74 | } |
| 39 | 75 | |
| 40 | 76 | @Override |
| 41 | 77 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 42 | - return openAiChatModelV1.stream(prompt); | |
| 78 | + return getChatModel(prompt).stream(buildApiPrompt(prompt)); | |
| 43 | 79 | } |
| 44 | 80 | |
| 45 | 81 | @Override |
| 46 | 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 | 29 | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; |
| 30 | 30 | import jakarta.annotation.Resource; |
| 31 | 31 | import lombok.extern.slf4j.Slf4j; |
| 32 | -import org.springaicommunity.qianfan.QianFanImageOptions; | |
| 33 | 32 | import org.springframework.ai.image.ImageModel; |
| 34 | 33 | import org.springframework.ai.image.ImageOptions; |
| 35 | 34 | import org.springframework.ai.image.ImagePrompt; |
| 36 | 35 | import org.springframework.ai.image.ImageResponse; |
| 37 | 36 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 38 | 37 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; |
| 39 | -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; | |
| 40 | 38 | import org.springframework.scheduling.annotation.Async; |
| 41 | 39 | import org.springframework.stereotype.Service; |
| 42 | 40 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService { |
| 168 | 166 | .model(model.getModel()).n(1) |
| 169 | 167 | .height(draw.getHeight()).width(draw.getWidth()) |
| 170 | 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 | 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 | import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; |
| 9 | 9 | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| 10 | 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 | 12 | import org.springframework.ai.anthropic.AnthropicChatOptions; |
| 14 | 13 | import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; |
| 15 | 14 | import org.springframework.ai.chat.messages.*; |
| ... | ... | @@ -17,14 +16,16 @@ import org.springframework.ai.chat.model.ChatResponse; |
| 17 | 16 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 18 | 17 | import org.springframework.ai.deepseek.DeepSeekAssistantMessage; |
| 19 | 18 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 20 | -import org.springframework.ai.minimax.MiniMaxChatOptions; | |
| 21 | 19 | import org.springframework.ai.ollama.api.OllamaChatOptions; |
| 22 | 20 | import org.springframework.ai.openai.OpenAiChatOptions; |
| 23 | 21 | import org.springframework.ai.tool.ToolCallback; |
| 24 | -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; | |
| 22 | +import org.springframework.core.env.Environment; | |
| 25 | 23 | |
| 26 | 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 | 30 | * Spring AI 工具类 |
| 30 | 31 | * |
| ... | ... | @@ -36,13 +37,42 @@ public class AiUtils { |
| 36 | 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 | 70 | * @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a> |
| 42 | 71 | * @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a> |
| 43 | 72 | */ |
| 44 | 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 | 76 | "qwen3.6-plus", "qwen3.6-flash", |
| 47 | 77 | "qwen3.5-plus", "qwen3.5-flash", |
| 48 | 78 | // qwen-vl 视觉理解 |
| ... | ... | @@ -72,27 +102,21 @@ public class AiUtils { |
| 72 | 102 | .enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置 |
| 73 | 103 | .multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型 |
| 74 | 104 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| 75 | - case YI_YAN: | |
| 76 | - return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); | |
| 77 | 105 | case DEEP_SEEK: |
| 78 | 106 | case DOU_BAO: // 复用 DeepSeek 客户端 |
| 79 | 107 | case HUN_YUAN: // 复用 DeepSeek 客户端 |
| 80 | 108 | case SILICON_FLOW: // 复用 DeepSeek 客户端 |
| 109 | + case YI_YAN: // 复用 DeepSeek 客户端 | |
| 110 | + case ZHI_PU: // 复用 DeepSeek 客户端 | |
| 81 | 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 | 116 | return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 83 | 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 | 118 | case OPENAI: |
| 94 | 119 | case GEMINI: // 复用 OpenAI 客户端 |
| 95 | - case BAI_CHUAN: // 复用 OpenAI 客户端 | |
| 96 | 120 | case GROK: // 复用 OpenAI 客户端 |
| 97 | 121 | return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) |
| 98 | 122 | .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); |
| ... | ... | @@ -159,4 +183,4 @@ public class AiUtils { |
| 159 | 183 | return MapUtil.getStr(output.getMetadata(), "reasoningContent"); |
| 160 | 184 | } |
| 161 | 185 | |
| 162 | -} | |
| 163 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 8 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | 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 | 15 | import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link BaiChuanChatModel} 集成测试 |
| ... | ... | @@ -23,22 +27,27 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 37 | .baseUrl(BaiChuanChatModel.BASE_URL) |
| 29 | - .apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey | |
| 38 | + .completionsPath(BaiChuanChatModel.COMPLETE_PATH) | |
| 39 | + .apiKey(API_KEY) | |
| 30 | 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 | 43 | .temperature(0.7) |
| 34 | 44 | .build()) |
| 35 | - .build(); | |
| 36 | - | |
| 37 | - private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel); | |
| 45 | + .build()); | |
| 38 | 46 | |
| 39 | 47 | @Test |
| 40 | 48 | @Disabled |
| 41 | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | |
| 42 | 51 | // 准备参数 |
| 43 | 52 | List<Message> messages = new ArrayList<>(); |
| 44 | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -48,11 +57,13 @@ public class BaiChuanChatModelTests { |
| 48 | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 49 | 58 | // 打印结果 |
| 50 | 59 | System.out.println(response); |
| 60 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 51 | 61 | } |
| 52 | 62 | |
| 53 | 63 | @Test |
| 54 | 64 | @Disabled |
| 55 | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | |
| 56 | 67 | // 准备参数 |
| 57 | 68 | List<Message> messages = new ArrayList<>(); |
| 58 | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -61,7 +72,10 @@ public class BaiChuanChatModelTests { |
| 61 | 72 | // 调用 |
| 62 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | 5 | import org.junit.jupiter.api.Test; |
| 5 | 6 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -14,6 +15,9 @@ import reactor.core.publisher.Flux; |
| 14 | 15 | |
| 15 | 16 | import java.util.ArrayList; |
| 16 | 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 | 23 | * {@link DeepSeekChatModel} 集成测试 |
| ... | ... | @@ -22,12 +26,17 @@ import java.util.List; |
| 22 | 26 | */ |
| 23 | 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 | 34 | private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder() |
| 26 | 35 | .deepSeekApi(DeepSeekApi.builder() |
| 27 | - .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey | |
| 36 | + .apiKey(API_KEY) // apiKey | |
| 28 | 37 | .build()) |
| 29 | 38 | .defaultOptions(DeepSeekChatOptions.builder() |
| 30 | - .model("deepseek-chat") // 模型 | |
| 39 | + .model(MODEL) // 模型 | |
| 31 | 40 | .temperature(0.7) |
| 32 | 41 | .build()) |
| 33 | 42 | .build(); |
| ... | ... | @@ -35,6 +44,7 @@ public class DeepSeekChatModelTests { |
| 35 | 44 | @Test |
| 36 | 45 | @Disabled |
| 37 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 38 | 48 | // 准备参数 |
| 39 | 49 | List<Message> messages = new ArrayList<>(); |
| 40 | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -43,12 +53,13 @@ public class DeepSeekChatModelTests { |
| 43 | 53 | // 调用 |
| 44 | 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 | 59 | @Test |
| 50 | 60 | @Disabled |
| 51 | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | |
| 52 | 63 | // 准备参数 |
| 53 | 64 | List<Message> messages = new ArrayList<>(); |
| 54 | 65 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -63,11 +74,12 @@ public class DeepSeekChatModelTests { |
| 63 | 74 | @Test |
| 64 | 75 | @Disabled |
| 65 | 76 | public void testStream_thinking() { |
| 77 | + validateApiKey(API_KEY); | |
| 66 | 78 | // 准备参数 |
| 67 | 79 | List<Message> messages = new ArrayList<>(); |
| 68 | 80 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | 81 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 70 | - .model("deepseek-reasoner") | |
| 82 | + .model(MODEL) | |
| 71 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link DouBaoChatModel} 集成测试 |
| ... | ... | @@ -23,19 +27,19 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 30 | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 31 | 37 | .baseUrl(DouBaoChatModel.BASE_URL) |
| 32 | 38 | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| 33 | - .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey | |
| 39 | + .apiKey(API_KEY) | |
| 34 | 40 | .build()) |
| 35 | 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 | 43 | .temperature(0.7) |
| 40 | 44 | .build()) |
| 41 | 45 | .build(); |
| ... | ... | @@ -45,6 +49,7 @@ public class DouBaoChatModelTests { |
| 45 | 49 | @Test |
| 46 | 50 | @Disabled |
| 47 | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | |
| 48 | 53 | // 准备参数 |
| 49 | 54 | List<Message> messages = new ArrayList<>(); |
| 50 | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -54,11 +59,13 @@ public class DouBaoChatModelTests { |
| 54 | 59 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 55 | 60 | // 打印结果 |
| 56 | 61 | System.out.println(response); |
| 62 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 57 | 63 | } |
| 58 | 64 | |
| 59 | 65 | @Test |
| 60 | 66 | @Disabled |
| 61 | 67 | public void testStream() { |
| 68 | + validateApiKey(API_KEY); | |
| 62 | 69 | // 准备参数 |
| 63 | 70 | List<Message> messages = new ArrayList<>(); |
| 64 | 71 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -67,25 +74,26 @@ public class DouBaoChatModelTests { |
| 67 | 74 | // 调用 |
| 68 | 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 | 83 | @Test |
| 74 | 84 | @Disabled |
| 75 | 85 | public void testStream_thinking() { |
| 86 | + validateApiKey(API_KEY); | |
| 76 | 87 | // 准备参数 |
| 77 | 88 | List<Message> messages = new ArrayList<>(); |
| 78 | 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 | 94 | flux.doOnNext(response -> { |
| 87 | 95 | // System.out.println(response); |
| 88 | - System.out.println(response.getResult().getOutput()); | |
| 96 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 89 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -15,6 +16,9 @@ import reactor.core.publisher.Flux; |
| 15 | 16 | |
| 16 | 17 | import java.util.ArrayList; |
| 17 | 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 | 24 | * {@link HunYuanChatModel} 集成测试 |
| ... | ... | @@ -23,14 +27,19 @@ import java.util.List; |
| 23 | 27 | */ |
| 24 | 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 | 35 | private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| 27 | 36 | .deepSeekApi(DeepSeekApi.builder() |
| 28 | 37 | .baseUrl(HunYuanChatModel.BASE_URL) |
| 29 | 38 | .completionsPath(HunYuanChatModel.COMPLETE_PATH) |
| 30 | - .apiKey("sk-abc") // apiKey | |
| 39 | + .apiKey(API_KEY) // apiKey | |
| 31 | 40 | .build()) |
| 32 | 41 | .defaultOptions(DeepSeekChatOptions.builder() |
| 33 | - .model(HunYuanChatModel.MODEL_DEFAULT) // 模型 | |
| 42 | + .model(MODEL) // 模型 | |
| 34 | 43 | .temperature(0.7) |
| 35 | 44 | .build()) |
| 36 | 45 | .build(); |
| ... | ... | @@ -40,6 +49,7 @@ public class HunYuanChatModelTests { |
| 40 | 49 | @Test |
| 41 | 50 | @Disabled |
| 42 | 51 | public void testCall() { |
| 52 | + validateApiKey(API_KEY); | |
| 43 | 53 | // 准备参数 |
| 44 | 54 | List<Message> messages = new ArrayList<>(); |
| 45 | 55 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -48,12 +58,13 @@ public class HunYuanChatModelTests { |
| 48 | 58 | // 调用 |
| 49 | 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 | 64 | @Test |
| 55 | 65 | @Disabled |
| 56 | 66 | public void testStream() { |
| 67 | + validateApiKey(API_KEY); | |
| 57 | 68 | // 准备参数 |
| 58 | 69 | List<Message> messages = new ArrayList<>(); |
| 59 | 70 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -68,12 +79,12 @@ public class HunYuanChatModelTests { |
| 68 | 79 | @Test |
| 69 | 80 | @Disabled |
| 70 | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | |
| 71 | 83 | // 准备参数 |
| 72 | 84 | List<Message> messages = new ArrayList<>(); |
| 73 | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 74 | 86 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 75 | - .model("hunyuan-a13b") | |
| 76 | -// .model("hunyuan-turbos-latest") | |
| 87 | + .model(MODEL) | |
| 77 | 88 | .build(); |
| 78 | 89 | |
| 79 | 90 | // 调用 |
| ... | ... | @@ -85,66 +96,4 @@ public class HunYuanChatModelTests { |
| 85 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 6 | import org.junit.jupiter.api.Test; |
| 5 | 7 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -7,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 7 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 24 | * {@link MiniMaxChatModel} 的集成测试 |
| ... | ... | @@ -22,14 +27,24 @@ import java.util.List; |
| 22 | 27 | */ |
| 23 | 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 | 44 | @Test |
| 31 | 45 | @Disabled |
| 32 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 33 | 48 | // 准备参数 |
| 34 | 49 | List<Message> messages = new ArrayList<>(); |
| 35 | 50 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -39,12 +54,13 @@ public class MiniMaxChatModelTests { |
| 39 | 54 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 40 | 55 | // 打印结果 |
| 41 | 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 | 60 | @Test |
| 46 | 61 | @Disabled |
| 47 | 62 | public void testStream() { |
| 63 | + validateApiKey(API_KEY); | |
| 48 | 64 | // 准备参数 |
| 49 | 65 | List<Message> messages = new ArrayList<>(); |
| 50 | 66 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -55,19 +71,19 @@ public class MiniMaxChatModelTests { |
| 55 | 71 | // 打印结果 |
| 56 | 72 | flux.doOnNext(response -> { |
| 57 | 73 | // System.out.println(response); |
| 58 | - System.out.println(response.getResult().getOutput()); | |
| 74 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 59 | 75 | }).then().block(); |
| 60 | 76 | } |
| 61 | 77 | |
| 62 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 63 | 78 | @Test |
| 64 | 79 | @Disabled |
| 65 | 80 | public void testStream_thinking() { |
| 81 | + validateApiKey(API_KEY); | |
| 66 | 82 | // 准备参数 |
| 67 | 83 | List<Message> messages = new ArrayList<>(); |
| 68 | 84 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 69 | - MiniMaxChatOptions options = MiniMaxChatOptions.builder() | |
| 70 | - .model("MiniMax-M1") | |
| 85 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() | |
| 86 | + .model(MODEL) | |
| 71 | 87 | .build(); |
| 72 | 88 | |
| 73 | 89 | // 调用 |
| ... | ... | @@ -75,7 +91,7 @@ public class MiniMaxChatModelTests { |
| 75 | 91 | // 打印结果 |
| 76 | 92 | flux.doOnNext(response -> { |
| 77 | 93 | // System.out.println(response); |
| 78 | - System.out.println(response.getResult().getOutput()); | |
| 94 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 79 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 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 | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | 8 | import org.springframework.ai.chat.messages.SystemMessage; |
| 10 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 11 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 12 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 24 | * {@link MoonshotChatModel} 的集成测试 |
| ... | ... | @@ -22,18 +27,27 @@ import java.util.List; |
| 22 | 27 | */ |
| 23 | 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 | 40 | .build()) |
| 29 | - .defaultOptions(MoonshotChatOptions.builder() | |
| 30 | - .model("kimi-k2-0711-preview") // 模型 | |
| 41 | + .defaultOptions(DeepSeekChatOptions.builder() | |
| 42 | + .model(MODEL) | |
| 43 | + .temperature(1D) | |
| 31 | 44 | .build()) |
| 32 | - .build(); | |
| 45 | + .build()); | |
| 33 | 46 | |
| 34 | 47 | @Test |
| 35 | 48 | @Disabled |
| 36 | 49 | public void testCall() { |
| 50 | + validateApiKey(API_KEY); | |
| 37 | 51 | // 准备参数 |
| 38 | 52 | List<Message> messages = new ArrayList<>(); |
| 39 | 53 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -43,12 +57,13 @@ public class MoonshotChatModelTests { |
| 43 | 57 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 44 | 58 | // 打印结果 |
| 45 | 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 | 63 | @Test |
| 50 | 64 | @Disabled |
| 51 | 65 | public void testStream() { |
| 66 | + validateApiKey(API_KEY); | |
| 52 | 67 | // 准备参数 |
| 53 | 68 | List<Message> messages = new ArrayList<>(); |
| 54 | 69 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -59,20 +74,19 @@ public class MoonshotChatModelTests { |
| 59 | 74 | // 打印结果 |
| 60 | 75 | flux.doOnNext(response -> { |
| 61 | 76 | // System.out.println(response); |
| 62 | - System.out.println(response.getResult().getOutput()); | |
| 77 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 63 | 78 | }).then().block(); |
| 64 | 79 | } |
| 65 | 80 | |
| 66 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 67 | 81 | @Test |
| 68 | 82 | @Disabled |
| 69 | 83 | public void testStream_thinking() { |
| 84 | + validateApiKey(API_KEY); | |
| 70 | 85 | // 准备参数 |
| 71 | 86 | List<Message> messages = new ArrayList<>(); |
| 72 | 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 | 90 | .build(); |
| 77 | 91 | |
| 78 | 92 | // 调用 |
| ... | ... | @@ -80,7 +94,7 @@ public class MoonshotChatModelTests { |
| 80 | 94 | // 打印结果 |
| 81 | 95 | flux.doOnNext(response -> { |
| 82 | 96 | // System.out.println(response); |
| 83 | - System.out.println(response.getResult().getOutput()); | |
| 97 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 84 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 4 | 5 | import org.junit.jupiter.api.Disabled; |
| 5 | 6 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -8,13 +9,14 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 8 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 9 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 10 | 11 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | -import org.springframework.ai.deepseek.DeepSeekChatModel; | |
| 12 | 12 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| 13 | -import org.springframework.ai.deepseek.api.DeepSeekApi; | |
| 14 | 13 | import reactor.core.publisher.Flux; |
| 15 | 14 | |
| 16 | 15 | import java.util.ArrayList; |
| 17 | 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 | 22 | * {@link XingHuoChatModel} 集成测试 |
| ... | ... | @@ -23,24 +25,23 @@ import java.util.List; |
| 23 | 25 | */ |
| 24 | 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 | 41 | @Test |
| 42 | 42 | @Disabled |
| 43 | 43 | public void testCall() { |
| 44 | + validateApiKey(API_KEY); | |
| 44 | 45 | // 准备参数 |
| 45 | 46 | List<Message> messages = new ArrayList<>(); |
| 46 | 47 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -55,6 +56,7 @@ public class XingHuoChatModelTests { |
| 55 | 56 | @Test |
| 56 | 57 | @Disabled |
| 57 | 58 | public void testStream() { |
| 59 | + validateApiKey(API_KEY); | |
| 58 | 60 | // 准备参数 |
| 59 | 61 | List<Message> messages = new ArrayList<>(); |
| 60 | 62 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -69,11 +71,12 @@ public class XingHuoChatModelTests { |
| 69 | 71 | @Test |
| 70 | 72 | @Disabled |
| 71 | 73 | public void testStream_thinking() { |
| 74 | + validateApiKey(API_KEY); | |
| 72 | 75 | // 准备参数 |
| 73 | 76 | List<Message> messages = new ArrayList<>(); |
| 74 | 77 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 75 | 78 | DeepSeekChatOptions options = DeepSeekChatOptions.builder() |
| 76 | - .model("x1") | |
| 79 | + .model(MODEL) | |
| 77 | 80 | .build(); |
| 78 | 81 | |
| 79 | 82 | // 调用 |
| ... | ... | @@ -81,7 +84,7 @@ public class XingHuoChatModelTests { |
| 81 | 84 | // 打印结果 |
| 82 | 85 | flux.doOnNext(response -> { |
| 83 | 86 | // System.out.println(response); |
| 84 | - System.out.println(response.getResult().getOutput()); | |
| 87 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 85 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 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 | 7 | import org.springframework.ai.chat.messages.Message; |
| 9 | 8 | import org.springframework.ai.chat.messages.UserMessage; |
| 10 | 9 | import org.springframework.ai.chat.model.ChatResponse; |
| 11 | 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 | 14 | import reactor.core.publisher.Flux; |
| 13 | 15 | |
| 14 | 16 | import java.util.ArrayList; |
| 15 | 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 | 25 | * @author fansili |
| 22 | 26 | */ |
| 23 | 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 | 44 | @Test |
| 33 | 45 | @Disabled |
| 34 | 46 | public void testCall() { |
| 47 | + validateApiKey(API_KEY); | |
| 35 | 48 | // 准备参数 |
| 36 | 49 | List<Message> messages = new ArrayList<>(); |
| 37 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | |
| 38 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 39 | 50 | messages.add(new UserMessage("1 + 1 = ?")); |
| 40 | 51 | |
| 41 | 52 | // 调用 |
| 42 | 53 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 43 | 54 | // 打印结果 |
| 44 | 55 | System.out.println(response); |
| 56 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 45 | 57 | } |
| 46 | 58 | |
| 47 | 59 | @Test |
| 48 | 60 | @Disabled |
| 49 | 61 | public void testStream() { |
| 62 | + validateApiKey(API_KEY); | |
| 50 | 63 | // 准备参数 |
| 51 | 64 | List<Message> messages = new ArrayList<>(); |
| 52 | - // TODO @芋艿:文心一言,只要带上 system message 就报错,已经各种测试,很莫名! | |
| 53 | -// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 54 | 65 | messages.add(new UserMessage("1 + 1 = ?")); |
| 55 | 66 | |
| 56 | 67 | // 调用 |
| 57 | 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 | 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 | 5 | import org.junit.jupiter.api.Disabled; |
| 4 | 6 | import org.junit.jupiter.api.Test; |
| 5 | 7 | import org.springframework.ai.chat.messages.Message; |
| ... | ... | @@ -7,31 +9,43 @@ import org.springframework.ai.chat.messages.SystemMessage; |
| 7 | 9 | import org.springframework.ai.chat.messages.UserMessage; |
| 8 | 10 | import org.springframework.ai.chat.model.ChatResponse; |
| 9 | 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 | 15 | import reactor.core.publisher.Flux; |
| 14 | 16 | |
| 15 | 17 | import java.util.ArrayList; |
| 16 | 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 | 26 | * @author 芋道源码 |
| 22 | 27 | */ |
| 23 | 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 | 42 | .build() |
| 30 | - ); | |
| 43 | + ).build()); | |
| 31 | 44 | |
| 32 | 45 | @Test |
| 33 | 46 | @Disabled |
| 34 | 47 | public void testCall() { |
| 48 | + validateApiKey(API_KEY); | |
| 35 | 49 | // 准备参数 |
| 36 | 50 | List<Message> messages = new ArrayList<>(); |
| 37 | 51 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -41,12 +55,13 @@ public class ZhiPuAiChatModelTests { |
| 41 | 55 | ChatResponse response = chatModel.call(new Prompt(messages)); |
| 42 | 56 | // 打印结果 |
| 43 | 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 | 61 | @Test |
| 48 | 62 | @Disabled |
| 49 | 63 | public void testStream() { |
| 64 | + validateApiKey(API_KEY); | |
| 50 | 65 | // 准备参数 |
| 51 | 66 | List<Message> messages = new ArrayList<>(); |
| 52 | 67 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| ... | ... | @@ -57,19 +72,19 @@ public class ZhiPuAiChatModelTests { |
| 57 | 72 | // 打印结果 |
| 58 | 73 | flux.doOnNext(response -> { |
| 59 | 74 | // System.out.println(response); |
| 60 | - System.out.println(response.getResult().getOutput()); | |
| 75 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 61 | 76 | }).then().block(); |
| 62 | 77 | } |
| 63 | 78 | |
| 64 | - // TODO @芋艿:暂时没解析 reasoning_content 结果,需要等官方修复 | |
| 65 | 79 | @Test |
| 66 | 80 | @Disabled |
| 67 | 81 | public void testStream_thinking() { |
| 82 | + validateApiKey(API_KEY); | |
| 68 | 83 | // 准备参数 |
| 69 | 84 | List<Message> messages = new ArrayList<>(); |
| 70 | 85 | messages.add(new UserMessage("详细分析下,如何设计一个电商系统?")); |
| 71 | - ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder() | |
| 72 | - .model("GLM-4.5") | |
| 86 | + DeepSeekChatOptions options = DeepSeekChatOptions.builder() | |
| 87 | + .model(MODEL) | |
| 73 | 88 | .build(); |
| 74 | 89 | |
| 75 | 90 | // 调用 |
| ... | ... | @@ -77,7 +92,7 @@ public class ZhiPuAiChatModelTests { |
| 77 | 92 | // 打印结果 |
| 78 | 93 | flux.doOnNext(response -> { |
| 79 | 94 | // System.out.println(response); |
| 80 | - System.out.println(response.getResult().getOutput()); | |
| 95 | + System.out.println(Objects.requireNonNull(response.getResult()).getOutput()); | |
| 81 | 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 | 1 | package cn.iocoder.yudao.module.ai.framework.ai.core.model.image; |
| 2 | 2 | |
| 3 | +import cn.hutool.system.SystemUtil; | |
| 3 | 4 | import org.junit.jupiter.api.Disabled; |
| 4 | 5 | import org.junit.jupiter.api.Test; |
| 5 | 6 | import org.springframework.ai.image.ImageOptions; |
| ... | ... | @@ -9,6 +10,8 @@ import org.springframework.ai.openai.OpenAiImageModel; |
| 9 | 10 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 10 | 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 | 16 | * {@link OpenAiImageModel} 集成测试类 |
| 14 | 17 | * |
| ... | ... | @@ -16,17 +19,20 @@ import org.springframework.ai.openai.api.OpenAiImageApi; |
| 16 | 19 | */ |
| 17 | 20 | public class OpenAiImageModelTests { |
| 18 | 21 | |
| 22 | + private static final String API_KEY = SystemUtil.get("OPENAI_API_KEY", "sk-xxxx"); | |
| 23 | + | |
| 19 | 24 | private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() |
| 20 | 25 | .baseUrl("https://api.holdai.top") // apiKey |
| 21 | - .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") | |
| 26 | + .apiKey(API_KEY) | |
| 22 | 27 | .build()); |
| 23 | 28 | |
| 24 | 29 | @Test |
| 25 | 30 | @Disabled |
| 26 | 31 | public void testCall() { |
| 32 | + validateApiKey(API_KEY); | |
| 27 | 33 | // 准备参数 |
| 28 | 34 | ImageOptions options = OpenAiImageOptions.builder() |
| 29 | - .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜 | |
| 35 | + .model("gpt-image-1") | |
| 30 | 36 | .height(256).width(256) |
| 31 | 37 | .build(); |
| 32 | 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 | 164 | client: |
| 165 | 165 | host: 127.0.0.1 |
| 166 | 166 | port: 19530 |
| 167 | - qianfan: # 文心一言 | |
| 168 | - api-key: x0cuLZ7XsaTCU08vuJWO87Lg | |
| 169 | - secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK | |
| 170 | - zhipuai: # 智谱 AI | |
| 171 | - api-key: 32f84543e54eee31f8d56b2bd6020573.3vh9idLJZ2ZhxDEs | |
| 172 | 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 | 170 | azure: # OpenAI 微软 |
| 176 | 171 | openai: |
| 177 | 172 | endpoint: https://eastusprejade.openai.azure.com |
| 178 | 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 | 176 | ollama: |
| 181 | 177 | base-url: http://127.0.0.1:11434 |
| 182 | 178 | chat: |
| 183 | 179 | model: llama3 |
| 184 | 180 | stabilityai: |
| 185 | - api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx | |
| 181 | + api-key: ${STABILITYAI_API_KEY:sk-xxxx} | |
| 186 | 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 | 184 | deepseek: # DeepSeek |
| 193 | - api-key: sk-e94db327cc7d457d99a8de8810fc6b12 | |
| 185 | + api-key: ${DEEPSEEK_API_KEY:sk-xxxx} | |
| 194 | 186 | chat: |
| 195 | 187 | options: |
| 196 | - model: deepseek-chat | |
| 188 | + model: deepseek-v4-flash | |
| 197 | 189 | model: |
| 198 | 190 | rerank: false # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启 |
| 199 | 191 | mcp: |
| ... | ... | @@ -218,34 +210,57 @@ yudao: |
| 218 | 210 | ai: |
| 219 | 211 | gemini: # 谷歌 Gemini |
| 220 | 212 | enable: true |
| 221 | - api-key: AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ | |
| 213 | + api-key: ${GEMINI_API_KEY:sk-xxxx} | |
| 222 | 214 | model: gemini-2.5-flash |
| 223 | 215 | doubao: # 字节豆包 |
| 224 | 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 | 219 | hunyuan: # 腾讯混元 |
| 228 | 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 | 223 | siliconflow: # 硅基流动 |
| 232 | 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 | 227 | xinghuo: # 讯飞星火 |
| 236 | 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 | 231 | baichuan: # 百川智能 |
| 241 | 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 | 259 | midjourney: |
| 245 | 260 | enable: true |
| 246 | 261 | # base-url: https://api.holdai.top/mj-relax/mj |
| 247 | 262 | base-url: https://api.holdai.top/mj |
| 248 | - api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf | |
| 263 | + api-key: ${MIDJOURNEY_API_KEY:sk-xxxx} | |
| 249 | 264 | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify |
| 250 | 265 | suno: |
| 251 | 266 | enable: true |
| ... | ... | @@ -253,7 +268,7 @@ yudao: |
| 253 | 268 | base-url: http://127.0.0.1:3001 |
| 254 | 269 | web-search: |
| 255 | 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 | 366 | message-bus: |
| 352 | 367 | type: redis # 消息总线的类型 |
| 353 | 368 | |
| 354 | -debug: false | |
| 355 | 369 | \ No newline at end of file |
| 370 | +debug: false | ... | ... |