日习一技 - 2025年7月
type: channel
link: https://i-study-everyday.online/2025/07
description: 每天学一点新技术，新框架，新工具，新名词。关于编程语言，数据库，数据结构，算法，机器学习，云计算。
items: 再学 RAGFlow 的问答流程
items: 学习 RAGFlow 的问答流程
items: 学习 RAGFlow 的检索流程
items: 构建和使用 RAGFlow 的标签集
items: 学习 RAGFlow 知识库高级配置
items: 学习 RAGFlow 的知识图谱功能
items: 学习 RAGFlow 的 RAPTOR 分块策略
items: 学习 RAGFlow 的 DeepDoc 技术之视觉处理
items: 学习 RAGFlow 的 DeepDoc 技术之解析器（二）
items: 学习 RAGFlow 的 DeepDoc 技术之解析器

再学 RAGFlow 的问答流程
type: item
date: 2025-07-31T07:11:00+00:00
description: 昨天，我们对 RAGFlow 的问答流程进行了全面的学习，主要介绍了聊天助手的核心配置，并从源码的角度研究了从用户发起查询开始，到后端系统如何一步步处理并最终生成答案的过程。不过，问答过程中还有不少有趣的点可以展开学习，我们今天就继续深入这些细节。SQL 检索优化当知识库配置的 field_map 不为空时，RAGFlow 会使用 SQL 检索替代向量检索，来优化查询：field_map = KnowledgebaseService.get_field_map(dialog.kb_ids) if field_map: ans = use_sql(questions[-1], field_map, dialog.tenant_id, chat_mdl, prompt_config.get("quote", True)) if ans: yield ans return那么这个 field_map 是什么意思呢？要了解这一点，我们还需要复习下之前的 Table 分块器的代码：def chunk(filename, binary=None, from_page=0, to_page=10000000000, lang="Chinese", callback=None, **kwargs): # 表格文件解析 if re.search(r"\.xlsx?$", filename, re.IGNORECASE): # 处理 Excel 文件 excel_parser = Excel() dfs = excel_parser(filename, binary, from_page=from_page, to_page=to_page, callback=callback) elif re.search(r"\.(txt|csv)$", filename, re.IGNORECASE): # 处理 TXT 或 CSV 文件 dfs = [pd.DataFrame(np.array(rows), columns=headers)] else: raise NotImplementedError("file type not supported yet(excel, text, csv supported)") res = [] # 组装返回结果 for df in dfs: # ... # 更新 field_map KnowledgebaseService.update_parser_config(kwargs["kb_id"], {"field_map": {k: v for k, v in clmns_map}}) return res可以看到当知识库使用 Table 分块策略时，会在知识库的 parser_config 字段中维护一个 field_map 字典，表明 ES 中存储的是表格数据，并且记录了表格的所有列信息，可以打开 MySQL 数据库的 knowledgebase 表，parser_config 字段的值如下：{ "pages": [ [ 1, 1000000 ] ], "field_map": { "xing_ming_tks": "姓名", "xue_hao_long": "学号", "nian_ling_long": "年龄", "xing_bie_tks": "性别", "cheng_ji_long": "成绩" } }在分块列表中可以看到，表格的每一行都是一个独立分块：而在 ES 中，每条记录不仅以文本的形式存储在 content_with_weight 字段中，而且每一列的值也都单独存储：这就给 SQL 检索提供了条件。后面的 use_sql() 函数逻辑如下：def use_sql(question, field_map, tenant_id, chat_mdl, quota=True): # 表数据查询 def get_table(): # 根据表结构和用户问题，生成 SQL sql = chat_mdl.chat(sys_prompt, [{"role": "user", "content": user_prompt}], {"temperature": 0.06}) # 调用 ES 的 SQL 查询接口 return settings.retrievaler.sql_retrieval(sql, format="json"), sql tbl, sql = get_table() # 如果出错，则提供错误信息，重新生成 SQL if tbl.get("error") and tried_times <= 2: tbl, sql = get_table() # 组装 Markdown 表格，并返回引用块 …

