背景

对于 Agent 开发,我们都知道 RAG 可以增强检索,但是本地知识库毕竟知识很有限,只包括我们整理过的内容,对于一些超出范围的问题,比如用户搜索行业动态信息,新闻资讯,股票信息等,本地知识库天然的回答不了,这时候就需要让 Agnet 去联网搜索,主动的去外部世界找答案,而不是直接说不知道,这也刚好填补了本地知识库这一块的空缺。

对于联网搜索,我推荐使用的是 SearXNG ,开源免费,并且集成了市面上几乎所有主流的搜索引擎,还可以自由的配置启用/禁用指定的搜索引擎,返回我们想要的 JSON 格式数据。

基础概念

SearXNG 是一款开源的,可以实现联网搜索的工具,它打通了 baidu,bing,google,360 等几十种搜索引擎,并且会自动的将搜索到的信息合并在一起返回给调用者。

github 地址:https://github.com/searxng/searxng

官方地址:https://docs.searxng.org/admin/installation.html

部署方式

要使用 SearXNG ,就需要将他部署在服务器上,比如使用 docker 来部署

第一步拉镜像

docker pull docker.io/searxng/searxng:latest

拉镜像的时候要注意:如果镜像源地址配置的不合适会超时,阿里的镜像源也不行,需要下面的镜像源,亲测可用。

{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io",
    "https://hub-mirror.c.163.com"
  ]
}

第二步运行镜像为容器

docker run --name searxng -d \
    -p 8888:8080 \
    -v "./config/:/etc/searxng/" \
    -v "./data/:/var/cache/searxng/" \
    docker.io/searxng/searxng:latest

第三步使用服务器的 ip 加 8888 端口就可以直接访问了

注意事项

需要去修改 SearXNG 的 settings.yml 配置文件,禁用掉比如 google 这样的浏览器搜索,因为它很有可能搜索超时。启用 baidu,bing,360 这样的国内搜索引擎。

disabled:true 表示禁用

disabled:false 表示启用

自己根据实际情况进行更改即可,更改完重启 SearXNG 容器才能生效。

Java 整合 SearXNG 实现联网搜索

如果你想让 SearXNG 返回的是 JSON 格式的数据,那么需要去修改 SearXNG 的配置文件 settings.yml 中的formats,默认是返回 html 格式的数据。

修改保存之后需要重启 SearXNG 的容器才会生效

formats:
 - html
 - json
    public static void main(String[] args) {
        String response = HttpUtil.get("http://47.109.158.183:8888/search", Map.of(
                "q", "你的问题"
                , "format", "json"
        ));
    }

RAG ➕ SearXNG 实现双栈检索智能体

RAG 增强检索通过向量库存储企业知识资产,在查询时进行语义检索,优先从知识库获取精准答案。但知识库覆盖范围有限,无法回答所有问题。因此,我们为智能体增加了联网搜索能力,当知识库无法提供答案时,自动切换到全网搜索,获取实时信息,实现与外部世界的实时连接,确保智能体能够回答更广泛的问题。

设计思路

  1. 优先使用 RAG 在本地知识库进行检索
  2. 当 RAG 本地知识库检索不到想要的信息时,再使用 SearXNG 进行联网搜索
  3. 设计了两个 tool,一个是 RAG 搜索的 tool,一个是 SearXNG 联网搜索的 tool,使用 Prompt 让大模型动态的控制工具的调用。

代码实现

  1. SearchByRAGTool 知识库搜索
