1. 本章目标
企业知识库资料存放在各类文件:产品说明书、员工手册、售后规则、技术文档、FAQ。使用大模型加载私有资料两大核心步骤:读取文件 → 切分文档
学习完成后掌握:
- 理解 LangChain Document 对象
- 加载 TXT、Markdown 文件
- 加载文本型 PDF 文件
- 理解长文档切分的必要性
- 使用 RecursiveCharacterTextSplitter
- 合理配置 chunk_size、chunk_overlap
- 保留来源、页码等元数据 metadata
- 实现企业知识库文档预处理完整案例
2. 文档处理标准流程
plaintext
TXT / MD / PDF 文件 ↓ Document Loader(文档加载器) ↓ Document 列表 ↓ Text Splitter(文本切分器) ↓ 小型 Document 文档块 chunk ↓ Embedding 向量化 → 向量数据库本章范围:文件 → Document → 文档块 下一章:文档块向量化、存入向量数据库
3. 安装依赖
bash
运行
pip install langchain-community langchain-text-splitters pypdf # 清华镜像加速 pip install langchain-community langchain-text-splitters pypdf -i https://pypi.tuna.tsinghua.edu.cn/simple表格
| 依赖 | 作用 |
|---|---|
| langchain-community | 提供各类文档加载器 |
| langchain-text-splitters | 文本切分工具 |
| pypdf | 解析文本型 PDF |
本章无需调用大模型,不会消耗 API 额度
4. Document 文档对象
LangChain 使用Document统一封装文本数据。两个核心属性:
page_content:文档正文内容metadata:元数据(字典格式,描述数据的数据)
示例代码:01_document_basic.py
python
运行
from langchain_core.documents import Document document = Document( page_content="公司所有正式员工每年享有 5 天带薪年假。", metadata={ "source": "员工手册.md", "category": "考勤制度", } ) print(document.page_content) print(document.metadata)常用元数据字段:文件路径、文件名、PDF 页码、文档分类、chunk_id、文本起始位置。作用:问答时展示资料来源;支持向量库进行条件过滤筛选。
5. 文件加载实战
5.1 TextLoader 加载 TXT
目录结构
plaintext
chapter06/ ├── data/ │ └── employee_handbook.txt └── 02_load_text.pypython
运行
from langchain_community.document_loaders import TextLoader loader = TextLoader( file_path="data/employee_handbook.txt", encoding="utf-8", ) documents = loader.load() print(f"文档数量:{len(documents)}") print(f"文档内容:\n{documents[0].page_content}") print(f"元数据:{documents[0].metadata}")重点:
load()返回Document 列表,单个文件同样返回列表encoding="utf-8"解决 Windows 环境中文乱码
5.2 TextLoader 加载 Markdown
md 文件可直接使用 TextLoader03_load_markdown.py
python
运行
from langchain_community.document_loaders import TextLoader loader = TextLoader( file_path="data/refund_policy.md", encoding="utf-8" ) documents = loader.load() print("文档数量:" + str(len(documents))) print(documents[0].page_content) print(documents[0].metadata)5.3 PyPDFLoader 加载 PDF
⚠️ 仅支持可复制文字的文本 PDF;扫描图片版 PDF 需要 OCR 识别04_load_pdf.py
python
运行
from langchain_community.document_loaders import PyPDFLoader loader = PyPDFLoader( file_path="data/XX销售有限公司员工守则.pdf" ) documents = loader.load() print("文档数量:" + str(len(documents))) PDF 每一页自动生成一个独立 Document for document in documents: print(document.page_content[-20:]) print(document.metadata) print("当前页码:", document.metadata['page'])注意:metadata 中page页码从 0 开始计数
6. 为什么需要切分长文档
超长文档直接存入向量库存在诸多问题:
- 文本长度超出大模型上下文窗口限制
- 全文参与向量化与检索,Token 成本高
- 用户仅询问局部信息,大量无关文本造成干扰
- 向量相似度检索难以精准定位片段
解决方案:将大文档切分成多个语义相对独立的小块(chunk)
7. RecursiveCharacterTextSplitter 递归文本切分器
中文知识库首选切分器。分隔符优先级:["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
切分逻辑:优先按段落切割;段落过长则逐级降级,依次使用换行、句号、逗号,最后强制切割,最大限度保证语句完整。
核心参数
chunk_size:单个文档块最大长度(默认按字符统计)chunk_overlap:相邻文档块重叠字符数量
重叠作用:避免完整语句被一刀切断,保留上下文关联
重叠不宜过大:会产生大量重复文本,增加存储压力与重复检索结果。推荐初始参数:
chunk_size=300,chunk_overlap=50
两个核心方法
split_text(text)接收普通字符串,返回字符串列表,丢失元数据,仅用于测试split_documents(documents)接收 Document 列表,返回 Document 列表,保留元数据,正式项目使用
案例 1:切分普通文本 05_split_text.py
python
运行
from langchain_text_splitters import RecursiveCharacterTextSplitter text = """ 员工考勤制度 工作时间为周一至周五,每天 9:00 至 18:00。 员工每月可以申请两次补卡。超过两次后,需要部门负责人审批。 员工请假制度 请假一天以内由直属负责人审批。 请假超过一天,需要部门负责人审批。 病假需要提供医院开具的有效证明。 员工年假制度 正式员工每年享有 5 天带薪年假。 工作满三年后,每年享有 10 天带薪年假。 """ splitter = RecursiveCharacterTextSplitter( chunk_size=300, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_text(text) print(len(chunks)) for index, chunk in enumerate(chunks, start=1): print("*" * 40) print(f"第{index}个文件块的内容:") print(chunk)补充:enumerate(对象, start=1)同时获取序号与内容,start 自定义起始编号
案例 2:切分 Document,保留元数据 06_split_documents.py
python
运行
from langchain_community.document_loaders import PyPDFLoader from langchain_core.documents import Document from langchain_text_splitters import RecursiveCharacterTextSplitter loader = PyPDFLoader( file_path="data/XX销售有限公司员工守则.pdf" ) documents = loader.load() print("原始文档数量:", len(documents)) splitter = RecursiveCharacterTextSplitter( chunk_size=200, chunk_overlap=30, add_start_index=True, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(documents) print("切分后文档数量:", len(chunks)) for index, chunk in enumerate(chunks, start=1): print(isinstance(chunk, Document)) print(f"第{index}文档预览:") print(chunk.page_content[:20]) print(chunk.metadata)add_start_index=True:自动在元数据中增加文本在原始文档内的起始位置
8. 企业综合案例:批量知识库预处理
需求
- 递归扫描知识库文件夹,读取所有文件
- 根据文件后缀自动选择对应加载器(txt/md/pdf)
- 统一追加自定义元数据
- 批量切分文档,产出可直接存入向量库的 chunk
前置知识点
- 列表 / 集合 / 字典推导式
python
运行
# 列表推导式 even = [i for i in range(1,11) if i%2==0] # 集合推导式(自动去重) set_data = {x for x in [1,2,2,3]} # 字典推导式 square_dict = {i:i*i for i in range(1,6)}- pathlib 文件路径操作
rglob("*"):递归遍历当前目录 + 所有子文件夹glob("*"):仅遍历当前文件夹,不进入子目录.suffix文件后缀;.name完整文件名;.stem不带后缀文件名
- append vs extend
append():将对象整体作为 1 个元素加入列表extend():遍历可迭代对象,逐个追加内部元素
document_processor.py 完整代码
python
运行
from pathlib import Path from langchain_community.document_loaders import TextLoader, PyPDFLoader from langchain_core.documents import Document from langchain_text_splitters import RecursiveCharacterTextSplitter def split_documents(documents: list[Document]) -> list[Document]: splitter = RecursiveCharacterTextSplitter( chunk_size=200, chunk_overlap=30, add_start_index=True, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) chunks = splitter.split_documents(documents) for index, chunk in enumerate(chunks, start=1): chunk.metadata['chunk_id'] = index return chunks def load_knowledge_base(root_dir: Path) -> list[Document]: all_documents = [] for f in root_dir.rglob("*"): if f.is_dir(): continue file_suffix = f.suffix.lower() file_path = str(f) docs = [] if file_suffix in ['.txt', '.md']: loader = TextLoader( file_path=file_path, encoding="utf-8" ) docs = loader.load() elif file_suffix == '.pdf': loader = PyPDFLoader(file_path=file_path) docs = loader.load() # 统一追加自定义元数据 for document in docs: document.metadata["file_name"] = f.name document.metadata["file_type"] = file_suffix all_documents.extend(docs) return all_documents def main(): kb_path = Path("knowledge_base") documents = load_knowledge_base(kb_path) print("原始文档数量:", len(documents)) chunks = split_documents(documents) print("切分后文档块数量:", len(chunks)) for chunk in chunks: print("~" * 30) metadata = chunk.metadata print(f"chunk_id:{metadata['chunk_id']}") print(f"来源文件:{metadata['file_name']}") print(f"内容预览:{chunk.page_content[:30]}") if name == 'main': main()运行命令
bash
运行
python document_processor.py9. 切分参数选型参考
表格
| 文档类型 | 配置思路 |
|---|---|
| FAQ 问答文档 | chunk_size 偏小,保证单条问答完整不拆分 |
| 员工制度、规范文档 | chunk_size 200 ~ 350 |
| 产品操作手册 | 块尺寸适当放大,避免操作步骤被切断 |
| 技术文档 | 尽量保留标题和对应正文,chunk 偏大 |
调优判断标准:语义完整、标题不与正文分离、操作步骤不截断、无大量重复 chunk,最终依靠检索效果验证参数。
10. 常见问题
chunk_size 统计字符还是 token?默认按照字符长度
len()计算;需要精确按 token 切割使用TokenTextSplitter(进阶)chunk_overlap 设置越大越好?不是,推荐取值为 chunk_size 的 10% ~ 20%
PDF 提取空白、乱码?文件为扫描图片 PDF、加密 PDF、特殊字体 PDF;本章加载器仅支持原生文本 PDF
为什么不能直接把完整文档交给模型?超长文本超出上下文限制、token 成本高、检索精准度差;RAG 标准流程必须分块
load() 和 lazy_load()
load():一次性全部加载到内存,适合小型知识库lazy_load():迭代器懒加载,节省内存,适合海量文件场景
11. 本章核心总结
- Document = page_content(正文) + metadata(元数据)
- TextLoader 加载 txt/md;PyPDFLoader 加载文本 PDF