学习 RAGFlow 的问答流程
type: item
date: 2025-07-30T07:06:00+00:00
description: 昨天，我们详细学习了 RAGFlow 的检索测试功能，深入了解了其如何通过混合检索、重排序、知识图谱等多种策略从知识库中高效地召回相关信息。然而，检索只是整个 RAG 流程的第一步，召回的文本块并不能直接作为最终答案呈现给用户。为了生成连贯、精准的回答，RAGFlow 还需要将这些检索到的信息与用户的问题一起，通过精心设计的提示词提交给大语言模型进行综合处理。今天，我们将深入探讨 RAGFlow 的问答流程，学习如何创建一个聊天助手，了解其背后的配置参数，并分析从接收用户问题、执行检索、组装上下文到最终生成回答的完整源码实现。此外，我们还会研究 RAGFlow 提供的独立 AI 搜索功能，看看它与检索测试或问答流程有何不同。创建聊天助手一旦你创建了知识库、完成了文件解析并进行了检索测试，就可以开始 AI 对话了。点击页面顶部中间的 “聊天” 标签，然后点击 “新建助理” 按钮，打开聊天配置对话框：创建聊天助手时，我们需要配置一系列参数，这些参数共同决定了问答的效果，这些配置分为三大块：助理设置、提示引擎、模型设置。助理设置参数如下：助理姓名：为聊天助手取个合适的名称。助理描述：简单描述聊天助手的使用场景。助理头像：为聊天助手配个好看的头像。空回复：如果在知识库中没有检索到用户的问题，将统一使用它作为兜底回复；如果希望让大模型来回答，请将此留空。设置开场白：用户打开对话窗口时，系统自动发送的第一条消息。显示引文：默认启用，展示回答所依据的信息来源。关键词分析：使用大模型分析用户的问题，提取在相关性计算中要强调的关键词。我们昨天在学习检索测试时遇到了一个没用的参数 req.get("keyword")，看来就是这个功能了。文本转语音：将回答内容转换为语音，请先在设置里面选择 TTS 模型。Tavily API Key：如果配置有效，将使用 Tavily 进行网络搜索作为知识库的补充。这个参数放在这感觉有点格格不入，改成 “开启联网搜索” 是不是更好点？知识库：将聊天助手与一个或多个知识库进行绑定。注意，如果这里选择多个知识库，请确保它们使用相同的嵌入模型，否则会发生错误。切换到提示引擎配置页面：这个页面的配置参数如下：系统提示词：大模型回答问题需要遵循的说明，比如角色设计、回复长度、回复语言等。如果模型原生支持在问答中推理，可以通过 //no_thinking 关闭自动推理。相似度阈值：如果查询和分块之间的相似度小于此阈值，则该分块将被过滤掉。默认设置为 0.2，也就是说文本块的混合相似度得分至少 20 才会被召回。关键字相似度权重：RAGFlow 使用混合相似度得分来评估两行文本之间的距离，它是加权关键词相似度和向量余弦相似度。两个权重的总和为 1.0。Top N：该参数决定最终输入大模型的有多少分块。也就是说，并非所有相似度得分高于相似度阈值的分块都会被提供给大语言模型，大模型只能看到这些 Top N 分块。多轮对话优化：在多轮对话的中，对知识库查询的问题进行优化。会调用大模型额外消耗令牌。使用知识图谱：是否检索与所选知识库对应的知识图谱相关文本块，以处理复杂的多跳问题？这一过程将涉及对实体、关系和社区报告文本块的多次检索，会显著延长检索时间。推理：在回复时使用类似 Deepseek-R1 或 OpenAI o1 等模型的推理过程，启用后，该功能允许模型访问外部知识，并借助思维链推理等技术逐步解决复杂问题。通过将问题分解为可处理的步骤，这种方法增强了模型提供准确回答的能力，从而在需要逻辑推理和多步思考的任务上表现更优。重排序模型：非必选项，若不选择，系统将默认采用关键词相似度与向量余弦相似度相结合的混合查询方式；如果设置了重排序模型，则混合查询中的向量相似度部分将被重排序打分替代。跨语言搜索：选择一种或多种语言进行跨语言搜索。如果未选择任何语言，系统将使用原始查询进行搜索。变量：通过变量动态调整大模型的系统提示词，{knowledge} 为系统预留变量，代表从指定知识库召回的文本块，所有变量都必须用大括号 {} 括起来。如果你在这里自定义了其他变量，唯一可以传递其值的方法是调用助手的对话 API，比如 SDK 或 HTTP 方式。可以看到这里的大多数配置参数昨天在学习 “检索测试” 时都已经见过，今天重点学习那些新的参数，比如多轮对话优化、推理、变量等。再切换到模型设置页面：这里主要是大模型相关的参数：模型：选择用于生成最终答案的大语言模型。温度：控制生成文本的随机性，值越高，答案越具创造性；值越低，答案越确定。Top P：一种采样策略，与温度类似，用于控制生成文本的多样性。存在处罚：这会通过惩罚对话中已经出现的单词来阻止模型重复相同的信息，更高的存在惩罚值意味着鼓励模型在回复中包含更多样化的标记。频率惩罚：与存在惩罚类似，这…

