SearXNG使用之引擎连接超时,响应成功但是空数据
前言:
又是在B站大学学习的一天,继续拥抱AI学习Java大模型应用开发,SpringAI集成SearXNG,实现聊天机器人联网搜索功能。
功能本质就是将SearXNG作为Spring AI应用的一个"工具",让其负责获取实时网络信息,再由大模型进行总结和回答。
实现步骤也很简单:
首先就是部署和配置SearXNG,安装并本地运行SearXNG,然后在SpringAI应用中编写代码调用SearXNG的搜索API实现联网搜索,最后将搜索结果与大模型提示词结合,让大模型基于网络搜索结果回答用户。听起来很简单,但是,哥们实现过程中却是bug频出。
在此我想吐槽一下在B站大学学习的一些感受,不光是我,相信在B站学习的小伙伴,基本都有一种体验就是:明明是跟着老师一步步操作的,代码也是一模拓刻,但就是会bug频出,大部分这种情况,弹幕也会有小伙伴给出解决bug的方法,但是bug这个东西过于玄学,明明同样的错误,用相同的解决方法,在别人那里就立竿见影,但在你这里就毫无暖用,笑死。
所以废话到此为止,本篇文章记录的是我使用SearXNG过程中的出现的第二个错误,第一个请移步到我上一篇文章:SearXNG使用之403:Forbidden,这俩错误是挨着来的,不同的错误,但均出现在SearXNG的配置文件中。
开发步骤:
-
Docker安装和部署SearXNG
bashdocker run --name mysearxng -d \ -p 8888:8080 \ -v "./config/:/etc/searxng/" \ -v "./data/:/var/cache/searxng/" \ -e "BASE_URL=http:localhost:$PORT/" \ -e "INSTANTCE_NAME=acefelix_INSTANCE" \ searxng/searxng:latest -
application.yml中配置SearXNG参数
yamlwebsearch: searxng: # API调用地址 url: 例:http://127.0.0.1:8888/search # 所在主机 host: 你searxng所在的主机 例:127.0.0.1(localhost) # 暴露端口 port:例:8888 # 获取的响应条数 count: 10 -
pom.xml关键依赖添加
xml<!-- http客户端for java --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> -
用于接收响应结果的实体类
less/** * 响应内容 * @author aceFelix */ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class WebSearchResult { private String title; private String url; private String content; private Double score; }less/** * SearXNG最终响应结果 * @author aceFleix */ @Data @ToString @AllArgsConstructor @NoArgsConstructor public class SearXNGResult { private String query; private List<WebSearchResult> results; } -
搜索服务SearXngServiceImpl.java
less/** * 联网搜索服务实现 * @author aceFelix */ @Service @RequiredArgsConstructor @Slf4j public class SearXngServiceImpl implements SearXngService { @Value("${websearch.searxng.url}") private String SEARXNG_URL; @Value("${websearch.searxng.count}") private Integer COUNT; private final OkHttpClient okHttpClient; /** * 使用SearXNG实现联网搜索 * @param query * @return */ @Override public List<WebSearchResult> search(String query) { // 构建url HttpUrl url = HttpUrl.get(SEARXNG_URL) .newBuilder() .addQueryParameter("q", query) .addQueryParameter("format", "json") .build(); log.info("搜索的url为: {}", url.url()); // 构建请求 Request request = new Request.Builder() .url(url) .build(); // 发送请求 try (Response response = okHttpClient.newCall(request).execute()){ // 判断请求是否成功 if (!response.isSuccessful()) { log.error("请求失败: {}, {}",response.message(), response.code()); throw new RuntimeException("请求失败:" + response.code()); } log.info("请求成功: {}, {}",response.message(), response.code()); // 获得响应数据 if (response.body() != null){ String responseBody = response.body().string(); log.info("原始响应: {}", JSONUtil.formatJsonStr(responseBody)); // 解析响应数据,构建响应结果类 SearXNGResult searXNGResult = JSONUtil.toBean(responseBody, SearXNGResult.class); // 筛选结果 return searXNGResult .getResults() .subList(0, Math.min(COUNT, searXNGResult.getResults().size())) .parallelStream() .sorted(Comparator.comparingDouble(WebSearchResult::getScore).reversed()) .limit(COUNT) .toList(); } } catch (IOException e) { throw new RuntimeException(e); } return Collections.emptyList(); } } -
Controller中调用测试
less
/**
* @author aceFelix
*/
@RestController
@RequestMapping("api/webSearch")
public class WebSearchController {
@Autowired
private SearXngService searXngService;
/**
* 联网搜索测试
* @param query
* @return
*/
@GetMapping("queryTest")
public List<WebSearchResult> search(String query) {
return searXngService.search(query);
}
}
问题说明:
启动SpringAI应用,浏览器访问接口http://localhost:8080/api/webSearch/queryTest?query=hello,拿到数据为空。

查看idea控制台也没有报错,成功响应200,但是原始响应数据为空,SearXNG 所有的后端搜索引擎均连接超时。


原因分析:
idea控制台没有报错,说明代码本身没有问题,SearXNG 确实是返回了空数据,问题就出在SearXNG 这边,SearXNG 无法连接到上游引擎导致数据为空。
通过访问本地部署的SearXNG 客户端:http://127.0.0.1:8888/,在首选项引擎配置中启用国内引擎,重启应用再次测试。



重启应用后浏览器访问后端接口依然拿不到数据,首选项中的配置没有生效。
直接通过SearXNG API搜索结果,拿到idea控制台日志输出的SearXNG 客户端搜索的url:http://127.0.0.1:8888/search?q=hello&format=json浏览器访问,正常输出响应结果没有问题。

客户端使用上游引擎是没有问题的,客户端的配置对客户端是生效的,那说明就是本地服务端的连接配置有问题。
查看SearXNG 本地服务运行日志。
docker logs 容器名或ID

确实如此,SearXNG 服务端默认连接的上游引擎是:brave、duckduckgo、google、startpage、wikipedia,均连接超时,因为是在国内,所以无法正常访问,需要在settings.yml配置文件中配置启用国内的引擎。
问题解决:
在SearXNG的settings.yml配置文件中启用启用国内的引擎。
将配置文件复制到主机中使用vim编辑修改后再复制会容器内,至于为啥要这么改,可以查看我上一篇文章。
-
复制配置文件到主机
rubydocker cp 容器名或ID:/etc/searxng/settings.yml ./searxng_settings.yml -
使用vim编辑文件,找到配置engine那一项,将国内引擎启用,就把百度、夸克和搜狗启用就行
vim searxng_settings.yml
-
把disabled哪一项改为false,be like:



-
配置完成后保存,再将配置文件复制回容器内,最后重启容器就OK了。
rubydocker cp ./searxng_settings.yml 容器名或ID:/etc/searxng/settings.yml docker restart 容器名或ID
最后启动SpringAi应用,浏览器再次访问http://localhost:8000/api/webSearch/queryTest?query=hello,成功拿到数据!


开发完善:
将联网搜索到的结果与大模型提示词结合,让大模型基于网络搜索结果回答用户。
-
ChatServiceImpl.java中组装提示词后实现与大模型交互
ini// 联网搜索提示词 private static final String SEARCH_PROMPT = """ 将联网搜索的返回结果作为上下文,理解并综合内容后回答问题: 【上下文】 {context} 【问题】 {question} 【输出】 如果没有查到,回复:我去你的,你这问题在网上查不到啊,太冷门了估计。 如果查到,理解并综合内容后回复,你自己可以润色,但核心内容不变,其他无关内容不必提及。 """; /** * 与大模型交互(启用联网搜索) * @param chatEntity */ @Override public void chatWebSearch(ChatEntity chatEntity) { String userId = chatEntity.getUserId(); String question = chatEntity.getMessage(); String botMessageId = chatEntity.getBotMessageId(); // 构建提示词 List<WebSearchResult> search = searXngService.search(question); StringBuffer context = new StringBuffer(); for (WebSearchResult webSearchResult : search) { // context.append(webSearchResult.getTitle()).append("\n").append(webSearchResult.getContent()).append("\n"); context.append( String.format("<context>\n【来源】%s \n【内容摘要】%s \n</context>\n", webSearchResult.getUrl(), webSearchResult.getContent() ) ); } // 组装提示词 Prompt prompt = new Prompt(SEARCH_PROMPT .replace("{context}", context) .replace("{question}", question) ); // 与大模型交互,并实时推送消息给前端 pushMessage(userId, String.valueOf(prompt), botMessageId); } /** * 与大模型交互,将响应内容实时推送给前端 * @param prompt * @param userId * @param botMessageId */ private void pushMessage(String userId, String prompt, String botMessageId) { Flux<String> result = qwen3MaxChatClient.prompt(prompt).stream().content(); List<String> contentList = result.toStream().map(chatResponse -> { String content = chatResponse.toString(); SSEServer.sendMessage(userId, content, SSEMessageType.ADD); log.info("响应内容:{}", content); return content; }).toList(); String fullContent = String.join("", contentList); ChatResponseEntity chatResponseEntity = new ChatResponseEntity(fullContent, botMessageId); SSEServer.sendMessage(userId, JSONUtil.toJsonStr(chatResponseEntity), SSEMessageType.FINISH); } -
Controller中集成调用
less/** * @author aceFelix */ @RestController @RequestMapping("api/webSearch") public class WebSearchController { @Autowired private SearXngService searXngService; /** * 启用联网搜索 * @param chatEntity * @return */ @GetMapping("query") public void webSearch(@RequestBody ChatEntity chatEntity, HttpServletResponse response) { response.setCharacterEncoding("UTF-8"); chatService.chatWebSearch(chatEntity); } } /** * @author aceFelix */ @RestController @RequestMapping("api/chat") public class ChatController { @Autowired private ChatService chatService; /** * 与大模型交互 * @param chatEntity */ @PostMapping public void doChat(@RequestBody ChatEntity chatEntity){ chatService.Chat(chatEntity); } }
启动Spring应用,进行前后端联调,前端我是用qoder写的,省时省力。
未开启联网搜索:

刷新页面启用联网搜索:

AI基于网络搜索的结果回答,没毛病。

问题总结:
这个错误让人懵B的就是,没有错误,断点调试他也是正常响应的,接口完全没毛病,一时间你还真摸不着头脑。日志算是帮上忙了,所以说,平时要养成打日志的习惯啊。其实看到上游引擎连接超时,就应该知道是SearXNG自身的引擎配置有问题,可能是客户端或服务端。总之,遇到这种没有报错的bug,代码本身也没有问题,那问题就可能出现在第三方工具身上,可能是网络、配置文件等。