大模型在很多领域都展现了强大的能力,但知识更新滞后是一个常见的问题。例如,当询问基于 Ollama 本地运行的 Mistral 模型 “谁赢得了 2024 年的 IPL 联赛?”时,它可能会给出过时的答案:“钦奈超级国王队赢得了 2018 年的 IPL 联赛。”本文将探讨如何使用 RAG (检索增强生成) 技术,结合 网络爬虫 从实时网络数据中提取信息,纠正大模型中的错误信息,使其能够准确回答关于 IPL 联赛的问题。

RAG 技术:弥合大模型知识与现实世界的鸿沟

RAG (检索增强生成) 是一种结合了信息检索和文本生成的技术。它的核心思想是:首先从外部数据源检索与用户查询相关的文档,然后将检索到的文档作为上下文信息,输入到大模型中,以生成更准确、更全面的答案。

RAG 技术包含两个主要步骤:

  1. 检索 (Retrieval):从外部数据源 (如网站、数据库、文档库等) 检索与用户查询相关的文档。检索方法有很多种,包括基于关键词的检索、基于语义的检索等。
  2. 增强生成 (Augmented Generation):将检索到的文档作为上下文信息,与用户查询一起输入到大模型中。大模型根据上下文信息生成答案。

RAG 技术可以有效地解决大模型的知识更新滞后问题,并提高答案的准确性和可靠性。通过实时检索最新的信息,RAG 可以确保大模型始终掌握最新的知识,从而更好地服务于用户。

构建实时 IPL 冠军查询系统:RAG 的实际应用

本文提供的案例展示了如何构建一个能够实时查询 IPL 冠军信息的系统。该系统利用 RAG 技术,结合 Spring BootSpring AIJSoupOllama,实现了从网页抓取 IPL 冠军列表、存储信息,并根据用户查询生成答案的功能。

设想构建一个系统,它能:

  • 抓取包含所有 IPL 冠军和亚军年份列表的实时网页
  • 将信息以语义方式存储在向量存储中
  • 回答诸如:“谁赢得了 2023 年的 IPL 联赛?” 或 “哪支队伍在 2016 年获得亚军?” 之类的用户查询

该系统采用了以下技术栈:

  • Spring Boot: 用于构建 REST API 和后端服务。
  • Spring AI: 简化了与大模型交互的过程,提供了诸如 ChatClientVectorStore 等组件。
  • JSoup: 用于从网页抓取 IPL 冠军和亚军数据。
  • 内存向量存储 (SimpleVectorStore): 用于存储抓取的数据,并进行快速语义搜索。
  • Ollama: 用于本地运行 Mistral 大模型。

系统设计:分步详解

该系统的核心分为两个步骤:

步骤 1:使用 JSoup 进行网页抓取

WebScrapperReader 类是一个 Spring @Component,负责从网页上抓取 IPL 冠军数据,将其分割成有意义的块,并在应用程序启动时将其存储到向量数据库 (内存中的 VectorStore) 中。

具体实现如下:

  1. 使用 JSoup 连接到 Jagran Josh IPL 页面并解析 HTML 内容。

  2. 选择第一个 HTML 表格,其中包含按年份排列的 IPL 冠军和亚军数据。

  3. 遍历表格行以提取:

    • 标题(例如,年份、冠军、亚军)
    • 每个 IPL 赛季的数据行
  4. 每一行被转换为人类可读的文本行,如下所示:

    2023 | Chennai Super Kings | Gujarat Titans | ...
    

    代码片段如下:

    package com.example.RAG;
    
    import jakarta.annotation.PostConstruct;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    import org.springframework.ai.document.Document;
    import org.springframework.ai.transformer.splitter.TokenTextSplitter;
    import org.springframework.ai.vectorstore.SimpleVectorStore;
    import org.springframework.ai.vectorstore.VectorStore;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import java.io.File;
    import java.io.IOException;
    import java.util.List;
    
    @Component
    public class WebScrapperReader {
    @Autowired
    private VectorStore vectorStore;
    
    @PostConstruct
    public void init() {
        try {
            String scrapedContext = fetchIPLWinnerList();
            System.out.println("Web scraping done at startup.");
            // Split and store scraped data into the VectorStore
            var textSplitter = new TokenTextSplitter();
            List<Document> documents = textSplitter.apply(List.of(new Document(scrapedContext)));
            vectorStore.accept(documents);
            ((SimpleVectorStore) vectorStore).save(new File("webscraper_vectorstore.json"));
        } catch (IOException e) {
            System.err.println("Failed to fetch IPL winner data: " + e.getMessage());
        }
    }
    
    private String fetchIPLWinnerList() throws IOException {
        org.jsoup.nodes.Document doc =
                Jsoup.connect("https://www.jagranjosh.com/general-knowledge/list-of-all-ipl-winner-teams-1527686257-1")
                        .get();
        // Select the first table on the page
        Element table = doc.select("table").first();
        StringBuilder builder = new StringBuilder();
        if (table != null) {
            Elements rows = table.select("tr");
            for (Element row : rows) {
                // Handle header or data rows
                Elements headers = row.select("th");
                if (!headers.isEmpty()) {
                    for (Element header : headers) {
                        builder.append(header.text()).append(" | ");
                    }
                } else {
                    Elements cols = row.select("td");
                    for (Element col : cols) {
                        builder.append(col.text()).append(" | ");
                    }
                }
                builder.append("\n");
            }
        } else {
            builder.append("No table found on the page.");
        }
        System.out.println(builder.toString());
        return "The winner of IPL year wise and runner up year wise " + builder;
    }
    

    }

    该类使用 @PostConstruct 注解,确保在应用程序启动时自动执行网页抓取和数据存储操作。 抓取的数据首先被分割成更小的块,然后存储在 VectorStore 中,以便进行高效的语义搜索。

