使用 LangChain 实现 PDF 文档问答系统

# 使用 LangChain 实现 PDF 文档问答系统

## 背景介绍

工作中,我们经常要和大量的 PDF 文档打交道。技术文档、论文报告、合同协议……从这些文件里找信息,真是件麻烦事。传统的关键词搜索听起来美好,但实际操作起来,要么搜不到想要的内容,要么返回一堆八竿子打不着的结果。

大语言模型出来以后,RAG(检索增强生成)架构给这个问题提供了一个新思路。让 AI 系统”读懂”文档内容,然后直接回答问题,这个场景终于变得可实现了。本文就手把手教你用 LangChain 框架,从零搭一个 PDF 问答系统。

## 问题描述

把 PDF 做成问答系统,坑不少。

PDF 本身是非结构化文档。文本可能分散在不同页面、不同栏目里,有些关键信息甚至是图片或表格形式存的,直接读文本根本抓不住。

再有,PDF 文档通常很长,LLM 根本没办法一次处理整个文件。必须把文档拆开,分块处理。

还有,用户提问的方式和文档原文的表达往往差别很大。关键词搜索肯定不好使,得用语义匹配才能找到真正相关的内容。

最后,直接把整个文档喂给 LLM,费用高、响应慢,根本不现实。

## 详细步骤

### 环境准备

先装必要的 Python 包,建个虚拟环境:

“`bash
python -m venv pdf-qa-env
source pdf-qa-env/bin/activate # Linux/Mac
# Windows: pdf-qa-env\Scripts\activate

pip install langchain langchain-community langchain-openai
pip install pypdf pydantic
pip install faiss-cpu openai
“`

### 第一步:加载 PDF 文档

LangChain 的 PyPDFLoader 用来加载 PDF:

“`python
from langchain_community.document_loaders import PyPDFLoader

def load_pdf(file_path):
loader = PyPDFLoader(file_path)
pages = loader.load()
return pages

# 加载文档
pages = load_pdf(“sample.pdf”)
print(f”共加载 {len(pages)} 页”)
print(f”第一页内容预览:{pages[0].page_content[:200]}”)
“`

### 第二步:文档分块

LLM 有上下文长度限制,得把文档拆成小块:

“`python
from langchain.text_splitter import RecursiveCharacterTextSplitter

def split_documents(pages, chunk_size=1000, chunk_overlap=200):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=[“\n\n”, “\n”, “。”, ” “, “”]
)
chunks = text_splitter.split_documents(pages)
return chunks

# 分块处理
chunks = split_documents(pages)
print(f”共分成 {len(chunks)} 个文本块”)
“`

### 第三步:创建向量存储

用文本嵌入模型把文档块转成向量,存到向量数据库里:

“`python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

def create_vector_store(chunks, openai_api_key):
embeddings = OpenAIEmbeddings(
model=”text-embedding-3-small”,
openai_api_key=openai_api_key
)
vector_store = FAISS.from_documents(
documents=chunks,
embedding=embeddings
)
return vector_store

# 创建向量存储(需要设置 OPENAI_API_KEY 环境变量)
import os
os.environ[“OPENAI_API_KEY”] = “your-api-key-here”
vector_store = create_vector_store(chunks, os.environ[“OPENAI_API_KEY”])
vector_store.save_local(“faiss_index”)
“`

### 第四步:构建问答链

把检索和问答串起来:

“`python
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA

def create_qa_chain(vector_store, openai_api_key):
llm = ChatOpenAI(
model=”gpt-3.5-turbo”,
temperature=0,
openai_api_key=openai_api_key
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type=”stuff”,
retriever=vector_store.as_retriever(
search_type=”similarity”,
search_kwargs={“k”: 3}
),
return_source_documents=True
)
return qa_chain

qa_chain = create_qa_chain(vector_store, os.environ[“OPENAI_API_KEY”])
“`

### 第五步:运行问答

提问,拿答案:

“`python
def ask_question(qa_chain, question):
result = qa_chain({“query”: question})
return result

# 示例问答
question = “这份文档的主要内容是什么?”
result = ask_question(qa_chain, question)

print(f”问题:{question}”)
print(f”答案:{result[\”result\”]}”)
print(“\n参考来源:”)
for i, doc in enumerate(result[“source_documents”]):
print(f”{i+1}. 第 {doc.metadata[\”page\”]} 页”)
“`

## 完整代码示例

完整的、可直接跑的示例:

“`python
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA

class PDFQASystem:
def __init__(self, pdf_path, openai_api_key):
self.pdf_path = pdf_path
self.openai_api_key = openai_api_key
self.qa_chain = None
self.vector_store = None

def initialize(self):
# 1. 加载 PDF
loader = PyPDFLoader(self.pdf_path)
pages = loader.load()

# 2. 文档分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = text_splitter.split_documents(pages)

# 3. 创建向量存储
embeddings = OpenAIEmbeddings(
model=”text-embedding-3-small”,
openai_api_key=self.openai_api_key
)
self.vector_store = FAISS.from_documents(
documents=chunks,
embedding=embeddings
)

# 4. 创建问答链
llm = ChatOpenAI(
model=”gpt-3.5-turbo”,
temperature=0,
openai_api_key=self.openai_api_key
)
self.qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type=”stuff”,
retriever=self.vector_store.as_retriever(k=3),
return_source_documents=True
)

def ask(self, question):
result = self.qa_chain({“query”: question})
return result

# 使用示例
if __name__ == “__main__”:
os.environ[“OPENAI_API_KEY”] = “your-api-key”

qa_system = PDFQASystem(
pdf_path=”sample.pdf”,
openai_api_key=os.environ[“OPENAI_API_KEY”]
)
qa_system.initialize()

questions = [
“文档的核心观点是什么?”,
“有哪些关键数据和结论?”,
“作者提出了哪些建议?”
]

for q in questions:
result = qa_system.ask(q)
print(f”Q: {q}\nA: {result[\”result\”]}\n”)
“`

## 运行结果

跑上面的代码,会得到类似这样的输出:

“`
Q: 文档的核心观点是什么?
A: 根据文档内容,本文主要讨论了机器学习在自然语言处理领域的应用。
文档指出Transformer架构的提出标志着NLP进入了一个新的发展阶段…

Q: 有哪些关键数据和结论?
A: 文档中提到了以下关键数据:
1. 模型参数量从2018年的1亿增长到2023年的万亿级别
2. 训练数据规模扩大了1000倍
3. 在多项基准测试上的性能提升了30%以上…

Q: 作者提出了哪些建议?
A: 文档作者提出了以下建议:
1. 在实际应用中应考虑模型的效率和成本的平衡
2. 建议采用RAG架构来增强模型的可靠性
3. 强调了对模型输出进行验证的重要性…
“`

检索系统会从原始文档里找到最相关的段落作为参考,保证回答的准确性。

## 总结

本文详细介绍了怎么用 LangChain 搭一个 PDF 文档问答系统。核心就这几步:加载 PDF、文档分块、创建向量存储、用 RAG 架构做问答。一个能”读懂”文档内容的智能问答系统就搭好了。

这个方案的几个好处:语义检索比关键词搜索强得多;RAG 架构让 LLM 基于真实文档内容回答,避免了瞎扯;分块加检索机制保证了长文档也能处理;参考来源功能让用户能验证答案靠不靠谱。

实际用的时候,你可以换不同的嵌入模型、向量数据库或者 LLM 来调优性能和成本。中文文档的话,建议用支持中文的嵌入模型,比如中文版 BGE 或者 Jina AI 的嵌入模型。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