J-LangChain - RAG - PDF问答

news/2025/2/27 8:08:11

系列文章索引
J-LangChain 入门

在现代自然语言处理(NLP)中,基于文档内容的问答系统变得愈发重要,尤其是当我们需要从大量文档中提取信息时。通过结合文档检索和生成模型(如RAG,Retrieval-Augmented Generation),我们可以构建强大的问答系统。在处理大量文本数据时,PDF 文件常常是最常见的格式之一。很多时候,我们需要从这些文档中提取信息并利用现代自然语言处理(NLP)技术进行问答。本文将通过一个完整的实例,展示如何使用 J-LangChain 来处理 PDF 文件并通过问答接口获取回答。

J-LangChain 简介

J-LangChain 是一个基于 Java 的链式模型开发框架,旨在帮助开发者利用现代语言模型(如 ChatGPT、Ollama 等)进行多步骤的推理和数据处理。它特别适合用于构建编排复杂的应用程序,这些应用程序涉及到多个步骤的转换、文档处理、模型推理等。

github:https://github.com/flower-trees/j-langchain

本文的目标

我们将展示如何:

  1. 使用 PdfboxLoader 加载 PDF 文档。
  2. 使用 StanfordNLPTextSplitter 对文档进行切分。
  3. 使用 OllamaEmbeddings 对文本进行向量化。
  4. 将文档转换为向量并使用 Milvus 向量存储。
  5. 构建一个多步骤的问答流程来处理用户问题。

项目依赖

为了使用 J-LangChain,你需要在你的 pom.xml 中添加相关的依赖项。这些依赖包括:

  • J-LangChain 大模型编排框架
  • Ollama 模型支持
  • Milvus 向量存储
  • Pdfbox 用于加载 PDF 文件

代码实现步骤

1. 加载 PDF 文档

首先,我们需要加载 PDF 文件并解析其中的文本。J-LangChain 提供了一个名为 PdfboxLoader 的类来帮助我们完成这一任务。你只需指定 PDF 文件的路径,它就会返回一个包含文档内容的 List<Document>

java">PdfboxLoader loader = PdfboxLoader.builder()
                .filePath("./files/pdf/en/Transformer.pdf")
                .extractImages(false) //不处理图片
                .build();
List<Document> documents = loader.load();
System.out.println("Load documents count:" + documents.size());
2. 切分文档

由于 PDF 文档可能很长,直接将整个文档传递给语言模型会导致性能问题。因此,我们需要将文档切分成较小的部分。J-LangChain 提供了一个 StanfordNLPTextSplitter,它可以根据指定的 chunk 大小和重叠量来切分文档。

java">StanfordNLPTextSplitter splitter = StanfordNLPTextSplitter.builder()
                .chunkSize(1000)
                .chunkOverlap(100)
                .build();
List<Document> splits = splitter.splitDocument(documents);
System.out.println("Splits count:" + splits.size());
3. 将文档转为向量存储

接下来,我们需要将文本信息转化为向量表示,这样可以进行快速的相似性检索。J-LangChain 提供了与 Milvus 向量存储集成的功能。首先,我们需要通过 OllamaEmbeddings 来生成文档的向量表示,然后使用 Milvus 保存这些向量。

java">VectorStore vectorStore = Milvus.fromDocuments(
                splits,
                OllamaEmbeddings.builder().model("nomic-embed-text").vectorSize(768).build(),
                "JLangChain");
System.out.println("Save success");
4. 构建问答流程

现在我们已经将文档切分并存储在向量数据库中,接下来就是通过用户输入的问题来进行检索和回答。我们需要一个 BaseRetriever 来从向量存储中检索相关文档,然后将检索到的文档与问题一起传递给语言模型进行处理。

首先,定义一个 promptTemplate,它包含了需要回答的问题和相关的文档内容:

