由于出差,断更一周,还请各位读者海涵~
在上一篇 LLM 系列(八):RAG 篇中,我大概阐述了 RAG 的基本流程和原理,本篇则是进一步针对 RAG 中的前期过程(文本切分和 Embedding)进行拆解,期望是能够通过此篇能够让各位读者在 RAG 落地上具备更高的参考价值。
这趟 RAG 的构建之旅,其起点并非高深的算法,而是看似平淡无奇的数据工程——如何将散落在企业各个角落的原始文档,一步步转化为大模型可以理解和利用的结构化知识。本文作为 RAG 知识构建的番外篇,将细致入微地描绘这一转化过程的全貌:从一份原始的 PDF 或网页,到最终成为向量数据库中一个可被精准检索的 知识向量。这条路径的质量,直接决定了整个 RAG 系统的成败。
在 RAG 架构中,文档扮演着“外部大脑”或“权威知识库”的角色。它的核心使命是为 LLM 提供一个事实的基石(grounding),当模型需要回答问题时,它不再仅仅依赖于自己内部模糊的、可能过时的记忆,而是可以查阅这些外部文档来获取最相关、最准确的信息。
这个过程具体表现为:系统将从文档中检索到的 事实 片段,连同用户的问题一起,打包成一个更丰富的提示(prompt)喂给 LLM。这种做法带来了两个立竿见影的好处:首先,它极大地减少了模型幻觉的概率;其次,它让模型的回答变得可以溯源,用户可以清楚地看到答案是基于哪些文档生成的,这对于需要高可信度的企业应用场景至关重要。
企业知识的形态是复杂多样的,它们散落在不同的系统和媒介中。一个强大的 RAG 系统必须具备从这些异构源头汲取知识的能力。常见的文档来源包括:
为了应对这种复杂性,像 LangChain
这样的开发框架提供了丰富的 文档加载器(Document Loaders) 。这些加载器就像是特制的 数据插头 ,每一种都针对特定的数据源,能够高效地读取内容并将其转换为统一的格式。LangChain 官方支持的加载器列表非常庞大,从常见的 PDF、网页,到 Slack 聊天记录、Figma 设计文件,甚至 Bilibili 视频字幕,几乎无所不包。
这种广泛的覆盖揭示了一个深刻的现实:对于 RAG 应用开发者而言,首要的挑战往往不是算法,而是数据集成。所谓的 “文档”,其实是对一个混乱、异构数据世界的便捷抽象。一个 RAG 项目的成败,很大程度上取决于能否打通连接到企业内部各个“知识孤岛”的数据管道。
数据加载进来后,我们便开启了整个流程的核心目标:将这些大小不一、格式混乱的原始数据,转化为一系列干净、语义连贯、尺寸统一的文本片段。这些片段在技术上被称为 “块”(Chunks) 或 “知识单元”(Knowledge Units)。
可以把这个过程想象成准备烹饪的食材。原始文档就像是刚从地里挖出来的土豆,上面还带着泥土,大小也不一。我们需要先把它洗干净(清洗),然后切成大小均匀的块(切分),这样才能方便后续的烹饪(向量化和检索)。这个预处理步骤是整个 RAG 流程的基石,因为这些“知识单元”的质量,将直接决定后续检索的精准度和最终生成答案的质量。
将原始文档转化为高质量的知识单元,是 RAG 系统工程中最具挑战也最关键的一步。这个过程包含两个核心环节:拆分(Chunking)与清洗(Cleaning)。
拆分(Chunking)面临一个经典的困境:知识单元既要足够小,以适应 Embedding 模型和 LLM 的上下文窗口限制;又要足够大,以保留完整的语义信息。
经验法则是:如果一段文本在没有上下文的情况下,人类能够理解其含义,那么语言模型大概率也能理解。这为我们追求“恰到好处”的切分提供了方向。
为了实现理想的切分,社区发展出了多种策略,从简单粗暴到智能精细,各有其适用场景。
chunk_size
(块大小)和chunk_overlap
(块重叠);重叠部分的存在是为了在两个连续的块之间保留一定的上下文,防止信息在边界处被完全切断。然而,这种“一刀切”的方式非常“天真”,它完全无视文本的自然结构,很可能在句子中间、一个完整的概念描述到一半时就强行分割,破坏语义完整性。["\n\n", "\n", " ", ""]
。它会首先尝试用最高优先级的“换段符” (\n\n
) 来分割文本。如果分割后的块仍然大于设定的 chunk_size
,它就会在这些大块上,用次一级优先级的“换行符” (\n
) 继续分割。这个过程会递归地进行下去,直到所有块都符合尺寸要求。这样做的效果是,它会优先保持段落的完整,其次是句子的完整,最后才是单词的完整。#
一级标题,##
二级标题)作为天然的分割点。LangChain 框架中的 MarkdownHeaderTextSplitter
就是为此而生,它可以确保每个标题下的所有内容形成一个独立的、上下文完整的知识单元。清洗的目标是去除文档中的无关信息(如广告、导航栏、脚本代码),同时保留有价值的结构和内容。
WX20250803-132126@2x
<script>
, <style>
)和冗余属性。<div><div><p>text</p></div></div>
简化为 <p>text</p>
。• Markdown 清洗:重点在于保留其结构化元素。例如,一个 Markdown 表格应该被识别并作为一个整体来处理,而不是被拆散成零散的文本行,代码块和列表也应同理。
现实世界的文档远不止纯文本。如何处理表格、代码和图片,是衡量一个 RAG 系统成熟度的重要标准。下表总结了处理这些复杂元素的一些最佳实践。
表1:复杂文档元素处理最佳实践
元素类型 | 推荐处理策略 | 关键考量 |
---|---|---|
表格 | 1. 整体处理:避免将一个表格分割到多个知识单元中。2. 结构化转换:在向量化之前,将表格转换为更清晰的 Markdown、JSON 或 CSV 格式。3. 自然语言摘要:使用 LLM 生成一段描述表格内容的自然语言摘要。4. 专用模型处理:TableRAG 等前沿方法建议将表格数据存入可供 SQL 查询的数据库中,以支持更复杂的推理。 | 核心目标是保护表格的行列结构,这承载了其核心语义。简单的文本提取会彻底破坏这种结构。将其转换为结构化格式是目前最稳妥的检索方案。 |
代码 | 1. 代码感知分割:基于函数、类或逻辑块进行分割,而非任意字符数。LangChain 为多种编程语言提供了专用的分割器。2. 使用专用 Embedding 模型:选择在代码上训练过的模型,能更好地捕捉代码的句法和语义。 | 代码具有严格的语法结构,必须得到尊重。在函数或语句中间分割会使其失去意义。代码块的上下文通常由其作用域(函数或类)定义。 |
图片/图表 | 1. 图像描述/摘要:使用多模态 LLM(如 GPT-4V, LLaVA)为图片或图表生成详细的文本描述,然后将这段描述文本进行向量化 。2. 多模态 Embedding:使用像 CLIP 这样的模型,它可以将图片和文本嵌入到同一个向量空间中,从而实现直接的“以文搜图”或“以图搜图” 。 | 这是多模态 RAG 的核心领域。选择生成描述还是使用多模态 Embedding 取决于具体应用。生成描述更灵活,因为产出的是标准文本,但可能丢失图像的细微差别;多模态 Embedding 更直接,但要求整个技术栈都能处理图像向量。 |
经过预处理,我们得到了一批高质量的知识单元。下一步,就是将这些文本转化为机器能够理解和比较的数学形式——这个过程就是向量化(Vectorization),也常被称为嵌入(Embedding)。
向量化,就是利用一个深度学习模型(即 Embedding 模型),将一段文本转换成一个由数百甚至数千个数字组成的列表,这个列表就是向量(Vector)。 打个比喻:Embedding 就像是为每一段文字分配了一个独特的“语义 GPS 坐标”。在由这些坐标构成的高维空间中,意思相近的文本,它们的坐标点在空间中的距离也会非常接近。例如,“苹果公司的最新财报”和“iPhone 制造商的季度收入”这两段话,它们的向量就会靠得很近。正是这种“语义相近,空间相邻”的特性,使得基于向量的**语义搜索 **成为可能,这也是 RAG 系统能够从海量知识中快速找到最相关信息的核心能力。
选择一个合适的 Embedding 模型,是决定 RAG 检索效果的关键决策。这通常需要在开源与商业、性能与成本之间做出权衡。
• 开源 vs. 商业 API
对于 MTEB 排行榜,一个常见的误区是直接选择平均分最高的模型 。然而,对于 RAG 应用来说,Retrieval
(检索)这项任务的得分才是最需要关注的指标。一个模型可能因为在分类、聚类等任务上表现优异而总分很高,但其检索能力可能平平。一个优秀的开发者会优先筛选出在检索任务上表现优异的模型,因为这直接关系到能否为 LLM 找到最优质的上下文。
在工程化落地过程中的策略往往是,从一个中等且性价比高的维度(如 768)开始,只有当评估发现检索质量成为瓶颈时,再考虑增加维度。
表2:主流 Embedding 模型实用对比
为了帮助开发者在繁多的模型中做出选择,下表对当前最受欢迎的几个模型系列进行了实用性对比。
模型系列 | 提供方/来源 | 核心优势 | 成本模型/投入 | 典型维度 |
---|---|---|---|---|
text-embedding-3-small/large | OpenAI | 顶尖性能,API 简洁,支持自定义维度以平衡成本与性能。 | API 按 Token 计费 | 1536 (small), 3072 (large) |
Embed v3 | Cohere | 专为检索任务优化,支持多语言,提供不同类型的模型(如 english, multilingual)。 | API 按 Token 计费 | 1024 |
BGE (BAAI General Embedding) | 北京智源人工智能研究院 (BAAI) | 在 MTEB 检索任务上持续名列前茅,中英文效果俱佳,完全开源。 | 自行部署(计算成本) | 1024 |
GTE (General Text Embeddings) | 阿里巴巴达摩院 | 强大的开源模型,性能与模型大小均衡,常被用作高质量的基线模型。 | 自行部署(计算成本) | 768 / 1024 |
当所有知识单元都被转换成向量后,我们需要一个地方来妥善地存储和管理它们,并支持高效的查询。这个“家”,就是向量数据库(Vector Database)。
向量数据库是一种专门为存储、索引和查询海量高维向量而设计的数据库系统。它的核心能力是执行极其快速的 近似最近邻(Approximate Nearest Neighbor, ANN)搜索。
当一个用户问题被转换成查询向量后,如果要在数百万甚至数十亿的知识向量中找到最相似的几个,通过逐一比较(精确搜索)的方式是完全不可行的,会耗费巨大的计算资源和时间。ANN
搜索则通过构建巧妙的索引结构(如 HNSW、IVF-PQ
等算法),能够在牺牲极小的精度的前提下,以毫秒级的速度找到“足够好”的近似匹配结果。这使得实时语义搜索成为可能。
市面上有多种向量数据库可供选择,其中 Milvus、FAISS 和 Weaviate
是最具代表性的三个。
这三者的选择,不仅仅是技术特性的比较,更深层次地反映了项目所处的阶段和团队的开发哲学。选择 FAISS 意味着“自己动手,丰衣足食”,适合研究和原型阶段;选择 Weaviate 意味着追求“全面的解决方案”,适合功能丰富的 MVP 或中型应用;而选择 Milvus 则代表着“为海量规模而生”,是超大规模生产部署的终极选择。
本篇从五花八门的原始文档(如 PDF、网页)出发,通过精细的清洗和策略性的切分,将其转化为一个个蕴含上下文的知识片段。接着,我们借助强大的 Embedding 模型,为每个片段赋予了独特的“数学灵魂”——向量。最后,这些向量被妥善地安置在专为高速检索设计的向量数据库中,静静等待着被唤醒,为 LLM 提供精准的知识弹药。
知识库已经建好,但真正的 RAG 才刚刚开始。当用户提出一个问题时,系统是如何从数百万个向量中,瞬间找到最相关的那几个?“检索”和“重排”之间有什么区别?LLM 最终又是如何将这些零散的知识片段,融合成一段通顺、准确、令人信服的回答?
敬请期待本系列的下一篇文章:《LLM 系列十:RAG 番外篇-向量检索与召回排序》,我们将深入探讨 RAG 的“检索”与“生成”环节,揭示知识被唤醒并最终服务于用户的完整链路。