上一篇学习了 RAG 的基本原理以及 HelloAgents 中 RAG 系统的整体架构。本篇继续结合 HelloAgents 的源码看看一份文档是如何一步步变成可以被大模型检索的知识。整个过程可以概括为一句话任意格式文档 → Markdown → 智能分块 → Embedding → 向量数据库。虽然流程并不复杂但每一步都承担着不同的职责。一、HelloAgents 的 RAG PipelineHelloAgents 并没有把 RAG 写成一个庞大的类而是将整个流程拆分成多个独立模块再统一封装到RAGTool中。文档中给出的整体流程如下任意格式文档 │ ▼ MarkItDown转换 │ ▼ Markdown文本 │ ▼ 智能分块Chunk │ ▼ Embedding │ ▼ Qdrant向量数据库这样的设计有两个优点每个模块职责明确方便维护和扩展。如果以后更换 Embedding 模型或向量数据库上层业务几乎不用修改。整个 RAG 系统对外统一由RAGTool提供接口开发者只需要调用一个工具即可完成知识导入、检索和管理。例如rag_tool RAGTool( knowledge_base_path./knowledge_base, collection_namerag_collection )可以看到真正复杂的流程都被封装到了工具内部。二、第一步MarkItDown 统一文档格式现实项目中的知识来源往往非常复杂。可能有PDFWordExcelPowerPointTXT图片HTMLMarkdown如果每种格式都单独处理代码会非常复杂。HelloAgents 的解决方案就是引入MarkItDown。它负责把不同类型的文档统一转换成 Markdown。源码中的核心逻辑如下def _convert_to_markdown(path: str) - str: md_instance _get_markitdown_instance() result md_instance.convert(path) return result.text_content无论输入是什么格式最终都会得到统一的 Markdown 文本。这样做最大的好处就是后面的文本切分、Embedding 和检索流程都只需要处理 Markdown不需要关心原始文件类型。三、为什么要转换成 Markdown很多人第一次看到这里都会有一个疑问为什么不用原始文本而要先转换成 Markdown主要原因有两个。第一Markdown 本身具有良好的结构。例如# RAG ## 什么是RAG RAG 是一种…… ## 工作流程 ……标题层级、段落、列表等信息都会被保留下来。第二Markdown 更方便后续进行语义分块。相比一整篇连续文本根据标题切分能够更好地保持上下文关系提高后续检索质量。因此HelloAgents 才会先完成格式统一再进入下一步处理。四、第二步智能分块Chunk完成格式转换之后并不会直接进行向量化。原因很简单一篇文档往往太长Embedding 模型无法一次处理。因此需要把文档切成多个 Chunk。HelloAgents 首先根据 Markdown 的标题进行分割。源码中可以看到def _split_paragraphs_with_headings(text): if raw.strip().startswith(#): # 根据标题划分层级例如# Java Java简介 ## JVM JVM介绍 ## JVM内存 ……最终会形成多个语义完整的小段落而不是随意按照字符数量截断。这样既保留了标题信息也避免一个知识点被拆得支离破碎。五、第三步Token 分块完成标题分割之后HelloAgents 还会继续按照 Token 数量进行控制。对应源码def _chunk_paragraphs( paragraphs, chunk_tokens, overlap_tokens ):为什么还需要第二次切分因为有些章节仍然很长。例如一本技术文档一个章节可能有几千字。Embedding 模型一般都会限制输入长度因此还需要进一步控制每个 Chunk 的大小。HelloAgents 根据 Token 数量生成最终的 Chunk。整个流程如下Markdown │ 标题切分 │ Token统计 │ Chunk这样生成的 Chunk 更适合后续进行向量化。六、Overlap 为什么存在源码中还有一个参数overlap_tokens它表示Chunk 之间保留一定重叠内容。例如Chunk1 A B C D E Chunk2 D E F G H其中 D、E 就属于重叠部分。为什么需要这样做因为很多知识并不是一句话能够表达完整。如果完全切断Chunk1 Java内存包括…… Chunk2 ……年轻代、老年代……那么第二块可能失去了前面的上下文。因此适当保留部分内容可以保证语义连续性提高检索质量。七、第四步Embedding 向量化完成 Chunk 之后就进入了 RAG 最重要的一步——Embedding。Embedding 的作用就是将文本转换成向量。例如Python是一门语言 ↓ [0.12,0.53,0.81,...]以后用户的问题也会转换成向量。然后通过计算两个向量之间的距离找到最相似的知识。HelloAgents 中统一使用了 Embedding 接口。源码中可以看到embedder get_text_embedder() part_vecs embedder.encode(part)这样做最大的优点就是Embedding 模型可以自由替换。文档中提到HelloAgents 默认支持百炼 APIsentence-transformersTF-IDF兜底方案如果想更换其他 Embedding 模型只需要修改这一层即可。八、第五步存入 Qdrant完成向量化之后就需要存入向量数据库。HelloAgents 默认使用的是Qdrant。整个流程可以理解为文本 ↓ Embedding ↓ 向量 ↓ Qdrant以后用户提出问题时同样会进行 Embedding。然后直接到 Qdrant 中寻找最相近的几个向量。这样就完成了一次语义检索。相比传统关键词搜索向量数据库更加关注的是语义是否相似而不是文字是否完全一致。因此即使用户换一种表达方式也有机会检索到正确内容。九、总结这一篇主要学习了 HelloAgents 中 RAG 的实现流程。整个 Pipeline 可以概括为五个步骤步骤作用MarkItDown将 PDF、Word 等各种文档统一转换为 MarkdownMarkdown 分块根据标题结构划分语义段落保持文档层次Token 分块控制 Chunk 大小并通过 Overlap 保持上下文连续性Embedding将文本转换为向量便于进行语义检索Qdrant存储向量并完成相似度搜索可以看到真正决定 RAG 检索效果的并不仅仅是大语言模型而是前面的整个知识处理流程。从文档格式统一、智能分块到向量化和存储每一步都会影响最终的检索质量。下一篇将继续学习 HelloAgents 中的高级检索策略包括MQE多查询扩展、HyDE假设文档嵌入等内容看看 RAG 是如何进一步提升知识召回率和回答准确性的。