diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README-zh.md b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README-zh.md new file mode 100644 index 0000000000..b9cbde1b29 --- /dev/null +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README-zh.md @@ -0,0 +1,18 @@ +# 百度搜索使用说明 + +## 普通搜索 + +普通搜索可以直接使用 BaiduSearchService.query 或 BaiduSearchService.apply 方法进行搜索 +可以使用 spring.ai.alibaba.toolcalling.baidu.search.maxResults 添加默认的最大条数 + +## 百度千帆智能搜索 + +使用百度千帆智能搜索需要添加如下配置 +spring.ai.alibaba.toolcalling.baidu.search.ai.api-key= +spring.ai.alibaba.toolcalling.baidu.search.ai.enabled=true + +其中 api-key 也可以通过环境变量 BAIDU_API_KEY 进行设置 + +然后使用 BaiduAiSearchService.query 或 BaiduAiSearchService.apply 方法进行搜索 + +参考文档 https://cloud.baidu.com/doc/AppBuilder/s/pmaxd1hvy diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README.md b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README.md new file mode 100644 index 0000000000..dce26211f7 --- /dev/null +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/README.md @@ -0,0 +1,20 @@ +# Baidu Search Usage Instructions + +## Regular Search + +Regular search can be performed directly using the `BaiduSearchService.query` or `BaiduSearchService.apply` methods. +You can use `spring.ai.alibaba.toolcalling.baidu.search.maxResults` to add a default maximum number of results. + +## Baidu Qianfan AI Search + +To use Baidu Qianfan AI Search, add the following configuration: +``` +spring.ai.alibaba.toolcalling.baidu.search.ai.api-key= +spring.ai.alibaba.toolcalling.baidu.search.ai.enabled=true +``` + +The api-key can also be set through the environment variable `BAIDU_API_KEY`. + +Then use the `BaiduAiSearchService.query` or `BaiduAiSearchService.apply` methods to perform searches. + +Reference documentation: https://cloud.baidu.com/doc/AppBuilder/s/pmaxd1hvy diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchProperties.java b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchProperties.java new file mode 100644 index 0000000000..5038d30b68 --- /dev/null +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchProperties.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.toolcalling.baidusearch; + +import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author HunterPorter + */ +@ConfigurationProperties(prefix = BaiduSearchConstants.CONFIG_PREFIX_AI) +public class BaiduAiSearchProperties extends CommonToolCallProperties { + + public BaiduAiSearchProperties() { + super("https://qianfan.baidubce.com"); + this.setPropertiesFromEnv(BaiduSearchConstants.API_KEY_ENV, null, null, null); + } + +} diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchService.java b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchService.java new file mode 100644 index 0000000000..0c316ccb47 --- /dev/null +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchService.java @@ -0,0 +1,213 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.toolcalling.baidusearch; + +import com.alibaba.cloud.ai.toolcalling.common.JsonParseTool; +import com.alibaba.cloud.ai.toolcalling.common.WebClientTool; +import com.alibaba.cloud.ai.toolcalling.common.interfaces.SearchService; +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.function.Function; + +/** + * baidu AI Search + * qianfan This version + * is relatively stable, but requires apiKey configuration, with 100 free queries per day + * + * @author HunterPorter + * @author HunterPorter + */ +public class BaiduAiSearchService + implements SearchService, Function { + + private static final Logger log = LoggerFactory.getLogger(BaiduAiSearchService.class); + + private final WebClientTool webClientTool; + + private final JsonParseTool jsonParseTool; + + private final BaiduAiSearchProperties properties; + + public BaiduAiSearchService(WebClientTool webClientTool, JsonParseTool jsonParseTool, + BaiduAiSearchProperties properties) { + this.webClientTool = webClientTool; + this.jsonParseTool = jsonParseTool; + this.properties = properties; + } + + @Override + public Response query(String query) { + return this.apply(Request.simplyQuery(query)); + } + + @Override + public Response apply(Request request) { + if (!StringUtils.hasText(properties.getApiKey())) { + throw new RuntimeException("Service Api Key is Invalid."); + } + try { + String responseStr = webClientTool.post("/v2/ai_search/chat/completions", request).block(); + log.debug("Response: {}", responseStr); + return jsonParseTool.jsonToObject(responseStr, Response.class); + } + catch (Exception e) { + log.error("Service Baidu AI Request Error: ", e); + throw new RuntimeException(e); + } + } + + @JsonClassDescription("Return real-time search results from web and other data sources based on questions or keywords") + @JsonInclude(JsonInclude.Include.NON_NULL) + public record Request(@JsonProperty(required = true, + value = "messages") @JsonPropertyDescription("Search input, including user query content") List messages, + @JsonProperty(value = "edition", + defaultValue = "standard") @JsonPropertyDescription(""" + Search version. Default is standard. + Optional values: + standard: Full version. + lite: Standard version, a simplified version of the full version with better latency performance, but slightly weaker effect.""") String edition, + @JsonProperty(value = "search_source", + defaultValue = "baidu_search_v2") @JsonPropertyDescription("Search engine version used; fixed value: baidu_search_v2") String searchSource, + @JsonProperty( + value = "resource_type_filter") @JsonPropertyDescription(""" + Support setting web, video, image, and Aladdin search modalities. The maximum value of top_k for web is 50, for video is 10, for image is 30, and for Aladdin is 5. The default value is: + [{"type": "web","top_k": 20},{"type": "video","top_k": 0},{"type": "image","top_k": 0},{"type": "aladdin","top_k": 0}] + Notes when using Aladdin: + 1. Aladdin does not support site and timeliness filtering. + 2. It is recommended to use it with web modality to increase the number of search returns. + 3. Aladdin's return parameters are in beta version and may change in the future.""") List resourceTypeFilter, + @JsonProperty( + value = "search_filter") @JsonPropertyDescription("Filter retrieval based on conditions") SearchFilter searchFilter, + @JsonProperty( + value = "block_websites") @JsonPropertyDescription("List of sites to be blocked") List blockWebsites, + @JsonProperty(value = "search_recency_filter") @JsonPropertyDescription(""" + Filter by web page publication time. + Enumeration values: + week: Last 7 days + month: Last 30 days + semiyear: Last 180 days + year: Last 365 days""") String searchRecencyFilter) implements SearchService.Request { + + @Override + public String getQuery() { + if (messages != null && !messages.isEmpty()) { + Message lastMessage = messages.get(messages.size() - 1); + if ("user".equals(lastMessage.role)) { + return lastMessage.content; + } + } + return null; + } + + public static Request simplyQuery(String query) { + return new Request(List.of(new Message("user", query)), "standard", "baidu_search_v2", + List.of(new SearchResource("web", 20)), null, null, null); + } + } + + public record Message( + @JsonProperty("role") @JsonPropertyDescription("Role setting, optional values: user: user; assistant: model") String role, + @JsonProperty("content") @JsonPropertyDescription(""" + When content is text, it corresponds to the dialog content, that is, the user's query question. Notes: + 1. Cannot be empty. + 2. In multi-round conversations, the last user input content cannot be empty characters, such as spaces, "\\n", "\\r", "\\f", etc.""") String content) { + } + + public record SearchResource(@JsonProperty("type") @JsonPropertyDescription(""" + Search resource type. Optional values: + web: Web page + video: Video + image: Image + aladdin: Aladdin""") String type, + @JsonProperty("top_k") @JsonPropertyDescription("Specify the maximum number of returns for the modality.") Integer topK) { + } + + public record SearchFilter(@JsonProperty("match") @JsonPropertyDescription("Site condition query") Match match, + @JsonProperty("range") @JsonPropertyDescription("Time range query") Range range) { + } + + public record Match( + @JsonProperty("site") @JsonPropertyDescription("Support setting search conditions for specified sites, that is, content search only in the set sites. Currently supports setting 20 sites. Example: [\"tieba.baidu.com\"]") List site) { + } + + public record Range(@JsonProperty("page_time") PageTime pageTime) { + } + + public record PageTime( + @JsonProperty("gte") @JsonPropertyDescription("Time query parameter, greater than or equal to. Supported time units: y (year), M (month), w (week), d (day), for example \"now-1w/d\", one week ago, rounded down") String gte, + @JsonProperty("gt") @JsonPropertyDescription("Time query parameter, greater than. Supported time units: y (year), M (month), w (week), d (day), for example \"now-1w/d\", one week ago, rounded up") String gt, + @JsonProperty("lte") @JsonPropertyDescription("Time query parameter, less than or equal to. Supported time units: y (year), M (month), w (week), d (day), for example \"now-1w/d\", one week ago, rounded up") String lte, + @JsonProperty("lt") @JsonPropertyDescription("Time query parameter, less than. Supported time units: y (year), M (month), w (week), d (day), for example \"now-1w/d\", one week ago, rounded down") String lt) { + } + + @JsonClassDescription("Baidu AI Search Response") + @JsonIgnoreProperties(ignoreUnknown = true) + public record Response(@JsonProperty("requestId") @JsonPropertyDescription("Request ID") String requestId, + @JsonProperty("code") @JsonPropertyDescription("Error code, returned when an exception occurs") String code, + @JsonProperty("message") @JsonPropertyDescription("Error message, returned when an exception occurs") String message, + @JsonProperty("references") @JsonPropertyDescription("Search result list") List references) + implements + SearchService.Response { + + @Override + public SearchResult getSearchResult() { + if (references == null || references.isEmpty()) { + return new SearchResult(List.of()); + } + + return new SearchResult(this.references() + .stream() + .map(item -> new SearchContent(item.title(), item.content(), item.url(), null)) + .toList()); + } + + public record Reference(@JsonProperty("icon") @JsonPropertyDescription("Website icon address") String icon, + @JsonProperty("id") @JsonPropertyDescription("Reference number") Integer id, + @JsonProperty("title") @JsonPropertyDescription("Title") String title, + @JsonProperty("url") @JsonPropertyDescription("URL") String url, + @JsonProperty("web_anchor") @JsonPropertyDescription("Anchor") String webAnchor, + @JsonProperty("website") @JsonPropertyDescription("Website name") String website, + @JsonProperty("content") @JsonPropertyDescription("Content") String content, + @JsonProperty("date") @JsonPropertyDescription("Date") String date, + @JsonProperty("type") @JsonPropertyDescription(""" + Retrieval resource type. Return values: + web: Web page + video: Video content + image: Image + aladdin: Aladdin""") String type, + @JsonProperty("image") @JsonPropertyDescription("Image information") ImageDetail image, + @JsonProperty("video") @JsonPropertyDescription("Video information") VideoDetail video, + @JsonProperty("is_aladdin") @JsonPropertyDescription("Whether it is Aladdin content") Boolean isAladdin, + @JsonProperty("aladdin") @JsonPropertyDescription("Aladdin content") Object aladdin) { + } + + public record ImageDetail(String url, String height, String width) { + } + + public record VideoDetail(String url, String height, String width, String size, String duration, + String hoverPic) { + } + } + +} diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchAutoConfiguration.java b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchAutoConfiguration.java index 818fb277c1..14217e71c5 100644 --- a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchAutoConfiguration.java +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchAutoConfiguration.java @@ -34,7 +34,7 @@ * @author KrakenZJC **/ @Configuration -@EnableConfigurationProperties(BaiduSearchProperties.class) +@EnableConfigurationProperties({ BaiduSearchProperties.class, BaiduAiSearchProperties.class }) @ConditionalOnProperty(prefix = BaiduSearchConstants.CONFIG_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) public class BaiduSearchAutoConfiguration { @@ -54,4 +54,18 @@ public BaiduSearchService baiduSearch(JsonParseTool jsonParseTool, BaiduSearchPr WebClientTool.builder(jsonParseTool, properties).httpHeadersConsumer(consumer).build()); } + @Bean(name = BaiduSearchConstants.TOOL_NAME_AI) + @ConditionalOnMissingBean + @Description("Use baidu ai search engine to query information.") + @ConditionalOnProperty(prefix = BaiduSearchConstants.CONFIG_PREFIX_AI, name = "enabled", havingValue = "true") + public BaiduAiSearchService baiduAiSearch(JsonParseTool jsonParseTool, BaiduAiSearchProperties properties) { + Consumer consumer = headers -> { + headers.add("Content-Type", "application/json"); + headers.add("Authorization", "Bearer " + properties.getApiKey()); + }; + return new BaiduAiSearchService( + WebClientTool.builder(jsonParseTool, properties).httpHeadersConsumer(consumer).build(), jsonParseTool, + properties); + } + } diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchConstants.java b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchConstants.java index 2f35784286..f05ef2ac8e 100644 --- a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchConstants.java +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/main/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduSearchConstants.java @@ -24,6 +24,12 @@ public final class BaiduSearchConstants { public static final String CONFIG_PREFIX = TOOL_CALLING_CONFIG_PREFIX + ".baidu.search"; + public static final String CONFIG_PREFIX_AI = TOOL_CALLING_CONFIG_PREFIX + ".baidu.search.ai"; + public static final String TOOL_NAME = "baiduSearch"; + public static final String TOOL_NAME_AI = "baiduAiSearch"; + + public static final String API_KEY_ENV = "BAIDU_API_KEY"; + } diff --git a/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/test/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchTest.java b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/test/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchTest.java new file mode 100644 index 0000000000..8efa27d094 --- /dev/null +++ b/community/tool-calls/spring-ai-alibaba-starter-tool-calling-baidusearch/src/test/java/com/alibaba/cloud/ai/toolcalling/baidusearch/BaiduAiSearchTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.toolcalling.baidusearch; + +import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallAutoConfiguration; +import com.alibaba.cloud.ai.toolcalling.common.CommonToolCallConstants; +import com.alibaba.cloud.ai.toolcalling.common.interfaces.SearchService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.logging.Logger; + +@SpringBootTest(classes = { CommonToolCallAutoConfiguration.class, BaiduSearchAutoConfiguration.class }, + properties = "spring.ai.alibaba.toolcalling.baidu.search.ai.enabled=true") +@DisplayName("Baidu AI Search Test") +@EnabledIfEnvironmentVariable(named = BaiduSearchConstants.API_KEY_ENV, + matches = CommonToolCallConstants.NOT_BLANK_REGEX) +public class BaiduAiSearchTest { + + @Autowired + private BaiduAiSearchService baiduAiSearchService; + + private static final Logger log = Logger.getLogger(BaiduAiSearchTest.class.getName()); + + @Test + @DisplayName("Tool-Calling Test") + public void testBaiduSearch() { + BaiduAiSearchService.Request request = BaiduAiSearchService.Request.simplyQuery("Spring AI Alibaba"); + var resp = baiduAiSearchService.apply(request); + assert resp != null && resp.references() != null; + log.info("results: " + resp.references()); + } + + @Autowired + @Qualifier("baiduAiSearch") + private SearchService searchService; + + @Test + @DisplayName("Abstract Search Service Test") + public void testAbstractSearch() { + var resp = searchService.query("Spring AI Alibaba"); + assert resp != null && resp.getSearchResult() != null && resp.getSearchResult().results() != null + && !resp.getSearchResult().results().isEmpty(); + log.info("results: " + resp.getSearchResult()); + } + +}