第一部分:导入模块
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_community.vectorstores import Chroma
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain.indexes import VectorstoreIndexCreator
解析:
| 模块 | 作用 |
|---|---|
PyMuPDFLoader |
用于加载 PDF 文件(基于 PyMuPDF,即 fitz 库) |
Chroma |
向量数据库,存储文本向量 |
OllamaEmbeddings |
用于生成文本嵌入(embedding)的模型(如 qwen3-embedding) |
ChatOllama |
用于生成自然语言回答的大模型(如 qwen3:8b) |
VectorstoreIndexCreator |
自动化工具,简化"加载 → 分块 → 嵌入 → 存储"流程 |
第二部分:加载 PDF 并构建索引(方式1)
from langchain_text_splitters import RecursiveCharacterTextSplitter
llm = ChatOllama(model="qwen3:8b")
pdf_loader = PyMuPDFLoader("docker_info.pdf")
pdf_pages = pdf_loader.load()
embed = OllamaEmbeddings(model="qwen3-embedding:0.6b")
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", ". ", "? ", "! ", "; ", " ", '"'],
chunk_size=50,
chunk_overlap=10,
length_function=len
)
inter_creator = VectorstoreIndexCreator(
vectorstore_cls=Chroma,
embedding=embed,
text_splitter=text_splitter,
vectorstore_kwargs={"persist_directory": "./test_db1"}
)
index = inter_creator.from_documents(pdf_pages)
res1 = index.query("Docker引擎主要有那两个版本", llm)
print(res1)
解析:
-
加载 PDF
PyMuPDFLoader("docker_info.pdf"):读取 PDF 内容。load()返回的是Document对象列表(每个页或段落是一个 Document)。
-
设置文本分块器
RecursiveCharacterTextSplitter:递归按字符分割文本。- 参数说明:
chunk_size=50:每块约 50 字符(中文),适合短句。chunk_overlap=10:相邻块之间重叠 10 字符,避免断句。separators:优先按换行、句号等切分。
-
创建向量化索引
VectorstoreIndexCreator是一个自动化工具,整合了:- 分块
- 嵌入
- 存入 Chroma
persist_directory="./test_db1":持久化保存数据到本地磁盘,程序重启后仍可使用。
-
查询
index.query(...):调用 LLM + 检索,自动完成"检索 + 生成"。- 输出是 LLM 生成的回答。
注意:这种方式是 一键式流程,适合快速原型开发。
第三部分:手动构建 RAG 流程(方式2,推荐用于生产)
from langchain.chains.retrieval_qa.base import RetrievalQA
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_ollama import ChatOllama, OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 初始化模型
llm = ChatOllama(model="qwen3:8b")
embedding = OllamaEmbeddings(model="qwen3-embedding:0.6b")
# 加载 PDF
pdf_loader = PyMuPDFLoader("docker_info.pdf")
pdf_docs = pdf_loader.load()
# 关键步骤:分块!
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块约 500 字符(中文)
chunk_overlap=50, # 块间重叠 50 字符,避免切断句子
separators=["\n\n", "\n", ". ", "? ", "! ", "; ", " ", '"']
)
split_docs = text_splitter.split_documents(pdf_docs) # 分块后的文档列表
# 创建向量库(使用分块后的文档)
db = Chroma.from_documents(split_docs, embedding=embedding)
# 构建 QA 链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 把检索结果拼接进 prompt
retriever=db.as_retriever(search_kwargs={"k": 2}), # 返回最相关的2个文档
return_source_documents=True # 可选:返回引用的原文
)
# 查询
result = qa_chain.invoke({"query": "2015年6月,Docker牵头成立了什么组织"})
print("回答:", result["result"])
print("\n引用来源:")
for doc in result["source_documents"]:
print("-" * 50)
print(doc.page_content[:200])
解析:
这是更 完整、可控、可调试 的 RAG 实现方式。
步骤详解:
-
初始化模型
llm: 大模型(qwen3:8b)用于生成答案。embedding: 嵌入模型(qwen3-embedding:0.6b)用于向量化。
-
加载文档
PyMuPDFLoader读取 PDF,得到原始Document列表。
-
分块(Chunking)
RecursiveCharacterTextSplitter按规则切分文本。- 设置
chunk_size=500更适合中文长句,避免切在中间。 chunk_overlap=50保证上下文连贯。
-
构建向量库
Chroma.from_documents(...):将分块后的文档存入向量库。- 元数据(如页码、标题)会自动保留。
-
构建 QA 链
RetrievalQA是标准 RAG 链。chain_type="stuff":把所有检索到的文档内容拼接进 Prompt。retriever.search_kwargs={"k": 2}:只取最相关的 2 个文档。return_source_documents=True:返回原始文档内容,方便溯源。
-
查询与输出
- 输入问题:"2015年6月,Docker牵头成立了什么组织?"
- 输出:
- 回答(LLM 生成)
- 引用来源(原文片段)
总结对比
| 方式 | 特点 | 适用场景 |
|---|---|---|
VectorstoreIndexCreator |
简单快捷,一键完成 | 快速原型、学习演示 |
| 手动构建 RAG 链 | 控制力强,可调试,支持复杂逻辑 | 生产环境、需要溯源、优化性能 |
建议与优化
-
调整 chunk_size
- 中文建议 300~500 字符,避免句子被截断。
-
启用持久化
db = Chroma.from_documents(..., persist_directory="./my_db")这样下次无需重新加载和分块。
-
增加过滤器
- 可以通过
metadata过滤特定章节或页码。
- 可以通过
-
使用
reranker或hybrid search- 提升检索准确率。
-
部署为 API
- 用 FastAPI + LangChain 封装成服务。