java">String promptTemplate = """
                Please provide the following text content:
                
                ${text}
                
                Answer the question:${question}
                """;

然后,我们通过 J-LangChain 构建一个包含多个步骤的流程。首先,我们从向量存储中检索与问题相关的文档,然后将文档和问题传递给 PromptTemplateChatOllama 进行处理,最后解析模型的输出。

java">BaseRunnable<StringPromptValue, ?> prompt = PromptTemplate.fromTemplate(promptTemplate);
ChatOllama llm = ChatOllama.builder().model("deepseek-r1:7b").build();

FlowInstance chain = chainActor.builder()
                .next(baseRetriever)
                .next(input -> { System.out.println("Query content:" + JsonUtil.toJson(input)); return input; })
                .next(formatDocs)
                .next(input -> Map.of("text", input, "question", ContextBus.get().getFlowParam()))
                .next(prompt)
                .next(llm)
                .next(new StrOutputParser())
                .build();

最后,调用 chainActor.invoke 执行流程,并打印出结果:

java">ChatGeneration result = chainActor.invoke(chain, "Why is masking necessary in the decoder’s self-attention mechanism?");
System.out.println("Chat Result:" + result);

代码完整实现

代码下载地址:https://github.com/flower-trees/j-langchain-example/blob/master/src/main/java/org/salt/jlangchain/demo/rag/pdf/PdfChatExample.java

java">@Component
public class PdfChatExample {

    @Autowired
    ChainActor chainActor;

    public void pdfChat() {
        PdfboxLoader loader = PdfboxLoader.builder()
                .filePath("./files/pdf/en/Transformer.pdf")
                .build();
        List<Document> documents = loader.load();
        System.out.println("Load documents count:" + documents.size());

        StanfordNLPTextSplitter splitter = StanfordNLPTextSplitter.builder()
                .chunkSize(1000)
                .chunkOverlap(100)
                .build();
        List<Document> splits = splitter.splitDocument(documents);
        System.out.println("Splits count:" + splits.size());

        VectorStore vectorStore = Milvus.fromDocuments(
                splits,
                OllamaEmbeddings.builder().model("nomic-embed-text").vectorSize(768).build(),
                "JLangChain");
        System.out.println("Save success");

        BaseRetriever baseRetriever = vectorStore.asRetriever();

        String promptTemplate = """
                Please provide the following text content:
                
                ${text}
                
                Answer the question:${question}
                """;

        BaseRunnable<StringPromptValue, ?> prompt = PromptTemplate.fromTemplate(promptTemplate);
        ChatOllama llm = ChatOllama.builder().model("deepseek-r1:7b").build();

        Function<Object, String> formatDocs = input -> {
            if (input == null) {
                return "";
            }
            List<Document> docs = (List<Document>) input;
            StringBuilder sb = new StringBuilder();
            for (Document doc : docs) {
                sb.append(doc.getPageContent()).append("\n");
            }
            return sb.toString();
        };

        FlowInstance chain = chainActor.builder()
                .next(baseRetriever)
                .next(input -> { System.out.println("Query content:" + JsonUtil.toJson(input)); return input; })
                .next(formatDocs)
                .next(input -> Map.of("text", input, "question", ContextBus.get().getFlowParam()))
                .next(prompt)
                .next(llm)
                .next(new StrOutputParser()).build();

        ChatGeneration result = chainActor.invoke(chain, "Why is masking necessary in the decoder’s self-attention mechanism?");
        System.out.println("Chat Result:" + result);
    }
}

代码执行

java">@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@SpringBootConfiguration
public class PdfExampleTest {

    @Autowired
    PdfChatExample pdfChatExample;

@Test
    public void PdfChatExample() {
        pdfChatExample.pdfChat();
    }
}

总结

本文展示了如何使用 J-LangChain 框架从 PDF 文件中提取信息并进行基于问题的回答。我们通过加载 PDF 文档、切分文档、向量化文档内容并存储到 Milvus 中,然后构建问答流程,最终利用语言模型来回答用户的问题。

通过这种方式,开发者可以轻松地将自然语言处理模型应用到 PDF 文档中,进行信息提取、问答等操作。

参考文档
LangChain教程 - RAG - PDF问答
使用 Apache PDFBox 提取 PDF 中的文本和图像
详细介绍Tess4J的使用:从PDF到图像的OCR技术实现
全面了解 Stanford NLP:强大自然语言处理工具的使用与案例
使用 Milvus 与 Ollama 进行文本向量存储与检索


http://www.niftyadmin.cn/n/5869776.html

相关文章

excel单、双字节字符转换函数(中英文输入法符号转换)

在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

MySQL的锁机制和锁算法

锁机制和InnoDB锁算法 MyISAM和InnoDB存储引擎使用的锁&#xff1a; MyISAM采用表级锁(table-level locking)。 InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁 表级锁和行级锁对比&#xff1a; 表级锁&#xff1a; MySQL中锁定 粒度最大 的一种锁&#xff0c;…

【cuda学习日记】4.4核函数带宽(矩阵转置问题)

4.4.1 内存带宽 理论带宽是当前硬件可以实现的绝对最大带宽。在cuda中获取 int dev 0;cudaSetDevice(dev);cudaDeviceProp deviceprop;CHECK(cudaGetDeviceProperties(&deviceprop,dev));printf("device %d: %s \n", dev, deviceprop.name);printf("Peak …

Java | 基于Kerberos认证对接华为云Elasticsearch

可以通过华为官方提供的Java客户端&#xff0c;来实现基于Kerberos认证访问和操作华为云Elasticsearch&#xff1b;亦可以使用更加通用的开源Elasticsearch Java客户端bboss&#xff0c;来实现基于Kerberos认证访问和操作华为云Elasticsearch。 本文介绍使用bboss实现基于Kerb…

2024年10月中科院一区SCI-雪橇犬优化算法Sled Dog Optimizer -附Matlab免费代码

引言 本期介绍了一种新的仿生元启发式算法——雪橇犬优化算法Sled Dog Optimizer&#xff0c;SDO。SDO的灵感主要来自雪橇犬的各种行为模式。重点通过模拟狗拉雪橇、训练和退役行为的过程&#xff0c;构建数学模型。该算法于2024年10月最新发表在JCR1区&#xff0c;中科院1区S…

winfrom的progressBar 鼠标移上去显示 进度条的时间

需求描述&#xff1a; 播放IPC摄像头&#xff08;海康、大华&#xff09;的录像回放&#xff0c;视频窗口下方有个进度条&#xff0c;能显示当前录像播放的进度&#xff0c;点击进度条能将视频跳转到指定的时间点继续播放... 现在需要再进度条上显示视频的时间&#xff0c;用来…

SSM个人交友网站

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 SS…

力扣——零钱兑换

题目链接&#xff1a; 链接 题目描述&#xff1a; 思路&#xff1a; 类似于“完全平方数” 设金额 i i i所需的最少个数是 d p [ i ] dp[i] dp[i] 则&#xff1a; d p [ i ] m i n { d p [ i ] , d p [ i − c o i n s [ j ] ] 1 } dp[i] min\{ dp[i],dp[i-coins[j]] 1…