学习 RAGFlow 的检索流程
type: item
date: 2025-07-29T07:04:00+00:00
description: 经过一段时间的学习，我们已经深入了解了 RAGFlow 从文件上传、解析、分块到知识库构建的全过程，并探索了 RAPTOR、知识图谱、标签集等高级功能。至此，知识库的构建阶段已经完成，接下来我们将进入 RAG 应用的核心环节：检索与问答。我们今天先学习 RAGFlow 的检索测试功能。运行检索测试当知识库构建完毕，我们需要验证下知识库的配置是否有效。RAGFlow 在知识库页面提供了一个 “检索测试” 的功能，我们可以在这里快速提问，检查是否能够检索到预期的文档块：注：在运行检索测试之前，确保你的文件已上传并成功解析。就像微调精密仪器一样，RAGFlow 需要仔细调试才能实现最佳问答性能，你的知识库设置、聊天助手配置以及所指定的模型配置都可能对最终结果产生重大影响。因此在继续配置聊天助手之前建议先运行一次检索测试，验证目标片段是否能够被检索出来，从而帮助你快速发现需要改进的地方或定位需要解决的问题。例如，在调试问答系统时，如果知道可以检索到正确的片段，就可以将精力集中在其他方面。你可以通过调整下面这些参数来运行检索测试：相似度阈值（Similarity threshold）：如果查询语句和分块之间的相似度小于此阈值，则该分块将被过滤掉，默认设置为 0.2，也就是说文本块的相似度得分至少 20 才会被召回；关键字相似度权重（Keyword similarity weight）：RAGFlow 使用混合相似度得分来评估两行文本之间的距离，它结合了关键词相似度和向量相似度，这个参数用于设置关键词相似度在混合相似度得分中的权重，默认情况下，该值为 0.7，使得另一个组成部分的权重为 0.3，两个权重之和为 1.0；重排序模型（Rerank model）：上面提到混合相似度得分结合了关键词相似度和向量相似度，这其实是没有配置重排序模型时的算法，当配置了重排序模型，那么混合相似度得分中的向量相似度将由重排序分数替代，混合相似度得分将结合关键词相似度和重排序分数；使用知识图谱（Use knowledge graph）：启用该配置前，确保知识图谱已成功构建，它会检索知识图谱相关文本块，以处理复杂的多跳问题。在知识图谱中，实体描述、关系描述或社区报告各自作为独立的片段存在，因此启用该配置将涉及对实体、关系和社区报告文本块的多次检索，会显著延长检索时间；跨语言搜索（Cross-language search）：选择一种或多种语言进行跨语言搜索，如果未选择任何语言，系统将使用原始查询进行搜索。检索测试源码解读检索测试调用的接口为 /v1/chunk/retrieval_test，入口代码位于 api/apps/chunk_app.py 文件：@manager.route('/retrieval_test', methods=['POST']) def retrieval_test(): # 查看知识库详情 kb = KnowledgebaseService.get_by_id(kb_ids[0]) # 开启跨语言搜索，将用户问题翻译成其他语言 if langs: question = cross_languages(kb.tenant_id, None, question, langs) # 从用户问题中提取关键词，拼接到用户问题上 if req.get("keyword", False): chat_mdl = LLMBundle(kb.tenant_id, LLMType.CHAT) question += keyword_extraction(chat_mdl, question) # 如果知识库配置了标签集，给用户问题打上标签 labels = label_question(question, [kb]) # 检索的核心逻辑 ranks = settings.retrievaler.retrieval(question, ..., rank_feature=labels) # 检索知识图谱，结果插到第一条 if use_kg: ck = settings.kg_retrievaler.retrieval(question, ...) if ck["content_with_weight"]: ranks["chunks"].insert(0, ck) return get_json_result(data=ranks)这里的 req.get("keyword") 这个参数比较奇怪，看起来应该是 “是否启用关键词” 之类的开关，但是我在页面上并没有找到对应的配置。核心逻辑已有注释标出，下面对这里的几个关键函数分节讨论。跨语言搜索默认情况下，未选择目标语言，系统只会使用原始查询进行搜索，当知识库包含多种语言文档时，或者存在某些专业术语在不同语言…

