在构建基于大语言模型(LLM)的智能应用中,处理原始文档是非常关键的一步。LangChain 作为一个强大的框架,提供了一整套用于文档处理的工具链,帮助开发者更高效地将非结构化文本转化为模型可理解的结构化信息。整个文档处理流程通常包括三个核心步骤:文档加载(Loading)、文档清理(Cleaning) 和 文档分割(Splitting)。
- 文档加载是处理流程的起点,负责从各种数据源中读取原始内容,例如本地文件、网页、数据库或云存储等。
- 文档清理则聚焦于内容的预处理,如去除无用信息、统一编码格式、移除 HTML 标签等,以提升后续处理质量。
- 文档分割是将长文档切分成更小的文本块,方便嵌入生成和检索任务,并能在保持上下文连贯的前提下控制每块的长度。
这三个步骤构成了 LangChain 在文本处理链条中的基础部分,为文档理解、知识库构建、RAG(Retrieval-Augmented Generation)等下游应用打下了坚实基础。接下来,我们将分别介绍这三个步骤的核心思路及其在 LangChain 中的典型实现方式。
1. 文档加载
from langchain_community.document_loaders import TextLoader from langchain_community.document_loaders import PyPDFLoader from langchain_community.document_loaders import WebBaseLoader from langchain_community.document_loaders import DirectoryLoader # 1. 读取 txt 文档 def demo01(): docs = TextLoader('documents/demo01.txt', encoding='utf-8').load() for doc in docs: print(doc) # 2. 读取 pdf 文档 def demo02(): docs = PyPDFLoader('documents/demo02.pdf').load() for doc in docs: print(doc) # 3. 读取网页文档 def demo03(): docs = WebBaseLoader('https://mengbaoliang.cn/archives/108979/').load() for doc in docs: print(doc) # 4. 读取目录 def demo04(): loader = DirectoryLoader('documents/text', glob='*.txt', loader_cls=lambda path: TextLoader(path, encoding='utf-8')) docs = loader.load() for doc in docs: print(doc.page_content) if __name__ == '__main__': # demo01() demo02() # demo03() # demo04()
2. 文档清理
from dotenv import load_dotenv load_dotenv('llm.env') from langchain_community.document_loaders import PyPDFLoader from langchain_core.documents import Document from langchain_deepseek import ChatDeepSeek from langchain.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser def demo(): docs = PyPDFLoader('documents/demo02.pdf', mode='page').load() print(len(docs[0].page_content)) return llm = ChatDeepSeek(model='deepseek-chat') template = ''' 请对以下非结构化文本进行清洗、提炼,并输出为纯中文、结构紧凑、无格式符号的纯文本,用于中文知识库。 要求如下: 清洗无用内容:删除所有 HTML 标签、脚本、页眉页脚、页码、脚注、参考文献格式、乱码、特殊符号、重复或无意义文字。 仅保留核心内容:提取正文主旨、核心概念、论点、结论等有价值的信息,其余略去。 统一翻译为中文:如原文为英文或其他语言,请翻译为通顺自然的中文表达,不逐句直译,注重信息准确。 不保留标题格式:不要将标题单独展示或加粗、加符号、换行。请将标题信息自然地融合进正文内容中进行描述。 不保留段落空行:段落之间请直接接续书写,不添加任何空行。 不使用 Markdown 或代码格式:输出中请勿包含任何 Markdown 标记、列表符号(如 - * # 等)、代码块标记(如```)。 如有表格,请转为自然语言描述,不保留表格格式。 最终输出应为纯中文内容,结构紧凑、语义清晰、段落连续、无格式符号的纯文本,适合知识库收录使用。 {doc} ''' chain = PromptTemplate.from_template(template) | llm | StrOutputParser() clean_docs = [] full_doc = Document() for doc in docs: clean_doc = chain.invoke({'doc': doc}) clean_doc = Document(page_content=clean_doc) clean_docs.append(clean_doc) full_doc += clean_doc break print(clean_docs) if __name__ == '__main__': demo()
3. 分档分割
from langchain.text_splitter import CharacterTextSplitter def demo01(): text = 'ab,cd,ef,gh,ij,kl,mn,op,qr,st,uv,wx,yz' # 1. 先根据分隔符划分出多个split ==> ['ab', 'cd', 'ef', 'gh', 'ij', 'kl', 'mn', 'op', 'qr', 'st', 'uv', 'wx', 'yz'] # 2. keep_separator='end' ==> ['ab,', 'cd,', 'ef,', 'gh,', 'ij,', 'kl,', 'mn,', 'op,', 'qr,', 'st,', 'uv,', 'wx,', 'yz'] # 3. 通过合并多个split构建每一个 chunk # 3.1 第一个 chunk ==> 'ab,cd,ef,' # 3.2 第二个 chuck ==> 需要重叠前面的 4 个字符 ',ef,',但是由于会截断前面的split 'cd,',所以只保留 'ef,' 从此处开始构建第二个 chunk,即:'ef,gh,ij,' # 3.3 以此类推... splitter = CharacterTextSplitter(separator=',', keep_separator='end', chunk_size=10, chunk_overlap=4) chunks = splitter.split_text(text) print([len(chunk) for chunk in chunks]) print(len(chunks), chunks) from langchain.text_splitter import RecursiveCharacterTextSplitter def demo02(): text = 'ab,cd。ef,gh。ij,kl。mn,op。qr,st。uv,wx。yz' splitter = RecursiveCharacterTextSplitter(separators=['。', ',', ''], keep_separator='end', chunk_size=2, chunk_overlap=1) chunks = splitter.split_text(text) print([len(chunk) for chunk in chunks]) print(len(chunks), chunks) if __name__ == '__main__': # demo01() print('-' * 100) demo02()