RAG技术(Elasticsearch版)

概论

检索增强⽣成( Retrieval-Augmented Generation , RAG ) 是⼀种结合信息检索与⽣成式 AI 的 技术,它的核⼼思想是在⽣成答案前,先从外部知识库中检索与⽤户问题相关的信息,并将这些信息 作为提示( Prompt )输⼊模型,从⽽弥补 LLM 的静态知识局限。

大模型本身有两个致命问题

  • 知识过时:模型训练数据是固定的,不会自动更新,回答会落后。
  • 容易一本正经胡说八道(幻觉):不知道就瞎编,看起来很对,其实错得离谱。

RAG 就是解决这两个问题的核心方案。

为什么要用 RAG,而不是直接微调模型?

  • 微调成本高:要重新训练、耗显卡、耗时间
  • 更新慢:知识变了要重新训
  • RAG 轻量灵活
    • 加新知识只需要往库里丢文档
    • 随时增删改,不用重训模型
    • 可控性强,回答必须基于给定资料,不乱编
通俗理解:

不用 RAG,模型是 “凭记忆答题”;

用了 RAG,模型是 “开卷考试”,更准、更新、更可信。

RAG 就是我们的外挂知识库,简单的理解,在向⼤模型提问前,我们需要预**先对⼤量私有化知识内 容进⾏梳理,提取(检索)出与提问相关的知识内容后整合到 Prompt 中再向⼤模型提问,让⼤模 型围绕“固定的知识”进⾏针对性回答。**从⽽提升模型输出内容的准确性和可靠性(增强⽣成),如下图所示。

请添加图片描述

RAG的作用

RAG是一种结合了检索和生成的技术,核心思想是:

检索:根据用户问题,从知识库(向量数据库)中检索出相关的文档或段落。

生成:基于检索到的内容,生成一个更准确的、更相关的回答。

通过RAG技术改造后,大模型就能:

1.准确回答关于特定内容的问题

2.在合适的时机推荐相关课程和服务

3.⽤特定的语⽓和⽤户交流

4.提供更新、更准确的建议

请添加图片描述

RAG工作流程

检索阶段

关键字检索

顾名思义就是我们⽇常⼯作中常⻅的检索⽅式,⽐如:通过输⼊订单标题关键词,模糊检索到 订单信息。

相似度检索

1.知识分块处理
请添加图片描述

2.知识向量化存储

请添加图片描述

3.问题处理

请添加图片描述

4.向量检索

通过计算向量之间的距离(如余弦距离、欧⽒距离等)来实现。

⽣成阶段

1.提示词⼯程

构建包含检索内容和⽤户查询的提示模版,设置系统⻆⾊和相关参数。

2.上下⽂增强⽣成

将检索到的⽂档作为上下⽂提供给⼤模型。然后⼤模型⽣成基于检索内容的准确回答。

请添加图片描述

Elasticsearch向量数据库

介绍

Elasticsearch 是世界上部署最⼴泛的开源向量数据库,为您提供⼤规模、⾼效地创建、存储和搜 索向量嵌⼊的有效⽅法。借助 Elastic 的企业级向量数据库,即使数据快速变化,您也可以实现快 速查询时间和最佳性能。它可根据扩展进⾏构建,在简化开发流程的同时提供相关的个性化搜索结 果。

Elasticsearch 是⼀个开源的、分布式的、 RESTful 的分析引擎,可以处理⽂本、数值、地理、 结构化和⾮结构化数据。基于 Apache Lucene ,最初于2010年由 Elasticsearch N.V. 发布。 Elasticsearch 是 Elastic堆栈的⼀部分,这是⼀套免费且开源的⼯具,⽤于 数据摄⼊、丰富、存储、分析和可视化。

官⽅⽹站: https://www.elastic.co/cn/elasticsearch/vector-database

常见应用场景:

1.全文搜索

Elasticsearch 凭借其强⼤、可扩展和快速的搜索功能,在全⽂搜索场景中表现出⾊。它允 许⽤户以近乎实时的响应执⾏复杂的查询,常⽤于⼤型⽹站和应⽤程序的搜索功能。

2.实时分析

Elasticsearch 能够实时执⾏分析,适⽤于跟踪实时数据(如⽤户活动、交易或传感器输 出)的仪表盘。它可以对结构化和⾮结构化数据进⾏索引和分析,⽀持聚合操作和复杂的数据 可视化。

4.地理数据应⽤

Elasticsearch 通过地理空间索引和搜索功能⽀持地理数据,这对于需要管理和可视化地理 信息的应⽤程序(如地图和基于位置的服务)⾮常有⽤。它使得执⾏邻近搜索和基于位置的数 据可视化成为可能。

5.⽇志和事件数据分析

Elasticsearch 常⽤于聚合、监控和分析来⾃各种来源的⽇志和事件数据。

6.推荐系统