构建和使用 RAGFlow 的标签集
type: item
date: 2025-07-28T06:54:00+00:00
description: 检索准确性是衡量生产级 RAG 框架的试金石。除了自动关键词提取、自动问题提取、知识图谱等提升检索效果的方法外，RAGFlow 还引入了自动提取标签的功能，它会根据每个知识块的相似性，自动将用户自定义标签集中的标签映射到知识库中的相关块。这些标签为现有的数据集增加了额外的层级领域知识，特别适用于各个片段彼此非常相似，以至于无法将目标片段与其他片段区分开的情况。例如，当你有少量关于 iPhone 的片段，而大多数是关于 iPhone 手机壳或配件时，如果没有额外信息，就很难检索到那些关于 iPhone 的片段。要使用此功能，你必须先创建一个标签集，然后在知识库的配置页面中指定该标签集。我们今天就来学习如何构建和使用 标签集（Tag Set）。构建标签集在前面学习的 11 种切片方法中有一种叫 Tag 分块，这是一种特殊的知识库配置，使用 Tag 作为切片方法的知识库不会参与 RAG 的检索和问答流程，而是充当标签集的角色。其他知识库可以使用它来标记自己的分块，对这些知识库的查询也将使用此标签集进行标记。首先我们创建一个知识库，选择 Tag 作为切片方法：然后上传提前准备好的 Excel 标签文件，文件内容如下：标签文件可以是 Excel、CSV 或 TXT 格式，文件内容需满足下面的格式要求：如果文件是 Excel 格式，它应包含两列：第一列用于标签描述，第二列用于标签名称；不需要标题行，且支持多个工作表（Sheet）；标签描述可以是示例分块或示例查询；如果文件是 CSV 和 TXT 格式，必须采用 UTF-8 编码，且使用制表符（TAB）作为分隔符来分隔描述和标签；在标签列中可以包含多个标签，使用逗号分隔；然后点击 “解析” 按钮，解析完成后查看分块列表：可以看到，标签集的第一列，也就是描述部分变成了知识库的分块，而第二列变成了该分块对应的标签列表。在知识库的配置页面底部，我们可以以标签云的形式查看整个标签集：标签越大代表出现的频次越高，或者以表格形式查看：构建标签集的实现构建标签集的逻辑主要位于 Tag 分块器，也就是 rag/app/tag.py 文件：def chunk(filename, binary=None, lang="Chinese", callback=None, **kwargs): # 分块基础信息 res = [] doc = { # 文件名 "docnm_kwd": filename, # 对文件名进行分词 "title_tks": rag_tokenizer.tokenize(re.sub(r"\.[a-zA-Z]+$", "", filename)) } # 处理 Excel 文件 if re.search(r"\.xlsx?$", filename, re.IGNORECASE): excel_parser = Excel() for ii, (q, a) in enumerate(excel_parser(filename, binary, callback)): res.append(beAdoc(deepcopy(doc), q, a, eng, ii)) return res # 处理 TXT 文件 elif re.search(r"\.(txt)$", filename, re.IGNORECASE): # ... # 处理 CSV 文件 elif re.search(r"\.(csv)$", filename, re.IGNORECASE): # ...其中比较关键的部分是 beAdoc() 函数，对每个标签描述和标签统一进行处理，组装文本块信息：def beAdoc(d, q, a, eng, row_num=-1): # 标签描述的原始内容 d["content_with_weight"] = q # 对标签描述进行分词 d["content_ltks"] = rag_tokenizer.tokenize(q) # 对标签描述再进行细粒度分词 d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"]) # 标签处理 d["tag_kwd"] = [t.strip().replace(".", "_") for t in a.split(",") if t.strip()] # 行号记录 if row_num >= 0: d["top_int"] = [row_num] return d其中 tokenize() 对原始文本进行首次分词，感兴趣的可以看看里面的实现，包含完整的文本预处理，比如全角转半角、繁体转简体、去除非词字符、大小写转换等，然后使用 正向/反向最大匹配算法 进行中文分词…

学习 RAGFlow 知识库高级配置
type: item
date: 2025-07-25T06:50:00+00:00
description: 目前为止，我们已经学习了很多关于 RAGFlow 的知识库配置，包括分块方法，PDF 解析器，嵌入模型，RAPTOR 策略，提取知识图谱等，除此之外，还剩下一些高级配置，我们今天一起来看下：页面排名（pagerank）当我们从多个指定的知识库中检索知识时，可能希望某些知识库的知识优先被检索到。比如我们有两个知识库：知识库 A 用于 2024 年新闻，知识库 B 用于 2025 年新闻，但希望优先显示 2025 年的新闻，那么提高知识库 B 的页面排名将非常有用。PageRank 最初被设计为一种对网页进行排名的算法，RAGFlow 中的这个参数用于为特定的知识库分配更高的 PageRank 分数，这个分数会在文档分块和解析时加到每个文本块上，处理逻辑位于任务执行器的 build_chunks() 方法中：async def build_chunks(task, progress_callback): # ... if task["pagerank"]: doc[PAGERANK_FLD] = int(task["pagerank"])需要注意的是，这个参数是针对整个知识库的，而不是针对单个文档。当我们修改知识库的 pagerank 值时，整个知识库的文本块都会同步更新：@manager.route('/update', methods=['post']) def update(): # 当 pagerank 参数有变动时 if kb.pagerank != req.get("pagerank", 0): # 该参数只支持 ES 文档引擎 if os.environ.get("DOC_ENGINE", "elasticsearch") != "elasticsearch": return get_data_error_result(message="'pagerank' can only be set when doc_engine is elasticsearch") if req.get("pagerank", 0) > 0: # 更新知识库中的所有文本块的 PAGERANK_FLD settings.docStoreConn.update({"kb_id": kb.id}, {PAGERANK_FLD: req["pagerank"]}, search.index_name(kb.tenant_id), kb.id) else: # 由于 ES 中的 PAGERANK_FLD 不能为 0，因此删除该字段 settings.docStoreConn.update({"exists": PAGERANK_FLD}, {"remove": PAGERANK_FLD}, search.index_name(kb.tenant_id), kb.id)可以在 ES 中查看文本块详情，会多一个 pagerank_fea 字段：自动关键词提取（auto_keywords）这个参数的作用是自动为每个文本块提取 N 个关键词，当用户问题包含这些关键词时，可以提高文本块的排名。一般情况下，如果分块大小在 1000 字符左右，那么推荐提取 3-5 个关键词即可；如果你的分块较大，可以适当提高这个值，但是请注意，随着数值的增加，边际收益会递减，最多是 30 个。提取关键词的逻辑同样位于任务执行器的 build_chunks() 方法中：async def build_chunks(task, progress_callback): # ... if task["parser_config"].get("auto_keywords", 0): # 绑定大模型 chat_mdl = LLMBundle(task["tenant_id"], LLMType.CHAT, llm_name=task["llm_id"], lang=task["language"]) # 调用大模型，为指定文本块生成 N 个关键词 async def doc_keyword_extraction(chat_mdl, d, topn): # 缓存机制 cached = get_llm_cache(chat_mdl.llm_name, d["content_with_weight"], "keywords", {"topn": topn}) if not cached: # 限制大模型并发路数，默认 10 路 async with chat_limiter: cached = await trio.to_thread.run_sync(lambda: keyword_extraction(chat_mdl, d["content_with_weight"], topn)) set…