步骤 2:使用 Spring Boot 公开 RAG API

WebScrapeRAGController 类是一个 Spring @RestController,它公开了一个 REST API 端点,允许用户查询与 IPL 相关的事实。它使用语义搜索和 ChatClient 来生成由 LLM 驱动的答案,并使用真实世界数据进行增强。

具体实现如下:

  1. 接收用户查询作为参数。
  2. 使用 VectorStoresimilaritySearch 方法检索相关的上下文信息。
  3. 将检索到的上下文信息构建成提示 (prompt),添加到用户查询中。
  4. 调用 ChatClientprompt() 方法,将构建的提示发送给 LLM
  5. 返回 LLM 生成的答案。

代码片段如下:

package com.example.RAG;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
public class WebScrapeRAGController {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public WebScrapeRAGController(ChatClient.Builder builder, VectorStore vectorStore) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }

    @GetMapping("/webscrape-rag")
    public String askWithWebData(@RequestParam("query") String query) {
        // Retrieve relevant context from the VectorStore
        List<Document> relevantContexts = vectorStore.similaritySearch(query);
        assert relevantContexts != null;
        List<String> list = relevantContexts.stream().map(Document::toString).toList();
        // Construct the prompt with the retrieved context
        StringBuilder promptBuilder = new StringBuilder("Use the following context to answer the query:\n\n");
        for (String context : list) {
            promptBuilder.append(context).append("\n");
        }
        promptBuilder.append("\nQuery: ").append(query);
        // Call the ChatClient with the constructed prompt
        return chatClient.prompt()
                .user(promptBuilder.toString())
                .call()
                .content();
    }
}

该类接收用户查询,从 VectorStore 中检索相关上下文,并将上下文和查询组合成一个提示,然后将其发送到 LLMLLM 根据提供的上下文生成答案,从而确保答案的准确性和相关性。

Maven 依赖:pom.xml 配置

以下是项目的 pom.xml 文件,其中包含了所有必要的依赖项:

<?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>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.5.0</version>
       <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>claude</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>claude</name>
    <description>Demo project for Spring Boot</description>
    <url/>
    <licenses>
       <license/>
    </licenses>
    <developers>
       <developer/>
    </developers>
    <scm>
       <connection/>
       <developerConnection/>
       <tag/>
       <url/>
    </scm>
    <properties>
       <java.version>17</java.version>
       <spring-ai.version>1.0.0-M6</spring-ai.version>
    </properties>
    <dependencies>
       <dependency>
          <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
          <version>1.0.0-M6</version>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
       </dependency>
       <dependency>
          <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-pdf-document-reader</artifactId>
       </dependency>
       <dependency>
          <groupId>org.jsoup</groupId>
          <artifactId>jsoup</artifactId>
          <version>1.17.2</version>
       </dependency>
       <dependency>
          <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-core</artifactId>
          <version>1.0.0-M6</version>
       </dependency>
       <!-- Web starter -->
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
       </dependency>
    </dependencies>
    <dependencyManagement>
       <dependencies>
          <dependency>
             <groupId>org.springframework.ai</groupId>
             <artifactId>spring-ai-bom</artifactId>
             <version>${spring-ai.version}</version>
             <type>pom</type>
             <scope>import</scope>
          </dependency>
       </dependencies>
    </dependencyManagement>
    <build>
       <plugins>
          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
       </plugins>
    </build>
</project>

该文件声明了 Spring BootSpring AIJSoup 等必要的依赖项,确保项目能够正常运行。

RAG 技术在其他领域的应用

除了 IPL 冠军查询,RAG 技术还可以应用于其他各种场景,例如:

  • 客户服务:将 RAG 技术应用于聊天机器人,使其能够根据最新的产品文档和知识库回答客户的问题。
  • 金融分析:将 RAG 技术应用于金融分析师助手,使其能够根据最新的市场数据和新闻报道生成投资建议。
  • 医疗诊断:将 RAG 技术应用于医疗诊断助手,使其能够根据最新的医学文献和病历数据辅助医生进行诊断。

总而言之,RAG 是一种非常有前景的技术,它可以有效地提高大模型的知识水平和应用价值。随着大模型的不断发展,RAG 技术将在越来越多的领域得到应用。

结论:RAG 技术让大模型更智能

RAG 技术是弥合 LLM 知识与现实世界之间差距的关键。只需几行 Spring 代码,您就可以将 LLM 从百科全书变成具有最新数据的智能助手。 通过结合 网络爬虫RAG 技术,我们可以有效地解决大模型的知识更新滞后问题,并提高其在各种实际应用中的表现。

RAG 技术融入到您的 OllamaMistral 模型中,可以极大地提升其回答问题的准确性。无论是查询 IPL 赛事结果,还是进行其他领域的知识检索,RAG 都能让您的 LLM 更加智能和实用。