基于⽤户的⾏为和兴趣, Elasticsearch 可以构建推荐系统,推荐相关的内容和产品,提升 ⽤户体验和转化率。

7.业务分析

Elasticsearch 还可⽤于企业数据分析、市场调研等业务分析场景。它可以对海量数据进⾏ 搜索、聚合和分析,⽀持多种数据格式和数据源,帮助⽤户了解业务情况、市场趋势等。

综上所述, Elasticsearch 适⽤于需要⼤规模数据存储、实时搜索和分析的场景,特别是对于结构 化和⾮结构化数据的全⽂搜索和聚合分析。它的⾼性能、可扩展性和易⽤性使得它成为许多企业和开 发者的⾸选⼯具。

VectorStore 的操作

SpringBoot 整合 Elasticsearch 是企业级开发中常⻅的需求,⽤于实现⾼效的全⽂检索、⽇志分 析等功能。

在这⾥,我们会结合 SpringAI 来对向量数据库进⾏操作。我们选择的组件为 SpringAI 中的 VectorStore 。 VectorStore 是⼀种⽤于存储和检索⾼维向量数据的数据库或存储解决⽅案,它在 AI 应⽤中扮演着⾄关重要的⻆⾊。

VectorStore 特别适⽤于处理那些经过嵌⼊模型转化后的数据。在 VectorStore 中,查询与传统 关系数据库不同,它执⾏的是相似性搜索,⽽⾮精确匹配。当给定向量作为查询时,它会返回与查询 向量相似的向量。

VectorStore 主要⽤于将数据与 AI 模型集成。它存储并⽀持对这些向量的相似性搜索,为 AI 模 型提供丰富的上下⽂信息,从⽽实现更精确、更智能的回复。这种技术被称为检索增强⽣成。

环境要求

Elasticsearch :需先安装并启动,这⾥使⽤到的版本是8.15.5。

Spring Boot :因为后期 AI 依赖⾼版本 Boot,这里是3.4.5。

JDK:最低要求 17。

Ollama 中安装⽂本嵌⼊模型 拉取⽂本嵌⼊模型 nomic-embed-text 。

拉取嵌入式模型

首先要安装Ollama,安装教程在主页的SpringAI篇有步骤。在终端运行指令进行安装。

SpringAI篇快速进入链接:https://blog.csdn.net/2401_83310208/article/details/159610180?spm=1001.2014.3001.5501

网址:nomic-embed-text

请添加图片描述

引入依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<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>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
</dependency>
<!-- 兼容的Elasticsearch客户端 -->
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>${elasticsearch.client.version}</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>${elasticsearch.client.version}</version>
</dependency>

配置文件application.yml

  elasticsearch:
    uris: http://192.168.128.65:9200/ #自己elasticsearch启动的地方(这里是虚拟机的)
    username: elastic #默认名字
    password: xxxxxx #自己的密码
  ai:
    ollama:
      base-url: http://localhost:11434  #默认
      embedding:
        model: nomic-embed-text:latest  #嵌入模型名字
      chat:
        model: qwen3:8b #大模型名字
        options:
          temperature: 0.7  #温度
    vectorstore:
      elasticsearch:
        dimensions: 768
        index-name: vectorstore1  #名字,随便取
        initialize-schema: true # 向量中的维数
        similarity: cosine # 要使⽤的相似性函数

相关操作

查看内容是否添加删除

启动elasticsearch后在浏览器输入启动的地址( 配置类的uris)+/配置类取的名字+/_search

添加文档
@Resource
private VectorStore vectorStore;

@Test
public void add1(){
    List<Document> documents = List.of(
            new Document("I like java"),
            new Document("I like springAi"),
            new Document("I like spring"),
            new Document("I like python")
    );
    vectorStore.add(documents);

}
查询
// 根据查询字符串进⾏相似性搜索,返回相似的⽂档列表。
@Test
public void find1() {
// 默认情况下,返回的数据为4条。
List<Document> documents = vectorStore.similaritySearch("I like");
documents.forEach(e -> log.info("e:{}", e));
}
@Test
public void find2(){
    SearchRequest searchRequest = SearchRequest.builder()
            .topK(5) //返回达到相似度的几条数据
       	 	.query("查询学院") //查询内容
        	.similarityThreshold(0.5)//达到多少相似度返回结果
            .build();
    List<Document> documents =vectorStore.similaritySearch(searchRequest);
    documents.forEach(System.out::println);
}
//全搜索
@Test
public void find3(){
    SearchRequest searchRequest = SearchRequest.builder()
            .topK(1000).similarityThreshold(0.0)
            .build();

    List<Document> documents = vectorStore.similaritySearch(searchRequest);
    documents.forEach(System.out::println);
}
删除⽂档
@Test
public void find4(){
    // 构建搜索请求对象,设置查询数量和相似度阈值
    SearchRequest searchRequest = SearchRequest.builder()
            .topK(1000) // 设置返回最大文档数量
            .similarityThreshold(0.0) // 设置相似度过滤阈值
            .build(); // 构建请求对象
    
    // 执行向量相似度搜索,获取文档列表
    List<Document> documents =vectorStore.similaritySearch(searchRequest);
    
    // 遍历搜索结果文档集合
    for (Document doc : documents){
        System.out.println("文档:"+doc); // 打印完整文档信息
        String id = doc.getId(); // 获取文档ID
        String text = doc.getText(); // 获取文档文本内容
        Double score = doc.getScore(); // 获取文档匹配相似度分数
        System.out.println("id:"+ id);
        System.out.println("内容:"+ text);
        System.out.println("相似度:"+ score);
        System.out.println("==================================");
    }
}
@Test
public void delete(){
    List<String> ids = List.of("1b8a5343-174a-4535-bd00-35a7abd13b88","76763e00-5432-4ce4-86a5-8304726565bf"); //id
    vectorStore.delete(ids);
}

