RAG

基于LangChain4J实现简单的RAG检索

使用LangChain4J的DefaultRetrievalAugmentor实现一个简单的RAG检索。

TL;DR

使用LangChain4J的DefaultRetrievalAugmentor实现一个简单的RAG检索。

使用LangChain4J的DefaultRetrievalAugmentor实现一个简单的RAG检索。

   @Autowired
   OpenAiChatModel chatModel;


   @RequestMapping("/retrieve")
    public String retrieve(HttpServletResponse response, String query, String filePath) {
        response.setCharacterEncoding("UTF-8");

        //1.加载文档
        Document document = loadDocument(filePath, new ApacheTikaDocumentParser());


        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(
                300,
                50
        );

        //2.分割文档
        List<TextSegment> textSegments = splitter.split(document);

        OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                .modelName("text-embedding-v4")  // 阿里云 DashScope 的 embedding 模型名称
                .dimensions(768)  // text-embedding-v4 支持 768 维度
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .apiKey("改成你自己的key").build();

        //3.生成embedding
        List<Embedding> allEmbeddings = new ArrayList<>();
        for (int i = 0; i < textSegments.size(); i = i + 9) {
            List<TextSegment> segmentList = textSegments.subList(i, Math.min(i + 9, textSegments.size()));
            List<Embedding> embeddings = embeddingModel.embedAll(segmentList).content();
            allEmbeddings.addAll(embeddings);
        }

        //4.向量存储
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        embeddingStore.addAll(allEmbeddings, textSegments);

        //5.构建上下文融合器
        DefaultContentInjector contentInjector = new DefaultContentInjector(new PromptTemplate("""
                 ## 角色定位
                 你是一位专业的RAG问答助手。请根据提供的上下文信息,详细、准确地回答用户的问题。如果参考文档没有内容,请务必不要胡编乱造,请直接说明"没有找到相关信息"。

                 ## 任务要求:
                 1. 请基于以下提供的参考文档内容,回答用户的问题。
                 2. 如果参考文档中没有相关信息,请直接说明"没有找到相关信息",不要编造内容。
                 3. 如果有了参考文档内容,请务必尽量回答问题。有可能用户的输入比较随意,你可以先尝试回答用户的问题,猜测他的实际需求,先给出回复,你需要尽量去贴合用户的问题需求。

                 ## 格式要求:
                 1. 你的所有回答必须使用Markdown格式进行排版。
                 2. 上下文信息中包含了图片描述标签,格式为:`<image src="URL" description="多模态描述"></image>`。
                 3. 如果图片与用户提问高度相关,请将此标签转换为标准的Markdown图片格式 `
> 🖼️ 原文引用的图片资源未包含在导出文件中:`图片`
`。
                 4. 仅在必要时包含图片,请注意千万不要输出重复的内容和图片,图片确保最终生成的URL不要重复。

                 ## 参考文档:
                {{contents}}

                 ## 用户问题:
                 {{userMessage}}

                 注意:如果参考文档下面的内容为空,请直接回答“没有找到相关信息”。
                """));

        //6.构建检索增强器
        DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
                .contentRetriever(EmbeddingStoreContentRetriever.builder()
                        .embeddingStore(embeddingStore)
                        .embeddingModel(embeddingModel)
                        .maxResults(5)
                        .minScore(0.7)
                        .build())
                .contentInjector(contentInjector)
                .build();


        //7.构建最终的AI服务
        LangChainAiService langChainAiService = AiServices.builder(LangChainAiService.class)
                .chatModel(chatModel)
                .retrievalAugmentor(retrievalAugmentor)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .build();

        //8.调用AI服务
        return langChainAiService.chat(query);
    }

需要注意的点: 1、embeddingStore.addAll(allEmbeddings, textSegments); 需要同时传入embeddings和segments,一定要注意textSegments记得传入,否则会报错。 java.lang.IllegalArgumentException: textSegment cannot be null 2、embeddingModel.embedAll(segmentList).content(); 需要先分批,否则会报错 或者也可以通过设置embeddingModel的maxSegmentsPerBatch来实现。

OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
        .modelName("text-embedding-v4")  // 阿里云 DashScope 的 embedding 模型名称
        .dimensions(768)  // text-embedding-v3 支持 768 维度
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
        .maxSegmentsPerBatch(9)
        .apiKey("sk-0227f9a97bef4f2c8fc899d82831aa25").build();

