
本文详解 langchain 结合 pypdfloader、openaiembeddings 与 chroma 构建 rag 系统时“检索返回空源文档”的常见原因及完整修复流程,涵盖文档加载、分块、向量化与持久化各关键环节。
在 LangChain 中构建基于 PDF 的检索增强生成(RAG)系统时,一个典型痛点是:retriever.get_relevant_documents(query) 能成功返回 Document 对象列表,但 len(docs) 却为 0 —— 表明检索器未实际加载或识别任何源文档。这通常并非模型或 API 问题,而是数据流水线中的关键步骤被遗漏或错配所致。
根本原因往往在于:Chroma.from_documents() 必须接收已加载并分块后的 Document 对象(即含 page_content 和 metadata 的 LangChain Document 实例),而不能直接传入原始文本字符串或未经处理的文件路径。若跳过 loader.load() 或误用 split_documents(),会导致 texts 为空或结构不合法,进而使 Chroma 库无法正确索引元数据(如来源文件路径、页码等),最终 get_relevant_documents() 返回空列表。
✅ 正确且完整的端到端代码如下:
from langchain.document_loaders import DirectoryLoader, PyPDFLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# 1. 加载所有 PDF 文档(必须显式调用 .load())
loader = DirectoryLoader('./files/', glob='*.pdf', loader_cls=PyPDFLoader)
documents = loader.load() # ⚠️ 关键:不可省略!返回 Document 列表
# 2. 分块(保持 Document 结构,自动继承 metadata)
text_splitter = CharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separator="\n"
)
texts = text_splitter.split_documents(documents) # ✅ 输入是 Document,输出仍是 Document
# 3. 创建嵌入与向量库(持久化)
embeddings = OpenAIEmbeddings()
persist_directory = "./chroma_db"
docsearch = Chroma.from_documents(
texts,
embeddings,
persist_directory=persist_directory
)
# 4. 加载并检索(确保使用同一 embedding_function)
docsearch = Chroma(persist_directory=persist_directory, embedding_function=embeddings)
retriever = docsearch.as_retriever(search_kwargs={"k": 5})
docs = retriever.get_relevant_documents("你的查询问题")
print(f"检索到 {len(docs)} 个相关文档")
for i, doc in enumerate(docs):
print(f"[{i+1}] 来源: {doc.metadata.get('source', 'unknown')}, 页码: {doc.metadata.get('page', 'N/A')}")? 关键注意事项:
- DirectoryLoader.load() 必须显式调用,否则 documents 为 DirectoryLoader 对象而非文档列表;
- split_documents()(非 split_text())才能保留 Document 的 metadata(含 source, page 等),这对溯源至关重要;
- 初始化 Chroma 时,embedding_function 参数必须与创建时完全一致(包括模型名称、API key 环境、版本),否则向量维度不匹配将导致静默失败;
- 若仍返回空,请检查 ./files/ 目录是否存在 PDF 文件,以及 persist_directory 是否被其他进程占用或权限受限;
- 推荐首次运行后打印 texts[0] 查看其结构,确认 page_content 和 metadata 字段存在且非空。
通过严格遵循上述流程,即可确保源文档被正确加载、分块、嵌入并持久化,从而让 get_relevant_documents() 稳定返回带完整元数据的 Document 对象,真正支撑可解释、可追溯的 RAG 应用。