学习 RAGFlow 的知识图谱功能
type: item
date: 2025-07-24T06:44:00+00:00
description: 昨天我们学习了 RAGFlow 的 RAPTOR 分块策略，今天我们将继续学习另一种高级配置 —— 提取知识图谱（use_graphrag）：该特性自 v0.16.0 起引入，开启该配置后，RAGFlow 会在当前知识库的分块上构建知识图谱，构建步骤位于数据抽取和索引之间，如下所示：知识图谱在涉及嵌套逻辑的多跳问答中尤其有用，当你在对书籍或具有复杂实体和关系的作品进行问答时，知识图谱的表现优于传统的抽取方法。请注意，构建知识图谱将消耗大量 token 和时间。开启 GraphRAG 任务GraphRAG 的逻辑位于任务执行器的 do_handle_task() 函数中：async def do_handle_task(task): # ... elif task.get("task_type", "") == "graphrag": # 绑定聊天模型 chat_model = LLMBundle(task_tenant_id, LLMType.CHAT, llm_name=task_llm_id, lang=task_language) # 运行 GraphRAG 逻辑 graphrag_conf = task["kb_parser_config"].get("graphrag", {}) with_resolution = graphrag_conf.get("resolution", False) with_community = graphrag_conf.get("community", False) async with kg_limiter: await run_graphrag(task, task_language, with_resolution, with_community, chat_model, embedding_model, progress_callback) return这个函数我们之前已经详细学习过，但是跳过了 GraphRAG 相关的逻辑，今天我们就继续来看下这个 run_graphrag() 的实现细节：async def run_graphrag(row: dict, language, with_resolution: bool, with_community: bool, chat_model, embedding_model, callback): # 检索原始分块列表 chunks = [] for d in settings.retrievaler.chunk_list(...): chunks.append(d["content_with_weight"]) # 使用 LightKGExt 或 GeneralKGExt 生成子图 subgraph = await generate_subgraph( LightKGExt if row["kb_parser_config"]["graphrag"]["method"] != "general" else GeneralKGExt, ... row["kb_parser_config"]["graphrag"]["entity_types"], ... ) # 将子图合并到知识图谱中 subgraph_nodes = set(subgraph.nodes()) new_graph = await merge_subgraph(...) # 实体消歧 if with_resolution: await resolve_entities(new_graph, subgraph_nodes, ...) # 社区报告 if with_community: await extract_community(new_graph, ...)整体的逻辑还是比较清晰的，首先通过 retrievaler.chunk_list() 检索出该文档原始的分块列表，然后基于配置的实体类型生成子图，然后将子图合并到知识图谱中，最后进行实体消歧和社区报告的生成。和 RAPTOR 任务一样，开启知识图谱也需要先执行一次标准的分块策略，生成原始的分块列表，在完成第一个任务后，会再生成一个知识图谱类型的任务，执行上面的代码逻辑。提取知识图谱涉及的配置参数如下：实体类型（entity_types） - 指定要提取的实体类型，默认类型包括：组织（organization）、人物（person）、事件（event）和类别（category），可根据具体的知识库内容添加或删除类型；方法（method） - 用于构建知识图谱的方法，RAGFlow 支持两种方法：通用（general）：使用 GraphRAG 提供的提示词提取实体和关系。轻量（light）：使用 LightRA…

