系列文章索引
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
本文的目标
我们将展示如何:
- 使用
PdfboxLoader
加载 PDF 文档。 - 使用
StanfordNLPTextSplitter
对文档进行切分。 - 使用
OllamaEmbeddings
对文本进行向量化。 - 将文档转换为向量并使用
Milvus
向量存储。 - 构建一个多步骤的问答流程来处理用户问题。
项目依赖
为了使用 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 构建一个包含多个步骤的流程。首先,我们从向量存储中检索与问题相关的文档,然后将文档和问题传递给 PromptTemplate
和 ChatOllama
进行处理,最后解析模型的输出。
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 {@AutowiredChainActor 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 {@AutowiredPdfChatExample pdfChatExample;@Testpublic void PdfChatExample() {pdfChatExample.pdfChat();}
}
总结
本文展示了如何使用 J-LangChain 框架从 PDF 文件中提取信息并进行基于问题的回答。我们通过加载 PDF 文档、切分文档、向量化文档内容并存储到 Milvus 中,然后构建问答流程,最终利用语言模型来回答用户的问题。
通过这种方式,开发者可以轻松地将自然语言处理模型应用到 PDF 文档中,进行信息提取、问答等操作。
参考文档
LangChain教程 - RAG - PDF问答
使用 Apache PDFBox 提取 PDF 中的文本和图像
详细介绍Tess4J的使用:从PDF到图像的OCR技术实现
全面了解 Stanford NLP:强大自然语言处理工具的使用与案例
使用 Milvus 与 Ollama 进行文本向量存储与检索