3、如果是word文档,需要使用ApacheTikaDocumentParser,否则无法解析,这个包来自:

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-document-parser-apache-tika</artifactId>
    <version>1.8.0-beta15</version>
    <scope>compile</scope>
</dependency>

其他的DocumentParse介绍: - TextDocumentParser : langchain4j中默认的的 TextDocumentParser,它可以解析纯文本格式(e.9.TXT、HTML、MD 等)的文件。 - ApachePdfBoxDocumentParser:需要依赖langchain4j-document-parser-apache-pdfbox,可以解析 PDF 文件 - ApachePoiDocumentParser:需要依赖langchain4j-document-parser-apache-poi,可以解析 MSOffice 文件格式(e.9.DOC、DOCX、PPT、PPTX、XLS、XLSX等) - ApacheTikaDocumentParser 需要依赖 langchain4j-document-parser-apache-tika ,可以自动检测和解析几乎所有现有的文件格式 比如使用TextDocumentParser来处理word文档的话,会报错java.lang.RuntimeException: The text "..." (1 characters long) doesn't fit into the maximum segment size (300 characters), and there is no subSplitter defined to split it further. 5、提示词模板中的占位符的名字不能随便改,需要和默认模板中保持一致,即{{contents}}表示检索到的文档内容,{{userMessage}}表示用户的问题。 6、文档的分片+向量化+入库,可以简化,使用EmbeddingStoreIngestor来实现:

EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
                .documentSplitter(DocumentSplitters.recursive(300, 50))
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .build();

        ingestor.ingest(document);

简化后的完整代码如下:

@RequestMapping("/retrieve1")
    public String retrieve1(HttpServletResponse response, String query, String filePath) {
        response.setCharacterEncoding("UTF-8");

        // 1. 配置 Embedding 模型
        OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                .modelName("text-embedding-v3")
                .dimensions(768)
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .maxSegmentsPerBatch(9)
                .apiKey("sk-0227f9a97bef4f2c8fc899d82831aa25")
                .build();

        // 2. 加载文档并生成 Embeddings
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        EmbeddingStoreIngestor.builder()
                .documentSplitter(DocumentSplitters.recursive(300, 50))
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .build()
                .ingest(loadDocument(filePath, new ApacheTikaDocumentParser()));

        // 3. 构建 RAG 增强器(使用链式调用)
        DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
                .contentRetriever(EmbeddingStoreContentRetriever.builder()
                        .embeddingStore(embeddingStore)
                        .embeddingModel(embeddingModel)
                        .maxResults(5)
                        .minScore(0.7)
                        .build())
                .contentInjector(new DefaultContentInjector(new PromptTemplate("""
                          ## 角色定位
                         你是一位专业的RAG问答助手。请根据提供的上下文信息,详细、准确地回答用户的问题。如果参考文档没有内容,请务必不要胡编乱造,请直接说明"没有找到相关信息"。

                         ## 任务要求:
                         1. 请基于以下提供的参考文档内容,回答用户的问题。
                         2. 如果参考文档中没有相关信息,请直接说明"没有找到相关信息",不要编造内容。
                         3. 如果有了参考文档内容,请务必尽量回答问题。有可能用户的输入比较随意,你可以先尝试回答用户的问题,猜测他的实际需求,先给出回复,你需要尽量去贴合用户的问题需求。

                         ## 格式要求:
                         1. 你的所有回答必须使用Markdown格式进行排版。
                         2. 上下文信息中包含了图片描述标签,格式为:`<image src="URL" description="多模态描述"></image>`。
                         3. 如果图片与用户提问高度相关,请将此标签转换为标准的Markdown图片格式 `
> 🖼️ 原文引用的图片资源未包含在导出文件中:`图片`
`。
                         4. 仅在必要时包含图片,请注意千万不要输出重复的内容和图片,图片确保最终生成的URL不要重复。

                         ## 参考文档:
                        {{contents}}

                         ## 用户问题:
                         {{userMessage}}

                         注意:如果参考文档下面的内容为空,请直接回答“没有找到相关信息”。
                        """)))
                .build();

        // 4. 构建 AI 服务并返回结果
        return AiServices.builder(LangChainAiService.class)
                .chatModel(chatModel)
                .retrievalAugmentor(retrievalAugmentor)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .build()
                .chat(query);
    }
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。