feat:【ai 大模型】联网搜索 AiWebSearchClient 封装
This commit is contained in:
@@ -13,6 +13,8 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
@@ -58,11 +60,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true")
|
||||
public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.GeminiProperties properties = yudaoAiProperties.getGemini();
|
||||
YudaoAiProperties.Gemini properties = yudaoAiProperties.getGemini();
|
||||
return buildGeminiChatClient(properties);
|
||||
}
|
||||
|
||||
public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.GeminiProperties properties) {
|
||||
public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.Gemini properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(GeminiChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -86,11 +88,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
|
||||
public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
|
||||
YudaoAiProperties.DouBao properties = yudaoAiProperties.getDoubao();
|
||||
return buildDouBaoChatClient(properties);
|
||||
}
|
||||
|
||||
public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBaoProperties properties) {
|
||||
public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBao properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -114,11 +116,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true")
|
||||
public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.SiliconFlowProperties properties = yudaoAiProperties.getSiliconflow();
|
||||
YudaoAiProperties.SiliconFlow properties = yudaoAiProperties.getSiliconflow();
|
||||
return buildSiliconFlowChatClient(properties);
|
||||
}
|
||||
|
||||
public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlowProperties properties) {
|
||||
public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlow properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -141,11 +143,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true")
|
||||
public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.HunYuanProperties properties = yudaoAiProperties.getHunyuan();
|
||||
YudaoAiProperties.HunYuan properties = yudaoAiProperties.getHunyuan();
|
||||
return buildHunYuanChatClient(properties);
|
||||
}
|
||||
|
||||
public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuanProperties properties) {
|
||||
public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuan properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -176,11 +178,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
|
||||
public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
|
||||
YudaoAiProperties.XingHuo properties = yudaoAiProperties.getXinghuo();
|
||||
return buildXingHuoChatClient(properties);
|
||||
}
|
||||
|
||||
public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuoProperties properties) {
|
||||
public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuo properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -208,11 +210,11 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true")
|
||||
public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.BaiChuanProperties properties = yudaoAiProperties.getBaichuan();
|
||||
YudaoAiProperties.BaiChuan properties = yudaoAiProperties.getBaichuan();
|
||||
return buildBaiChuanChatClient(properties);
|
||||
}
|
||||
|
||||
public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuanProperties properties) {
|
||||
public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuan properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
@@ -235,7 +237,7 @@ public class AiAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
|
||||
public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney();
|
||||
YudaoAiProperties.Midjourney config = yudaoAiProperties.getMidjourney();
|
||||
return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl());
|
||||
}
|
||||
|
||||
@@ -261,4 +263,12 @@ public class AiAutoConfiguration {
|
||||
return SpringUtil.getBean(ToolCallingManager.class);
|
||||
}
|
||||
|
||||
// ========== Web Search 相关 ==========
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.web-search.enable", havingValue = "true")
|
||||
public AiWebSearchClient webSearchClient(YudaoAiProperties yudaoAiProperties) {
|
||||
return new AiBoChaWebSearchClient(yudaoAiProperties.getWebSearch().getApiKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,51 +16,51 @@ public class YudaoAiProperties {
|
||||
/**
|
||||
* 谷歌 Gemini
|
||||
*/
|
||||
private GeminiProperties gemini;
|
||||
private Gemini gemini;
|
||||
|
||||
/**
|
||||
* 字节豆包
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private DouBaoProperties doubao;
|
||||
private DouBao doubao;
|
||||
|
||||
/**
|
||||
* 腾讯混元
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private HunYuanProperties hunyuan;
|
||||
private HunYuan hunyuan;
|
||||
|
||||
/**
|
||||
* 硅基流动
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private SiliconFlowProperties siliconflow;
|
||||
private SiliconFlow siliconflow;
|
||||
|
||||
/**
|
||||
* 讯飞星火
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private XingHuoProperties xinghuo;
|
||||
private XingHuo xinghuo;
|
||||
|
||||
/**
|
||||
* 百川
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private BaiChuanProperties baichuan;
|
||||
private BaiChuan baichuan;
|
||||
|
||||
/**
|
||||
* Midjourney 绘图
|
||||
*/
|
||||
private MidjourneyProperties midjourney;
|
||||
private Midjourney midjourney;
|
||||
|
||||
/**
|
||||
* Suno 音乐
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private SunoProperties suno;
|
||||
private Suno suno;
|
||||
|
||||
/**
|
||||
* 网络搜索
|
||||
*/
|
||||
private WebSearch webSearch;
|
||||
|
||||
@Data
|
||||
public static class GeminiProperties {
|
||||
public static class Gemini {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
@@ -73,7 +73,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DouBaoProperties {
|
||||
public static class DouBao {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
@@ -86,7 +86,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class HunYuanProperties {
|
||||
public static class HunYuan {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
@@ -100,7 +100,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SiliconFlowProperties {
|
||||
public static class SiliconFlow {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
@@ -113,7 +113,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class XingHuoProperties {
|
||||
public static class XingHuo {
|
||||
|
||||
private String enable;
|
||||
private String appId;
|
||||
@@ -128,7 +128,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BaiChuanProperties {
|
||||
public static class BaiChuan {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
@@ -141,7 +141,7 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MidjourneyProperties {
|
||||
public static class Midjourney {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
@@ -152,12 +152,21 @@ public class YudaoAiProperties {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SunoProperties {
|
||||
public static class Suno {
|
||||
|
||||
private boolean enable = false;
|
||||
private boolean enable;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WebSearch {
|
||||
|
||||
private boolean enable;
|
||||
|
||||
private String apiKey;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,
|
||||
url);
|
||||
return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> {
|
||||
YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
.getMidjourney();
|
||||
return new MidjourneyApi(url, apiKey, properties.getNotifyUrl());
|
||||
});
|
||||
@@ -409,7 +409,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link AiAutoConfiguration#douBaoChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private ChatModel buildDouBaoChatModel(String apiKey) {
|
||||
YudaoAiProperties.DouBaoProperties properties = new YudaoAiProperties.DouBaoProperties()
|
||||
YudaoAiProperties.DouBao properties = new YudaoAiProperties.DouBao()
|
||||
.setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildDouBaoChatClient(properties);
|
||||
}
|
||||
@@ -418,7 +418,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link AiAutoConfiguration#hunYuanChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private ChatModel buildHunYuanChatModel(String apiKey, String url) {
|
||||
YudaoAiProperties.HunYuanProperties properties = new YudaoAiProperties.HunYuanProperties()
|
||||
YudaoAiProperties.HunYuan properties = new YudaoAiProperties.HunYuan()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildHunYuanChatClient(properties);
|
||||
}
|
||||
@@ -427,7 +427,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link AiAutoConfiguration#siliconFlowChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private ChatModel buildSiliconFlowChatModel(String apiKey) {
|
||||
YudaoAiProperties.SiliconFlowProperties properties = new YudaoAiProperties.SiliconFlowProperties()
|
||||
YudaoAiProperties.SiliconFlow properties = new YudaoAiProperties.SiliconFlow()
|
||||
.setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildSiliconFlowChatClient(properties);
|
||||
}
|
||||
@@ -485,7 +485,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
private static XingHuoChatModel buildXingHuoChatModel(String key) {
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
YudaoAiProperties.XingHuoProperties properties = new YudaoAiProperties.XingHuoProperties()
|
||||
YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo()
|
||||
.setAppKey(keys.get(0)).setSecretKey(keys.get(1));
|
||||
return new AiAutoConfiguration().buildXingHuoChatClient(properties);
|
||||
}
|
||||
@@ -494,7 +494,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link AiAutoConfiguration#baiChuanChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private BaiChuanChatModel buildBaiChuanChatModel(String apiKey) {
|
||||
YudaoAiProperties.BaiChuanProperties properties = new YudaoAiProperties.BaiChuanProperties()
|
||||
YudaoAiProperties.BaiChuan properties = new YudaoAiProperties.BaiChuan()
|
||||
.setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildBaiChuanChatClient(properties);
|
||||
}
|
||||
@@ -540,10 +540,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.GeminiProperties)}
|
||||
* 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.Gemini)}
|
||||
*/
|
||||
private static GeminiChatModel buildGeminiChatModel(String apiKey) {
|
||||
YudaoAiProperties.GeminiProperties properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
YudaoAiProperties.Gemini properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
.getGemini().setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildGeminiChatClient(properties);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
/**
|
||||
* 网络搜索客户端接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface AiWebSearchClient {
|
||||
|
||||
/**
|
||||
* 网页搜索
|
||||
*
|
||||
* @param request 搜索请求
|
||||
* @return 搜索结果
|
||||
*/
|
||||
AiWebSearchResponse search(AiWebSearchRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AiWebSearchRequest {
|
||||
|
||||
/**
|
||||
* 用户的搜索词
|
||||
*/
|
||||
@NotEmpty(message = "搜索词不能为空")
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 是否显示文本摘要
|
||||
*
|
||||
* true - 显示
|
||||
* false - 不显示(默认)
|
||||
*/
|
||||
private Boolean summary;
|
||||
|
||||
/**
|
||||
* 返回结果的条数
|
||||
*/
|
||||
@NotNull(message = "返回结果条数不能为空")
|
||||
@Min(message = "返回结果条数最小为 1", value = 1)
|
||||
@Max(message = "返回结果条数最大为 50", value = 50)
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class AiWebSearchResponse {
|
||||
|
||||
/**
|
||||
* 总数(总共匹配的网页数)
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<WebPage> lists;
|
||||
|
||||
/**
|
||||
* 网页对象
|
||||
*/
|
||||
@Data
|
||||
public static class WebPage {
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*
|
||||
* 例如说:搜狐网
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*
|
||||
* 例如说:186页|阿里巴巴:2024年环境、社会和治理(ESG)报告
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* URL
|
||||
*
|
||||
* 例如说:https://m.sohu.com/a/815036254_121819701/?pvid=000115_3w_a
|
||||
*/
|
||||
@SuppressWarnings("JavadocLinkAsPlainText")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 内容的简短描述
|
||||
*/
|
||||
private String snippet;
|
||||
/**
|
||||
* 内容的文本摘要
|
||||
*/
|
||||
private String summary;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* 博查 {@link AiWebSearchClient} 实现类
|
||||
*
|
||||
* @see <a href="https://open.bochaai.com/overview">博查 AI 开放平台</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AiBoChaWebSearchClient implements AiWebSearchClient {
|
||||
|
||||
public static final String BASE_URL = "https://api.bochaai.com";
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String BEARER_PREFIX = "Bearer ";
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful();
|
||||
|
||||
private final Function<Object, Function<ClientResponse, Mono<? extends Throwable>>> EXCEPTION_FUNCTION =
|
||||
reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> {
|
||||
log.error("[AiBoChaWebSearchClient] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody);
|
||||
sink.error(new IllegalStateException("[AiBoChaWebSearchClient] 调用失败!"));
|
||||
});
|
||||
|
||||
public AiBoChaWebSearchClient(String apiKey) {
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.defaultHeaders((headers) -> {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add(AUTHORIZATION_HEADER, BEARER_PREFIX + apiKey);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiWebSearchResponse search(AiWebSearchRequest request) {
|
||||
// 转换请求参数
|
||||
WebSearchRequest webSearchRequest = new WebSearchRequest(
|
||||
request.getQuery(),
|
||||
request.getSummary(),
|
||||
request.getCount()
|
||||
);
|
||||
// 调用博查 API
|
||||
CommonResult<WebSearchResponse> response = this.webClient.post()
|
||||
.uri("/v1/web-search")
|
||||
.bodyValue(webSearchRequest)
|
||||
.retrieve()
|
||||
.onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(webSearchRequest))
|
||||
.bodyToMono(new ParameterizedTypeReference<CommonResult<WebSearchResponse>>() {})
|
||||
.block();
|
||||
if (response == null) {
|
||||
throw new IllegalStateException("[search][搜索结果为空]");
|
||||
}
|
||||
if (response.getData() == null) {
|
||||
throw new IllegalStateException(String.format("[search][搜索失败,code = %s, msg = %s]",
|
||||
response.getCode(), response.getMsg()));
|
||||
}
|
||||
WebSearchResponse data = response.getData();
|
||||
|
||||
// 转换结果
|
||||
AiWebSearchResponse result = new AiWebSearchResponse();
|
||||
if (data.webPages() == null || CollUtil.isEmpty(data.webPages().value())) {
|
||||
return result.setTotal(0L).setLists(List.of());
|
||||
}
|
||||
return result.setTotal(data.webPages().totalEstimatedMatches())
|
||||
.setLists(convertList(data.webPages().value(), page -> new AiWebSearchResponse.WebPage()
|
||||
.setName(page.siteName()).setIcon(page.siteIcon())
|
||||
.setTitle(page.name()).setUrl(page.url())
|
||||
.setSnippet(page.snippet()).setSummary(page.summary())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索请求参数
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchRequest(
|
||||
String query,
|
||||
Boolean summary,
|
||||
Integer count
|
||||
) {
|
||||
public WebSearchRequest {
|
||||
Assert.notBlank(query, "query 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索响应
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchResponse(
|
||||
WebSearchWebPages webPages
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页搜索结果
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebSearchWebPages(
|
||||
String webSearchUrl,
|
||||
Long totalEstimatedMatches,
|
||||
List<WebPageValue> value,
|
||||
Boolean someResultsRemoved
|
||||
) {
|
||||
|
||||
/**
|
||||
* 网页结果值
|
||||
*/
|
||||
@JsonInclude(value = JsonInclude.Include.NON_NULL)
|
||||
public record WebPageValue(
|
||||
String id,
|
||||
String name,
|
||||
String url,
|
||||
String displayUrl,
|
||||
String snippet,
|
||||
String summary,
|
||||
String siteName,
|
||||
String siteIcon,
|
||||
String datePublished,
|
||||
String dateLastCrawled,
|
||||
String cachedPageUrl,
|
||||
String language,
|
||||
Boolean isFamilyFriendly,
|
||||
Boolean isNavigational
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.websearch;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* {@link AiBoChaWebSearchClient} 集成测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AiBoChaWebSearchClientTest {
|
||||
|
||||
private final AiBoChaWebSearchClient webSearchClient = new AiBoChaWebSearchClient(
|
||||
"sk-40500e52840f4d24b956d0b1d80d9abe");
|
||||
|
||||
@Test
|
||||
public void testSearch() {
|
||||
AiWebSearchRequest request = new AiWebSearchRequest()
|
||||
.setQuery("阿里巴巴")
|
||||
.setCount(3);
|
||||
AiWebSearchResponse response = webSearchClient.search(request);
|
||||
System.out.println(JsonUtils.toJsonPrettyString(response));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -234,6 +234,9 @@ yudao:
|
||||
enable: true
|
||||
# base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app
|
||||
base-url: http://127.0.0.1:3001
|
||||
web-search:
|
||||
enable: true
|
||||
api-key: sk-40500e52840f4d24b956d0b1d80d9abe
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
||||
|
||||
Reference in New Issue
Block a user