引⼊外部⽂档

依赖
<!-- pdf⽂件读取 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
<!-- ⽂本⽂件读取 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!-- markdown⽂件读取 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>
外部文档

请添加图片描述

导入向量数据库
@Value("classpath:101.txt")
private org.springframework.core.io.Resource txtResource;
@Value("classpath:RAG.pdf")
private org.springframework.core.io.Resource pdfResource;
@Value("classpath:101.md")
private org.springframework.core.io.Resource mdResource;

@Test
public void add3(){
    TextReader textReader = new TextReader(txtResource);
    List<Document> documents = textReader.get();
    //转换:按Token拆分
    TokenTextSplitter splitter =new TokenTextSplitter();
    List<Document> chunks = splitter.apply(documents);
    //加载:存储到ES向量数据库
    vectorStore.add(chunks);
}
@Test
public void add4(){
    PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfResource);
    List<Document> documents = pdfReader.get();
    //转换:按Token拆分
    TokenTextSplitter splitter =new TokenTextSplitter();
    List<Document> chunks = splitter.apply(documents);
    //加载:存储到ES向量数据库
    vectorStore.add(chunks);
}
@Test
public void add5(){
    MarkdownDocumentReaderConfig config =
            MarkdownDocumentReaderConfig.builder()
                    .withHorizontalRuleCreateDocument(true)
                    .withIncludeCodeBlock(false)
                    .withIncludeBlockquote(false)
                    .withAdditionalMetadata("filename",
                            mdResource.getFilename())
                    .build();
    MarkdownDocumentReader markdownDocumentReader = new
            MarkdownDocumentReader(mdResource, config);
    List<Document> documents = markdownDocumentReader.get();
    //转换:按Token拆分
    TokenTextSplitter splitter =new TokenTextSplitter();
    List<Document> chunks = splitter.apply(documents);
    //加载:存储到ES向量数据库
    vectorStore.add(chunks);
}

搭建本地 RAG 系统

配置类config
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;


import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 标记为Spring配置类
public class ChatClientConfig {
    @Resource // Spring依赖注入注解
    private OllamaChatModel openAiChatModel; // 注入Ollama聊天模型

    @Bean("openAiChatClient") // 注册Bean并指定名称
    public ChatClient openAiChatClient(){
        return ChatClient.builder(openAiChatModel) // 构建ChatClient,绑定模型
                .build(); // 创建ChatClient实例
    }
}
关键代码controller
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/rag")
public class RagController {
    @Resource // Spring依赖注入,注入聊天客户端
    private ChatClient openAiChatClient;
    @Resource
    private VectorStore vectorStore; // 注入向量数据库存储对象

    // 定义GET接口,设置响应类型为html,支持流式返回
    @GetMapping(value = "/stream", produces = "text/html;charset=utf-8")
    public Flux<String> stream(@RequestParam("question") String question) { // 接收前端传入的问题参数
        // 1. 构建向量搜索请求
        SearchRequest searchRequest = SearchRequest.builder()
                .query(question) // 设置搜索关键词
                .topK(5) // 设置返回最大匹配数
                .similarityThreshold(0.5) // 设置相似度阈值
                .build(); // 构建请求对象
        // 执行向量相似度搜索,获取匹配文档
        List<Document> documents = vectorStore.similaritySearch(searchRequest);
        // 遍历打印匹配的文档日志
        documents.forEach(e -> {
            log.info("e:{}", e);
        });
        
        // 2. 定义大模型提示词模板
        String prompt = """
                #请根据一下上下文回答问题
                {doc}
                #问题
                {question}
                """;
        PromptTemplate promptTemplate = new PromptTemplate(prompt); // 创建提示词模板
        
        // 给模板参数赋值:拼接文档内容
        promptTemplate.add("doc", documents.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n")));
        promptTemplate.add("question", question); // 绑定用户问题
        
        // 调用大模型,流式返回回答内容
        return openAiChatClient.prompt(promptTemplate.create())
                .stream().content(); // 获取流式响应文本
    }
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