@Slf4j
@Component
public class SearchByRAGTool implements BiFunction<String, ToolContext,String> {
    /**
     * Redis向量库
     */
    private final RedisVectorStore redisVectorStore;
    public SearchByRAGTool(RedisVectorStore redisVectorStore){
       this.redisVectorStore = redisVectorStore;
    }
    @Override
    public String apply(String s, ToolContext toolContext) {
        log.info("开始检索RAG知识库:{}",s);
        // 根据用户的question 从RAG知识库中检索相关信息
        List<Document> documents = redisVectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(s)
                        .similarityThreshold(0.75)
                        .build()
        );
        String context = "未检索到上下文信息";
        StringBuilder stringBuilder = new StringBuilder();
        for (Document document : documents) {
          stringBuilder.append(document.getText());
        }
        if (!stringBuilder.isEmpty()){
            context = stringBuilder.toString();
        }
        log.info("RAG知识库检索的内容:{}",context);
        return context;
    }

    public static ToolCallback createToolCallback(RedisVectorStore redisVectorStore){
        return FunctionToolCallback.builder("searchByRAGTool",new SearchByRAGTool(redisVectorStore))
                .description("根据用户问题,从RAG知识库中检索相关信息")
                .inputType(String.class)
                .build();
    }
}

  1. SearchBySearXNGTool 联网搜索
@Slf4j
public class SearchBySearXNGTool implements BiFunction<String, ToolContext,String> {
    /**
     * searXNG请求地址
     */
    private final String SEAR_XNG_URL = "http://47.109.158.183:8888/search";
    @Override
    public String apply(String s, ToolContext toolContext) {
       log.info("开始联网搜索");
        String response = HttpUtil.get(SEAR_XNG_URL, Map.of(
                "q", s
                , "format", "json"
        ));
        SearXNGResponse searXNGResponse = JSON.parseObject(response, SearXNGResponse.class);
        log.info("联网搜索的结果:{}",searXNGResponse);
        String context = "未检索到上下文信息";
        StringBuilder stringBuilder = new StringBuilder();
        for (SearchResult result : searXNGResponse.getResults()) {
            stringBuilder.append(result.toString());
        }
        if (!stringBuilder.isEmpty()){
            context = stringBuilder.toString();
        }
        return context;
    }

    public static ToolCallback createToolCallback(){
       return FunctionToolCallback.builder("searchBySearXNGTool",new SearchBySearXNGTool())
               .description("通过SearXNG实现联网搜索信息")
               .inputType(String.class)
               .build();
    }
}
  1. 智能体实现
/**
     * 测试RAG 和 SearXNG搜索
     * 让Agent真正具备与世界对话的能力
     */
@GetMapping("/testRAGAndSearXNGSearch")
public String testRAGAndSearXNGSearch(String question) throws GraphRunnerException {
    DashScopeApi dashScopeApi = DashScopeApi.builder()
        .apiKey("你的key")
        .build();
    // 创建大模型
    ChatModel chatModel = DashScopeChatModel.builder()
        .dashScopeApi(dashScopeApi)
        .build();
    //初始化提示词,基于提示词去动态的选择tool,准确率很难达到100%
    String prompt = """
            你是一名智能助手,需要基于“上下文+用户问题”给出准确有条理的回答。
            【规则】(必须严格遵守)
            1. 你有两个工具可调用
            - searchByRAGtool:从本地知识库检索与问题相关的上下文信息。
            - searchBySearXNGTool:联网搜索与问题相关的上下文信息。
            2. 始终优先调用searchByRAGtool进行信息的检索。
            3. 当searchByRAGtool返回"未检索到上下文信息"或者返回空时,再调用searchBySearXNGTool工具进行联网搜索。
            4. 严禁在searchByRAGtool工具已经返回有效上下文信息时还去调用searchBySearXNGTool进行联网搜索,这将视为回答失败。
        
            上下文:{context}
            问题: {question}
    """;
    // 通过RAG从本地知识库检索信息的tool
    ToolCallback searchByRAGTool = SearchByRAGTool.createToolCallback(redisVectorStore);

    // 通过SearXNG联网检索信息的tool
    ToolCallback searchBySearXNGTool = SearchBySearXNGTool.createToolCallback();

    // 创建ReactAgent
    ReactAgent reactAgent =ReactAgent.builder()
        .name("智能小助手")
        .model(chatModel)
        .systemPrompt(prompt)
        .tools(List.of(searchByRAGTool,searchBySearXNGTool))
        .build();
    // 开始执行
    AssistantMessage call = reactAgent.call(question);
    return call.getText();

}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