掘金 人工智能 09月20日 11:37
LangChain4j 实现 RAG(知识库)功能
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了如何使用 LangChain4j 构建检索增强生成(RAG)系统,为大模型赋予“实时百科大脑”,以克服知识遗忘和幻觉回复的问题。文章阐述了 RAG 的核心理念,即通过“先查资料后回答”的机制,让大模型能够访问外部信息源,提升回答的专业性和准确性。文中深入分析了 RAG 的索引和检索两个关键阶段,并提供了 LangChain4j 在这两个阶段的理论及实战应用,包括文档加载、解析、转换、拆分、嵌入和存储等步骤。最后,通过一个使用 Alibaba Java 开发手册作为知识库的 Easy RAG 示例,展示了如何在 Spring Boot 环境下集成 LangChain4j,实现问答功能。

💡 RAG(检索增强生成)技术的核心在于为大型语言模型(LLM)配备一个“实时百科大脑”,通过“先检索信息,再生成回答”的机制,有效解决 LLM 的知识遗忘和幻觉回复问题。它使得 LLM 能够访问更广泛的信息源,从而提供更专业、更准确的回答,尤其是在处理行业专业术语或公司内部数据时,RAG 能够弥补 LLM 自身知识的不足。

📚 RAG 的实现流程主要分为两个阶段:索引(Indexing)和检索(Retrieval)。索引阶段负责预处理文档,将其分割、嵌入并存储到向量数据库中,以便后续高效检索。此过程通常是离线进行的,可以定期执行。检索阶段则是在用户提问时在线进行,通过将用户查询嵌入并与向量数据库进行相似度搜索,找出最相关的文档片段,然后将这些片段与原始查询一同注入到 LLM 的提示中,以生成最终的回答。

🚀 LangChain4j 提供了构建 RAG 系统的强大支持,其组织结构包含文档加载器(如 FileSystemDocumentLoader)、文档解析器(如 ApacheTikaDocumentParser)、文档转换器(如 DocumentTransformer)和文档拆分器(如 DocumentByParagraphSplitter)。在实际应用中,LangChain4j 提供了 Easy RAG、Naive RAG 和 Advanced RAG 三种风格,其中 Easy RAG 是入门 RAG 最简单的方式,适合快速集成。

💻 文章通过一个实战案例,展示了如何在 Spring Boot 项目中使用 LangChain4j 实现 RAG 功能。该案例利用 Alibaba Java 开发手册作为知识库,并采用内存向量数据库(InMemoryEmbeddingStore)来存储嵌入数据。通过配置 LLM 的 API Key、模型名称和基础 URL,并结合 `EmbeddingStoreIngestor` 来加载和处理文档,最终实现了一个能够结合外部知识库进行问答的聊天助手。

13. LangChain4j + 加入检索增加生成 RAG(知识库)

@[toc]

RAG 的概念

核心设计理念: RAG技术就像给AI大模型装上了「实时百科大脑」,为了让大模型获取足够的上下文,以便获得更加广泛的信息源,通过先查资料后回答的机制,让AI摆脱传统模型的"知识遗忘和幻觉回复"困境。

一句话简单的说:就是让大模型有一个类似于小抄的,当大模型在回答你的问题的时候,先检索你给大模型提供的(RAG 小抄的资料),结合你提供的小抄的资料,在回答你的问题,减少了大模型的“幻觉”,同时也让大模型更加专业,准确。 大模型是基于网络公开的数据信息资料,回答你的,但是一些行业专业术语,以及公司内部的数据资料,大模型是无法获取到的,而我们的 RAG(小抄)就解决了,这个问题。我们可以将我们的内部资料设置为(RAG 小抄),让大模型回答的时候,根据我们给它提供的 RAG(小抄)回答我们的问题。

LangChain4j RAG 的使用理论

RAG 流程分为两个不同的阶段:索引 + 检索

在索引阶段,文档会被预处理,以便在检索阶段进行高效搜索。

这个过程可能因使用的信息检索方法而异。 对于向量搜索,这通常涉及清理文档、用额外数据和元数据丰富文档、 将文档分割成更小的片段(也称为分块)、嵌入这些片段,最后将它们存储在嵌入存储(也称为向量数据库)中。

索引阶段通常是离线进行的,这意味着最终用户不需要等待其完成。 例如,可以通过定时任务在周末每周重新索引一次公司内部文档来实现。 负责索引的代码也可以是一个单独的应用程序,只处理索引任务。

