SearXNG使用之引擎连接超时,响应成功但是空数据

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的配置文件中。

开发步骤:

  1. Docker安装和部署SearXNG

    bash 复制代码
    docker 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
  2. application.yml中配置SearXNG参数

    yaml 复制代码
    websearch:
      searxng:
        # API调用地址
        url: 例:http://127.0.0.1:8888/search
        # 所在主机
        host: 你searxng所在的主机 例:127.0.0.1(localhost)
        # 暴露端口
        port:例:8888
        # 获取的响应条数
        count: 10
  3. pom.xml关键依赖添加

    xml 复制代码
    <!-- http客户端for java -->
    <dependency>
    	<groupId>com.squareup.okhttp3</groupId>
    	<artifactId>okhttp</artifactId>
    	<version>4.12.0</version>
    </dependency>
  4. 用于接收响应结果的实体类

    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;
    }
  5. 搜索服务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();
        }
    }
  6. 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编辑修改后再复制会容器内,至于为啥要这么改,可以查看我上一篇文章

  • 复制配置文件到主机

    ruby 复制代码
    docker cp 容器名或ID:/etc/searxng/settings.yml ./searxng_settings.yml
  • 使用vim编辑文件,找到配置engine那一项,将国内引擎启用,就把百度、夸克和搜狗启用就行

    复制代码
    vim searxng_settings.yml
  • 把disabled哪一项改为false,be like:

  • 配置完成后保存,再将配置文件复制回容器内,最后重启容器就OK了。

    ruby 复制代码
    docker cp ./searxng_settings.yml 容器名或ID:/etc/searxng/settings.yml
    docker restart 容器名或ID

最后启动SpringAi应用,浏览器再次访问http://localhost:8000/api/webSearch/queryTest?query=hello,成功拿到数据!

开发完善:

将联网搜索到的结果与大模型提示词结合,让大模型基于网络搜索结果回答用户。

  1. 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);
        }
  2. 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,代码本身也没有问题,那问题就可能出现在第三方工具身上,可能是网络、配置文件等。

相关推荐
木木一直在哭泣2 小时前
我是怎么用 Redis 把 ERP→CRM 同步提速到“几秒钟”的
后端
零日失眠者2 小时前
【Python好用到哭的库】pandas-数据分析神器
后端·python·ai编程
读书郎霍2 小时前
linux 从docker官网源码安装docker
后端
零日失眠者2 小时前
【Python好用到哭的库】numpy-数值计算基础
后端·python·ai编程
创新技术阁2 小时前
CryptoAiAdmin 项目后端启动过程详解
后端·python·fastapi
何中应2 小时前
【面试题-2】Java集合
java·开发语言·后端·面试题
a程序小傲2 小时前
scala中的Array
开发语言·后端·scala
coderCatIce2 小时前
JDK 动态代理
后端
kk哥88992 小时前
scala 介绍
开发语言·后端·scala