Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into feature/dingTalkTool
Browse files Browse the repository at this point in the history
# Conflicts:
#	community/plugins/spring-ai-alibaba-starter-plugin-bingsearch/src/main/java/com/alibaba/cloud/ai/plugin/bing/BingSearchService.java
#	community/plugins/spring-ai-alibaba-starter-plugin-bingsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
#	spring-ai-alibaba-examples/function-calling-example/pom.xml
#	spring-ai-alibaba-examples/function-calling-example/src/main/resources/application.yml
  • Loading branch information
LoongYun committed Nov 26, 2024
2 parents e7233c6 + 1ca8719 commit 7be051c
Show file tree
Hide file tree
Showing 16 changed files with 481 additions and 251 deletions.
5 changes: 3 additions & 2 deletions community/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 插件命名为:`spring-ai-alibaba-starter-plugin-${pluginName}`,例如:`spring-ai-alibaba-starter-plugin-baidusearch`
* 包名前缀命名为:`com.alibaba.cloud.ai.plugin.${pluginName}`,例如:`com.alibaba.cloud.ai.plugin.baidusearch`
* AutoConfiguration 配置类命名为:`${pluginName}AutoConfiguration`,例如:`BaidusearchAutoConfiguration`
* Function bean name 命名为:`${pluginName}Service`,通常是由声明 Bean 注解的方法名确定,如 `baiduSearchService`(建议,请根据插件实际情况确定)
3. 使用 `@Description("xxx")` 注解描述插件的功能,应提供对插件功能清晰明确的描述,例如:`@Description("百度搜索插件,用于查询百度上的新闻事件等信息")`
4. 如插件自身有配置参数,请使用 `@ConfigurationProperties(prefix = "spring.ai.alibabaplugin.${pluginName}")` 注解,例如:
4. 如插件自身有配置参数,请使用 `@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.${pluginName}")` 注解,例如:
```java
@ConfigurationProperties(prefix = "spring.ai.alibabaplugin.baidusearch")
@ConfigurationProperties(prefix = "spring.ai.alibaba.plugin.baidusearch")
public class BaidusearchProperties {}
```
5. 请在根目录 pom.xml 中添加 module 配置,如 `<module>community/plugin/spring-ai-alibaba-starter-plugin-baidusearch</module>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
@Configuration
@ConditionalOnClass(BaiduSearchService.class)
public class BaiduSearchPluginConfiguration {
@Bean
@ConditionalOnMissingBean
@Description("Use baidu search engine to query for the latest news.") // function
// description
public BaiduSearchService baiduSearchService() {
return new BaiduSearchService();
}

@Bean
@ConditionalOnMissingBean
@Description("Use baidu search engine to query for the latest news.") // function
// description
public BaiduSearchService baiduSearchService() {
return new BaiduSearchService();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,142 +44,159 @@
**/

public class BaiduSearchService implements Function<BaiduSearchService.Request, BaiduSearchService.Response> {

private static final Logger logger = LoggerFactory.getLogger(BaiduSearchService.class);

private static final String BAIDU_SEARCH_API_URL = "https://www.baidu.com/s?wd=";

private static final int MAX_RESULTS = 20;

private final WebClient webClient;
private static final int Memory_Size = 5;
private static final int Memory_Unit = 1024;
private static final int Max_Memory_In_MB = Memory_Size * Memory_Unit * Memory_Unit;

public BaiduSearchService() {
this.webClient = WebClient.builder().defaultHeader(HttpHeaders.USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
.defaultHeader(HttpHeaders.ACCEPT,
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.defaultHeader(HttpHeaders.REFERER, "https://www.baidu.com/")
.defaultHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.9,ja;q=0.8")
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(Max_Memory_In_MB)).build();
}

@Override
public BaiduSearchService.Response apply(BaiduSearchService.Request request) {
if (request == null || !StringUtils.hasText(request.query)) {
return null;
}

int limit = request.limit == 0 ? MAX_RESULTS : request.limit;

String url = BAIDU_SEARCH_API_URL + request.query;
try {
Mono<String> responseMono = webClient.get().uri(url).retrieve().bodyToMono(String.class);
String html = responseMono.block();
assert html != null;

List<SearchResult> results = parseHtml(html);
if (CollectionUtils.isEmpty(results)) {
return null;
}

logger.info("baidu search: {},result number:{}", request.query, results.size());
for (SearchResult d : results) {
logger.info(d.title() + "\n" + d.abstractText());
}
return new Response(results.subList(0, Math.min(results.size(), limit)));
} catch (Exception e) {
logger.error("failed to invoke baidu search caused by:{}", e.getMessage());
return null;
}

}

private List<SearchResult> parseHtml(String htmlContent) {
try {
Document doc = Jsoup.parse(htmlContent);
Element contentLeft = doc.selectFirst("div#content_left"); // 选择具有特定 ID 的 div
// 元素
Elements divContents = contentLeft.children();
List<SearchResult> listData = new ArrayList<>();

for (Element div : divContents) {
if (!div.hasClass("c-container")) {
continue;
}
String title = "";
String abstractText = "";

try {
if (div.hasClass("xpath-log") || div.hasClass("result-op")) {
if (div.selectFirst("h3") != null) {
title = div.selectFirst("h3").text().trim();
} else {
title = div.text().trim().split("\n", 2)[0];
}

if (div.selectFirst("div.c-abstract") != null) {
abstractText = div.selectFirst("div.c-abstract").text().trim();
} else if (div.selectFirst("div") != null) {
abstractText = div.selectFirst("div").text().trim();
} else {
abstractText = div.text().trim().split("\n", 2)[1].trim();
}
} else if ("se_com_default".equals(div.attr("tpl"))) {
if (div.selectFirst("h3") != null) {
title = div.selectFirst("h3").text().trim();
} else {
title = div.children().get(0).text().trim();
}

if (div.selectFirst("div.c-abstract") != null) {
abstractText = div.selectFirst("div.c-abstract").text().trim();
} else if (div.selectFirst("div") != null) {
abstractText = div.selectFirst("div").text().trim();
} else {
abstractText = div.text().trim();
}
} else {
continue;
}
} catch (Exception e) {
logger.error("failed to parse baidu search html,caused by:{}", e.getMessage());
continue;
}

listData.add(new SearchResult(title, abstractText));
}

return listData;
} catch (Exception e) {
logger.error("failed to parse baidu search html,caused by:{}", e.getMessage());
return null;
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonClassDescription("Baidu search API request")
public record Request(
@JsonProperty(required = true, value = "query") @JsonPropertyDescription("The query keyword e.g. Alibaba") String query,
@JsonProperty(required = true, value = "limit") @JsonPropertyDescription("The limit count of the number of returned results e.g. 20") int limit) {

}

/**
* Baidu search Function response.
*/
@JsonClassDescription("Baidu search API response")
public record Response(List<SearchResult> results) {

}

public record SearchResult(String title, String abstractText) {

}



private static final Logger logger = LoggerFactory.getLogger(BaiduSearchService.class);

private static final String BAIDU_SEARCH_API_URL = "https://www.baidu.com/s?wd=";

private static final int MAX_RESULTS = 20;

private final WebClient webClient;

private static final int Memory_Size = 5;

private static final int Memory_Unit = 1024;

private static final int Max_Memory_In_MB = Memory_Size * Memory_Unit * Memory_Unit;

public BaiduSearchService() {
this.webClient = WebClient.builder()
.defaultHeader(HttpHeaders.USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
.defaultHeader(HttpHeaders.ACCEPT,
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded")
.defaultHeader(HttpHeaders.REFERER, "https://www.baidu.com/")
.defaultHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.9,ja;q=0.8")
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(Max_Memory_In_MB))
.build();
}

@Override
public BaiduSearchService.Response apply(BaiduSearchService.Request request) {
if (request == null || !StringUtils.hasText(request.query)) {
return null;
}

int limit = request.limit == 0 ? MAX_RESULTS : request.limit;

String url = BAIDU_SEARCH_API_URL + request.query;
try {
Mono<String> responseMono = webClient.get().uri(url).retrieve().bodyToMono(String.class);
String html = responseMono.block();
assert html != null;

List<SearchResult> results = parseHtml(html);
if (CollectionUtils.isEmpty(results)) {
return null;
}

logger.info("baidu search: {},result number:{}", request.query, results.size());
for (SearchResult d : results) {
logger.info(d.title() + "\n" + d.abstractText());
}
return new Response(results.subList(0, Math.min(results.size(), limit)));
}
catch (Exception e) {
logger.error("failed to invoke baidu search caused by:{}", e.getMessage());
return null;
}

}

private List<SearchResult> parseHtml(String htmlContent) {
try {
Document doc = Jsoup.parse(htmlContent);
Element contentLeft = doc.selectFirst("div#content_left"); // 选择具有特定 ID 的 div
// 元素
Elements divContents = contentLeft.children();
List<SearchResult> listData = new ArrayList<>();

for (Element div : divContents) {
if (!div.hasClass("c-container")) {
continue;
}
String title = "";
String abstractText = "";

try {
if (div.hasClass("xpath-log") || div.hasClass("result-op")) {
if (div.selectFirst("h3") != null) {
title = div.selectFirst("h3").text().trim();
}
else {
title = div.text().trim().split("\n", 2)[0];
}

if (div.selectFirst("div.c-abstract") != null) {
abstractText = div.selectFirst("div.c-abstract").text().trim();
}
else if (div.selectFirst("div") != null) {
abstractText = div.selectFirst("div").text().trim();
}
else {
abstractText = div.text().trim().split("\n", 2)[1].trim();
}
}
else if ("se_com_default".equals(div.attr("tpl"))) {
if (div.selectFirst("h3") != null) {
title = div.selectFirst("h3").text().trim();
}
else {
title = div.children().get(0).text().trim();
}

if (div.selectFirst("div.c-abstract") != null) {
abstractText = div.selectFirst("div.c-abstract").text().trim();
}
else if (div.selectFirst("div") != null) {
abstractText = div.selectFirst("div").text().trim();
}
else {
abstractText = div.text().trim();
}
}
else {
continue;
}
}
catch (Exception e) {
logger.error("failed to parse baidu search html,caused by:{}", e.getMessage());
continue;
}

listData.add(new SearchResult(title, abstractText));
}

return listData;
}
catch (Exception e) {
logger.error("failed to parse baidu search html,caused by:{}", e.getMessage());
return null;
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonClassDescription("Baidu search API request")
public record Request(
@JsonProperty(required = true,
value = "query") @JsonPropertyDescription("The query keyword e.g. Alibaba") String query,
@JsonProperty(required = true,
value = "limit") @JsonPropertyDescription("The limit count of the number of returned results e.g. 20") int limit) {

}

/**
* Baidu search Function response.
*/
@JsonClassDescription("Baidu search API response")
public record Response(List<SearchResult> results) {

}

public record SearchResult(String title, String abstractText) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,27 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;

class BaiduSearchServiceTest {

private BaiduSearchService baiduSearchService;

@BeforeEach
public void setUp() {
baiduSearchService = new BaiduSearchService();
}

@Test
public void testSearch() {
BaiduSearchService.Request request = new BaiduSearchService.Request("阿里巴巴上市时间", 10);
BaiduSearchService.Response response = baiduSearchService.apply(request);

assertNotNull(response);
assertFalse(response.results().isEmpty());
assertEquals(10, response.results().size());

for (BaiduSearchService.SearchResult result : response.results()) {
assertNotNull(result.title());
assertNotNull(result.abstractText());
}
}

private BaiduSearchService baiduSearchService;

@BeforeEach
public void setUp() {
baiduSearchService = new BaiduSearchService();
}

@Test
public void testSearch() {
BaiduSearchService.Request request = new BaiduSearchService.Request("阿里巴巴上市时间", 10);
BaiduSearchService.Response response = baiduSearchService.apply(request);

assertNotNull(response);
assertFalse(response.results().isEmpty());
assertEquals(10, response.results().size());

for (BaiduSearchService.SearchResult result : response.results()) {
assertNotNull(result.title());
assertNotNull(result.abstractText());
}
}

}
Loading

0 comments on commit 7be051c

Please sign in to comment.