Skip to content

Commit 232c2d5

Browse files
committed
feat: add web search
Signed-off-by: yuluo-yx <yuluo08290126@gmail.com>
1 parent 5946d6c commit 232c2d5

File tree

28 files changed

+1671
-22
lines changed

28 files changed

+1671
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.alibaba.cloud.ai.application.advisor;
2+
3+
import java.util.List;
4+
import java.util.Objects;
5+
6+
import org.springframework.ai.chat.client.advisor.api.AdvisedRequest;
7+
import org.springframework.ai.chat.client.advisor.api.AdvisedResponse;
8+
import org.springframework.ai.chat.client.advisor.api.BaseAdvisor;
9+
import org.springframework.ai.chat.messages.AssistantMessage;
10+
import org.springframework.ai.chat.model.ChatResponse;
11+
import org.springframework.ai.chat.model.Generation;
12+
import org.springframework.util.StringUtils;
13+
14+
/**
15+
* @author yuluo
16+
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
17+
* 将 deepseek-r1 的 reasoning content 整合到输出中
18+
*/
19+
20+
public class ReasonContentAdvisor implements BaseAdvisor {
21+
22+
private final int order;
23+
24+
public ReasonContentAdvisor(Integer order) {
25+
this.order = order != null ? order : 0;
26+
}
27+
28+
@Override
29+
public AdvisedRequest before(AdvisedRequest request) {
30+
31+
return request;
32+
}
33+
34+
@Override
35+
public AdvisedResponse after(AdvisedResponse advisedResponse) {
36+
37+
ChatResponse resp = advisedResponse.response();
38+
if (Objects.isNull(resp)) {
39+
40+
return advisedResponse;
41+
}
42+
43+
String reasoningContent = resp.getMetadata().get("reasoning_content");
44+
if (StringUtils.hasText(reasoningContent)) {
45+
List<Generation> thinkGenerations = resp.getResults().stream()
46+
.map(generation -> {
47+
AssistantMessage output = generation.getOutput();
48+
// 将 think 思维链的内容整合到原始的输出中
49+
AssistantMessage thinkAssistantMessage = new AssistantMessage(
50+
String.format("<think>%s</think>", reasoningContent) + output.getContent(),
51+
output.getMetadata(),
52+
output.getToolCalls(),
53+
output.getMedia()
54+
);
55+
return new Generation(thinkAssistantMessage, generation.getMetadata());
56+
}).toList();
57+
58+
ChatResponse thinkChatResp = ChatResponse.builder().from(resp).generations(thinkGenerations).build();
59+
return AdvisedResponse.from(advisedResponse).response(thinkChatResp).build();
60+
61+
}
62+
63+
return advisedResponse;
64+
}
65+
66+
@Override
67+
public int getOrder() {
68+
69+
return this.order;
70+
}
71+
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.alibaba.cloud.ai.application.config;
2+
3+
import org.springframework.ai.chat.memory.ChatMemory;
4+
import org.springframework.ai.chat.memory.InMemoryChatMemory;
5+
import org.springframework.boot.autoconfigure.AutoConfiguration;
6+
import org.springframework.context.annotation.Bean;
7+
8+
/**
9+
* @author yuluo
10+
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
11+
*/
12+
13+
@AutoConfiguration
14+
public class AppConfig {
15+
16+
@Bean
17+
public ChatMemory chatMemory() {
18+
return new InMemoryChatMemory();
19+
}
20+
21+
}

spring-ai-alibaba-integration-example/backend/src/main/java/com/alibaba/cloud/ai/application/controller/SAAWebSearchController.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.alibaba.cloud.ai.application.controller;
22

3+
import java.util.List;
4+
35
import com.alibaba.cloud.ai.application.entity.result.Result;
46
import com.alibaba.cloud.ai.application.service.SAAWebSearch;
7+
import com.alibaba.cloud.ai.application.utils.ValidText;
58
import io.swagger.v3.oas.annotations.tags.Tag;
69

10+
import org.springframework.ai.document.Document;
711
import org.springframework.web.bind.annotation.GetMapping;
812
import org.springframework.web.bind.annotation.RequestMapping;
913
import org.springframework.web.bind.annotation.RequestParam;
@@ -26,10 +30,14 @@ public SAAWebSearchController(SAAWebSearch webSearch) {
2630
}
2731

2832
@GetMapping("/search")
29-
public Result<Object> search(
30-
@RequestParam(value = "query", required = true) String query
33+
public Result<List<Document>> search(
34+
@RequestParam(value = "query") String query
3135
) {
3236

37+
if (!ValidText.isValidate(query)) {
38+
return Result.failed("Invalid query");
39+
}
40+
3341
return Result.success(webSearch.search(query));
3442
}
3543

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package com.alibaba.cloud.ai.application.service;
22

3+
import java.util.List;
4+
5+
import com.alibaba.cloud.ai.application.websearch.core.IQSSearchEngine;
6+
import com.alibaba.cloud.ai.application.websearch.data.DataClean;
7+
import com.alibaba.cloud.ai.application.websearch.entity.GenericSearchResult;
8+
9+
import org.springframework.ai.document.Document;
310
import org.springframework.stereotype.Service;
411

512
/**
@@ -10,16 +17,25 @@
1017
@Service
1118
public class SAAWebSearch {
1219

13-
// private final GoogleSearchEngine googleSearchEngine;
20+
private final IQSSearchEngine searchEngine;
21+
22+
private final DataClean dataClean;
23+
24+
public SAAWebSearch(IQSSearchEngine searchEngine, DataClean dataClean) {
25+
this.searchEngine = searchEngine;
26+
this.dataClean = dataClean;
27+
}
28+
29+
public List<Document> search(String query) {
1430

15-
// public SAAWebSearch(GoogleSearchEngine googleSearchEngine) {
16-
// this.googleSearchEngine = googleSearchEngine;
17-
// }
31+
// 获取搜索结果
32+
GenericSearchResult search = searchEngine.search(query);
1833

19-
public Object search(String query) {
34+
// 数据清洗
35+
List<Document> data = dataClean.getData(search);
2036

21-
// return googleSearchEngine.search(query);
22-
return null;
37+
// 返回结果
38+
return data;
2339
}
2440

2541
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.alibaba.cloud.ai.application.websearch;
2+
3+
import org.springframework.boot.context.properties.ConfigurationProperties;
4+
5+
/**
6+
* @author yuluo
7+
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
8+
*/
9+
10+
@ConfigurationProperties("spring.iqs.search")
11+
public class IQSSearchProperties {
12+
13+
private String apiKey;
14+
15+
public String getApiKey() {
16+
return this.apiKey;
17+
}
18+
19+
public void setApiKey(String apiKey) {
20+
this.apiKey = apiKey;
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.alibaba.cloud.ai.application.websearch.config;
2+
3+
import com.alibaba.cloud.ai.application.websearch.rag.postretrieval.DashScopeDocumentRanker;
4+
import com.alibaba.cloud.ai.model.RerankModel;
5+
6+
import org.springframework.boot.autoconfigure.AutoConfiguration;
7+
import org.springframework.context.annotation.Bean;
8+
9+
/**
10+
* @author yuluo
11+
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
12+
*/
13+
14+
@AutoConfiguration
15+
public class WeSearchConfiguration {
16+
17+
@Bean
18+
public DashScopeDocumentRanker dashScopeDocumentRanker(
19+
RerankModel rerankModel
20+
) {
21+
return new DashScopeDocumentRanker(rerankModel);
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.alibaba.cloud.ai.application.websearch.core;
2+
3+
import java.util.Objects;
4+
import java.util.function.Consumer;
5+
6+
import com.alibaba.cloud.ai.application.exception.SAAAppException;
7+
import com.alibaba.cloud.ai.application.websearch.IQSSearchProperties;
8+
import com.alibaba.cloud.ai.application.websearch.entity.GenericSearchResult;
9+
10+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
11+
import org.springframework.http.HttpHeaders;
12+
import org.springframework.http.HttpStatus;
13+
import org.springframework.http.MediaType;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.stereotype.Component;
16+
import org.springframework.util.StringUtils;
17+
import org.springframework.web.client.ResponseErrorHandler;
18+
import org.springframework.web.client.RestClient;
19+
20+
/**
21+
* @author yuluo
22+
* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>
23+
*/
24+
25+
@Component
26+
@EnableConfigurationProperties(IQSSearchProperties.class)
27+
public class IQSSearchEngine {
28+
29+
private final IQSSearchProperties iqsSearchProperties;
30+
31+
private final RestClient restClient;
32+
33+
private static final String BASE_URL = "https://cloud-iqs.aliyuncs.com/";
34+
35+
private static final String TIME_RANGE = "OneWeek";
36+
37+
public IQSSearchEngine(
38+
IQSSearchProperties iqsSearchProperties,
39+
RestClient.Builder restClientBuilder,
40+
ResponseErrorHandler responseErrorHandler
41+
) {
42+
43+
this.iqsSearchProperties = iqsSearchProperties;
44+
this.restClient = restClientBuilder.baseUrl(BASE_URL)
45+
.defaultHeaders(getHeaders())
46+
.defaultStatusHandler(responseErrorHandler)
47+
.build();
48+
}
49+
50+
public GenericSearchResult search(String query) {
51+
52+
// String encodeQ = URLEncoder.encode(query, StandardCharsets.UTF_8);
53+
ResponseEntity<GenericSearchResult> resultResponseEntity = run(query);
54+
55+
return genericSearchResult(resultResponseEntity);
56+
}
57+
58+
private ResponseEntity<GenericSearchResult> run(String query) {
59+
60+
return this.restClient.get()
61+
.uri(
62+
"/search/genericAdvancedSearch?query={query}&timeRange={timeRange}",
63+
query,
64+
TIME_RANGE
65+
).retrieve()
66+
.toEntity(GenericSearchResult.class);
67+
}
68+
69+
private GenericSearchResult genericSearchResult(ResponseEntity<GenericSearchResult> response) {
70+
71+
if ((Objects.equals(response.getStatusCode(), HttpStatus.OK)) && Objects.nonNull(response.getBody())) {
72+
return response.getBody();
73+
}
74+
75+
throw new SAAAppException("Failed to search" + response.getStatusCode().value());
76+
}
77+
78+
private Consumer<HttpHeaders> getHeaders() {
79+
80+
return httpHeaders -> {
81+
82+
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
83+
httpHeaders.set("user-agent", userAgent());
84+
85+
if (StringUtils.hasText(this.iqsSearchProperties.getApiKey())) {
86+
httpHeaders.set("X-API-Key", this.iqsSearchProperties.getApiKey());
87+
}
88+
};
89+
}
90+
91+
private static String userAgent() {
92+
93+
return String.format("%s/%s; java/%s; platform/%s; processor/%s", "SpringAiAlibabaPlayground", "1.0.0", System.getProperty("java.version"), System.getProperty("os.name"), System.getProperty("os.arch"));
94+
}
95+
96+
}

0 commit comments

Comments
 (0)