学习 RAGFlow 的 RAPTOR 分块策略
type: item
date: 2025-07-23T06:32:00+00:00
description: 在学习知识库配置时，我们提到了一个高级配置 —— 使用召回增强 RAPTOR 策略（use_raptor）：开启该配置后，RAGFlow 会使用 RAPTOR 分块策略，这是去年提出的一种增强型文档预处理技术，旨在解决多跳问答问题，通过对文档片段进行递归聚类和摘要，构建分层树状结构，使得在长文档中实现更具上下文感知的检索。RAGFlow 从 v0.6.0 版本开始集成 RAPTOR 策略，用于文档聚类，作为其数据预处理流程中数据提取与索引之间的一部分，如下所示：使用这种新方法，RAGFlow 在复杂多步推理的问答任务上展示了 SOTA 结果，通过将 RAPTOR 与其他内置的分块或检索方法结合使用，可以进一步提升问答的准确性。RAPTOR 简介RAPTOR 是一种新型的信息检索方法，全称为 用于树形组织检索的递归抽象处理，由 Parth Sarthi 等人于 2024 年发表的论文 《RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval》 中提出：https://arxiv.org/abs/2401.18059传统的 RAG 方法仅从检索语料库中检索短的连续片段，这限制了大模型对整体文档上下文的全面理解，导致其在总结型、整合型的问题上表现堪忧；而 RAPTOR 则通过递归地对文本片段进行嵌入、聚类和摘要，从下到上构建具有不同摘要层级的树结构，从而提高检索的效率和问答的效果。RAPTOR 的本质是树的构建和检索。树的构建流程如下图所示：首先，将原始文本切分成块（chunks），然后计算每个分块的向量，这一步和传统 RAG 没有区别，这些分块构成树的叶节点层（Leaf layer）；接着，对分块进行聚类，将相似的分块归为一类，使用语言模型对每一类分块进行摘要，生成新的分块，这些分块构成树的中间层；重复执行上述步骤，直到分块不可再分为止，即树的根节点层（Root layer）；对于每个非叶子节点，都存储着自己的索引、子节点、总结后的文本以及对应的向量。构建好树形结构之后，就是对树的检索。RAPTOR 提供了两种检索树的方法，树遍历（Tree Traversal Retrieval） 和 压缩树（Collapsed Tree Retrieval）：树遍历的流程如下：从根节点层出发，选取跟问题最相似的 topK 个节点，记为 S1；再在 S1 的子节点中选取跟问题最相似的 topK 个节点，记为 S2；重复这个过程，一直执行到叶节点层为止，构成 S1、S2、S3... 等；将 S1、S2、S3... 合并，组装为上下文用于大模型回答；而压缩树就要简单的多了，它直接将整个树的所有节点（包括根节点和叶子节点）都放在一个集合里，然后走传统 RAG 一样的检索方法，选出 topK 个相似节点作为上下文。RAPTOR 的实现在 Github 上开源了，可以参考其官方仓库：https://github.com/parthsarthi03/raptor开启 RAPTOR 任务RAPTOR 的逻辑位于任务执行器的 do_handle_task() 函数中：async def do_handle_task(task): # ... if task.get("task_type", "") == "raptor": # 绑定聊天模型 chat_model = LLMBundle(task_tenant_id, LLMType.CHAT, llm_name=task_llm_id, lang=task_language) # 运行 RAPTOR 逻辑 async with kg_limiter: chunks, token_count = await run_raptor(task, chat_model, embedding_model, vector_size, progress_callback)这个函数我们之前已经详细学习过，但是跳过了 RAPTOR 相关的逻辑，今天我们就继续来看下这个 run_raptor() 的实现细节：async def run_raptor(row, chat_mdl, embd_mdl, vector_size, callback=None): # 检索原始分块列表 chunks = [] vctr_nm = "q_%d_vec"%vector_size for d in settings.retrievaler.chunk_list(row["doc_id"], ...): chunks.append((d["content_with_weight"], np.array(d[vctr_nm]))) # 初始化 RAPTOR 实例 ra…

