Skip to content

Commit

Permalink
add news function calling
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaohai-78 committed Dec 24, 2024
1 parent 1ab9acc commit 4829ed7
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba</artifactId>
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>

<artifactId>spring-ai-alibaba-starter-function-calling-sinanews</artifactId>
<name>spring-ai-alibaba-starter-function-calling-sinanews</name>
<description>Sina News tool for Spring AI Alibaba</description>
<url>https://github.com/alibaba/spring-ai-alibaba</url>

<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>

<build>
<finalName>spring-ai-alibaba-starter-function-calling-sinanews</finalName>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.alibaba.cloud.ai.functioncalling.sinanews;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

/**
* @Author: XiaoYunTao
* @Date: 2024/12/18
*/
@Configuration
@ConditionalOnClass(SinaNewsService.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.sinanews", name = "enabled", havingValue = "true")
public class SinaNewsAutoConfiguration {

@Bean
@ConditionalOnMissingBean
@Description("Get the news from the Sina news (获取新浪新闻).")
public SinaNewsService getSinaNewsFunction() {
return new SinaNewsService();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.alibaba.cloud.ai.functioncalling.sinanews;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
* @Author: XiaoYunTao
* @Date: 2024/12/18
*/
public class SinaNewsService implements Function<SinaNewsService.Request, SinaNewsService.Response> {

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

private static final String API_URL = "https://newsapp.sina.cn/api/hotlist?newsId=HB-1-snhs%2Ftop_news_list-all";

private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";

private static final String ACCEPT_ENCODING = "gzip, deflate";

private static final String ACCEPT_LANGUAGE = "zh-CN,zh;q=0.9,ja;q=0.8";

private static final String CONTENT_TYPE = "application/json";

private static final WebClient WEB_CLIENT = WebClient.builder()
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, ACCEPT_ENCODING)
.defaultHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)
.defaultHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(5 * 1024 * 1024))
.build();

@Override
public Response apply(Request request) {
JsonNode rootNode = fetchDataFromApi();

List<SinaNewsService.HotEvent> hotEvents = parseHotEvents(rootNode);

logger.info("{} hotEvents: {}", this.getClass().getSimpleName(), hotEvents);
return new Response(hotEvents);
}

protected JsonNode fetchDataFromApi() {
return WEB_CLIENT.get()
.uri(API_URL)
.retrieve()
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
clientResponse -> Mono
.error(new RuntimeException("API call failed with status " + clientResponse.statusCode())))
.bodyToMono(JsonNode.class)
.block();
}

protected List<HotEvent> parseHotEvents(JsonNode rootNode) {
if (rootNode == null || !rootNode.has("data")) {
throw new RuntimeException("Failed to retrieve or parse response data");
}

JsonNode hotList = rootNode.get("data").get("hotList");
List<HotEvent> hotEvents = new ArrayList<>();

for (JsonNode itemNode : hotList) {
if (!itemNode.has("info")) {
continue;
}
String title = itemNode.get("info").get("title").asText();
hotEvents.add(new HotEvent(title));
}

return hotEvents;
}

public record HotEvent(String title) {
}

public record Request() {
}

public record Response(List<SinaNewsService.HotEvent> events) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.alibaba.cloud.ai.functioncalling.sinanews.SinaNewsAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba</artifactId>
<version>${revision}</version>
<relativePath>../../../pom.xml</relativePath>
</parent>

<artifactId>spring-ai-alibaba-starter-function-calling-toutiaonews</artifactId>
<name>spring-ai-alibaba-starter-function-calling-toutiaonews</name>
<description>Toutiao News tool for Spring AI Alibaba</description>
<url>https://github.com/alibaba/spring-ai-alibaba</url>

<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>

<build>
<finalName>spring-ai-alibaba-starter-function-calling-toutiaonews</finalName>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.alibaba.cloud.ai.functioncalling.toutiaonews;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

/**
* @Author: XiaoYunTao
* @Date: 2024/12/18
*/
@Configuration
@ConditionalOnClass(ToutiaoNewsService.class)
@ConditionalOnProperty(prefix = "spring.ai.alibaba.plugin.toutiaonews", name = "enabled", havingValue = "true")
public class TiaotiaoNewsAutoConfiguration {

@Bean
@ConditionalOnMissingBean
@Description("Get the news from the toutiao news (获取今日头条新闻).")
public ToutiaoNewsService getToutiaoNewsFunction() {
return new ToutiaoNewsService();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.alibaba.cloud.ai.functioncalling.toutiaonews;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
* @Author: XiaoYunTao
* @Date: 2024/12/18
*/
public class ToutiaoNewsService implements Function<ToutiaoNewsService.Request, ToutiaoNewsService.Response> {

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

private static final String API_URL = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc";

private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";

private static final String ACCEPT_ENCODING = "gzip, deflate";

private static final String ACCEPT_LANGUAGE = "zh-CN,zh;q=0.9,ja;q=0.8";

private static final String CONTENT_TYPE = "application/json";

private static final WebClient WEB_CLIENT = WebClient.builder()
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, ACCEPT_ENCODING)
.defaultHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)
.defaultHeader(HttpHeaders.ACCEPT_LANGUAGE, ACCEPT_LANGUAGE)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(5 * 1024 * 1024))
.build();

@Override
public ToutiaoNewsService.Response apply(ToutiaoNewsService.Request request) {
JsonNode rootNode = fetchDataFromApi();

List<HotEvent> hotEvents = parseHotEvents(rootNode);

logger.info("{} hotEvents: {}", this.getClass().getSimpleName(), hotEvents);
return new Response(hotEvents);
}

protected JsonNode fetchDataFromApi() {
return WEB_CLIENT.get()
.uri(API_URL)
.retrieve()
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
clientResponse -> Mono
.error(new RuntimeException("API call failed with status " + clientResponse.statusCode())))
.bodyToMono(JsonNode.class)
.block();
}

protected List<HotEvent> parseHotEvents(JsonNode rootNode) {
if (rootNode == null || !rootNode.has("data")) {
throw new RuntimeException("Failed to retrieve or parse response data");
}

JsonNode dataNode = rootNode.get("data");
List<HotEvent> hotEvents = new ArrayList<>();

for (JsonNode itemNode : dataNode) {
if (!itemNode.has("Title")) {
continue;
}
String title = itemNode.get("Title").asText();
hotEvents.add(new HotEvent(title));
}

return hotEvents;
}

public record HotEvent(String title) {
}

public record Request() {
}

public record Response(List<HotEvent> events) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.alibaba.cloud.ai.functioncalling.toutiaonews.TiaotiaoNewsAutoConfiguration
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<module>community/function-calling/spring-ai-alibaba-starter-function-calling-regex</module>
<module>community/function-calling/spring-ai-alibaba-starter-function-calling-jsonprocessor</module>
<module>community/function-calling/spring-ai-alibaba-starter-function-calling-serpapi</module>
<module>community/function-calling/spring-ai-alibaba-starter-function-calling-sinanews</module>
<module>community/function-calling/spring-ai-alibaba-starter-function-calling-toutiaonews</module>

<module>community/document-readers/github-reader</module>
<module>community/document-readers/poi-document-reader</module>
Expand Down

0 comments on commit 4829ed7

Please sign in to comment.