Commit ca34d1650e2e3d9d96370455eb6af3d1c212f48a
1 parent
6f795186
feat:【ai 大模型】联网搜索 AiWebSearchClient 封装
Showing
9 changed files
with
360 additions
and
43 deletions
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
| @@ -13,6 +13,8 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo | @@ -13,6 +13,8 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo | ||
| 13 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; | 13 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; | 14 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; | 15 | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 16 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; | ||
| 17 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; | ||
| 16 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
| 17 | import org.springframework.ai.deepseek.DeepSeekChatModel; | 19 | import org.springframework.ai.deepseek.DeepSeekChatModel; |
| 18 | import org.springframework.ai.deepseek.DeepSeekChatOptions; | 20 | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| @@ -58,11 +60,11 @@ public class AiAutoConfiguration { | @@ -58,11 +60,11 @@ public class AiAutoConfiguration { | ||
| 58 | @Bean | 60 | @Bean |
| 59 | @ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true") | 61 | @ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true") |
| 60 | public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) { | 62 | public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) { |
| 61 | - YudaoAiProperties.GeminiProperties properties = yudaoAiProperties.getGemini(); | 63 | + YudaoAiProperties.Gemini properties = yudaoAiProperties.getGemini(); |
| 62 | return buildGeminiChatClient(properties); | 64 | return buildGeminiChatClient(properties); |
| 63 | } | 65 | } |
| 64 | 66 | ||
| 65 | - public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.GeminiProperties properties) { | 67 | + public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.Gemini properties) { |
| 66 | if (StrUtil.isEmpty(properties.getModel())) { | 68 | if (StrUtil.isEmpty(properties.getModel())) { |
| 67 | properties.setModel(GeminiChatModel.MODEL_DEFAULT); | 69 | properties.setModel(GeminiChatModel.MODEL_DEFAULT); |
| 68 | } | 70 | } |
| @@ -86,11 +88,11 @@ public class AiAutoConfiguration { | @@ -86,11 +88,11 @@ public class AiAutoConfiguration { | ||
| 86 | @Bean | 88 | @Bean |
| 87 | @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true") | 89 | @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true") |
| 88 | public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) { | 90 | public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) { |
| 89 | - YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao(); | 91 | + YudaoAiProperties.DouBao properties = yudaoAiProperties.getDoubao(); |
| 90 | return buildDouBaoChatClient(properties); | 92 | return buildDouBaoChatClient(properties); |
| 91 | } | 93 | } |
| 92 | 94 | ||
| 93 | - public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) { | 95 | + public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBao properties) { |
| 94 | if (StrUtil.isEmpty(properties.getModel())) { | 96 | if (StrUtil.isEmpty(properties.getModel())) { |
| 95 | properties.setModel(DouBaoChatModel.MODEL_DEFAULT); | 97 | properties.setModel(DouBaoChatModel.MODEL_DEFAULT); |
| 96 | } | 98 | } |
| @@ -114,11 +116,11 @@ public class AiAutoConfiguration { | @@ -114,11 +116,11 @@ public class AiAutoConfiguration { | ||
| 114 | @Bean | 116 | @Bean |
| 115 | @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true") | 117 | @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true") |
| 116 | public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) { | 118 | public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) { |
| 117 | - YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow(); | 119 | + YudaoAiProperties.SiliconFlow properties = yudaoAiProperties.getSiliconflow(); |
| 118 | return buildSiliconFlowChatClient(properties); | 120 | return buildSiliconFlowChatClient(properties); |
| 119 | } | 121 | } |
| 120 | 122 | ||
| 121 | - public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) { | 123 | + public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlow properties) { |
| 122 | if (StrUtil.isEmpty(properties.getModel())) { | 124 | if (StrUtil.isEmpty(properties.getModel())) { |
| 123 | properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT); | 125 | properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT); |
| 124 | } | 126 | } |
| @@ -141,11 +143,11 @@ public class AiAutoConfiguration { | @@ -141,11 +143,11 @@ public class AiAutoConfiguration { | ||
| 141 | @Bean | 143 | @Bean |
| 142 | @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true") | 144 | @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true") |
| 143 | public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) { | 145 | public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) { |
| 144 | - YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan(); | 146 | + YudaoAiProperties.HunYuan properties = yudaoAiProperties.getHunyuan(); |
| 145 | return buildHunYuanChatClient(properties); | 147 | return buildHunYuanChatClient(properties); |
| 146 | } | 148 | } |
| 147 | 149 | ||
| 148 | - public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuanProperties properties) { | 150 | + public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuan properties) { |
| 149 | if (StrUtil.isEmpty(properties.getModel())) { | 151 | if (StrUtil.isEmpty(properties.getModel())) { |
| 150 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); | 152 | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); |
| 151 | } | 153 | } |
| @@ -176,11 +178,11 @@ public class AiAutoConfiguration { | @@ -176,11 +178,11 @@ public class AiAutoConfiguration { | ||
| 176 | @Bean | 178 | @Bean |
| 177 | @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") | 179 | @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") |
| 178 | public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { | 180 | public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { |
| 179 | - YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); | 181 | + YudaoAiProperties.XingHuo properties = yudaoAiProperties.getXinghuo(); |
| 180 | return buildXingHuoChatClient(properties); | 182 | return buildXingHuoChatClient(properties); |
| 181 | } | 183 | } |
| 182 | 184 | ||
| 183 | - public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) { | 185 | + public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuo properties) { |
| 184 | if (StrUtil.isEmpty(properties.getModel())) { | 186 | if (StrUtil.isEmpty(properties.getModel())) { |
| 185 | properties.setModel(XingHuoChatModel.MODEL_DEFAULT); | 187 | properties.setModel(XingHuoChatModel.MODEL_DEFAULT); |
| 186 | } | 188 | } |
| @@ -208,11 +210,11 @@ public class AiAutoConfiguration { | @@ -208,11 +210,11 @@ public class AiAutoConfiguration { | ||
| 208 | @Bean | 210 | @Bean |
| 209 | @ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true") | 211 | @ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true") |
| 210 | public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) { | 212 | public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) { |
| 211 | - YudaoAiProperties.BaiChuanProperties properties = yudaoAiProperties.getBaichuan(); | 213 | + YudaoAiProperties.BaiChuan properties = yudaoAiProperties.getBaichuan(); |
| 212 | return buildBaiChuanChatClient(properties); | 214 | return buildBaiChuanChatClient(properties); |
| 213 | } | 215 | } |
| 214 | 216 | ||
| 215 | - public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuanProperties properties) { | 217 | + public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuan properties) { |
| 216 | if (StrUtil.isEmpty(properties.getModel())) { | 218 | if (StrUtil.isEmpty(properties.getModel())) { |
| 217 | properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); | 219 | properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); |
| 218 | } | 220 | } |
| @@ -235,7 +237,7 @@ public class AiAutoConfiguration { | @@ -235,7 +237,7 @@ public class AiAutoConfiguration { | ||
| 235 | @Bean | 237 | @Bean |
| 236 | @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") | 238 | @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") |
| 237 | public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { | 239 | public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { |
| 238 | - YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney(); | 240 | + YudaoAiProperties.Midjourney config = yudaoAiProperties.getMidjourney(); |
| 239 | return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl()); | 241 | return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl()); |
| 240 | } | 242 | } |
| 241 | 243 | ||
| @@ -261,4 +263,12 @@ public class AiAutoConfiguration { | @@ -261,4 +263,12 @@ public class AiAutoConfiguration { | ||
| 261 | return SpringUtil.getBean(ToolCallingManager.class); | 263 | return SpringUtil.getBean(ToolCallingManager.class); |
| 262 | } | 264 | } |
| 263 | 265 | ||
| 266 | + // ========== Web Search 相关 ========== | ||
| 267 | + | ||
| 268 | + @Bean | ||
| 269 | + @ConditionalOnProperty(value = "yudao.ai.web-search.enable", havingValue = "true") | ||
| 270 | + public AiWebSearchClient webSearchClient(YudaoAiProperties yudaoAiProperties) { | ||
| 271 | + return new AiBoChaWebSearchClient(yudaoAiProperties.getWebSearch().getApiKey()); | ||
| 272 | + } | ||
| 273 | + | ||
| 264 | } | 274 | } |
| 265 | \ No newline at end of file | 275 | \ No newline at end of file |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
| @@ -16,51 +16,51 @@ public class YudaoAiProperties { | @@ -16,51 +16,51 @@ public class YudaoAiProperties { | ||
| 16 | /** | 16 | /** |
| 17 | * 谷歌 Gemini | 17 | * 谷歌 Gemini |
| 18 | */ | 18 | */ |
| 19 | - private GeminiProperties gemini; | 19 | + private Gemini gemini; |
| 20 | 20 | ||
| 21 | /** | 21 | /** |
| 22 | * 字节豆包 | 22 | * 字节豆包 |
| 23 | */ | 23 | */ |
| 24 | - @SuppressWarnings("SpellCheckingInspection") | ||
| 25 | - private DouBaoProperties doubao; | 24 | + private DouBao doubao; |
| 26 | 25 | ||
| 27 | /** | 26 | /** |
| 28 | * 腾讯混元 | 27 | * 腾讯混元 |
| 29 | */ | 28 | */ |
| 30 | - @SuppressWarnings("SpellCheckingInspection") | ||
| 31 | - private HunYuanProperties hunyuan; | 29 | + private HunYuan hunyuan; |
| 32 | 30 | ||
| 33 | /** | 31 | /** |
| 34 | * 硅基流动 | 32 | * 硅基流动 |
| 35 | */ | 33 | */ |
| 36 | - @SuppressWarnings("SpellCheckingInspection") | ||
| 37 | - private SiliconFlowProperties siliconflow; | 34 | + private SiliconFlow siliconflow; |
| 38 | 35 | ||
| 39 | /** | 36 | /** |
| 40 | * 讯飞星火 | 37 | * 讯飞星火 |
| 41 | */ | 38 | */ |
| 42 | - @SuppressWarnings("SpellCheckingInspection") | ||
| 43 | - private XingHuoProperties xinghuo; | 39 | + private XingHuo xinghuo; |
| 44 | 40 | ||
| 45 | /** | 41 | /** |
| 46 | * 百川 | 42 | * 百川 |
| 47 | */ | 43 | */ |
| 48 | - @SuppressWarnings("SpellCheckingInspection") | ||
| 49 | - private BaiChuanProperties baichuan; | 44 | + private BaiChuan baichuan; |
| 50 | 45 | ||
| 51 | /** | 46 | /** |
| 52 | * Midjourney 绘图 | 47 | * Midjourney 绘图 |
| 53 | */ | 48 | */ |
| 54 | - private MidjourneyProperties midjourney; | 49 | + private Midjourney midjourney; |
| 55 | 50 | ||
| 56 | /** | 51 | /** |
| 57 | * Suno 音乐 | 52 | * Suno 音乐 |
| 58 | */ | 53 | */ |
| 59 | @SuppressWarnings("SpellCheckingInspection") | 54 | @SuppressWarnings("SpellCheckingInspection") |
| 60 | - private SunoProperties suno; | 55 | + private Suno suno; |
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * 网络搜索 | ||
| 59 | + */ | ||
| 60 | + private WebSearch webSearch; | ||
| 61 | 61 | ||
| 62 | @Data | 62 | @Data |
| 63 | - public static class GeminiProperties { | 63 | + public static class Gemini { |
| 64 | 64 | ||
| 65 | private String enable; | 65 | private String enable; |
| 66 | private String apiKey; | 66 | private String apiKey; |
| @@ -73,7 +73,7 @@ public class YudaoAiProperties { | @@ -73,7 +73,7 @@ public class YudaoAiProperties { | ||
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | @Data | 75 | @Data |
| 76 | - public static class DouBaoProperties { | 76 | + public static class DouBao { |
| 77 | 77 | ||
| 78 | private String enable; | 78 | private String enable; |
| 79 | private String apiKey; | 79 | private String apiKey; |
| @@ -86,7 +86,7 @@ public class YudaoAiProperties { | @@ -86,7 +86,7 @@ public class YudaoAiProperties { | ||
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | @Data | 88 | @Data |
| 89 | - public static class HunYuanProperties { | 89 | + public static class HunYuan { |
| 90 | 90 | ||
| 91 | private String enable; | 91 | private String enable; |
| 92 | private String baseUrl; | 92 | private String baseUrl; |
| @@ -100,7 +100,7 @@ public class YudaoAiProperties { | @@ -100,7 +100,7 @@ public class YudaoAiProperties { | ||
| 100 | } | 100 | } |
| 101 | 101 | ||
| 102 | @Data | 102 | @Data |
| 103 | - public static class SiliconFlowProperties { | 103 | + public static class SiliconFlow { |
| 104 | 104 | ||
| 105 | private String enable; | 105 | private String enable; |
| 106 | private String apiKey; | 106 | private String apiKey; |
| @@ -113,7 +113,7 @@ public class YudaoAiProperties { | @@ -113,7 +113,7 @@ public class YudaoAiProperties { | ||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | @Data | 115 | @Data |
| 116 | - public static class XingHuoProperties { | 116 | + public static class XingHuo { |
| 117 | 117 | ||
| 118 | private String enable; | 118 | private String enable; |
| 119 | private String appId; | 119 | private String appId; |
| @@ -128,7 +128,7 @@ public class YudaoAiProperties { | @@ -128,7 +128,7 @@ public class YudaoAiProperties { | ||
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | @Data | 130 | @Data |
| 131 | - public static class BaiChuanProperties { | 131 | + public static class BaiChuan { |
| 132 | 132 | ||
| 133 | private String enable; | 133 | private String enable; |
| 134 | private String apiKey; | 134 | private String apiKey; |
| @@ -141,7 +141,7 @@ public class YudaoAiProperties { | @@ -141,7 +141,7 @@ public class YudaoAiProperties { | ||
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | @Data | 143 | @Data |
| 144 | - public static class MidjourneyProperties { | 144 | + public static class Midjourney { |
| 145 | 145 | ||
| 146 | private String enable; | 146 | private String enable; |
| 147 | private String baseUrl; | 147 | private String baseUrl; |
| @@ -152,12 +152,21 @@ public class YudaoAiProperties { | @@ -152,12 +152,21 @@ public class YudaoAiProperties { | ||
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | @Data | 154 | @Data |
| 155 | - public static class SunoProperties { | 155 | + public static class Suno { |
| 156 | 156 | ||
| 157 | - private boolean enable = false; | 157 | + private boolean enable; |
| 158 | 158 | ||
| 159 | private String baseUrl; | 159 | private String baseUrl; |
| 160 | 160 | ||
| 161 | } | 161 | } |
| 162 | 162 | ||
| 163 | + @Data | ||
| 164 | + public static class WebSearch { | ||
| 165 | + | ||
| 166 | + private boolean enable; | ||
| 167 | + | ||
| 168 | + private String apiKey; | ||
| 169 | + | ||
| 170 | + } | ||
| 171 | + | ||
| 163 | } | 172 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java
| @@ -272,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -272,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 272 | String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, | 272 | String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, |
| 273 | url); | 273 | url); |
| 274 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { | 274 | return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> { |
| 275 | - YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class) | 275 | + YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class) |
| 276 | .getMidjourney(); | 276 | .getMidjourney(); |
| 277 | return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); | 277 | return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); |
| 278 | }); | 278 | }); |
| @@ -409,7 +409,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -409,7 +409,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 409 | * 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)} | 409 | * 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)} |
| 410 | */ | 410 | */ |
| 411 | private ChatModel buildDouBaoChatModel(String apiKey) { | 411 | private ChatModel buildDouBaoChatModel(String apiKey) { |
| 412 | - YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties() | 412 | + YudaoAiProperties.DouBao properties = new YudaoAiProperties.DouBao() |
| 413 | .setApiKey(apiKey); | 413 | .setApiKey(apiKey); |
| 414 | return new AiAutoConfiguration().buildDouBaoChatClient(properties); | 414 | return new AiAutoConfiguration().buildDouBaoChatClient(properties); |
| 415 | } | 415 | } |
| @@ -418,7 +418,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -418,7 +418,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 418 | * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)} | 418 | * 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)} |
| 419 | */ | 419 | */ |
| 420 | private ChatModel buildHunYuanChatModel(String apiKey, String url) { | 420 | private ChatModel buildHunYuanChatModel(String apiKey, String url) { |
| 421 | - YudaoAiProperties.HunYuanProperties properties = new YudaoAiProperties.HunYuanProperties() | 421 | + YudaoAiProperties.HunYuan properties = new YudaoAiProperties.HunYuan() |
| 422 | .setBaseUrl(url).setApiKey(apiKey); | 422 | .setBaseUrl(url).setApiKey(apiKey); |
| 423 | return new AiAutoConfiguration().buildHunYuanChatClient(properties); | 423 | return new AiAutoConfiguration().buildHunYuanChatClient(properties); |
| 424 | } | 424 | } |
| @@ -427,7 +427,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -427,7 +427,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 427 | * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} | 427 | * 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)} |
| 428 | */ | 428 | */ |
| 429 | private ChatModel buildSiliconFlowChatModel(String apiKey) { | 429 | private ChatModel buildSiliconFlowChatModel(String apiKey) { |
| 430 | - YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties() | 430 | + YudaoAiProperties.SiliconFlow properties = new YudaoAiProperties.SiliconFlow() |
| 431 | .setApiKey(apiKey); | 431 | .setApiKey(apiKey); |
| 432 | return new AiAutoConfiguration().buildSiliconFlowChatClient(properties); | 432 | return new AiAutoConfiguration().buildSiliconFlowChatClient(properties); |
| 433 | } | 433 | } |
| @@ -485,7 +485,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -485,7 +485,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 485 | private static XingHuoChatModel buildXingHuoChatModel(String key) { | 485 | private static XingHuoChatModel buildXingHuoChatModel(String key) { |
| 486 | List<String> keys = StrUtil.split(key, '|'); | 486 | List<String> keys = StrUtil.split(key, '|'); |
| 487 | Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); | 487 | Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式"); |
| 488 | - YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties() | 488 | + YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo() |
| 489 | .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); | 489 | .setAppKey(keys.get(0)).setSecretKey(keys.get(1)); |
| 490 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); | 490 | return new AiAutoConfiguration().buildXingHuoChatClient(properties); |
| 491 | } | 491 | } |
| @@ -494,7 +494,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -494,7 +494,7 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 494 | * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} | 494 | * 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)} |
| 495 | */ | 495 | */ |
| 496 | private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { | 496 | private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) { |
| 497 | - YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties() | 497 | + YudaoAiProperties.BaiChuan properties = new YudaoAiProperties.BaiChuan() |
| 498 | .setApiKey(apiKey); | 498 | .setApiKey(apiKey); |
| 499 | return new AiAutoConfiguration().buildBaiChuanChatClient(properties); | 499 | return new AiAutoConfiguration().buildBaiChuanChatClient(properties); |
| 500 | } | 500 | } |
| @@ -540,10 +540,10 @@ public class AiModelFactoryImpl implements AiModelFactory { | @@ -540,10 +540,10 @@ public class AiModelFactoryImpl implements AiModelFactory { | ||
| 540 | } | 540 | } |
| 541 | 541 | ||
| 542 | /** | 542 | /** |
| 543 | - * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.GeminiProperties)} | 543 | + * 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.Gemini)} |
| 544 | */ | 544 | */ |
| 545 | private static GeminiChatModel buildGeminiChatModel(String apiKey) { | 545 | private static GeminiChatModel buildGeminiChatModel(String apiKey) { |
| 546 | - YudaoAiProperties.GeminiProperties properties = SpringUtil.getBean(YudaoAiProperties.class) | 546 | + YudaoAiProperties.Gemini properties = SpringUtil.getBean(YudaoAiProperties.class) |
| 547 | .getGemini().setApiKey(apiKey); | 547 | .getGemini().setApiKey(apiKey); |
| 548 | return new AiAutoConfiguration().buildGeminiChatClient(properties); | 548 | return new AiAutoConfiguration().buildGeminiChatClient(properties); |
| 549 | } | 549 | } |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.webserch; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 网络搜索客户端接口 | ||
| 5 | + * | ||
| 6 | + * @author 芋道源码 | ||
| 7 | + */ | ||
| 8 | +public interface AiWebSearchClient { | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * 网页搜索 | ||
| 12 | + * | ||
| 13 | + * @param request 搜索请求 | ||
| 14 | + * @return 搜索结果 | ||
| 15 | + */ | ||
| 16 | + AiWebSearchResponse search(AiWebSearchRequest request); | ||
| 17 | + | ||
| 18 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchRequest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.webserch; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.Max; | ||
| 4 | +import jakarta.validation.constraints.Min; | ||
| 5 | +import jakarta.validation.constraints.NotEmpty; | ||
| 6 | +import jakarta.validation.constraints.NotNull; | ||
| 7 | +import lombok.Data; | ||
| 8 | + | ||
| 9 | +@Data | ||
| 10 | +public class AiWebSearchRequest { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * 用户的搜索词 | ||
| 14 | + */ | ||
| 15 | + @NotEmpty(message = "搜索词不能为空") | ||
| 16 | + private String query; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 是否显示文本摘要 | ||
| 20 | + * | ||
| 21 | + * true - 显示 | ||
| 22 | + * false - 不显示(默认) | ||
| 23 | + */ | ||
| 24 | + private Boolean summary; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 返回结果的条数 | ||
| 28 | + */ | ||
| 29 | + @NotNull(message = "返回结果条数不能为空") | ||
| 30 | + @Min(message = "返回结果条数最小为 1", value = 1) | ||
| 31 | + @Max(message = "返回结果条数最大为 50", value = 50) | ||
| 32 | + private Integer count; | ||
| 33 | + | ||
| 34 | +} |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/AiWebSearchResponse.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.webserch; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +@Data | ||
| 8 | +public class AiWebSearchResponse { | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * 总数(总共匹配的网页数) | ||
| 12 | + */ | ||
| 13 | + private Long total; | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 数据列表 | ||
| 17 | + */ | ||
| 18 | + private List<WebPage> lists; | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 网页对象 | ||
| 22 | + */ | ||
| 23 | + @Data | ||
| 24 | + public static class WebPage { | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 名称 | ||
| 28 | + * | ||
| 29 | + * 例如说:搜狐网 | ||
| 30 | + */ | ||
| 31 | + private String name; | ||
| 32 | + /** | ||
| 33 | + * 图标 | ||
| 34 | + */ | ||
| 35 | + private String icon; | ||
| 36 | + | ||
| 37 | + /** | ||
| 38 | + * 标题 | ||
| 39 | + * | ||
| 40 | + * 例如说:186页|阿里巴巴:2024年环境、社会和治理(ESG)报告 | ||
| 41 | + */ | ||
| 42 | + private String title; | ||
| 43 | + /** | ||
| 44 | + * URL | ||
| 45 | + * | ||
| 46 | + * 例如说:https://m.sohu.com/a/815036254_121819701/?pvid=000115_3w_a | ||
| 47 | + */ | ||
| 48 | + @SuppressWarnings("JavadocLinkAsPlainText") | ||
| 49 | + private String url; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 内容的简短描述 | ||
| 53 | + */ | ||
| 54 | + private String snippet; | ||
| 55 | + /** | ||
| 56 | + * 内容的文本摘要 | ||
| 57 | + */ | ||
| 58 | + private String summary; | ||
| 59 | + | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | +} | ||
| 0 | \ No newline at end of file | 63 | \ No newline at end of file |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/webserch/bocha/AiBoChaWebSearchClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 5 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 6 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; | ||
| 7 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest; | ||
| 8 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | ||
| 9 | +import com.fasterxml.jackson.annotation.JsonInclude; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 11 | +import org.springframework.core.ParameterizedTypeReference; | ||
| 12 | +import org.springframework.http.HttpStatusCode; | ||
| 13 | +import org.springframework.http.MediaType; | ||
| 14 | +import org.springframework.web.reactive.function.client.ClientResponse; | ||
| 15 | +import org.springframework.web.reactive.function.client.WebClient; | ||
| 16 | +import reactor.core.publisher.Mono; | ||
| 17 | + | ||
| 18 | +import java.util.List; | ||
| 19 | +import java.util.function.Function; | ||
| 20 | +import java.util.function.Predicate; | ||
| 21 | + | ||
| 22 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + * 博查 {@link AiWebSearchClient} 实现类 | ||
| 26 | + * | ||
| 27 | + * @see <a href="https://open.bochaai.com/overview">博查 AI 开放平台</a> | ||
| 28 | + * | ||
| 29 | + * @author 芋道源码 | ||
| 30 | + */ | ||
| 31 | +@Slf4j | ||
| 32 | +public class AiBoChaWebSearchClient implements AiWebSearchClient { | ||
| 33 | + | ||
| 34 | + public static final String BASE_URL = "https://api.bochaai.com"; | ||
| 35 | + private static final String AUTHORIZATION_HEADER = "Authorization"; | ||
| 36 | + private static final String BEARER_PREFIX = "Bearer "; | ||
| 37 | + | ||
| 38 | + private final WebClient webClient; | ||
| 39 | + | ||
| 40 | + private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful(); | ||
| 41 | + | ||
| 42 | + private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION = | ||
| 43 | + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { | ||
| 44 | + log.error("[AiBoChaWebSearchClient] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody); | ||
| 45 | + sink.error(new IllegalStateException("[AiBoChaWebSearchClient] 调用失败!")); | ||
| 46 | + }); | ||
| 47 | + | ||
| 48 | + public AiBoChaWebSearchClient(String apiKey) { | ||
| 49 | + this.webClient = WebClient.builder() | ||
| 50 | + .baseUrl(BASE_URL) | ||
| 51 | + .defaultHeaders((headers) -> { | ||
| 52 | + headers.setContentType(MediaType.APPLICATION_JSON); | ||
| 53 | + headers.add(AUTHORIZATION_HEADER, BEARER_PREFIX + apiKey); | ||
| 54 | + }) | ||
| 55 | + .build(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + public AiWebSearchResponse search(AiWebSearchRequest request) { | ||
| 60 | + // 转换请求参数 | ||
| 61 | + WebSearchRequest webSearchRequest = new WebSearchRequest( | ||
| 62 | + request.getQuery(), | ||
| 63 | + request.getSummary(), | ||
| 64 | + request.getCount() | ||
| 65 | + ); | ||
| 66 | + // 调用博查 API | ||
| 67 | + CommonResult<WebSearchResponse> response = this.webClient.post() | ||
| 68 | + .uri("/v1/web-search") | ||
| 69 | + .bodyValue(webSearchRequest) | ||
| 70 | + .retrieve() | ||
| 71 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(webSearchRequest)) | ||
| 72 | + .bodyToMono(new ParameterizedTypeReference<CommonResult<WebSearchResponse>>() {}) | ||
| 73 | + .block(); | ||
| 74 | + if (response == null) { | ||
| 75 | + throw new IllegalStateException("[search][搜索结果为空]"); | ||
| 76 | + } | ||
| 77 | + if (response.getData() == null) { | ||
| 78 | + throw new IllegalStateException(String.format("[search][搜索失败,code = %s, msg = %s]", | ||
| 79 | + response.getCode(), response.getMsg())); | ||
| 80 | + } | ||
| 81 | + WebSearchResponse data = response.getData(); | ||
| 82 | + | ||
| 83 | + // 转换结果 | ||
| 84 | + AiWebSearchResponse result = new AiWebSearchResponse(); | ||
| 85 | + if (data.webPages() == null || CollUtil.isEmpty(data.webPages().value())) { | ||
| 86 | + return result.setTotal(0L).setLists(List.of()); | ||
| 87 | + } | ||
| 88 | + return result.setTotal(data.webPages().totalEstimatedMatches()) | ||
| 89 | + .setLists(convertList(data.webPages().value(), page -> new AiWebSearchResponse.WebPage() | ||
| 90 | + .setName(page.siteName()).setIcon(page.siteIcon()) | ||
| 91 | + .setTitle(page.name()).setUrl(page.url()) | ||
| 92 | + .setSnippet(page.snippet()).setSummary(page.summary()))); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * 网页搜索请求参数 | ||
| 97 | + */ | ||
| 98 | + @JsonInclude(value = JsonInclude.Include.NON_NULL) | ||
| 99 | + public record WebSearchRequest( | ||
| 100 | + String query, | ||
| 101 | + Boolean summary, | ||
| 102 | + Integer count | ||
| 103 | + ) { | ||
| 104 | + public WebSearchRequest { | ||
| 105 | + Assert.notBlank(query, "query 不能为空"); | ||
| 106 | + } | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + /** | ||
| 110 | + * 网页搜索响应 | ||
| 111 | + */ | ||
| 112 | + @JsonInclude(value = JsonInclude.Include.NON_NULL) | ||
| 113 | + public record WebSearchResponse( | ||
| 114 | + WebSearchWebPages webPages | ||
| 115 | + ) { | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + /** | ||
| 119 | + * 网页搜索结果 | ||
| 120 | + */ | ||
| 121 | + @JsonInclude(value = JsonInclude.Include.NON_NULL) | ||
| 122 | + public record WebSearchWebPages( | ||
| 123 | + String webSearchUrl, | ||
| 124 | + Long totalEstimatedMatches, | ||
| 125 | + List<WebPageValue> value, | ||
| 126 | + Boolean someResultsRemoved | ||
| 127 | + ) { | ||
| 128 | + | ||
| 129 | + /** | ||
| 130 | + * 网页结果值 | ||
| 131 | + */ | ||
| 132 | + @JsonInclude(value = JsonInclude.Include.NON_NULL) | ||
| 133 | + public record WebPageValue( | ||
| 134 | + String id, | ||
| 135 | + String name, | ||
| 136 | + String url, | ||
| 137 | + String displayUrl, | ||
| 138 | + String snippet, | ||
| 139 | + String summary, | ||
| 140 | + String siteName, | ||
| 141 | + String siteIcon, | ||
| 142 | + String datePublished, | ||
| 143 | + String dateLastCrawled, | ||
| 144 | + String cachedPageUrl, | ||
| 145 | + String language, | ||
| 146 | + Boolean isFamilyFriendly, | ||
| 147 | + Boolean isNavigational | ||
| 148 | + ) { | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | +} |
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/websearch/AiBoChaWebSearchClientTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.framework.ai.core.websearch; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 4 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest; | ||
| 5 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | ||
| 6 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; | ||
| 7 | +import org.junit.jupiter.api.Test; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * {@link AiBoChaWebSearchClient} 集成测试类 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class AiBoChaWebSearchClientTest { | ||
| 15 | + | ||
| 16 | + private final AiBoChaWebSearchClient webSearchClient = new AiBoChaWebSearchClient( | ||
| 17 | + "sk-40500e52840f4d24b956d0b1d80d9abe"); | ||
| 18 | + | ||
| 19 | + @Test | ||
| 20 | + public void testSearch() { | ||
| 21 | + AiWebSearchRequest request = new AiWebSearchRequest() | ||
| 22 | + .setQuery("阿里巴巴") | ||
| 23 | + .setCount(3); | ||
| 24 | + AiWebSearchResponse response = webSearchClient.search(request); | ||
| 25 | + System.out.println(JsonUtils.toJsonPrettyString(response)); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} | ||
| 0 | \ No newline at end of file | 29 | \ No newline at end of file |
yudao-server/src/main/resources/application.yaml
| @@ -234,6 +234,9 @@ yudao: | @@ -234,6 +234,9 @@ yudao: | ||
| 234 | enable: true | 234 | enable: true |
| 235 | # base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app | 235 | # base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app |
| 236 | base-url: http://127.0.0.1:3001 | 236 | base-url: http://127.0.0.1:3001 |
| 237 | + web-search: | ||
| 238 | + enable: true | ||
| 239 | + api-key: sk-40500e52840f4d24b956d0b1d80d9abe | ||
| 237 | 240 | ||
| 238 | --- #################### 芋道相关配置 #################### | 241 | --- #################### 芋道相关配置 #################### |
| 239 | 242 |