LangChain 学习笔记四文档加载、文本切分与 Embedding一次搞懂 RAG 入库前处理这是 LangChain 学习笔记系列的第四篇。前三篇主要整理了基础调用链路、工具调用、结构化输出、Few-shot 和 RAG 入门。从这一篇开始进入 RAG 真正落地时绕不开的工程细节文档如何加载、如何切分、如何变成向量以及这些步骤为什么会影响最终检索效果。1. 为什么这一篇很重要很多人刚开始学 RAG会把注意力放在怎么调用大模型 怎么用向量数据库 怎么写 RAG chain但真正做项目时很快会发现RAG 效果差很多时候不是模型不行 而是文档处理、文本切分和 embedding 阶段就已经出问题了。比如PDF 里的图片没有被解析 一个完整 QA 被切成两半 一个 chunk 里混了多个不相关主题 chunk_size 太小导致上下文不完整 chunk_size 太大导致检索不精准 query embedding 和 document embedding 没搞清楚 metadata 里没有保留 source回答无法追溯来源。所以这一篇要解决的问题是RAG 里的外部文档在进入向量数据库之前到底要经历哪些处理主线是原始文件 - Document - 文本切分 - chunk - Embedding - 准备进入 VectorStore2. 从前三篇衔接到这里前三篇里我们已经建立了这条主线第一篇 如何组织输入并调用模型。 第二篇 如何让模型调用工具并稳定输出结构化结果。 第三篇 如何让模型参考示例和外部知识回答。第三篇讲到 RAG 的时候重点是先检索再生成。但当时只是总览文档加载 文本切分 Embedding VectorStore Retriever LLM这一篇专门展开前半段文档加载 文本切分 Embedding也就是文档进入向量库之前应该怎么处理。3. RAG 入库前的完整流程一个典型 RAG 系统在入库前大概是PDF / Markdown / TXT / HTML / Word | v Document Loader | v Document(page_content, metadata) | v Text Splitter | v 多个 chunk Document | v Embedding Model | v document embedding | v VectorStore这一篇先讲到 embedding 为止。下一篇会继续讲这些 embedding 和 metadata 存进 Redis / RediSearch 后到底长什么样。4. Document 是什么LangChain 里文档通常被包装成Document。fromlangchain_core.documentsimportDocument docDocument(page_contentLangChain 是一个用于开发 LLM 应用的框架。,metadata{source:course-note.md,category:LangChain,page:1,})Document主要有两部分page_content 真正要给模型检索和理解的正文。 metadata 文档的附加信息比如来源、页码、分类、编号。在 RAG 里metadata很重要。它可以用来追踪答案来源 做 metadata 过滤 显示引用页码 区分文档类型 保留原始文件路径 后续做权限控制。比如Document(page_contentRedis 支持向量搜索。,metadata{source:redis-vector.md,category:vectorstore,num:8})后面存进向量库时通常会变成text page_content metadata source / category / num embedding page_content 对应的向量5. Document Loader 是什么Document Loader 的作用是把不同来源的文件加载成 LangChain 标准 Document。比如TXT 文件 PDF 文件 Markdown 文件 网页 CSV Word 数据库记录这些原始格式不同但加载后都可以统一成Document(page_content...,metadata{...})这就是标准化的意义。只要变成Document后面就都能走同一套流程split_documents embedding vector_store.add_documents retriever.invoke6. PDF 文档加载为什么容易出问题PDF 是 RAG 里最常见、也最容易踩坑的文档类型。因为 PDF 可能有很多形态纯文本 PDF 扫描版 PDF 图片 文本混合 PDF 表格型 PDF 双栏论文 PDF PPT 导出的 PDF 带复杂图表的 PDF如果是纯文本 PDF普通 PDF loader 通常能抽取文本。流程是PDF 文本层 - PDF Loader 抽取文字 - Document但如果 PDF 里的关键信息是图片、截图、流程图、扫描件普通文本解析器可能抽不到。这时会出现PDF 页面看起来有内容 但 loader 抽出来的 page_content 很少甚至为空。根本原因是普通 PDF loader 主要读文本层 图片里的内容不是文本层。7. PDF 里有图片怎么办如果 PDF 里有图片或扫描页常见处理方式有三种。7.1 OCROCR 是把图片里的文字识别出来。流程PDF 页面 - 渲染成图片 - OCR 识别文字 - 转成 Document适合扫描版 PDF 图片里主要是文字 表格不复杂。7.2 多模态 LLM如果图片里有流程图、架构图、截图、复杂图表可以交给支持视觉的模型。流程PDF 页面 / 图片 - 多模态模型理解图片 - 生成文字描述 - 转成 Document它的优势是不仅能识别文字还能理解图里表达的关系。比如图里是用户 - Retriever - VectorStore - LLMOCR 可能只识别出文字多模态模型可以总结成这张图表达了 RAG 查询流程用户问题先进入检索器从向量库取回相关文档再交给 LLM 生成答案。7.3 混合方案真实项目里更推荐混合PDF 文本层 - 文本解析 PDF 图片/扫描页 - OCR 或多模态模型 两者合并 - Document也就是说不是简单地PDF 直接给 LLM而是根据 PDF 内容类型选择解析方式。8. 为什么不能直接把整篇文档塞给模型假设你有一个 100 页 PDF。如果直接塞给模型会有几个问题超过模型上下文窗口 大量内容和问题无关 token 成本高 模型容易抓不住重点 无法精准定位答案来源。所以 RAG 不会直接把整篇文档给模型。它会先把文档切成小块Document - chunk1 - chunk2 - chunk3查询时只召回最相关的几个 chunk。这就是文本切分的意义。9. 文本切分要解决什么问题文本切分的目标不是简单地“切小”。它要同时兼顾chunk 不能太长 chunk 不能太短 语义尽量完整 检索时能准确召回 生成时上下文足够回答问题。切太小检索很精确但信息不完整。切太大信息完整但检索不精准容易混入无关内容。所以切分是一种工程取舍。10.chunk_size是什么chunk_size表示每个 chunk 的目标大小。例如chunk_size800意思不是每块都必须等于 800而是尽量让每个 chunk 不超过或接近这个大小。具体怎么算取决于length_function或 tokenizer。如果用length_functionlen那一般就是按字符数算。如果用from_tiktoken_encoder(...)那就更接近按 token 算。11.chunk_overlap是什么chunk_overlap表示相邻 chunk 之间重复多少内容。例如chunk_size800chunk_overlap100意思是chunk1 的结尾一部分会出现在 chunk2 的开头。为什么需要 overlap因为语义可能刚好被切在边界上。比如chunk1: Redis 是一个内存数据库支持多种数据结构。它也可以通过 RediSearch... chunk2: ...实现全文搜索和向量搜索。如果没有 overlapchunk2可能缺少前文。有 overlap 后chunk2 会带上前一段末尾内容。但 overlap 太大也有问题重复内容变多 向量库体积变大 检索结果更容易重复 token 成本增加。常见经验chunk_overlap 通常设为 chunk_size 的 10% 到 20%。例如chunk_size800 chunk_overlap10012. CharacterTextSplitter按字符/分隔符切最基础的是按字符和分隔符切。fromlangchain_text_splittersimportCharacterTextSplitter text_splitterCharacterTextSplitter(separator\n\n,chunk_size800,chunk_overlap100,length_functionlen,)参数含义separator 优先按什么分隔符切。这里是双换行。 chunk_size 目标块大小。 chunk_overlap 相邻块重叠大小。 length_function 如何计算文本长度。len 表示字符数。它适合结构比较简单 段落分明 不需要复杂递归策略。但如果一个段落特别长它可能切得不够自然。13. 按 token 切为什么更贴近模型大模型的上下文限制和计费通常按 token 算不是按字符算。比如中文、英文、标点、代码token 计算方式都不一样。所以有时更适合按 token 控制长度fromlangchain_text_splittersimportCharacterTextSplitter text_splitterCharacterTextSplitter.from_tiktoken_encoder(encoding_namecl100k_base,chunk_size500,chunk_overlap80,)这里encoding_namecl100k_base表示使用 tiktoken 的一种编码方式。chunk_size500更接近500 个 token。适合严格控制模型输入长度 控制成本 避免上下文超限。14. RecursiveCharacterTextSplitter递归切分实际项目里最常见的是fromlangchain_text_splittersimportRecursiveCharacterTextSplitter text_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size800,chunk_overlap100,length_functionlen,is_separator_regexFalse,)它的逻辑是先按大分隔符切 如果切出来仍然太长再按更小分隔符切 如果还太长继续往下 最后实在不行按字符硬切。例如分隔符列表\n\n 段落 \n 行 。 句号 感叹号 问号 分号 逗号 空格 单词间隔 最后兜底按字符切这就是“递归”的意思。它比单一分隔符更适合普通中文文档。15.separators怎么选separators是切分优先级列表。普通中文文档推荐separators[\n\n,\n,。,,,,, ,]Markdown 文档可以优先按标题切separators[\n# ,\n## ,\n### ,\n\n,\n,。,]代码文档不要简单按句号切更适合按函数 类 代码块 空行因为代码的语义单位不是自然语言句子。16.is_separator_regex是什么is_separator_regexFalse表示separators里的内容是普通字符串。如果你想用正则表达式作为分隔符可以设为is_separator_regexTrue比如想按某些格式化标题切第1章 xxx 第2章 xxx可以考虑正则。但要注意结构特别明确的文档最好先自己做结构化解析 不要完全依赖通用 splitter。17. 如果语义被切断怎么办这是 RAG 里非常常见的问题。解决办法有几种增加 chunk_overlap 调大 chunk_size 优化 separators 先按文档结构切 对特殊格式单独解析。比如中文文档只用separators[\n\n,\n, ]可能不够。因为中文文本没有空格分词。更合理是加中文标点separators[\n\n,\n,。,,,,, ,]18. 如果一个段落太长怎么办如果一个段落特别长且没有合适分隔符递归切分器最后还是会硬切。这不是 bug而是兜底机制。否则它无法保证 chunk 不超过长度限制。解决方式加入更细的中文标点分隔符 用 token-based splitter 先清洗 PDF 抽取文本 对特殊格式做结构化解析。例如text_splitterRecursiveCharacterTextSplitter.from_tiktoken_encoder(encoding_namecl100k_base,separators[\n\n,\n,。,,,,, ,],chunk_size500,chunk_overlap80,)19. QA 文档应该怎么切如果文档天然是 QA 格式不建议完全依赖通用 splitter。比如问题LangChain 是什么 答案LangChain 是一个用于开发 LLM 应用的框架。 问题RAG 是什么 答案RAG 是检索增强生成。最好的策略是先按 QA 边界切 每个 QA 作为一个 Document 如果某个答案太长再在这个 QA 内部二次切分 每个子 chunk 都保留 question 和 qa_id。示例importrefromlangchain_core.documentsimportDocument patternr问题[:]\s*(.*?)\n答案[:]\s*(.*?)(?\n问题[:]|\Z)documents[]fori,matchinenumerate(re.finditer(pattern,text,flagsre.S)):questionmatch.group(1).strip()answermatch.group(2).strip()documents.append(Document(page_contentf问题{question}\n答案{answer},metadata{type:qa,qa_id:i,question:question,}))这样可以保证一个 QA 不会被随便拆散。如果答案太长再二次切inner_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size500,chunk_overlap80,)final_docs[]forqa_docindocuments:questionqa_doc.metadata[question]chunksinner_splitter.split_text(qa_doc.page_content)foridx,chunkinenumerate(chunks):final_docs.append(Document(page_contentf问题{question}\n答案片段{chunk},metadata{**qa_doc.metadata,chunk_index:idx,}))核心原则结构边界优先 长度控制兜底。20. chunk_size 太大会怎样如果chunk_size太大可能出现一个 chunk 里包含多个问题和答案 一个 chunk 混入多个主题 检索时召回不够精准 模型回答时引用了无关内容。例如chunk: 问题1Redis 是什么 答案1... 问题2Pinecone 是什么 答案2... 问题3FAISS 是什么 答案3...用户只问 Redis但 chunk 里还有 Pinecone 和 FAISS。模型可能被干扰。21. chunk_size 太小会怎样如果chunk_size太小可能出现答案被切碎 检索到的片段缺少上下文 模型只能看到半句话 需要更多 chunk 才能拼出完整答案。例如chunk1: RediSearch 是 Redis 的搜索模块 chunk2: 它支持全文检索、标签过滤、数字范围过滤和向量搜索。用户问 RediSearch 支持什么能力如果只召回 chunk1就回答不完整。22. 怎么判断切分效果好不好不要只看代码是否能运行还要抽样检查 chunk。可以打印前几个 chunkchunkstext_splitter.split_documents(docs)fori,chunkinenumerate(chunks[:5]):print(*40)print(chunk,i)print(chunk.page_content)print(chunk.metadata)检查是否语义完整 是否有明显断句 是否混入多个主题 metadata 是否保留 source / page chunk 长度是否合理 QA 是否被拆散。资深一点的做法是准备一批测试问题 观察每个问题能否检索到正确 chunk 根据召回结果反调 chunk_size、overlap 和 separators。这比拍脑袋设参数更可靠。23. Embedding 是什么Embedding 是把文本转成向量。例如Redis 支持向量搜索会变成类似[0.012, -0.384, 0.771, ...]真实向量维度可能是几百到几千维。语义相近的文本向量距离会更近。比如Redis 可以做向量检索 Redis 支持语义搜索它们字面不完全一样但语义接近所以向量也会更接近。24. document embedding 是什么入库时每个 chunk 都会生成一个 document embedding。流程Document chunk - embedding model - document embedding - 存入 VectorStore比如chunk text: Redis 支持向量搜索可以用于 RAG 系统中的语义检索。生成document_embedding [0.12, -0.03, 0.88, ...]后面这个向量会跟原文一起存入向量库。25. query embedding 是什么查询时用户问题也要转成向量。用户问题 Redis 怎么做语义检索调用 embedding 模型后得到query_embedding [0.11, -0.01, 0.85, ...]然后系统会拿 query embedding 去和 document embedding 比较相似度。所以document embedding文档片段的向量表示。 query embedding用户问题的向量表示。它们必须在同一个向量空间里比较。26. query embedding 和 document embedding 的关系RAG 检索阶段可以理解成query embedding 去向量库里找最接近的 document embedding例如query: Redis 向量索引是什么 doc1: Redis key 是真实数据地址。 doc2: RediSearch Index 可以基于 embedding 做向量相似搜索。 doc3: PromptTemplate 用于构造提示词。向量检索大概率返回doc2因为它和 query 的语义最接近。27. Embedding 和普通关键词搜索有什么区别关键词搜索关注字面匹配。例如用户问语义检索怎么做文档写的是向量搜索可以找到意思相近的文本。关键词搜索可能匹配不到因为没有共同关键词。Embedding 检索更关注语义语义检索 ≈ 向量搜索所以可以找到。但 embedding 也不是万能的。它可能受这些因素影响embedding 模型能力 chunk 切分质量 query 表达是否清楚 文档语言和 query 是否一致 领域术语是否被模型理解 metadata 过滤是否合理。28. Embedding 模型怎么选选择 embedding 模型要看语言支持 向量维度 成本 速度 领域适配 是否和向量库兼容 是否支持你的部署环境。课程里常见fromlangchain_openaiimportOpenAIEmbeddings embeddingsOpenAIEmbeddings(modeltext-embedding-3-large)注意同一个向量库里的 document embedding 和 query embedding 应该用同一个 embedding 模型生成。否则向量空间不一致检索结果会很差。29. 入库前处理的完整代码骨架下面是一个简化版流程。fromlangchain_community.document_loadersimportTextLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_openaiimportOpenAIEmbeddings# 1. 加载文档loaderTextLoader(docs/course.md,encodingutf-8)docsloader.load()# 2. 切分文档text_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size800,chunk_overlap100,length_functionlen,)chunkstext_splitter.split_documents(docs)# 3. 定义 embedding 模型embeddingsOpenAIEmbeddings(modeltext-embedding-3-large)# 4. 后续会把 chunks 和 embeddings 交给 VectorStore# vector_store.add_documents(chunks)这段代码到这里还没有真正进入向量库。它完成的是文档加载 文本切分 准备 embedding 模型下一篇会继续这些 chunks 和 embedding 怎么存进 Redis / RediSearch。30. 容易混淆的点30.1 Document 是原始文件吗不是。Document 是 LangChain 对文本内容的统一包装。一个 PDF 可以加载成多个 Document。一个 Document 也可以再被切成多个 chunk Document。30.2 chunk_size 越大越好吗不是。太大容易混入多个主题检索不精准。30.3 chunk_size 越小越好吗也不是。太小会破坏语义完整性。30.4 overlap 越大越好吗不是。overlap 太大会增加重复、成本和检索冗余。30.5 PDF 解析不到图片是不是模型问题不一定。很多时候是 PDF loader 只抽文本层图片内容没有被 OCR 或多模态模型处理。30.6 query embedding 和 document embedding 是两个模型吗通常不是。它们应该由同一个 embedding 模型生成只是输入不同query embedding 输入用户问题 document embedding 输入文档 chunk。31. 工程实践RAG 效果很多时候输在入库阶段很多人刚开始做 RAG 时容易觉得RAG 向量库 LLM但工程里更准确是RAG 文档处理质量 检索质量 Prompt 组织 模型生成如果文档入库阶段做得不好PDF 内容丢失 chunk 语义破碎 metadata 缺失 embedding 不匹配 chunk 粒度不合理后面再换更贵的模型也不一定能救回来。所以资深一点的 RAG 优化往往不是先调模型而是先问文档解析完整吗 chunk 是否合理 问题能否召回正确 chunk metadata 是否能过滤 是否需要结构化切分 是否需要混合检索这就是这一篇的工程意义。32. 本篇总结这一篇主要整理了 RAG 入库前的三个关键步骤第一文档加载把 PDF、Markdown、TXT 等原始文件变成 Document。第二文本切分把长文档切成适合检索和生成的 chunk。第三Embedding把 chunk 和用户问题转成向量方便后续做语义检索。几个关键结论Document page_content metadata。 PDF 解析不一定能拿到图片里的内容。 文本切分要兼顾长度和语义完整。 RecursiveCharacterTextSplitter 是常用通用方案。 QA 文档最好先按结构切再按长度兜底。 chunk_size 和 chunk_overlap 都是工程取舍。 document embedding 用于表示文档 chunk。 query embedding 用于表示用户问题。 两者要在同一个向量空间里比较。一句话概括RAG 的效果不是从检索器才开始决定的文档加载、文本切分和 embedding 阶段已经决定了很大一部分上限。33. 下一篇预告下一篇会继续整理Redis 向量存储、RediSearch Index 与 metadata 过滤重点回答这些问题Document 存进 Redis 后到底长什么样 Redis key、hash、RediSearch Index 是什么关系 index_name 为什么要起名字 metadata_schema 是做什么的 TAG、NUMERIC、TEXT、VECTOR 分别是什么 query vector 是怎么在 RediSearch Index 里找相似 key 的 为什么知道 key 可以直接查但 RAG 还需要 index也就是从“文档如何变成向量”继续走到“向量和 metadata 如何被存储和搜索”。
LangChain 学习笔记(四):文档加载、文本切分与 Embedding,一次搞懂 RAG 入库前处理
LangChain 学习笔记四文档加载、文本切分与 Embedding一次搞懂 RAG 入库前处理这是 LangChain 学习笔记系列的第四篇。前三篇主要整理了基础调用链路、工具调用、结构化输出、Few-shot 和 RAG 入门。从这一篇开始进入 RAG 真正落地时绕不开的工程细节文档如何加载、如何切分、如何变成向量以及这些步骤为什么会影响最终检索效果。1. 为什么这一篇很重要很多人刚开始学 RAG会把注意力放在怎么调用大模型 怎么用向量数据库 怎么写 RAG chain但真正做项目时很快会发现RAG 效果差很多时候不是模型不行 而是文档处理、文本切分和 embedding 阶段就已经出问题了。比如PDF 里的图片没有被解析 一个完整 QA 被切成两半 一个 chunk 里混了多个不相关主题 chunk_size 太小导致上下文不完整 chunk_size 太大导致检索不精准 query embedding 和 document embedding 没搞清楚 metadata 里没有保留 source回答无法追溯来源。所以这一篇要解决的问题是RAG 里的外部文档在进入向量数据库之前到底要经历哪些处理主线是原始文件 - Document - 文本切分 - chunk - Embedding - 准备进入 VectorStore2. 从前三篇衔接到这里前三篇里我们已经建立了这条主线第一篇 如何组织输入并调用模型。 第二篇 如何让模型调用工具并稳定输出结构化结果。 第三篇 如何让模型参考示例和外部知识回答。第三篇讲到 RAG 的时候重点是先检索再生成。但当时只是总览文档加载 文本切分 Embedding VectorStore Retriever LLM这一篇专门展开前半段文档加载 文本切分 Embedding也就是文档进入向量库之前应该怎么处理。3. RAG 入库前的完整流程一个典型 RAG 系统在入库前大概是PDF / Markdown / TXT / HTML / Word | v Document Loader | v Document(page_content, metadata) | v Text Splitter | v 多个 chunk Document | v Embedding Model | v document embedding | v VectorStore这一篇先讲到 embedding 为止。下一篇会继续讲这些 embedding 和 metadata 存进 Redis / RediSearch 后到底长什么样。4. Document 是什么LangChain 里文档通常被包装成Document。fromlangchain_core.documentsimportDocument docDocument(page_contentLangChain 是一个用于开发 LLM 应用的框架。,metadata{source:course-note.md,category:LangChain,page:1,})Document主要有两部分page_content 真正要给模型检索和理解的正文。 metadata 文档的附加信息比如来源、页码、分类、编号。在 RAG 里metadata很重要。它可以用来追踪答案来源 做 metadata 过滤 显示引用页码 区分文档类型 保留原始文件路径 后续做权限控制。比如Document(page_contentRedis 支持向量搜索。,metadata{source:redis-vector.md,category:vectorstore,num:8})后面存进向量库时通常会变成text page_content metadata source / category / num embedding page_content 对应的向量5. Document Loader 是什么Document Loader 的作用是把不同来源的文件加载成 LangChain 标准 Document。比如TXT 文件 PDF 文件 Markdown 文件 网页 CSV Word 数据库记录这些原始格式不同但加载后都可以统一成Document(page_content...,metadata{...})这就是标准化的意义。只要变成Document后面就都能走同一套流程split_documents embedding vector_store.add_documents retriever.invoke6. PDF 文档加载为什么容易出问题PDF 是 RAG 里最常见、也最容易踩坑的文档类型。因为 PDF 可能有很多形态纯文本 PDF 扫描版 PDF 图片 文本混合 PDF 表格型 PDF 双栏论文 PDF PPT 导出的 PDF 带复杂图表的 PDF如果是纯文本 PDF普通 PDF loader 通常能抽取文本。流程是PDF 文本层 - PDF Loader 抽取文字 - Document但如果 PDF 里的关键信息是图片、截图、流程图、扫描件普通文本解析器可能抽不到。这时会出现PDF 页面看起来有内容 但 loader 抽出来的 page_content 很少甚至为空。根本原因是普通 PDF loader 主要读文本层 图片里的内容不是文本层。7. PDF 里有图片怎么办如果 PDF 里有图片或扫描页常见处理方式有三种。7.1 OCROCR 是把图片里的文字识别出来。流程PDF 页面 - 渲染成图片 - OCR 识别文字 - 转成 Document适合扫描版 PDF 图片里主要是文字 表格不复杂。7.2 多模态 LLM如果图片里有流程图、架构图、截图、复杂图表可以交给支持视觉的模型。流程PDF 页面 / 图片 - 多模态模型理解图片 - 生成文字描述 - 转成 Document它的优势是不仅能识别文字还能理解图里表达的关系。比如图里是用户 - Retriever - VectorStore - LLMOCR 可能只识别出文字多模态模型可以总结成这张图表达了 RAG 查询流程用户问题先进入检索器从向量库取回相关文档再交给 LLM 生成答案。7.3 混合方案真实项目里更推荐混合PDF 文本层 - 文本解析 PDF 图片/扫描页 - OCR 或多模态模型 两者合并 - Document也就是说不是简单地PDF 直接给 LLM而是根据 PDF 内容类型选择解析方式。8. 为什么不能直接把整篇文档塞给模型假设你有一个 100 页 PDF。如果直接塞给模型会有几个问题超过模型上下文窗口 大量内容和问题无关 token 成本高 模型容易抓不住重点 无法精准定位答案来源。所以 RAG 不会直接把整篇文档给模型。它会先把文档切成小块Document - chunk1 - chunk2 - chunk3查询时只召回最相关的几个 chunk。这就是文本切分的意义。9. 文本切分要解决什么问题文本切分的目标不是简单地“切小”。它要同时兼顾chunk 不能太长 chunk 不能太短 语义尽量完整 检索时能准确召回 生成时上下文足够回答问题。切太小检索很精确但信息不完整。切太大信息完整但检索不精准容易混入无关内容。所以切分是一种工程取舍。10.chunk_size是什么chunk_size表示每个 chunk 的目标大小。例如chunk_size800意思不是每块都必须等于 800而是尽量让每个 chunk 不超过或接近这个大小。具体怎么算取决于length_function或 tokenizer。如果用length_functionlen那一般就是按字符数算。如果用from_tiktoken_encoder(...)那就更接近按 token 算。11.chunk_overlap是什么chunk_overlap表示相邻 chunk 之间重复多少内容。例如chunk_size800chunk_overlap100意思是chunk1 的结尾一部分会出现在 chunk2 的开头。为什么需要 overlap因为语义可能刚好被切在边界上。比如chunk1: Redis 是一个内存数据库支持多种数据结构。它也可以通过 RediSearch... chunk2: ...实现全文搜索和向量搜索。如果没有 overlapchunk2可能缺少前文。有 overlap 后chunk2 会带上前一段末尾内容。但 overlap 太大也有问题重复内容变多 向量库体积变大 检索结果更容易重复 token 成本增加。常见经验chunk_overlap 通常设为 chunk_size 的 10% 到 20%。例如chunk_size800 chunk_overlap10012. CharacterTextSplitter按字符/分隔符切最基础的是按字符和分隔符切。fromlangchain_text_splittersimportCharacterTextSplitter text_splitterCharacterTextSplitter(separator\n\n,chunk_size800,chunk_overlap100,length_functionlen,)参数含义separator 优先按什么分隔符切。这里是双换行。 chunk_size 目标块大小。 chunk_overlap 相邻块重叠大小。 length_function 如何计算文本长度。len 表示字符数。它适合结构比较简单 段落分明 不需要复杂递归策略。但如果一个段落特别长它可能切得不够自然。13. 按 token 切为什么更贴近模型大模型的上下文限制和计费通常按 token 算不是按字符算。比如中文、英文、标点、代码token 计算方式都不一样。所以有时更适合按 token 控制长度fromlangchain_text_splittersimportCharacterTextSplitter text_splitterCharacterTextSplitter.from_tiktoken_encoder(encoding_namecl100k_base,chunk_size500,chunk_overlap80,)这里encoding_namecl100k_base表示使用 tiktoken 的一种编码方式。chunk_size500更接近500 个 token。适合严格控制模型输入长度 控制成本 避免上下文超限。14. RecursiveCharacterTextSplitter递归切分实际项目里最常见的是fromlangchain_text_splittersimportRecursiveCharacterTextSplitter text_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size800,chunk_overlap100,length_functionlen,is_separator_regexFalse,)它的逻辑是先按大分隔符切 如果切出来仍然太长再按更小分隔符切 如果还太长继续往下 最后实在不行按字符硬切。例如分隔符列表\n\n 段落 \n 行 。 句号 感叹号 问号 分号 逗号 空格 单词间隔 最后兜底按字符切这就是“递归”的意思。它比单一分隔符更适合普通中文文档。15.separators怎么选separators是切分优先级列表。普通中文文档推荐separators[\n\n,\n,。,,,,, ,]Markdown 文档可以优先按标题切separators[\n# ,\n## ,\n### ,\n\n,\n,。,]代码文档不要简单按句号切更适合按函数 类 代码块 空行因为代码的语义单位不是自然语言句子。16.is_separator_regex是什么is_separator_regexFalse表示separators里的内容是普通字符串。如果你想用正则表达式作为分隔符可以设为is_separator_regexTrue比如想按某些格式化标题切第1章 xxx 第2章 xxx可以考虑正则。但要注意结构特别明确的文档最好先自己做结构化解析 不要完全依赖通用 splitter。17. 如果语义被切断怎么办这是 RAG 里非常常见的问题。解决办法有几种增加 chunk_overlap 调大 chunk_size 优化 separators 先按文档结构切 对特殊格式单独解析。比如中文文档只用separators[\n\n,\n, ]可能不够。因为中文文本没有空格分词。更合理是加中文标点separators[\n\n,\n,。,,,,, ,]18. 如果一个段落太长怎么办如果一个段落特别长且没有合适分隔符递归切分器最后还是会硬切。这不是 bug而是兜底机制。否则它无法保证 chunk 不超过长度限制。解决方式加入更细的中文标点分隔符 用 token-based splitter 先清洗 PDF 抽取文本 对特殊格式做结构化解析。例如text_splitterRecursiveCharacterTextSplitter.from_tiktoken_encoder(encoding_namecl100k_base,separators[\n\n,\n,。,,,,, ,],chunk_size500,chunk_overlap80,)19. QA 文档应该怎么切如果文档天然是 QA 格式不建议完全依赖通用 splitter。比如问题LangChain 是什么 答案LangChain 是一个用于开发 LLM 应用的框架。 问题RAG 是什么 答案RAG 是检索增强生成。最好的策略是先按 QA 边界切 每个 QA 作为一个 Document 如果某个答案太长再在这个 QA 内部二次切分 每个子 chunk 都保留 question 和 qa_id。示例importrefromlangchain_core.documentsimportDocument patternr问题[:]\s*(.*?)\n答案[:]\s*(.*?)(?\n问题[:]|\Z)documents[]fori,matchinenumerate(re.finditer(pattern,text,flagsre.S)):questionmatch.group(1).strip()answermatch.group(2).strip()documents.append(Document(page_contentf问题{question}\n答案{answer},metadata{type:qa,qa_id:i,question:question,}))这样可以保证一个 QA 不会被随便拆散。如果答案太长再二次切inner_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size500,chunk_overlap80,)final_docs[]forqa_docindocuments:questionqa_doc.metadata[question]chunksinner_splitter.split_text(qa_doc.page_content)foridx,chunkinenumerate(chunks):final_docs.append(Document(page_contentf问题{question}\n答案片段{chunk},metadata{**qa_doc.metadata,chunk_index:idx,}))核心原则结构边界优先 长度控制兜底。20. chunk_size 太大会怎样如果chunk_size太大可能出现一个 chunk 里包含多个问题和答案 一个 chunk 混入多个主题 检索时召回不够精准 模型回答时引用了无关内容。例如chunk: 问题1Redis 是什么 答案1... 问题2Pinecone 是什么 答案2... 问题3FAISS 是什么 答案3...用户只问 Redis但 chunk 里还有 Pinecone 和 FAISS。模型可能被干扰。21. chunk_size 太小会怎样如果chunk_size太小可能出现答案被切碎 检索到的片段缺少上下文 模型只能看到半句话 需要更多 chunk 才能拼出完整答案。例如chunk1: RediSearch 是 Redis 的搜索模块 chunk2: 它支持全文检索、标签过滤、数字范围过滤和向量搜索。用户问 RediSearch 支持什么能力如果只召回 chunk1就回答不完整。22. 怎么判断切分效果好不好不要只看代码是否能运行还要抽样检查 chunk。可以打印前几个 chunkchunkstext_splitter.split_documents(docs)fori,chunkinenumerate(chunks[:5]):print(*40)print(chunk,i)print(chunk.page_content)print(chunk.metadata)检查是否语义完整 是否有明显断句 是否混入多个主题 metadata 是否保留 source / page chunk 长度是否合理 QA 是否被拆散。资深一点的做法是准备一批测试问题 观察每个问题能否检索到正确 chunk 根据召回结果反调 chunk_size、overlap 和 separators。这比拍脑袋设参数更可靠。23. Embedding 是什么Embedding 是把文本转成向量。例如Redis 支持向量搜索会变成类似[0.012, -0.384, 0.771, ...]真实向量维度可能是几百到几千维。语义相近的文本向量距离会更近。比如Redis 可以做向量检索 Redis 支持语义搜索它们字面不完全一样但语义接近所以向量也会更接近。24. document embedding 是什么入库时每个 chunk 都会生成一个 document embedding。流程Document chunk - embedding model - document embedding - 存入 VectorStore比如chunk text: Redis 支持向量搜索可以用于 RAG 系统中的语义检索。生成document_embedding [0.12, -0.03, 0.88, ...]后面这个向量会跟原文一起存入向量库。25. query embedding 是什么查询时用户问题也要转成向量。用户问题 Redis 怎么做语义检索调用 embedding 模型后得到query_embedding [0.11, -0.01, 0.85, ...]然后系统会拿 query embedding 去和 document embedding 比较相似度。所以document embedding文档片段的向量表示。 query embedding用户问题的向量表示。它们必须在同一个向量空间里比较。26. query embedding 和 document embedding 的关系RAG 检索阶段可以理解成query embedding 去向量库里找最接近的 document embedding例如query: Redis 向量索引是什么 doc1: Redis key 是真实数据地址。 doc2: RediSearch Index 可以基于 embedding 做向量相似搜索。 doc3: PromptTemplate 用于构造提示词。向量检索大概率返回doc2因为它和 query 的语义最接近。27. Embedding 和普通关键词搜索有什么区别关键词搜索关注字面匹配。例如用户问语义检索怎么做文档写的是向量搜索可以找到意思相近的文本。关键词搜索可能匹配不到因为没有共同关键词。Embedding 检索更关注语义语义检索 ≈ 向量搜索所以可以找到。但 embedding 也不是万能的。它可能受这些因素影响embedding 模型能力 chunk 切分质量 query 表达是否清楚 文档语言和 query 是否一致 领域术语是否被模型理解 metadata 过滤是否合理。28. Embedding 模型怎么选选择 embedding 模型要看语言支持 向量维度 成本 速度 领域适配 是否和向量库兼容 是否支持你的部署环境。课程里常见fromlangchain_openaiimportOpenAIEmbeddings embeddingsOpenAIEmbeddings(modeltext-embedding-3-large)注意同一个向量库里的 document embedding 和 query embedding 应该用同一个 embedding 模型生成。否则向量空间不一致检索结果会很差。29. 入库前处理的完整代码骨架下面是一个简化版流程。fromlangchain_community.document_loadersimportTextLoaderfromlangchain_text_splittersimportRecursiveCharacterTextSplitterfromlangchain_openaiimportOpenAIEmbeddings# 1. 加载文档loaderTextLoader(docs/course.md,encodingutf-8)docsloader.load()# 2. 切分文档text_splitterRecursiveCharacterTextSplitter(separators[\n\n,\n,。,,,,, ,],chunk_size800,chunk_overlap100,length_functionlen,)chunkstext_splitter.split_documents(docs)# 3. 定义 embedding 模型embeddingsOpenAIEmbeddings(modeltext-embedding-3-large)# 4. 后续会把 chunks 和 embeddings 交给 VectorStore# vector_store.add_documents(chunks)这段代码到这里还没有真正进入向量库。它完成的是文档加载 文本切分 准备 embedding 模型下一篇会继续这些 chunks 和 embedding 怎么存进 Redis / RediSearch。30. 容易混淆的点30.1 Document 是原始文件吗不是。Document 是 LangChain 对文本内容的统一包装。一个 PDF 可以加载成多个 Document。一个 Document 也可以再被切成多个 chunk Document。30.2 chunk_size 越大越好吗不是。太大容易混入多个主题检索不精准。30.3 chunk_size 越小越好吗也不是。太小会破坏语义完整性。30.4 overlap 越大越好吗不是。overlap 太大会增加重复、成本和检索冗余。30.5 PDF 解析不到图片是不是模型问题不一定。很多时候是 PDF loader 只抽文本层图片内容没有被 OCR 或多模态模型处理。30.6 query embedding 和 document embedding 是两个模型吗通常不是。它们应该由同一个 embedding 模型生成只是输入不同query embedding 输入用户问题 document embedding 输入文档 chunk。31. 工程实践RAG 效果很多时候输在入库阶段很多人刚开始做 RAG 时容易觉得RAG 向量库 LLM但工程里更准确是RAG 文档处理质量 检索质量 Prompt 组织 模型生成如果文档入库阶段做得不好PDF 内容丢失 chunk 语义破碎 metadata 缺失 embedding 不匹配 chunk 粒度不合理后面再换更贵的模型也不一定能救回来。所以资深一点的 RAG 优化往往不是先调模型而是先问文档解析完整吗 chunk 是否合理 问题能否召回正确 chunk metadata 是否能过滤 是否需要结构化切分 是否需要混合检索这就是这一篇的工程意义。32. 本篇总结这一篇主要整理了 RAG 入库前的三个关键步骤第一文档加载把 PDF、Markdown、TXT 等原始文件变成 Document。第二文本切分把长文档切成适合检索和生成的 chunk。第三Embedding把 chunk 和用户问题转成向量方便后续做语义检索。几个关键结论Document page_content metadata。 PDF 解析不一定能拿到图片里的内容。 文本切分要兼顾长度和语义完整。 RecursiveCharacterTextSplitter 是常用通用方案。 QA 文档最好先按结构切再按长度兜底。 chunk_size 和 chunk_overlap 都是工程取舍。 document embedding 用于表示文档 chunk。 query embedding 用于表示用户问题。 两者要在同一个向量空间里比较。一句话概括RAG 的效果不是从检索器才开始决定的文档加载、文本切分和 embedding 阶段已经决定了很大一部分上限。33. 下一篇预告下一篇会继续整理Redis 向量存储、RediSearch Index 与 metadata 过滤重点回答这些问题Document 存进 Redis 后到底长什么样 Redis key、hash、RediSearch Index 是什么关系 index_name 为什么要起名字 metadata_schema 是做什么的 TAG、NUMERIC、TEXT、VECTOR 分别是什么 query vector 是怎么在 RediSearch Index 里找相似 key 的 为什么知道 key 可以直接查但 RAG 还需要 index也就是从“文档如何变成向量”继续走到“向量和 metadata 如何被存储和搜索”。