然而,在某些情况下,最终用户可能希望上传自己的自定义文档,使 LLM 能够访问这些文档。 在这种情况下,索引应该在线进行,并成为主应用程序的一部分。

以下是索引阶段的简化图表:

简单的理解就是:一般我们的提供给大模型的 RAG(小抄)的内容是很大的,数据量大,同时要提高大模型查找我们的 RAG(小抄),我们就需要将我们的 RAG(小抄),通过向量大模型将 RAG(小抄)转换为向量数据,存储到向量数据库当中。减少空间的占用,以及提高效率。

同时:索引阶段通常是离线进行的,这意味着最终用户不需要等待其完成。 例如,可以通过定时任务在周末每周重新索引一次公司内部文档来实现。 负责索引的代码也可以是一个单独的应用程序,只处理索引任务。

检索阶段通常在线进行,当用户提交一个应该使用索引文档回答的问题时。

这个过程可能因使用的信息检索方法而异。 对于向量搜索,这通常涉及嵌入用户的查询(问题) 并在嵌入存储中执行相似度搜索。 然后将相关片段(原始文档的片段)注入到提示中并发送给 LLM。

以下是检索阶段的简化图表:

FileSystemDocumentLoader: 从文件系统加载文档UrlDocumentLoader: 从 URL 加载文档AmazonS3DocumentLoader: 从 Amazon S3 加载文档AzureBlobStorageDocumentLoader: 从 Azure Blob 存储加载文档GitHubDocumentLoader: 从 GitHub 仓库加载文档TencentCosDocumentLoader: 从腾讯云 COS 加载文档

DocumentTransformer 用于对文档执行各种转换,如清理、过滤、增强或总结。

DocumentByParagraphSplitter: 按段落拆分DocumentBySentenceSplitter: 按句子拆分DocumentByWordSplitter: 按单词拆分DocumentByCharacterSplitter: 按字符拆分DocumentByRegexSplitter: 按正则表达式拆分

使用 LangChain4j 构建 RAG 的一般步骤:

    加载文档:使用适当的DocumentLoader和DocumentParser加载文档转换文档:使用DocumentTransformer清理或增强文档(可选)拆分文档:使用DocumentSplitter将文档拆分为更小的片段(可选)嵌入文档:使用EmbeddingModel将文档片段转换为嵌入向量存储嵌入:使用EmbeddingStoreIngestor存储嵌入向量检索相关内容:根据用户查询,从EmbeddingStore检索最相关的文档片段生成响应:将检索到的相关内容与用户查询一起提供给语言模型,生成最终响应

LangChain4j RAG 的实战

LangChain4j 提供了三种 RAG 风格:

这里我们使用 Easy RAG ,同时这里我们在使用“内存向量”,作为向量数据库,存储我们 RAG(小抄)转化为的向量数据。我们设计给大模型提供一个 RAG(内容是:阿里巴巴 Java 开发手册当中的错误码),配置好后,问大模型 错误码:00000,A0001 是什么含义。

    创建对应项目的 module 模块内容:导入相关的 pom.xml 的依赖,这里我们采用流式输出的方式,导入_ 整合 Spring Boot ,langchain4j-open-ai-spring-boot-starterlangchain4j-spring-boot-starter ,同时我们加入我们的操作 RAG 的 jak 依赖。因为我们这里使用的是“内存的向量数据库”所以不需要额外的引入其他的向量数据库包。这里我们不指定版本,而是通过继承的 pom.xml 当中获取。_

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <dependency>            <groupId>dev.langchain4j</groupId>            <artifactId>langchain4j</artifactId>        </dependency>        <dependency>            <groupId>dev.langchain4j</groupId>            <artifactId>langchain4j-open-ai</artifactId>        </dependency>        <!--easy-rag-->        <dependency>            <groupId>dev.langchain4j</groupId>            <artifactId>langchain4j-easy-rag</artifactId>            <version>1.2.0-beta8</version>        </dependency>
    设置 applcation.yaml / properties 配置文件,其中指明我们的输出响应的编码格式,因为如果不指定的话,存在返回的中文,就是乱码了。
