我们已经或多或少知道了大模型存在的缺陷数据不实时缺少垂直领域数据和私域数据等。解决这些缺陷的主要方法是通过检索增强生成RAG。首先检索外部数据然后在执行生成步骤时将其传递给LLM。LangChain为RAG应用程序提供了从简单到复杂的所有构建块数据检索Retrieval模块包括与检索步骤相关的所有内容例如数据的获取、切分、向量化、向量存储、向量检索等模块。1.Document loaders 文档加载模块在上面的图中已经清晰的知道第一步是必须有数据源第二步就是读取数据源LangChain为开发者封装了很多文档加载模块例如 pdf、csv、json、word、md格式文件我们先看一下如何加载1.加载本地pdf、word文件LangChain中加载pdf使用的是pypdf需要先安装一下我们在前面其实也读取过pdf不过用的是原生的方式如果要做那种可以更精细化的处理例如读取pdf中的表格行用原生的比较好pip install pypdfpdf加载from langchain_community.document_loaders import PyPDFLoader loader PyPDFLoader(物理知识点.pdf) print(loader) page loader.load() print(page[0].page_content)word加载from langchain_community.document_loaders import UnstructuredWordDocumentLoader # 指定要加载的Word文档路径 loader UnstructuredWordDocumentLoader(语文.docx) print(loader) # 加载文档并分割成段落或元素 documents loader.load() print(documents) # 输出加载的内容 for doc in documents: print(doc.page_content)2.文档切分模块上一步我们已经对已有的数据源进行加载当然也不局限于本地文件其实数据源可以来源于很多途径数据库查询接口获取甚至用于直接通过接口调用写入的都归属于数据加载这一环节那么文档切分模块就是对已经加载的数据源进行切分然后向量化那么为什么要切分呢简单说分割就是为了检索精度和信息完整性之间找一个平衡点如果不进行分割直接把所有数据作为检索单元会存在一些问题1.把整个源数据作为转换为向量那么整个向量的语义就会非常宽泛2.大语言模型的上下文窗口是有限的处理太长的文本会增加计算成本3.将整个数据作为上下文相当于把答案藏在大海模型需要从里面筛选信息无异于大海捞针而且容易被一些不相关的噪声干扰导致质量下降当然分割也不是越细越好如果仅仅按照固定字符强行切断很容易破坏数据完整性例如:1.上下文丢失例如原文是“我是小余。我喜欢吃西瓜”如果被切分成“我是小余”和 ”我喜欢吃西瓜“2个块当用户问“小余喜欢吃什么“的时候或者包含”我喜欢吃西瓜”的时候可能就检索不到。2.破坏结构对于表格、列表、操作步骤这一些结构化的的内容分割不好会将他们拆散导致查出来的信息缺胳膊少腿用都没法用。所以一些做的比较好的检索会采用一些更智能的分割方法例如按语义分割在段落、章节这些自然的边界上进行切分保证从语义上相对完整。什么是语义简单来说就是归类例如当一辆无人驾驶的汽车通过摄像头看到前方的景像有汽车道路、行人、建筑语义分割就是所有道路的像素都被标记为灰色。所有汽车的像素都被标记为蓝色。所有行人的像素都被标记为红色。所有建筑的像素都被标记为紫色这样汽车就不再是看一张复杂的图片而是得到了一张清晰的指令图灰色区域可以行驶红色区域必须避让同理文档也是一样核心就是分类。重叠分割在相邻的块之间保留一部分重叠来让上下文衔接增加连贯性这也是用的比较多的一种直接上图。在LangChain中框架也提供了许多不同类型的文本切分器分割器类型核心逻辑适用场景爽点坑点CharacterTextSplitter数着字数切不管内容啥意思凑够指定字数就咔嚓一刀。简单的纯文本或者对语义没啥要求的场景。快不需要加载任何模型简单直接。傻很容易把一句完整的话从中间切断导致上下文不连贯。RecursiveCharacterTextSplitter层层递进地切先试着按段落切切不开再按换行切再切不开按句号切...直到切小为止。最推荐。适合绝大多数长文档、文章、书籍。聪明尽量保证不把句子切断保留了语义完整性。需要稍微花点心思调整分隔符的优先级。TokenTextSplitter按模型“词汇量”切按 Token 数量切而不是按字数。因为大模型是按 Token 收费和计数的。需要严格控制成本或者怕超出模型上下文限制比如 4K/8K 限制时。精准能完美匹配大模型的输入限制不会超支。计算稍微复杂一点点其实也就是一瞬间的事。Language 专属分割器按代码语法切懂编程语言的逻辑按函数、类来切而不是瞎切。处理源代码Python, JS, Java 等。专业能把完整的函数或类打包在一起不会把代码切碎。挑食只支持特定的编程语言普通文本用不了。SemanticChunker按意思切利用 AI 模型判断两句话“像不像”意思变了就切一刀。对检索质量要求极高的场景比如复杂的知识库问答。高质量切出来的每一段都是一个完整的意思检索效果最好。贵且慢因为要调用 AI 模型去理解全文费钱又费时。MarkdownHeaderTextSplitter按标题切专门识别 Markdown 的#号把每个章节单独切出来。结构化文档比如 GitHub 的 README 文件、技术文档。有结构能保留文档的层级关系知道这段话属于哪个标题。挑食只认 Markdown 格式普通文本用不了。首选万金油方案就是使用RecursiveCharacterTextSplitter是LangChain默认推荐的因为它在速度和语义之间取得了平衡。建议配置chunk_size1000, chunk_overlap200 不过具体数值视模型上下文而定。如果还有比较特殊格式处理像代码库问答那必须用 Language作为分割器.例如我用cursor写c#他就是基于Tree-sitter来进行语法分块然后向量化当用户询问某个函数的逻辑他会把匹配的分块结果自行交给大模型推导语义无需 Roslyn 这类重型的编译器介入不过这里不要误解Tree-sitter号称支持50语言的语法分析。这里再扩展一下语义和语法的区别这个之前学习roslyn时里面提到过会解析语法树好多api的意思之前我也有点懵逼现在搞清白了语法就是长什么样而语义就是代表“代码实际在做什么”下面来用RecursiveCharacterTextSplitter来进行分档分割他有几个重点参数1.chunk_size每个切块的token数量2.chunk_overlap相邻2个块重复的token数就是重叠分割的意思重叠几个字符看上面的图就能明白假设一个数字一个token 。# 导入 PDF 加载器模块 from langchain_community.document_loaders import PyPDFLoader # 导入递归字符文本分割器模块 from langchain_text_splitters import RecursiveCharacterTextSplitter # 1. 加载 PDF 文档 # 使用 PyPDFLoader 初始化加载器指定文件路径为 财务管理文档.pdf loader PyPDFLoader(葵花宝典完整版.pdf) # 调用 load_and_split() 方法加载文档并按页进行初步分割返回一个包含 Document 对象的列表 pages loader.load_and_split() # 2. 初始化文本分割器 # 创建 RecursiveCharacterTextSplitter 实例用于将长文本切分成小块 text_splitter RecursiveCharacterTextSplitter( chunk_size200, # 每个文本块的最大字符数设置为 200 chunk_overlap100, # 文本块之间的重叠字符数设置为 100以保持上下文连贯 length_functionlen # 使用内置的 len 函数来计算文本长度按字符数计算 ) # 3. 数据清洗与提取 # 遍历 pages 列表提取每页的 page_content 内容并使用 replace 方法去除所有的换行符(\n)和空格 # 如果 pages 不为空则执行列表推导式否则 data 被赋值为空列表 [] data [page.page_content.replace(\n,).replace( ,) for page in pages ] if pages else [] # 4. 执行文本切分 # 使用 text_splitter 将清洗后的纯文本列表 (data) 转换为 Document 对象列表 result text_splitter.create_documents(data) # 5. 打印结果 print(result) # 打印整个 result 列表查看对象概览 # 6. 遍历并输出每个文本块的详细信息 for page in result: # 打印每个文本块的具体内容和其对应的字符长度 print(page.page_content, len(page.page_content))这个网站可以可视化展示文本如何分割