
RAG 介绍什么是 RAG核心定义RAG 是 Retrieval-Augmented Generation 的缩写中文通常译为检索增强生成它是一种将大语言模型内部知识与外部知识库结合起来的技术范式主要用来缓解 LLM 只依赖训练参数回答问题时可能出现的知识过时、细节不准和缺少依据等问题从知识来源看LLM 在预训练过程中学到的内容可以称为参数化知识这类知识被固化在模型权重中具有泛化能力强但更新困难的特点企业文档、数据库、网页、知识库和实时业务数据则属于非参数化知识这类知识更具体、更可控也可以在不重新训练模型的情况下持续更新RAG 的关键价值就是在模型生成答案之前先从外部知识库中检索相关材料再把这些材料作为上下文交给模型生成回答从而提升回答的准确性、可解释性和时效性一句话理解RAG 就是让 LLM 从闭卷答题变成开卷答题它既可以调用自己已经学到的通用知识也可以临时查阅外部资料来回答具体问题工作原理RAG 系统通常通过两个阶段完成参数化知识与非参数化知识的结合第一阶段是检索目标是从外部知识库中找到与用户问题最相关的内容第二阶段是生成目标是让 LLM 基于检索到的上下文组织答案检索阶段寻找非参数化知识知识向量化嵌入模型负责把文本、段落或文档片段编码成向量表示这些向量会被写入向量索引或向量数据库便于后续按语义相似度进行搜索语义召回当用户提出问题时系统会使用同一个或兼容的嵌入模型把问题转换成向量随后通过相似度搜索从大量文档片段中召回最可能包含答案的内容生成阶段融合两类知识上下文整合生成模块会接收用户原始问题以及检索阶段返回的相关文档片段这些信息会被组织进提示词形成模型可理解的上下文指令引导生成系统通过预设 Prompt 明确回答方式、引用依据和边界要求引导 LLM 基于检索材料生成更可控、更有依据的答案例如可以要求模型只依据上下文回答缺少依据时直接说明不知道技术演进RAG 的技术架构经历了从简单线性流程到复杂可编排系统的演进按照常见划分方式大致可以分为初级 RAG、高级 RAG 和模块化 RAG 三类维度初级 RAGNaive RAG高级 RAGAdvanced RAG模块化 RAGModular RAG流程离线索引在线检索 → 生成离线索引在线检索前优化 → 检索 → 检索后优化 → 生成积木式、可编排、可替换的流程特点基础线性流程容易搭建在检索前后加入优化步骤效果更稳定模块化、可组合、可动态调整适合复杂业务系统关键技术基础向量检索查询重写Query Rewrite、结果重排Rerank动态路由Routing、查询转换Query Transformation、多路融合Fusion局限性效果不稳定排查和优化空间有限流程仍相对固定系统弹性有限系统复杂度高对工程设计、评估和运维能力要求更高这里的离线是指用户提问前提前完成的数据预处理工作例如文档清洗、切分、向量化和索引构建在线是指用户发起请求之后实时发生的检索、排序、上下文组装和生成流程为什么要使用 RAGRAG 与微调在为大模型应用选择技术路线时一个核心原则是先使用成本更低、对模型改动更小的方法一般来说可以优先尝试提示词工程Prompt Engineering再考虑 RAG最后才考虑微调Fine-tuning从优化对象看提示词工程主要优化输入方式不改变模型权重也不额外引入知识库RAG 仍然不改变模型权重但会通过外部知识库增强上下文微调则会直接更新模型参数更适合改变模型的行为模式、风格偏好或输出格式基于这一思路常见选择路径如下先尝试提示词工程如果任务较简单且模型本身已经具备相关知识可以通过更清晰的角色设定、任务描述、输出格式和示例来提升回答质量再选择 RAG如果模型缺少特定领域知识、私有知识或最新知识就可以通过外部知识库为模型补充上下文让模型有依据地回答最后考虑微调如果目标不是让模型知道更多而是让模型更稳定地按照特定方式做事例如严格遵循某种输出格式、模仿特定对话风格或把复杂指令内化为模型行为微调才更合适RAG 填补了通用模型与专业领域之间的鸿沟当 LLM 遇到下面的典型局限时RAG 通常是一种更直接、更经济的解决方案问题RAG 的解决方案静态知识局限实时检索外部知识库支持知识动态更新幻觉Hallucination基于检索内容生成回答降低无依据编造的概率领域专业性不足引入医疗、法律、金融、企业制度等领域知识库数据隐私风险支持本地化或私有化部署知识库减少敏感数据外泄风险核心优势准确性与可信度的双重提升RAG 最核心的价值是突破模型预训练知识的边界它可以补充专业领域的知识盲区也可以通过向模型提供具体参考材料降低模型一本正经胡说八道的概率相关研究表明RAG 生成内容在具体性和多样性上通常优于纯 LLM 回答更重要的是RAG 具备可溯源性回答可以关联到原始文档出处这种有据可查的能力能显著提升法律、医疗、金融和企业知识问答等严肃场景中的可信度时效性保障LLM 的训练数据通常存在时间边界因此模型可能不知道训练截止日期之后发生的事件、政策变化或业务更新RAG 通过把知识库与模型本体解耦让新政策、新产品资料或新业务数据在入库后即可被检索使用这种能力也常被称为索引热拔插Index Hot-swapping它类似于为系统切换一套可更新的外部知识存储而不需要重新训练模型显著的综合成本效益从成本角度看RAG 通常比频繁微调更经济它避免了每次知识更新都重新训练模型的高昂算力成本也允许团队在部分专业场景中使用参数规模更小的基础模型当外部知识检索足够准确时小模型也可能在特定任务上获得接近大模型的效果从而进一步降低推理成本灵活的模块化可扩展性RAG 架构天然支持多源数据集成PDF、Word、网页、数据库、内部知识库和结构化表格都可以经过处理后进入知识系统同时RAG 将检索与生成解耦使团队能够独立优化嵌入模型、向量数据库、重排模型、Prompt 模板和生成模型而不必每次都重建整套系统适用场景与风险RAG 并不是只要接入知识库就万无一失不同场景的风险不同对质量控制、人类审核和可解释性的要求也不同风险等级案例RAG 适用性低风险翻译、语法检查、普通资料问答通常可靠性较高可作为自动化工具使用中风险合同起草、法律咨询、医疗资料解释需要结合人工审核并保留引用来源高风险证据分析、签证决策、诊疗建议、金融决策需要严格质量控制机制不应完全自动化决策如何上手 RAG基础工具链构建 RAG 系统通常涉及开发框架、数据处理、向量存储、检索策略、生成模型和评估工具等环节在开发模式上可以使用 LangChain 或 LlamaIndex 等成熟框架快速搭建也可以选择不依赖框架的原生开发方式以获得更细粒度的流程控制LangChain 当前官方文档会把常见 RAG 实现区分为 RAG agent 与 2-Step RAG chain 等形态LlamaIndex 则强调从数据加载、索引、检索到生成的完整流程在记忆载体方面向量数据库负责存储和检索文本向量Milvus、Pinecone 等更适合大规模或生产级场景FAISS、Chroma 等更适合本地实验、轻量应用或快速原型在效果评估方面可以引入 Ragas 或 TruLens 等工具把回答相关性、忠实性、上下文召回率等指标纳入持续评估流程其中 Ragas 官方文档已经将其定位为面向 LLM 应用的系统化评估与实验工具最小可行系统MVP第一步 数据准备与清洗这是 RAG 系统的地基首先需要把 PDF、Word、网页和数据库导出的多源异构数据转换成统一文本或结构化格式随后要去除噪声、重复内容和无效片段并设计合理的分块策略相比机械地按固定字符数切分按标题、段落、语义边界或业务单元切分通常更有利于保留上下文完整性第二步 索引构建将切分后的文本片段交给嵌入模型转换成向量后写入向量数据库或向量索引在这个阶段建议同步保存元数据例如来源文件、章节、页码、更新时间和权限标签这些元数据不仅方便引用溯源也能用于权限过滤和结果排序第三步 检索策略优化不要只依赖单一向量搜索在实际业务中可以组合向量检索与关键词检索形成混合检索策略提高召回率对于召回结果还可以加入重排序模型进行二次筛选让 LLM 优先看到与问题最相关、质量最高的上下文第四步 生成与提示工程最后需要设计清晰的 Prompt 模板把用户问题、检索上下文、回答要求和安全边界组织在一起建议明确要求模型基于给定上下文回答无法从上下文判断时说明不知道并在需要时给出引用来源这样可以减少幻觉也能让用户更容易追溯答案依据进阶挑战当一个基础 RAG 系统能够跑通之后下一阶段的重点不再是能不能回答而是回答是否稳定、是否正确、是否可解释以及出现错误时能否定位原因评估维度与挑战一套 RAG 系统的质量不能只凭主观感觉判断业界通常会从检索相关性、上下文召回率、生成质量、回答忠实性和术语准确性等维度进行评估其中检索相关性关注系统是否找到了包含答案的材料生成质量关注回答是否清晰、完整且符合任务要求语义准确性关注回答含义是否正确词汇匹配度则关注专业术语是否使用得当这些评估维度也对应着 RAG 的主要挑战如果检索系统召回了错误材料再强的 LLM 也可能基于错误上下文生成貌似合理但事实错误的答案对于需要跨多个文档、多个实体或多个时间点综合分析的多跳推理任务普通线性 RAG 流程也常常力不从心优化方向与架构演进针对这些问题社区和工业界已经探索出多种优化路径在性能层面可以通过索引分层、缓存高频数据、批量向量化和检索结果复用来提升效率在能力层面可以扩展到多模态检索让图片、表格、图表和结构化数据也参与问答在架构层面简单的检索再生成流程正在被更灵活的设计模式取代例如系统可以通过分支模式并行触发多路检索再通过融合策略汇总结果也可以通过循环模式让模型检查答案质量、发现信息不足并再次检索这些模式让 RAG 从单一链路逐步演进为可诊断、可调度、可自我修正的知识系统数据加载文档加载器在 RAG 系统中数据加载是整个数据管道的第一步也是不可省略的一步文档加载器负责把 PDF、Word、Markdown、HTML 等非结构化文档转换为程序可以处理的结构化数据加载效果会直接影响索引构建、召回质量和最终生成质量文档加载器通常需要完成三个核心任务第一解析不同格式的原始文档把 PDF、Word、Markdown 等内容提取为可处理的文本第二在解析过程中同步抽取文档来源、页码、作者、标题层级等元数据第三把文本和元数据整理成统一的数据结构便于后续切分、向量化和入库从数据工程角度看文档加载器做的事情类似抽取、转换、加载ETL它的目标不是简单把文件读成字符串而是把杂乱的原始文档清洗、解析和标准化变成适合检索与建模的语料不同加载器的优势并不相同有些适合轻量文本有些适合复杂 PDF有些适合网页抓取还有些更适合企业级多格式解析实际选型时需要结合文档类型、解析精度、速度、部署方式和成本综合判断当前主流 RAG 文档加载器工具名称特点适用场景性能表现PyMuPDF4LLM面向 LLM 场景的 PDF 到 Markdown 转换适合保留段落、标题和表格等结构科研文献、技术手册开源免费解析速度快依赖轻TextLoader基础文本文件加载通常用于 .txt 等纯文本文件纯文本处理轻量高效适合入门和简单数据DirectoryLoader批量加载目录下的文件可搭配不同文件加载器处理多种格式混合格式文档库支持多格式扩展适合批处理Unstructured多格式文档解析支持 PDF、Word、HTML、Markdown 等格式通用文档预处理、RAG 数据入库统一接口结构识别能力强FireCrawlLoader网页内容抓取与清洗适合在线资料获取在线文档、新闻、网页知识库支持实时内容获取LlamaParse面向复杂 PDF 的结构化解析服务法律合同、学术论文、复杂版式 PDF解析精度较高通常以商业 API 形式使用Docling模块化企业级文档解析工具强调结构保留和企业场景企业合同、报告、内部知识文档IBM 生态兼容适合严肃文档处理MarkerPDF 到 Markdown 转换工具重点处理论文、书籍等长文档科研文献、书籍专注 PDF 转换可利用 GPU 加速MinerU面向复杂文档的多模态解析工具兼顾布局、文字、表格和公式学术文献、财务报表集成布局分析与目标检测等能力Unstructured 文档处理库核心优势Unstructured 是一个面向非结构化数据预处理的文档解析库常用于 RAG 和 AI 微调前的数据准备它的核心价值在于提供统一接口把不同格式的文件解析为一组带有类型和元数据的文档元素从而降低多格式文档接入成本Unstructured 在格式支持和结构识别方面都有明显优势一方面它支持 PDF、Word、Excel、HTML、Markdown 等多种文档格式可以避免为每种格式单独编写解析代码另一方面它可以识别标题、正文、列表、表格、页眉、页脚、图片说明等文档结构并保留来源文件、页码、文件类型、坐标等元数据信息支持的文档元素类型Unstructured 会把文档解析成一系列 Element 对象每个元素通常包含元素类型、文本内容、元素 ID 和元数据这种结构比普通字符串更适合 RAG因为后续可以按元素类型过滤噪声也可以用元数据实现溯源、分页引用和权限控制Unstructured 官方文档中列出的常见元素类型如下元素类型描述Title文档标题或章节标题NarrativeText由多个完整句子组成的正文文本不包括标题、页眉、页脚和说明文字ListItem列表项属于列表中的正文文本元素Table表格内容Image图像相关元数据Formula文档中的公式元素Address物理地址EmailAddress邮箱地址FigureCaption图片标题或说明文字Header文档页眉Footer文档页脚CodeSnippet代码片段PageBreak页面分隔符PageNumber页码UncategorizedText未分类的自由文本CompositeElement分块处理后产生的复合元素CompositeElement 是分块处理时产生的特殊元素类型由一个或多个连续文本元素组合而成例如多个连续列表项可能会被合并为一个块官方文档也强调CompositeElement 只会在分块阶段产生不是原始分区时直接识别出的基础元素使用 Unstructured 加载文档LangChain 提供的 UnstructuredMarkdownLoader是框架基于 Unstructured 能力封装的专用文档加载器能够适配 LangChain 完整工作流快速完成 Markdown 文档的读取与加载依据新版 LangChain 官方文档该加载器归属在 langchain_community.document_loaders 模块使用前需安装 langchain-community 与 unstructured 依赖库同时原生支持 load()、lazy_load() 等多种文档加载方式而直接调用 Unstructured 原生 API能够实现更细粒度的自定义控制开发者可自主指定 PDF 拆分规则、调整解析策略、配置 OCR 识别语言开启表格结构解析还能单独提取图片、表格等模块内容面对复杂排版 PDF、扫描件文档或是需要完整保留原文结构的业务场景直接使用 Unstructured 原生接口会比 LangChain 封装工具更加灵活可控示例代码安装 Poppler底层 PDF 工具负责读取 PDF 结构、把 PDF 每页转成图片、获取页数https://github.com/oschwartz10612/poppler-windows/releases/download/v25.12.0-0/Release-25.12.0-0.zip解压到纯英文路径复制路径如 C:\poppler\Library\bin加入系统环境变量 Path重启终端 / 编辑器安装 Tesseract-OCR开源 OCR 工具识别图片、扫描件里的文字https://github.com/UB-Mannheim/tesseract/releases/download/v5.4.0.20240606/tesseract-ocr-w64-setup-5.4.0.20240606.exe步骤同上下载中文语言包 chi_sim.traineddata 放入目录Tesseract-OCR\tessdata 下使用 Unstructured 解析一个 PDF 文件from collections import Counter from pathlib import Path from unstructured.partition.auto import partition # PDF 文件路径 pdf_path Path(./test.pdf) # 使用 Unstructured 的 partition 函数解析 PDFpartition 会根据文件类型自动选择合适的解析方式 elements partition( filenamestr(pdf_path), # PDF 文件路径需要转成字符串 content_typeapplication/pdf, # 指定文件类型为 PDF ) # 统计所有解析元素的文本字符总数str(element) 可以拿到该元素的文本内容 total_chars sum(len(str(element)) for element in elements) print(f解析完成: {len(elements)} 个元素, {total_chars} 字符) # 统计不同元素类型的数量 # element.category 表示元素类别例如 Title、NarrativeText、ListItem、Table 等 types Counter(element.category for element in elements) print(f元素类型: {dict(types)}) # 所有解析出来的元素 print(\n所有元素:) for index, element in enumerate(elements, 1): # 当前元素的编号和类型 print(fElement {index} ({element.category}):) # 当前元素的文本内容 print(element) print( * 91)代码执行过程首先 partition 会读取指定 PDF 文件并根据 content_typeapplication/pdf 直接按 PDF 类型处理接着解析结果会以元素列表形式返回每个元素都有自己的类别和文本内容最后代码统计元素类别并逐个打印元素方便观察 PDF 被解析成了哪些结构通用解析函数 partitionpartition 是 Unstructured 提供的通用文档解析入口它会根据文件类型自动路由到对应的专用解析函数例如 PDF 会路由到 partition_pdfMarkdown 会路由到 partition_mdHTML 会路由到 partition_html如果系统安装了 libmagic会优先使用它识别文件类型否则会回退到文件扩展名识别参数说明filename本地文档路径适合处理磁盘上的文件content_type可选参数用于指定 MIME 类型例如 application/pdf传入后可以绕过部分自动检测逻辑file文件对象与 filename 二选一使用url远程文档地址支持直接处理网络文档include_page_breaks是否在输出中包含页面分隔符仅部分文件类型支持strategyPDF 和图片等文件的解析策略常见值包括 auto、fast、hi_res、ocr_onlyencoding文本编码格式默认会自动检测入门学习时使用 partition 足够简单便捷但在生产环境中如果已经明确文件类型更推荐直接调用对应的专用解析函数既能减少自动类型检测的开销也能使用更多针对该文件类型的专属参数PDF 解析函数 partition_pdf如果需要更专业地处理 PDF可直接使用 partition_pdf它支持 OCR 语言配置、表格结构推断、图片与表格块提取等 PDF 专用能力根据 Unstructured 官方文档PDF 解析策略包含 auto、fast、hi_res 和 ocr_only 四种模式from pathlib import Path from unstructured.partition.pdf import partition_pdf pdf_path Path(./test.pdf) elements partition_pdf( filenamestr(pdf_path), strategyhi_res, # 使用高精度解析策略 infer_table_structureTrue, # 尽量识别表格结构 ) for element in elements: print(element.category, str(element)[:80])几种策略可以这样理解策略适用情况特点auto不确定文档质量时的默认选择自动根据文档特征和参数选择策略fastPDF 本身可复制、可提取文本速度快主要使用规则和传统文本提取方式hi_res复杂版式、表格、图片和布局结构重要使用布局检测模型结构识别更强但速度较慢ocr_only扫描件或图片型 PDF通过 OCR 提取文字再按文本进行解析在实际应用中PDF 处理往往是 RAG 数据加载中最容易踩坑的部分如果文档包含复杂表格、公式、图片、脚注、多栏排版或扫描页除了 Unstructured也可以评估 PaddleOCR、MinerU、Marker、Docling 等工具工具选择不应只看是否能读取文本还要看结构保留、表格还原、公式识别、速度、部署成本和后续检索效果文本分块文本分块Text Chunking是构建 RAG 流程的关键步骤它的作用是把加载后的长篇文档切分成更小、更容易处理的内容单元这些内容单元就是后续向量检索、重排和大模型生成时使用的基本单位可以把文本分块理解为把一本书拆成若干张知识卡片如果卡片太大检索时很难准确命中关键内容如果卡片太小又可能丢失上下文导致模型不知道这句话属于哪个主题优秀的分块策略目标是在语义完整性、检索精度和模型上下文限制之间取得平衡为什么需要文本分块上下文长度限制文本分块的首要原因是为了适应 RAG 系统中两个核心组件的输入长度限制嵌入模型和大语言模型嵌入模型Embedding Model负责把文本块转换成向量这类模型通常有明确的输入长度上限例如常见的 bge-base-zh-v1.5 这类模型通常以约 512 token 作为默认最大输入长度具体限制应以模型配置为准如果文本块超过输入上限超出部分可能被截断导致信息丢失最终生成的向量也无法完整代表原文语义大语言模型LLM负责根据检索到的上下文生成答案LLM 也有上下文窗口限制虽然这个窗口通常比嵌入模型大很多可能从几千 token 到上百万 token 不等检索到的所有文本块、用户问题、系统提示词和回答格式要求都必须放进这个窗口如果单个块过大模型一次能参考的块数量就会减少回答时可利用的信息广度也会受限因此文本分块是保证文本能够被嵌入模型和大语言模型完整、有效处理的基础它不是可有可无的预处理步骤而是 RAG 系统稳定运行的前提块过大的问题假设嵌入模型最多能处理 8192 个 token是否应该把每个块都切到接近 8000 token答案通常是否定的块大小并不是越大越好过大的块反而会降低 RAG 系统的检索和生成效果嵌入过程中的信息损失大多数嵌入模型都基于 Transformer 编码器其处理流程通常包括分词、向量化和池化三个阶段分词Tokenization把输入文本拆分成一个个 token向量化VectorizationTransformer 为每个 token 生成高维向量表示池化Pooling通过 [CLS] 向量、平均池化Mean Pooling等方式把所有 token 的向量压缩成一个单一向量用这个向量表示整个文本块的语义[CLS] 是 BERT 等 Transformer 模型在输入文本开头添加的特殊标记它会通过自注意力机制聚合整个序列的上下文信息在某些模型中最终的 [CLS] 向量会被用来表示整体语义在这种压缩过程中信息损失不可避免一个 768 维或 1024 维向量需要概括整个文本块的内容文本块越长包含的语义点越多单一向量承载的信息就越稀释这会让向量表示变得笼统关键细节被模糊化进而降低检索精度生成阶段的关键信息淹没即使把多个大块文本都放进 LLM 的长上下文窗口也可能出现关键信息被大量无关内容淹没的问题研究表明当模型处理很长且信息密集的上下文时往往更容易利用开头和结尾的信息而中间部分更容易被忽略这就是常说的 Lost in the Middle 现象如果提供给 LLM 的上下文块又大又杂里面充满与问题无关的噪声模型就更难找到真正关键的信息这会导致回答质量下降甚至诱发幻觉主题稀释导致检索失败一个好的文本块应该尽量聚焦于一个明确主题如果一个块同时包含太多不相关主题它的语义表示会被稀释检索时就很难被精确匹配合理分块可以提升检索信噪比让生成环节拿到更相关、更干净的上下文因此分块策略不只是工程细节而是影响 RAG 质量的核心因素基础分块策略长度分块 CharacterTextSplitter长度分块是最容易理解的分块方法它会根据预设分隔符和目标块大小将文本切成若干块在 LangChain 中CharacterTextSplitter 的默认分隔符通常是 \n\n因此它并不是完全机械地按固定字符数硬切而是会优先按段落边界切分再通过合并逻辑控制块大小根据 LangChain 的实现逻辑这个过程可以拆成两个阶段按段落分割使用默认分隔符 \n\n 将文本分成若干段落片段智能合并调用父类的合并逻辑把片段依次合并成块并根据 chunk_size 与 chunk_overlap 控制目标长度和重叠内容需要注意CharacterTextSplitter 不是严格意义上的固定大小切分它会优先保持段落完整性只有添加新段落会超过目标大小时才结束当前块如果单个段落本身超过 chunk_size可能会形成超长块因此更准确的理解是段落感知的长度分块from langchain_community.document_loaders import TextLoader from langchain_text_splitters import CharacterTextSplitter # 加载纯文本文件返回 Document 对象列表 loader TextLoader(./test.txt, encodingutf-8) documents loader.load() # 按段落优先切分文本并限制每个块的大致长度 text_splitter CharacterTextSplitter( separator\n\n, # 优先用空行作为分隔符 chunk_size200, # 每个文本块的目标最大长度 chunk_overlap10, # 相邻文本块之间保留 10 个字符重叠 ) # 把加载得到的 Document 列表切分成多个更小的块 chunks text_splitter.split_documents(documents) print(f文本被切分为 {len(chunks)} 个块\n) print(--- 前 5 个块内容示例 ---) for index, chunk in enumerate(chunks[:5], 1): print( * 78) # page_content 是当前文本块的正文内容 print(f块 {index} 长度: {len(chunk.page_content)}) print(chunk.page_content)这种方法的优势是实现简单、速度快、计算开销小劣势是它对语义边界的理解有限可能在语义上相关的内容之间切断文本因此它适合日志分析、规则化文本预处理、简单知识库入门实验等场景但不一定适合复杂知识文档递归字符分块 RecursiveCharacterTextSplitterRecursiveCharacterTextSplitter 是 LangChain 官方文档中推荐优先尝试的通用分块器它使用一组按优先级排列的分隔符递归切分文本目标是在尽量保持段落、句子、词语等自然结构完整的同时让每个块满足目标大小它的默认分隔符通常是 [\n\n, \n, , ]含义是先尽量按段落切分如果块仍然过大再按换行切分再按空格切分最后才按字符切分对于中文、日文、泰文等没有明显词间空格的语言可以额外加入中文标点、全角标点和零宽空格算法流程可以概括为三步第一步 寻找有效分隔符从分隔符列表前到后遍历找到当前文本中存在的第一个分隔符如果都不存在就使用最后一个分隔符通常是空字符串第二步 切分与分类处理使用选定分隔符切分文本不超过块大小的片段会暂存并等待合并超过块大小的片段会继续递归使用更细粒度的分隔符第三步 合并最终片段把剩余的合格片段按 chunk_size 与 chunk_overlap 合并成最终文本块与 CharacterTextSplitter 相比递归字符分块最大的优势是能继续处理超长段落如果段落太长它会尝试句子级、词级甚至字符级切分而不是只保留超长块from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(./test.txt, encodingutf-8) documents loader.load() text_splitter RecursiveCharacterTextSplitter( separators[\n\n, \n, \u3002, \uff0c, , ], # 段落 行 中文句号 中文逗号 空格 空字符串 chunk_size200, chunk_overlap10, ) chunks text_splitter.split_documents(documents) print(f文本被切分为 {len(chunks)} 个块)对于中文等语言可以使用下面这种更完整的分隔符配置separators [ \n\n, \n, , ., ,, \u200b, # 零宽空格 \uff0c, # 中文逗号 \u3001, # 中文顿号 \uff0e, # 全角句号 \u3002, # 中文句号 , # 中文逗号 , ]RecursiveCharacterTextSplitter 还可以针对代码文件使用语言特化分隔符from langchain_text_splitters import Language, RecursiveCharacterTextSplitter # 创建一个适用于 Python 代码的递归切分器 # 它会优先按照 Python 代码中更自然的结构进行切分例如类、函数、空行、换行等而不是直接生硬地按字符数截断 splitter RecursiveCharacterTextSplitter.from_language( languageLanguage.PYTHON, # 指定要切分的内容是 Python 代码 chunk_size500, # 每个代码块的目标最大长度约为 500 个字符 chunk_overlap50, # 相邻代码块之间保留 50 个字符重叠避免上下文被切断 )LangChain 官方文档列出了多种支持语言例如 Python、Java、JavaScript、TypeScript、C、C、Markdown、HTML、LaTeX 等这些语言预设会尽量在类、函数、控制流等结构附近切分使代码块更符合程序逻辑语义分块 SemanticChunker语义分块Semantic Chunking是一种更智能的切分方法它不只依赖字符数或固定分隔符而是尝试在语义主题发生明显变化的地方切开文本这样得到的每个块往往具有更强的内部语义一致性LangChain 提供了实验性的 SemanticChunker该组件位于 langchain_experimental.text_splitter 模块中使用前需要额外安装 LangChain 实验扩展包因为它依赖嵌入模型计算句子之间的语义距离所以速度通常慢于字符分块成本也更高pip install langchain_experimentalSemanticChunker 的工作流程可以概括为五步第一步 句子切分先按句子规则把文本拆成句子列表第二步 上下文感知嵌入通过 buffer_size 把每个句子与前后若干句子组合后再做嵌入让句子向量包含局部上下文第三步 语义距离计算计算相邻句子嵌入向量之间的余弦距离第四步 断点识别根据统计方法识别语义距离明显变大的位置第五步 合并成块按断点把句子序列合并为语义更连贯的文本块SemanticChunker 支持多种断点识别方法可以通过 breakpoint_threshold_type 设置方法含义适用理解percentile百分位法默认方法只把语义差异最大的少数位置作为断点standard_deviation标准差法把超过平均值若干倍标准差的位置视为异常跳跃interquartile四分位距法用 IQR 识别语义差异异常值gradient梯度法关注语义差异变化率适合语义整体较平稳但局部有拐点的文本from langchain_community.document_loaders import TextLoader from langchain_community.embeddings import DashScopeEmbeddings from langchain_experimental.text_splitter import SemanticChunker # 创建文本嵌入模型用于把句子转换成向量 embeddings DashScopeEmbeddings( modeltext-embedding-v4, ) # 创建语义切分器 # 它会先按句子拆分文本再根据句子之间的语义关系决定在哪里切块 text_splitter SemanticChunker( embeddingsembeddings, # 使用上面的嵌入模型做语义判断 breakpoint_threshold_typepercentile, # 用百分位方法决定切分阈值 sentence_split_regexr(?[\u3002\uff01\uff1f.!?])\s*, # (正则表达式)按中英文句末标点切句 ) loader TextLoader(./test.txt, encodingutf-8) documents loader.load() # 按语义而不是按固定字符数切分文本 chunks text_splitter.split_documents(documents) print(f文本被切分为 {len(chunks)} 个语义块)语义分块适合主题变化明显、段落边界不够可靠、且对检索质量要求较高的文档它的主要成本是需要调用嵌入模型并且参数调优比字符分块更复杂文档结构分块 MarkdownHeaderTextSplitter对于结构明确的文档格式例如 Markdown、HTML、LaTeX直接利用标题和结构标记进行分块通常更合理这类方法不是只看文本长度而是先理解文档的层级结构再把内容组织成逻辑块以 Markdown 为例LangChain 提供了 MarkdownHeaderTextSplitter它可以按标题层级切分文档并把标题路径保存到每个块的元数据中根据当前官方文档该类同样从 langchain_text_splitters 导入from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter # 一段 Markdown 格式的文本示例 markdown_document # 第三章 模型评估 ## 2.5 评估指标 准确率、召回率和 F1 是常见评估指标 # 指定要识别哪些 Markdown 标题层级 # 每个元组表示(Markdown 标记, metadata 中对应的字段名) headers_to_split_on [ (#, Header 1), (##, Header 2), (###, Header 3), ] # 创建 Markdown 标题切分器 markdown_splitter MarkdownHeaderTextSplitter( headers_to_split_onheaders_to_split_on, strip_headersFalse, # 切分后的文本内容中保留标题本身 ) # 先按 Markdown 标题结构切分 # 返回的是 Document 列表每个 Document 除了正文还有标题层级 metadata header_splits markdown_splitter.split_text(markdown_document) # 创建递归字符切分器 # 把按标题切好的内容继续按长度切成更小的块 text_splitter RecursiveCharacterTextSplitter( chunk_size250, # 每个块的目标最大长度约为 250 个字符 chunk_overlap30, # 相邻块之间保留 30 个字符重叠 ) # 对按标题切好的文档再次切分 chunks text_splitter.split_documents(header_splits) print(f文本被切分为 {len(chunks)} 个块) # 1 for chunk in chunks: print(chunk.page_content) # 每个块的文本内容 print(chunk.metadata) # 每个块的 metadata # {Header 1: 第三章 模型评估, Header 2: 2.5 评估指标}这个流程可以理解为两阶段分块第一步使用 MarkdownHeaderTextSplitter 按标题结构形成较大的逻辑块并给每个块写入标题元数据第二步再使用 RecursiveCharacterTextSplitter 控制块大小让最终块既保留文档结构又满足检索和模型输入长度要求标题元数据非常有价值例如某个段落位于 第三章 模型评估 下的 2.5 评估指标 中分块后它的元数据可以记录 {Header 1: 第三章 模型评估, Header 2: 3.2 评估指标}这样模型在回答问题时不仅能看到局部内容还能知道内容来自哪个章节背景这种结构化分块特别适合技术文档、产品手册、政策文件、课程讲义和知识库文章它的局限是依赖文档本身结构质量如果原始 Markdown 标题混乱或层级不清分块效果也会受到影响其他框架的分块策略Unstructured 元素分块Unstructured 也提供了实用的文档分块能力它的思路与普通文本分块不同不是先把文档变成纯文本再按字符切而是先通过分区Partitioning把 PDF、HTML、Word 等文档解析成一组结构化元素再在元素列表上进行分块Unstructured 官方文档强调分块是在文档元素上执行的通常会把连续元素组合成尽可能接近目标大小的块如果单个元素本身超过最大块大小才会对该元素进行文本切分这种做法能尽量保留标题、段落、列表、表格等语义单元的完整性Unstructured 主要提供两类分块策略策略说明适用场景basic默认策略连续组合元素直到接近 max_characters如果单个元素过长则拆分该元素通用文档处理by_title在 basic 基础上增加章节感知遇到 Title 元素时开启新块报告、书籍、论文、结构化文档Unstructured 支持在分区时直接指定分块策略也支持先分区再调用分块函数前者流程更简洁后者更适合调参from unstructured.partition.html import partition_html # 解析网页并在解析时直接完成切块 chunks partition_html( urlhttps://2778.com, # 要解析的网页地址 chunking_strategyby_title, # 按标题结构切块尽量保持语义完整 max_characters1000, # 每个块的最大字符数上限 new_after_n_chars800, # 块内容达到约 800 字符后倾向于开始新块 )这种先理解再分割的策略让 Unstructured 在处理版式复杂文档时更有优势尤其是 PDF、报告、网页、扫描件和包含表格的资料通常比普通纯文本切分更稳定LlamaIndex 节点解析LlamaIndex 将数据处理流程抽象为节点Node操作文档被加载后会被解析成一系列节点分块只是节点转换Transformation中的一环这种设计让 LlamaIndex 更强调数据结构、元数据和索引之间的关系LlamaIndex 的节点解析器大致可以分为三类结构感知型例如 MarkdownNodeParser、JSONNodeParser、CodeSplitter适合按 Markdown 标题、JSON 结构或代码函数切分语义感知型例如 SemanticSplitterNodeParser会使用嵌入模型检测句子之间的语义断点SentenceWindowNodeParser 会把文档切成句子节点并在元数据中保留前后相邻句子的窗口常规型例如 TokenTextSplitter、SentenceSplitter适合按 token 数量或句子边界进行常规切分SentenceWindowNodeParser 是一个很有代表性的设计它先用单个句子做精确检索再把句子附近的上下文窗口一起送给 LLM这样既能提升检索精度又能让生成阶段拿到足够上下文LlamaIndex 还支持灵活的转换流水线例如可以先用 MarkdownNodeParser 按章节切分再对章节节点使用 SentenceSplitter 做更细粒度切分每个节点都会携带来源和上下文元数据方便后续检索、溯源和调试另外LlamaIndex 提供了 LangchainNodeParser可以把 LangChain 的 TextSplitter 封装成 LlamaIndex 的节点解析器这让两套生态之间具备较好的互操作性ChunkViz 可视化工具ChunkViz 是一个简单直观的可视化分块工具本章开头展示的分块图就是通过 ChunkViz 生成的它可以把文档和分块配置作为输入用不同颜色展示每个 chunk 的边界和重叠部分方便观察分块是否过大、过碎或重叠不合理对于新手来说ChunkViz 的价值在于把抽象的分块参数变成可见结果当你修改 chunk_size、chunk_overlap 或分隔符时可以直观看到块边界如何变化从而更容易理解不同策略的效果