LangChain 文档加载与文本切分:从零搭建企业知识库预处理流水线

LangChain 文档加载与文本切分:从零搭建企业知识库预处理流水线 手把手教你读取 PDF、TXT、Markdown并用智能切分让 AI 精准检索一、前言为什么需要文档加载和切分前几章我们处理的数据都来自代码里的字符串。但真实的企业知识库——产品说明书、员工手册、售后规则、技术文档——都静静地躺在各类文件里。要让大模型“读懂”这些资料第一步就是把这些文件加载成程序可处理的结构化数据。然而一个文件动辄几十页甚至上百页直接喂给模型会遇到三大拦路虎上下文长度限制模型的输入窗口有限放不下整本书成本高昂每次请求都带全量文档token 消耗飙升检索精度低用户只问一个小问题却要扫描整本书既慢又容易漏掉关键信息div aligncenter img srchttps://img-blog.csdnimg.cn/direct/placeholder-doc-pipeline.png alt文档处理流程概览 width700/ br/ em图文档预处理 → 切分 → 向量化 → 检索回答的完整链条/em /div本章聚焦在“文档加载”与“文本切分”这两个前置环节为后续的向量检索和 RAG 打下坚实基础。二、核心概念Document 对象LangChain 用统一的Document类来表示一段文档它有两个核心属性属性类型说明page_contentstr文档的正文内容metadatadict附加信息如文件路径、页码、分类等python# 01_document_basic.py from langchain_core.documents import Document doc Document( page_content这是员工手册的正文内容……, metadata{ source: data/employee_handbook.txt, file_type: txt, page: 1 } ) print(doc.page_content) print(doc.metadata)元数据为什么重要当模型根据某个文档块生成答案后我们可以通过元数据告诉用户“这个答案来自《员工手册》第 3 页”提升可信度。三、加载各类文件3.1 安装依赖在项目环境中安装所需包国内可换清华源bashpip install langchain-community langchain-text-splitters pypdf依赖作用langchain-community提供文档加载器langchain-text-splitters提供文本切分器pypdf读取 PDF 文件3.2 加载 TXT 文件使用TextLoader它会自动读取文本文件并包装成Document。准备测试文件data/employee_handbook.txttext员工考勤制度 1. 工作时间上午 9:00 - 下午 18:00午休 1 小时。 2. 迟到早退每月累计迟到超过 3 次扣除绩效奖金。 3. 请假流程需提前一天在 OA 系统提交申请。加载代码02_load_text.pypythonfrom langchain_community.document_loaders import TextLoader loader TextLoader(data/employee_handbook.txt, encodingutf-8) docs loader.load() # 返回 List[Document] for doc in docs: print(doc.page_content) print(doc.metadata)⚠️ 注意即使单个文件load()依然返回列表方便统一处理。3.3 加载 Markdown 文件Markdown 本质也是文本直接用TextLoader即可。准备文件data/refund_policy.mdmarkdown# 售后退款政策 ## 适用条件 - 商品未拆封7 天内可无理由退货。 - 质量问题15 天内可换货或退款。 ## 退款流程 1. 联系客服提交申请。 2. 寄回商品运费由我方承担。 3. 审核通过后 3 个工作日内原路退款。加载代码03_load_markdown.pypythonloader TextLoader(data/refund_policy.md, encodingutf-8) docs loader.load() print(docs[0].page_content)3.4 加载 PDF 文件文本型对于文字型 PDF非扫描版使用PyPDFLoader它会按页拆分文档每页生成一个Document。准备文件将任意文本型 PDF 放到data/product_manual.pdf。加载代码04_load_pdf.pypythonfrom langchain_community.document_loaders import PyPDFLoader loader PyPDFLoader(data/product_manual.pdf) docs loader.load() for doc in docs: print(f页码: {doc.metadata[page]}) print(doc.page_content[:100]) # 只打印每页前100字符 print(- * 30)每个Document的元数据包含source文件路径和page页码从 0 开始。局限性扫描版 PDF 需要 OCR如pypdfocr或unstructured复杂表格可能需要专用解析器本章不展开。四、为什么需要切分长文档假设员工手册有 200 页直接喂给模型会带来超长上下文超过模型窗口限制高成本每次请求都传 200 页内容低召回用户问“考勤制度”你却要扫描整本手册检索效率低下所以必须把长文档切成语义独立的文档块chunksdiv aligncenter img srchttps://img-blog.csdnimg.cn/direct/placeholder-split-concept.png alt长文档切分示意图 width600/ br/ em图长文档 → 分割成若干语义块 → 每个块可独立检索/em /div五、RecursiveCharacterTextSplitter智能切分器LangChain 推荐使用RecursiveCharacterTextSplitter它会按一组分隔符递归切分优先保留段落和句子的完整性。5.1 核心参数参数作用chunk_size每个文档块的最大字符数不是 tokenchunk_overlap相邻块之间重叠的字符数用于保留上下文separators分隔符列表默认[\n\n, \n, , ]5.2 理解chunk_size与chunk_overlap举个例子文档内容为textA B C D E F G H I J K L M N O P Q R S T U V W X Y Zchunk_size10chunk_overlap2切分结果text块1: A B C D E F G H I J 块2: I J K L M N O P Q R S 重叠 I J 块3: R S T U V W X Y Z 重叠 R S重叠部分可以让被切断的句子在相邻块中都能保留完整语义避免“话说到一半”导致信息丢失。5.3 切分普通文本示例05_split_text.pypythonfrom langchain_text_splitters import RecursiveCharacterTextSplitter text 员工考勤制度 1. 工作时间上午 9:00 - 下午 18:00午休 1 小时。 2. 迟到早退每月累计迟到超过 3 次扣除绩效奖金。 3. 请假流程需提前一天在 OA 系统提交申请。 员工福利 1. 五险一金按国家规定缴纳。 2. 年度体检每年 10 月统一安排。 3. 团建活动每季度一次。 splitter RecursiveCharacterTextSplitter( chunk_size50, chunk_overlap10, separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_text(text) for i, chunk in enumerate(chunks, start1): print(f块 {i}:\n{chunk}\n)5.4 切分 Document 并保留元数据真实项目中我们更常用split_documents()它不仅切分文本还会保留原 Document 的元数据并自动添加_start_index记录每个块在原文中的起始位置。06_split_documents.pypythonfrom langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(data/employee_handbook.txt, encodingutf-8) docs loader.load() splitter RecursiveCharacterTextSplitter( chunk_size100, chunk_overlap20 ) chunks splitter.split_documents(docs) for i, chunk in enumerate(chunks, start1): print(f块 {i} (来源: {chunk.metadata[source]})) print(chunk.page_content) print(- * 40)输出中会看到每个块的元数据都包含了原始文件路径以及_start_index等额外信息。六、企业级案例知识库文档预处理6.1 需求描述公司内部知识库目录包含三种文件data/employee_handbook.txt员工制度data/refund_policy.md售后规则data/product_manual.pdf产品手册需要编写一个预处理程序完成递归扫描目录按扩展名加载文件为每个文档块添加filename、file_type等元数据用RecursiveCharacterTextSplitter切分所有文档输出文档块总数及前 3 块预览6.2 完整代码document_processor.pypythonfrom pathlib import Path from langchain_community.document_loaders import TextLoader, PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter def load_documents_from_directory(directory: str): 递归加载目录下所有 .txt, .md, .pdf 文件 base_path Path(directory) all_docs [] # 递归遍历所有文件 for file_path in base_path.rglob(*): if not file_path.is_file(): continue suffix file_path.suffix.lower() try: if suffix in [.txt, .md]: loader TextLoader(str(file_path), encodingutf-8) elif suffix .pdf: loader PyPDFLoader(str(file_path)) else: continue # 跳过其他格式 docs loader.load() # 追加文件级别的元数据 for doc in docs: doc.metadata[filename] file_path.name doc.metadata[file_type] suffix[1:] # 去掉点号 doc.metadata[source_path] str(file_path) all_docs.extend(docs) except Exception as e: print(f加载 {file_path} 失败: {e}) return all_docs def split_documents(docs, chunk_size300, chunk_overlap50): 使用递归字符切分器拆分文档列表 splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapchunk_overlap, separators[\n\n, \n, 。, , , , ] ) return splitter.split_documents(docs) if __name__ __main__: # 1. 加载所有文档 raw_docs load_documents_from_directory(knowledge_base) print(f共加载 {len(raw_docs)} 个原始文档段如 PDF 按页拆分) # 2. 切分 chunks split_documents(raw_docs) print(f切分后共有 {len(chunks)} 个文档块) # 3. 预览前3块 print(\n--- 文档块预览 ---) for i, chunk in enumerate(chunks[:3], start1): print(f块 {i}) print(f来源文件: {chunk.metadata.get(filename)}) print(f内容预览: {chunk.page_content[:80]}...) print(- * 40)6.3 运行效果text共加载 5 个原始文档段如 PDF 按页拆分 切分后共有 23 个文档块 --- 文档块预览 --- 块 1 来源文件: employee_handbook.txt 内容预览: 员工考勤制度 1. 工作时间上午 9:00 - 下午 18:00午休 1 小时。 2. 迟到早退每月累计迟到超过 3 次... ---------------------------------------- 块 2 来源文件: employee_handbook.txt 内容预览: 2. 迟到早退每月累计迟到超过 3 次扣除绩效奖金。 3. 请假流程需提前一天在 OA 系统提交申请。 ...七、如何选择合适的切分参数没有放之四海而皆准的参数需要根据文档类型和检索效果调整。文档类型建议策略短 FAQchunk_size200~300尽量让每个问答独立规章制度按段落切分overlap可稍大50~100产品手册可稍大500~800避免操作步骤被拆散技术文档保留代码块和注释的完整性适当增大分隔符列表验证方法切分后人工抽查几个块看语义是否完整再结合后续检索测试观察召回准确率。八、常见问题Q:chunk_size是字符数还是 token 数A:RecursiveCharacterTextSplitter默认按字符数len()不是 token。若需按 token 切分可使用TokenTextSplitter需安装 tiktoken。Q:chunk_overlap越大越好吗A: 不是。适当重叠10%~20%能保留上下文但过大会增加重复存储和检索噪音。Q: PDF 提取出来是空白或乱码怎么办A: 很可能是扫描版 PDF图片。需要 OCR 工具如pytesseract或使用UnstructuredPDFLoader等更强大的加载器。Q: 为什么不直接整篇文档交给模型A: 成本、窗口限制、检索精度三座大山。切分是 RAG 系统的标准前置步骤。九、本章总结知识点要点Documentpage_contentmetadata加载文件TextLoaderTXT/MDPyPDFLoaderPDF切分必要性解决超长文本、成本、检索精度问题推荐切分器RecursiveCharacterTextSplitter关键参数chunk_size和chunk_overlap保留元数据用split_documents()而不是split_text()完整流程文件 → Loader → Document → Splitter → 文档块供后续向量化下一章我们将把这些切分好的文档块转化为向量并存入向量数据库正式构建 RAG 检索系统敬请期待