学习 RAGFlow 的 DeepDoc 技术之视觉处理
type: item
date: 2025-07-22T06:22:00+00:00
description: 我们之前已经学过，DeepDoc 由 解析器（parser） 和 视觉处理（vision） 两个部分组成。解析器提供了不同格式文档的通用解析方法，我们花了两天时间，对这 10 个解析器的源码做了深入分析；今天我们将学习 DeepDoc 的视觉处理部分，包括 光学字符识别（OCR，Optical Character Recognition） 、 文档布局识别（DLR，Document Layout Recognition） 和 表格结构识别（TSR，Table Structure Recognition） 等高级特性，可以对 PDF 文件实现更好的识别效果。测试脚本为了更直观的体验 OCR、DLR 和 TSR 的效果，DeepDoc 提供了两个测试脚本，位于 deepdoc/vision 目录下：t_ocr.py - 调用 文本检测模型 (det.onnx) 和 文本识别模型 (rec.onnx) 测试 OCR 功能；t_recognizer.py - 调用 布局分析模型 (layout.onnx) 和 表格结构识别模型 (tsr.onnx) 测试 DLR 和 TSR 功能；拿一个简单的 PDF 文档测试一下 OCR 功能：$ python deepdoc/vision/t_ocr.py --inputs /path/to/demo.pdf运行之后会在当前目录下生成一个 ./ocr_outputs 目录，里面包含了 OCR 的结果。每个页面生成两个文件：一个是 jpg 图片，对应文本检测结果，使用黑框将检测到的文本区域框了起来；另一个是 txt 文本，包含文本识别的结果：表格页面的识别结果：继续使用下面的命令测试一下 DLR 功能（注意后面的 --mode layout 参数，表示布局分析模式）：$ python deepdoc/vision/t_recognizer.py --inputs /path/to/demo.pdf --mode layout--inputs 输入可以是 PDF 文件，也可以是图片文件，还可以是包含 PDF 或图片的目录。另外，它还有一个 --output_dir OUTPUT_DIR 参数，可以指定输出位置。运行之后会在当前目录下生成一个 ./layout_outputs 目录，里面包含了 DLR 的结果。每个页面生成一个 jpg 图片，识别到区域使用不同颜色的框框了出来：可以注意到框的左上角还打上了标签，比如 title 标题，text 正文。下面是表格页面的识别结果：左上角的标签为 table 表格，表示模型成功识别出了表格区域。最后将上面的表格区域抠出来，另存成一个张独立的图片，测试一下 TSR 功能（注意后面的 `--mode tsr 参数，表示表格结构识别模式）：$ python deepdoc/vision/t_recognizer.py --inputs /path/to/table.png --mode tsr运行之后的结果同样保存在 ./layout_outputs 目录，里面有一个 jpg 图片，标识出了更加详细的表格区域：上面绿色的框左上角标签为 table row 表示行，红色的框左上角标签为 table column 表示列。另外，该目录下还生成了一个 html 文件，以 HTML 形式展示了表格的结构： 姓名 学号 年龄 性别 成绩 张三 001 18 男 100 李四 002 19 女 99 王五 003 20 男 98 文本检测文本检测是 OCR 流水线的第一步，为后续的文本识别提供准确的文本区域定位。它的代码逻辑位于 deepdoc/vision/ocr.py 文件中，实现类 TextDetector 如下：它的核心流程分为三个部分：预处理阶段，模型推理阶段，后处理阶段。预处理阶段data = transform(data, self.preprocess_op)这里的 self.preprocess_op 有点意思，它是通过下面这个 create_operators() 函数创建：def create_operators(op_param_list, global_config=None): ops = [] for operator in op_param_list: op_name = list(operator)[0] param = {} if operator[op_name] is None else operator[op_name] if global_config is not None: param.update(global_config) op = getattr(operators, op_name)(**param) ops.appen…

学习 RAGFlow 的 DeepDoc 技术之解析器（二）
type: item
date: 2025-07-21T05:49:00+00:00
description: 在上一篇文章中，我们学习了 RAGFlow 的 DeepDoc 技术，并对 DeepDoc 的 10 大解析器做了个概览，目前我们已经学习了其中的 3 种解析器，包括：DOCX 解析器、Excel 解析器 和 PPT 解析器。今天我们继续学习剩下的解析器。TXT 解析器TXT 解析器的实现类为 RAGFlowTxtParser，代码如下：它通过 get_text() 直接读取文本，并使用 parser_txt() 对文本进行分块处理，默认分块大小为 128，分隔符为 "\n!?;。；！？"。具体的分块逻辑如下：从 delimiter 中提取出所有的分割符；支持多字符分隔符，确保用反引号（``）把它们括起来；比如 `n`##`;`，表示在行尾、双井号（##）和分号处进行分隔；使用 | 将所有的分割符拼接（这是一个合法的正则表达式），并使用 re.split() 对原文本进行分割得到 secs 数组；遍历 secs 数组，如果数据和分隔符一样，忽略之，否则加入分块；这一点感觉有点粗暴，比如 hello!world! 分块后的结果为 helloworld，分割符号会丢掉；使用 tiktoken 的 cl100k_base 编码器计算分块的 token 数量，如果没有超过 chunk_token_num，则进行合并，最终得到 cks 数组；注意解析器最后返回的 [[c, ""] for c in cks] 格式，这个 [c, ""] 是为了统一不同解析器的返回，第一个元素表示文本内容，第二个元素表示位置信息或其他元数据。可以在 native 分块器代码里看到其他的解析器的处理：吐槽一句，RAGFlow 中解析器返回的格式有点乱，有的是在解析器里返回这个格式，有的是在分块器中拼这个格式。HTML 解析器HTML 解析器的实现类为 RAGFlowHtmlParser，代码如下：这个解析器的实现比较简单，直接使用 readability-lxml 和 html-text 两个库来读取 HTML 标题和内容，最后返回使用换行符分割后的数组。可以看到，这个返回格式和 TXT 解析器就不一样，因此要在分块器中进行适配。这里比较有意思的是它对 HTML 文件编码的处理，我们可以参考下，使用 chardet 来检测文件编码：import chardet all_codecs = [ 'utf-8', 'gb2312', 'gbk', 'utf_16', 'ascii', 'big5', 'big5hkscs', # 这里省略一堆编码 ... ] def find_codec(blob): detected = chardet.detect(blob[:1024]) if detected['confidence'] > 0.5: if detected['encoding'] == "ascii": return "utf-8" for c in all_codecs: try: blob[:1024].decode(c) return c except Exception: pass try: blob.decode(c) return c except Exception: pass return "utf-8"Markdown 解析器Markdown 解析器的实现类为 RAGFlowMarkdownParser，代码如下：和之间的解析器不一样的是，这里只有一个 extract_tables_and_remainder() 函数，用于从 Markdown 文本中提出表格数据和剩余的文本内容。从代码中可以看到它支持三种不同的表格形式：标准 Markdown 表格无边框表格HTML 表格下面是三种表格的示例：# 标准 Markdown 表格 | 姓名 | 年龄 | | ---- | ---- | | 张三 | 18 | | 李四 | 19 | # 无边框表格 姓名 | 年龄 ---- | ---- 张三 | 18 李四 | 19 # HTML 表格 姓名 年龄 张三 18 李四 19 光看这里的代码可能有点蒙，这个解析器咋就一个函数，入口都没有。很显然，这又是 RAGFlow 解析器不规范的一个例子。实际上，在 native 分块器里可以看到 Markdown 解析器的完整实现：从 Markdown 提取出表格内容和剩余文本后，文本段落按标题进行分块，表格部分使用 markdown 库将表格转换为 HTML 格式；最后返回两部分：文本段落（sections） 和 表格（tbls），用于后续的 RAG 处理。看来，解析器和分块器的代码耦合还是很多啊！JSON 解析器JSON 解析器的实现类为 RAGFlowJsonParser，代码如下：这是一个挺有意…

学习 RAGFlow 的 DeepDoc 技术之解析器
type: item
date: 2025-07-18T08:56:00+00:00
description: 我们昨天将任务执行器中的 do_handle_task() 函数从头到尾梳理了一遍，详细学习了 RAGFlow 的文件解析和分块逻辑。其中还遗漏了一些关键技术点，包括 DeepDoc 深度解析文档、RAPTOR 分块策略、GraphRAG 构建知识图谱，以及自动提取关键字、自动生成问题、标签集的构建和使用等高级配置。让我们一样一样来，先从 DeepDoc 技术学起。在知识库的配置页面，当我们选择与 PDF 兼容的切片方法时，比如 General、Manual、Paper、Book、Laws、Presentation 和 One 这几个，会出现 PDF 解析器下拉菜单：默认支持 DeepDoc 和 Native 两种方式：DeepDoc - 基于 OCR、表格识别、布局分析等视觉模型，深度分析 PDF 文档，有效地提取文档标题、文本块、图像和表格等内容；Native - 仅提取 PDF 中的纯文本内容；当用户的 PDF 仅包含纯文本时，可以选择该选项，从而减少整体解析时间；目前最新版本中还有个实验特性，如果用户配置了第三方视觉模型（也就是之前配置的 Img2txt 模型），也可以使用这些视觉模型来做 PDF 解析。我们重点关注 DeepDoc 方式，因为这是 RAGFlow 的默认方式，也是最强大的方式。注意 DeepDoc 选项暂时不适用于 Word 文档，若要使用该功能，可以将 Word 文档转换为 PDF 格式。DeepDoc 简介DeepDoc 是 RAGFlow 的核心特性之一，可以对来自不同领域、具有不同格式和不同检索要求的文档进行深度分析，它负责将各类复杂文档转换为结构化的文本格式，为后续的分块和向量化处理打下基础。从 v0.17.0 版本开始，RAGFlow 将数据提取任务与分块方法解耦，成为两个独立的模块。这种解耦的设计具有更高的灵活性，支持更深层次的定制，以适应更复杂的用例。用户能够自主选择适合的视觉模型，在速度和性能之间取得平衡，以满足具体的用例需求。分块的逻辑位于 rag/app 目录：$ tree rag/app rag/app ├── __init__.py ├── audio.py ├── book.py ├── email.py ├── laws.py ├── manual.py ├── naive.py ├── one.py ├── paper.py ├── picture.py ├── presentation.py ├── qa.py ├── resume.py ├── table.py └── tag.py这些文件就对应我们之前学习过的十四种不同的 分块器（Chunker），RAGFlow 会根据不同的文档类型使用不同的分块器，对分块逻辑感兴趣的朋友可以研究下这里的代码。有很多分块器依赖于 DeepDoc 来解析文档，DeepDoc 的逻辑位于 deepdoc 目录：$ tree deepdoc deepdoc ├── __init__.py ├── parser │ └── ... └── vision ├── layout_recognizer.py ├── ocr.py └── table_structure_recognizer.py从目录结构可以看出，DeepDoc 由两个部分组成：解析器（parser） 和 视觉处理（vision），解析器提供了不同格式文档的通用解析方法，供分块器使用；而视觉处理部分则提供了 OCR、表格识别、布局分析等高级特性，可以对 PDF 和 图片等文件实现更好的识别效果。分块器、解析器和视觉处理这三者之间的关系如下：解析器概览今天我们主要关注 DeepDoc 的解析器部分，视觉处理部分我们放到下一篇文章中学习。$ tree deepdoc/parser deepdoc/parser ├── __init__.py ├── docx_parser.py ├── excel_parser.py ├── figure_parser.py ├── html_parser.py ├── json_parser.py ├── markdown_parser.py ├── pdf_parser.py ├── ppt_parser.py ├── resume │ ├── step_one.py │ └── step_two.py ├── txt_parser.py └── utils.py从 deepdoc/parser 目录下的文件可以看到，DeepDoc 内置了 10 种不同的解析器，支持 Word、Excel、PPT、PDF、HTML、Markdown、JSON、图片等文件的处理和解析；此外，由于简历是一种非常复杂的文档，包含各种格式的非结构化文本，可以被解析为…