server.port=9012spring.application.name=langchain4j-12chat-rag# 设置响应的字符编码,避免流式返回输出乱码server.servlet.encoding.charset=utf-8server.servlet.encoding.enabled=trueserver.servlet.encoding.force=true# https://docs.langchain4j.dev/tutorials/spring-boot-integration#langchain4j.open-ai.chat-model.api-key=${aliQwen-api}#langchain4j.open-ai.chat-model.model-name=qwen-plus#langchain4j.open-ai.chat-model.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1# 大模型调用不可以明文配置,你如何解决该问题# 1 yml:                ${aliQwen-api},从环境变量读取# 2 config配置类:      System.getenv("aliQwen-api")从环境变量读取
    编写让大模型做什么事情——>这里是聊天的,接口类 ChatAssistant

package com.rainbowsea.langchain4jchatrag.service;/** */public interface ChatAssistant {    /**     * 聊天     *     * @param message 消息     * @return {@link String }     */    String chat(String message);}
    编写大模型三件套(大模型 key,大模型 name,大模型 url) 三件套的大模型配置类。同时也需要配置,我们的向量数据库(内存向量数据库),将我们的 RAG(小抄(Alibaba Java 开发手册)) 信息存储到内存向量数据库当中,供大模型读取使用。

package com.rainbowsea.langchain4jchatrag.config;import com.rainbowsea.langchain4jchatrag.service.ChatAssistant;import dev.langchain4j.data.segment.TextSegment;import dev.langchain4j.memory.chat.MessageWindowChatMemory;import dev.langchain4j.model.chat.ChatModel;import dev.langchain4j.model.openai.OpenAiChatModel;import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;import dev.langchain4j.service.AiServices;import dev.langchain4j.store.embedding.EmbeddingStore;import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** */@Configurationpublic class LLMConfig{    @Bean    public ChatModel chatModel()    {        return OpenAiChatModel.builder()                    .apiKey(System.getenv("aliQwen_api"))                    .modelName("qwen-plus")                    .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")                .build();    }    /**     * 需要预处理文档并将其存储在专门的嵌入存储(也称为矢量数据库)中。当用户提出问题时,这对于快速找到相关信息是必要的。     * 我们可以使用我们支持的 15 多个嵌入存储中的任何一个,但为了简单起见,我们将使用内存中的嵌入存储:     *     * https://docs.langchain4j.dev/integrations/embedding-stores/in-memory     *     * @return     */    @Bean    public InMemoryEmbeddingStore<TextSegment> embeddingStore() {        return new InMemoryEmbeddingStore<>();    }        @Bean    public ChatAssistant assistant(ChatModel chatModel, EmbeddingStore<TextSegment> embeddingStore)    {        return AiServices.builder(ChatAssistant.class)                    .chatModel(chatModel)                    .chatMemory(MessageWindowChatMemory.withMaxMessages(50))                    .contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))                .build();    }}
    编写对外访问的 ctroller 层:将我们的将我们的 RAG(小抄(Alibaba Java 开发手册)) 信息存储到内存向量数据库当中,供大模型读取使用。再向大模型提问,大模型就会结合其中 Alibaba Java 开发手册当中的内容回答。

package com.rainbowsea.langchain4jchatrag.controller;import com.rainbowsea.langchain4jchatrag.service.ChatAssistant;import dev.langchain4j.data.document.Document;import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;import jakarta.annotation.Resource;import lombok.extern.slf4j.Slf4j;import dev.langchain4j.data.segment.TextSegment;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;/** */@RestController@Slf4jpublic class RAGController{    @Resource    InMemoryEmbeddingStore<TextSegment> embeddingStore;    @Resource    ChatAssistant chatAssistant;    // http://localhost:9012/rag/add    @GetMapping(value = "/rag/add")    public String testAdd() throws FileNotFoundException    {        //Document document = FileSystemDocumentLoader.loadDocument("D:\\44\\alibaba-java.docx");        File file = new File(getClass().getClassLoader().getResource("static/Alibaba_Java.docx").getFile());        FileInputStream fileInputStream = new FileInputStream(file);;        Document document = new ApacheTikaDocumentParser().parse(fileInputStream);        EmbeddingStoreIngestor.ingest(document, embeddingStore);        String result = chatAssistant.chat("错误码00000和A0001分别是什么");        System.out.println(result);        return result;    }}

运行测试:

最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain4j RAG Retrieval-Augmented Generation 知识库 AI LLM Java Spring Boot 向量数据库 阿里云
相关文章