跳到主要内容

进阶召回 — RAPTOR、GraphRAG 与查询分解

30 秒导读: 第 03 章的混合检索(向量 + BM25 + 重排)是「把问句和一堆平铺 chunk 逐个比相似度」。 但有三类问题,平铺 chunk 天生接不住:问「这份年报整体讲了啥」——答案不在任何单个 chunk 里; 问「A 的老板的老板是谁」——要沿关系跳好几步;问「对比 X 和 Y 在成本和性能上的差异」——一个问句里 塞了多个子意图。本章讲 RAGFlow 为这三类分别造的三件结构化召回武器,外加两个「顺藤摸瓜」的辅助召回。

本章是第 03 章 混合检索与融合重排 的进阶篇。通用的打分、融合、重排不再重复 (见第 03 章的 Dealer.retrieval / rerank);这里只讲「结构化 / 多跳」召回策略本身。


1. 第 03 章接不住的三类问题(本章要补的洞)

先说清楚为什么还要有这一章。第 03 章的检索单元是「一段一段平铺的原文 chunk」,召回的本质是 「问句向量 vs chunk 向量的近邻 + 关键词命中」。这套办法有三个结构性盲区:

盲区(接不住的问题)白话例子为什么平铺 chunk 接不住
跨段主旨型「这份报告整体结论是什么?」主旨是若干段落合起来才浮现的,任何单个 chunk 都只讲局部
多跳关系型「张三所在公司的竞争对手有谁?」答案要沿实体关系跳 2~3 步,而相似度只看「和问句像不像」
复合多意图型「对比 A、B 在价格和续航上的优劣」一个问句里几个子问题,单轮检索会被主意图带偏、漏掉次意图

RAGFlow 的三件进阶武器,正好一一对应;再加两个结构辅助召回:

进阶召回全景(在第 03 章通用检索之上叠加)

┌─────────────────────── 写入侧(入库时预先构建结构) ───────────────────────┐
│ │
│ RAPTOR 建树 GraphRAG 建图 │
│ 把 chunk 自底向上聚类 从 chunk 抽实体/关系 → 社区发现 → 社区摘要 │
│ 逐层 LLM 摘要 摘要节点也当成 chunk 入库 │
│ │ │ │
└────────┼────────────────────────┼────────────────────────────────────────┘
│ 摘要 chunk │ 实体/关系/社区报告 chunk
▼ (raptor_kwd) ▼ (knowledge_graph_kwd)
┌───────────────────────── 同一个索引库 ──────────────────────────┐
│ 原文 chunk + RAPTOR 摘要 chunk + 图谱 chunk │
└───────────────────────────────────────────────────────────────┘
▲ ▲ ▲
┌────────┼────────────────────────┼────────────────────────┼──────────┐
│ │ │ │ │
│ ① RAPTOR 召回 ② GraphRAG 召回(KGSearch) ③ 查询分解 │
│ (透明:摘要就是普通 query_rewrite→实体/关系/ research 递归拆 │
│ chunk,被通用检索 类型召回→n-hop 扩展→ 子问题,逐层检索 │
│ 顺带捞回) 社区报告 汇总 │
│ │
│ ④ 结构辅助:retrieval_by_toc(按目录补章节) / retrieval_by_children │
│ (子块命中→回捞父块) │
└── 读取侧(检索时) ─────────────────────────────────────────────────┘

怎么读这张图: 上半是「入库时」预先造好的结构(树 / 图),它们产出的摘要节点都当普通 chunk 存进同一个 索引;下半是「检索时」三种策略如何用上这些结构。关键设计:RAPTOR 摘要对检索侧透明——它就是多了一批 更「宏观」的 chunk,通用检索会自然捞到;而 GraphRAG 和查询分解则是独立的召回入口,产出后拼接进结果。

三种主武器 + 两种辅助,一句话各自定位:

手段解决哪个盲区核心动作代价
RAPTOR 层次摘要跨段主旨型入库时聚类 + 逐层摘要,摘要入库入库慢、多花 LLM 摘要 token
GraphRAG 图谱召回多跳关系型建图 + 按实体/关系/社区召回 + n-hop建图极贵(逐 chunk 抽取 + 社区摘要)
树状查询分解复合多意图型递归拆子问题、逐层检索汇总检索时多轮 LLM,延迟高
retrieval_by_toc章节完整性命中后按文档目录补齐相关章节一次额外 LLM 选章
retrieval_by_children上下文完整性子块命中 → 回捞父块无(纯索引查)

2. RAPTOR — 层次摘要树(接住「跨段主旨型」)

2.1 它要解决的小问题

问「整篇讲了什么」,答案散在几十个 chunk 里,没有哪一段能单独命中。RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval,递归抽象化的树组织检索)的思路是: 入库时就先把相近的 chunk 聚成簇、让 LLM 写一段簇摘要,再把摘要继续往上聚、再摘要,一层层堆到树顶。 这些摘要节点也作为 chunk 存进索引——于是「宏观主旨」也有了可被向量命中的落点。

2.2 直觉:自底向上盖一座「摘要金字塔」

第 2 层摘要 [ 全文主旨摘要 ] ← 越高越宏观
/ \
第 1 层摘要 [簇A摘要] [簇B摘要] ← LLM 对簇写的摘要
/ \ / \
第 0 层原文 c1 c2 c3 c4 ... ← 原始 chunk(叶子)

底层是原始 chunk;每一层「聚类 → 摘要」得到更少、更概括的节点;顶层逼近全文主旨。问宏观问题时, 高层摘要节点向量上更接近问句,于是被召回;问细节问题时,底层原文照旧命中。两头都不落空。

2.3 两种建树策略:经典 GMM 树 vs Psi 合并树

RAGFlow 的 RAPTOR 实现支持两种 tree_builder,常量定义在 rag/utils/raptor_utils.py(RAPTOR_TREE_BUILDER = "raptor"PSI_TREE_BUILDER = "psi"):

策略怎么分层适合
经典 RAPTOR("raptor")每层用 UMAP 降维 + GaussianMixture(高斯混合模型)软聚类,一簇一摘要,逐层收敛通用,论文原版
Psi 合并树("psi")按 chunk 两两余弦相似度排序,并查集自底向上「最相似先合并」建二叉合并树,再按高度分层摘要想要确定性、可控 fanout 的合并结构

入口是同一个 __call__,按 tree_builder 分流(rag/raptor.py:649 __call__):

# rag/raptor.py:656 __call__ 里的分流(节选)
if self._tree_builder == PSI_TREE_BUILDER:
return await self._build_psi_layers(chunks, callback, task_id) # Psi 合并树
# 否则走下面的经典 GMM/AHC 循环 ...

2.4 经典树:UMAP 降维 + GMM 聚类,一层层摘上去

经典路径是个 while end - start > 1 的循环,每轮处理「上一层新产出的节点区间」:

# rag/raptor.py:690 经典 RAPTOR 每层的降维 + 聚类(节选)
n_neighbors = int((len(embeddings) - 1) ** 0.8)
reduced_embeddings = umap.UMAP( # 先把高维向量降到 ≤12 维,聚类更稳
n_neighbors=max(2, n_neighbors),
n_components=min(12, len(embeddings) - 2),
metric="cosine",
).fit_transform(embeddings)
# 默认用 GMM:选 BIC 最优的簇数,再软分配
n_clusters = self._get_optimal_clusters(reduced_embeddings, random_state, task_id=task_id)

几个关键设计点:

  • 簇数不是拍脑袋定的。 _get_optimal_clusters(rag/raptor.py:235)在 1..max_cluster 里逐个试 GaussianMixture,挑 BIC(贝叶斯信息准则,越低越好)最小的簇数——自动决定这层该分几簇。
  • 软分配 + 阈值。 GMM 给每个点算「属于各簇的概率」,probs > self._threshold 的簇都算数 (rag/raptor.py:720),即一个 chunk 可以进多个簇(默认取第一个)。
  • 还有一条 AHC 备选。clustering_method == "ahc",改用 Ward 层次聚类 + 树状图「最大间隙」启发式 自动定簇数(_get_clusters_ahc,rag/raptor.py:256),再用 _adjust_tree_nodes 按最近质心迭代微调。
  • 簇摘要即新 chunk。 每个簇的原文拼起来喂 LLM 出一段摘要,连同它的 embedding 追加进 chunks (_summarize_texts,rag/raptor.py:309;循环里 summarizerag/raptor.py:664 把结果 append)。 这层摘完,layers.append((end, len(chunks))) 记下这层的区间,下一轮就对这批摘要再聚类。

2.5 Psi 合并树:并查集把「最像的先合并」

Psi 路径不做降维聚类,而是精确算所有 chunk 对的余弦相似度、从高到低排序,用并查集依次合并,建出一棵 自底向上的合并树。核心数据结构是本文件里两个类:

  • _PsiTreeNode(rag/raptor.py:48):一个树节点,含 index / text / embedding / children / parent
  • _PsiUnionFind(rag/raptor.py:59):带秩的并查集,union(i, j)(rag/raptor.py:115)把两个最相似的 节点合并,并在紧凑的 _tree 数组里记「子 → 父」边;tree 属性(rag/raptor.py:150)导出这张父指针表。

建树主流程(_build_psi_structure,rag/raptor.py:563):

# rag/raptor.py:488 精确 Psi 子树:按相似度排序的对,并查集合并到只剩一棵(节选)
ranked_pairs = self._rank_leaf_pairs(nodes) # 所有叶子对,按余弦相似度降序
union_find = _PsiUnionFind(len(nodes))
for left_idx, right_idx in ranked_pairs:
if union_find.union(int(left_idx), int(right_idx)):
merges += 1
if merges == len(nodes) - 1: # 合并 n-1 次 → 连成一棵树
break

工程上的两个防爆点:

  • 大规模分桶。 chunk 太多时「所有对」是 O(n²),_split_psi_buckets(rag/raptor.py:375)先用类 k-means 把节点切成 ≤psi_bucket_size 的桶,桶内精确合并,再对桶根合并(_build_bucketed_psi_structure, rag/raptor.py:522)。psi_exact_max_leaves / psi_bucket_size 控制这两个阈值。
  • 重平衡 fanout。 二叉合并树太瘦,_rebalance_psi_tree(rag/raptor.py:443)把超过 max_cluster 个孩子的节点分组,保证每个内部节点的孩子数不超标,再逐层摘要(_build_psi_layerssummarize_node,rag/raptor.py:598 / 606)。

2.6 摘要如何入库、如何被召回(和 task_executor 的挂接)

RAPTOR 由后台任务触发,入口是 run_raptor_for_kb(rag/svr/task_executor.py:1013)。它读知识库的 raptor 配置,构造 RecursiveAbstractiveProcessing4TreeOrganizedRetrieval,跑出 (chunks, layers), 然后把新增的摘要节点打上标记存进和原文同一个索引:

# rag/svr/task_executor.py:1086 摘要 chunk 的标记(节选)
"raptor_kwd": "raptor", # 标记:这是 RAPTOR 摘要,不是原文
"extra": {"raptor_method": tree_builder}, # 记下用哪种 builder,便于增量/清理
# ... 每个摘要还写 raptor_layer_int(它在第几层) 和 make_raptor_summary_chunk_id 生成的稳定 id

召回侧不需要任何特殊逻辑——这是 RAPTOR 设计最漂亮的地方。摘要就是索引里多出来的、更宏观的 chunk, 第 03 章的通用 Dealer.retrieval 会像对待原文一样把它们一并向量召回、重排。跨段主旨型问题因此天然 命中高层摘要节点。raptor_kwd / raptor_layer_int 只是入库时的血统标记,用于增量更新与清理 (如 has_raptor_chunks / delete_raptor_chunks,rag/svr/task_executor.py:958 / 964),不改检索路径。

2.7 关键细节 / 坑

  • 摘要有 token 预算保护。 _summarize_texts 里按 (max_length - max_token)/簇大小 给每个 chunk 截断再拼(rag/raptor.py:313),避免一簇太大撑爆上下文。
  • 容错不中断。 单簇摘要失败会计数跳过,累计到 max_errors(默认 3)才整体中止 (rag/raptor.py:343-349),避免个别 LLM 抖动毁掉整棵树。
  • 可取消。 全程 _check_task_canceled 埋点,任务取消会抛 TaskCanceledException 并回滚半成品摘要 (rag/svr/task_executor.py:1300 附近)。

3. GraphRAG — 图谱召回(接住「多跳关系型」)

3.1 它要解决的小问题

「张三的公司的竞争对手」——答案要沿 张三 → 公司 → 竞争对手 跳两步。向量相似度只会把「和问句字面像」的 chunk 捞上来,跳不动关系。GraphRAG 的办法:入库时从文本里抽出实体和关系,建成一张知识图谱,并做社区 发现 + 社区摘要;检索时按实体/关系/社区来召回,还能沿关系做 n-hop 邻域扩展。

3.2 建图侧(入库时,一句话带过)

建图是重头戏但不在本章召回主线,只交代产物,细节看代码地图:

阶段干什么代码
子图抽取每篇文档用 LLM 或 spaCy NER 抽实体/关系,存成 subgraph chunkgenerate_subgraph(rag/graphrag/general/index.py:654);抽取器有 general/light/ner 三档
实体消解把「IBM」「I.B.M.」这类同物异名合并EntityResolution(rag/graphrag/entity_resolution.py:49)
社区发现Leiden 层次聚类把图切成社区run(rag/graphrag/general/leiden.py:95),内部调 hierarchical_leiden
社区摘要每个社区让 LLM 写一份「社区报告」CommunityReportsExtractor;run_graphrag_for_kb(rag/graphrag/general/index.py:257)编排全流程

实体消解的相似判定值得看一眼——不是纯向量,而是规则:英文用编辑距离(≤ 半长算同名)、含数字的 2-gram 差异直接判否、其余按字符集合 Jaccard ≥ 0.8(is_similarity,rag/graphrag/entity_resolution.py:297)。

关键约定:实体、关系、社区报告全都作为 chunk 存进同一个索引,靠 knowledge_graph_kwd 字段区分 (取值 entity / relation / community_report / subgraph)。这让图谱召回复用同一套 doc store。

3.3 召回侧:KGSearch 五步走

召回逻辑在 KGSearch(rag/graphrag/search.py:35),它继承第 03 章的 Dealer,复用其向量检索能力, 只重写「怎么召回图元素」。主入口 retrieval(rag/graphrag/search.py:150):

怎么读:从问句出发,先让 LLM 把问句拆成「实体词 + 答案类型」,再分三路召回图元素,
n-hop 扩展关系,最后附上社区报告,拼成一个大 chunk 返回。

问句 Q

▼ ① query_rewrite:LLM 抽「实体词」和「答案类型词」
[ents: 张三, 公司] [type_kwds: 组织, 人物]
│ │
├── ② get_relevant_ents_by_keywords ─→ 按实体词向量召回实体
├── get_relevant_ents_by_types ───→ 按答案类型直接召回该类实体
├── ③ get_relevant_relations_by_txt ─→ 按问句召回关系
│ │
▼ ④ n-hop 扩展:从命中实体的 n_hop_ents 沿路径加权补关系
[实体集 + 关系集(含多跳)]

▼ ⑤ _community_retrieval_:按命中实体捞它们所在社区的报告
拼成一个 content_with_weight 大 chunk 返回,插到结果最前

逐步看:

① 查询改写。 query_rewrite(rag/graphrag/search.py:46)用 minirag_query2kwd prompt 让 LLM 输出 answer_type_keywords(答案应是什么类型)和 entities_from_query(问句里的实体,取前 5),用 json_repair 容错解析。

② / ③ 三路召回。 三个 get_relevant_* 方法本质都是「加个 knowledge_graph_kwd 过滤 + 向量/排序召回」:

# rag/graphrag/search.py:116 按实体关键词召回实体(节选)
filters["knowledge_graph_kwd"] = "entity" # 只在实体 chunk 里找
matchDense = self.get_vector(", ".join(keywords), emb_mdl, 1024, sim_thr) # 复用 Dealer 的向量
es_res = self.dataStore.search([...], [], filters, [matchDense], OrderByExpr(), 0, N, idxnms, kb_ids)

get_relevant_ents_by_types(rag/graphrag/search.py:138)则不靠向量,直接按 entity_type_kwd 过滤 + 按 rank_flt(PageRank)降序取——这是「答案类型」这一路。

④ n-hop 多跳扩展。 每个命中实体的 chunk 里预存了 n_hop_with_weight(邻域路径)。retrieval 遍历这些 路径,把路径上的相邻实体对当成额外关系,并按跳数衰减累加权重(rag/graphrag/search.py:186-197):

# rag/graphrag/search.py:189 沿 n-hop 路径,越远权重越低(节选)
for i in range(len(path) - 1):
f, t = path[i], path[i + 1]
nhop_pathes[(f, t)]["sim"] += ent["sim"] / (2 + i) # 跳得越远(i 越大)贡献越小
nhop_pathes[(f, t)]["pagerank"] = max(nhop_pathes[(f, t)].get("pagerank", 0), wts[i])

打分逻辑:P(E|Q) ≈ P(E)·P(Q|E) = pagerank · sim 代码注释直接写出了这个贝叶斯直觉 (rag/graphrag/search.py:204)。命中「答案类型」的实体 sim 翻倍(rag/graphrag/search.py:208);关系的 sim 按「两端实体是否命中类型 + 是否在 n-hop 上」放大(rag/graphrag/search.py:220)。最后实体、关系都按 sim * pagerank 排序取 topn(rag/graphrag/search.py:234-237)。

⑤ 社区报告召回。 _community_retrieval_(rag/graphrag/search.py:303)按上一步选出的实体,过滤 knowledge_graph_kwd == "community_report"entities_kwd 命中这些实体的社区报告,按 weight_flt 取 topn,拼成「Community Report」段。

产物形态。 整个 retrieval 最终把实体表(CSV)、关系表(CSV)、社区报告拼成一个 content_with_weight 大字符串,包成一个 similarity=1.0 的合成 chunk 返回(rag/graphrag/search.py:286)。在 dialog_service.py:742-746,它被 insert(0, ck) 插到检索结果最前面,喂给生成模型。

3.4 边界

  • 建图极贵。 逐 chunk LLM 抽取 + 社区摘要,大库慢且烧钱;所以是可选开关(use_kg)。
  • 召回是「合成一大段」而非分散 chunk。 图谱结果不参与第 03 章的逐 chunk 重排,而是整块前置注入, 引用粒度较粗(整块标为一个来源)。

4. 树状查询分解(接住「复合多意图型」)

4.1 它要解决的小问题

「对比 A、B 在价格和续航上的优劣」——一个问句里其实有 4 个子问题。单轮检索会被主意图带走。 TreeStructuredQueryDecompositionRetrieval(rag/advanced_rag/tree_structured_query_decomposition_retrieval.py:26, 即产品里的「深度研究 / Deep Research」)的思路:检索一轮 → 让 LLM 判断信息够不够 → 不够就生成子问题 → 对每个子问题递归再来一轮,像剥洋葱一样把复合问题拆开逐个击破,最后汇总所有轮次捞到的 chunk。

4.2 递归结构

怎么读:每个节点先检索、再自查「够了吗」;不够就裂成几个子问题,各自再递归,depth 递减到 0 停。

research(Q0, depth=3)
│ 检索 Q0 → sufficiency_check 够了吗?
│ 不够 → multi_queries_gen 生成子问题
├── _research(Q1, depth=2) 够了?不够再裂 ...
├── _research(Q2, depth=2)
└── _research(Q3, depth=2) ← 多个子问题并发(asyncio.gather)
每一轮的 chunk 都并进同一个 chunk_info(去重)

4.3 一层递归里发生了什么

核心是 _research(rag/advanced_rag/tree_structured_query_decomposition_retrieval.py:104):

# tree_structured_query_decomposition_retrieval.py:104 _research 主体(节选)
if depth == 0:
return "" # 到底,停止递归
ret = await self._retrieve_information(query) # 这一轮的多源检索
await self._async_update_chunk_info(chunk_info, ret) # 并进总结果(按 chunk_id 去重)
suff = await sufficiency_check(self.chat_mdl, question, ret) # LLM:信息够回答了吗?
if suff.get("is_sufficient"):
return ret # 够了,这一支收工
# 不够 → 让 LLM 基于「缺什么」生成下一批子问题
succ = await multi_queries_gen(self.chat_mdl, question, query, suff.get("missing_information", []), ret)
steps = [asyncio.create_task(self._research(chunk_info, s["question"], s["query"], depth-1, callback))
for s in succ["questions"]] # 每个子问题并发递归
results = await asyncio.gather(*steps, return_exceptions=True)

三个要点:

  • 多源检索。 每轮 _retrieve_information(...:41)同时打三处:知识库检索(_kb_retrieve)、可选联网 (Tavily)、可选知识图谱(_kg_retrieve,即第 3 节的 KGSearch)——把 GraphRAG 也当成分解的一路子召回。
  • 充分性驱动分解。 是否继续拆,由 LLM 的 sufficiency_check 判定,而不是死板拆到底;够了就早停。
  • 结果去重合并。 _async_update_chunk_info(...:72)用 asyncio.Lock 保护,按 chunk_id / doc_id 去重合并各轮 chunk,避免并发写坏 + 重复引用。

4.4 怎么被触发

dialog_service.py:702,开启深度研究时构造 reasonerawait reasoner.research(...),通过 callback 把「正在搜 X」「信息够了」等中间态以 <START_DEEP_RESEARCH> / <END_DEEP_RESEARCH> 包裹流式吐给前端。

4.5 边界

  • 延迟高、token 贵。 每层每子问题都要检索 + 至少一次 LLM 判定,深度 3 会放大成多轮。
  • 默认深度 3(research(..., depth=3)),靠 depth 硬上限防止无限递归。

5. 结构化辅助召回(顺藤摸瓜补全上下文)

这两个不是独立「入口」,而是在通用检索拿到 chunk 之后做的补全,收在 Dealer 里。

5.1 retrieval_by_toc — 按文档目录补齐章节

问题: 命中了某文档的几个 chunk,但答案需要「整章」才完整。retrieval_by_toc (rag/nlp/search.py:864)先按命中 chunk 的相似度之和选出得分最高的那篇文档,取它预存的目录 (toc_kwd == "toc" 的 chunk),让 LLM 从目录里挑出与问句相关的章节(relevant_chunks_with_toc), 再把这些章节对应的 chunk 回捞进结果。等于「顺着目录补齐相关章节」。

5.2 retrieval_by_children — 子块命中,回捞父块

问题: 入库时大块被切成小子块(child)以利精确匹配,但小块上下文不足。retrieval_by_children (rag/nlp/search.py:928)扫结果里带 mom_id(父块 id)的子块,按父块聚合,用父块的全文替换掉零散 子块,相似度取子块均值(rag/nlp/search.py:967)。父块不在索引里时回退用子块(...:952)。等于 「小块负责精准命中,命中后换成大块给足上下文」。

两者的挂接见 dialog_service.py:733(toc,受 toc_enhance 开关)与 dialog_service.py:736(children, 默认执行);Agent 检索工具里对应 toc_enhance / use_kg 参数(agent/tools/retrieval.py:219 / 231)。


6. 五种手段横向对比 / 怎么选

手段触发开关结构建在何时召回时做什么何时开
RAPTORraptor.use_raptor入库(建树)无特殊(摘要即普通 chunk,通用检索捞回)常问「整体/主旨」的库
GraphRAGuse_kg入库(建图,很贵)KGSearch 五步 + n-hop + 社区报告强关系、多跳、需要「谁和谁有关」的库
查询分解深度研究开关无(检索时动态)递归拆子问题、多源检索、充分性早停复合问题、研究型问答;可叠加前两者当子召回
retrieval_by_toctoc_enhance入库(存 TOC)选最相关文档 + LLM 挑章节回捞长文档、需要章节完整性
retrieval_by_children默认入库(父子分块)子块命中 → 换父块小块精确匹配 + 需要大块上下文

它们可叠加。 查询分解的每一轮子召回,内部就同时用了知识库检索(可含 RAPTOR 摘要)和 GraphRAG;而 toc/children 是在通用检索结果上再补全。真实链路是「多种进阶手段协同」,不是二选一。


7. 边界与局限(诚实)

  • 进阶 = 更贵。 RAPTOR / GraphRAG 都把成本压在入库侧(建树、建图、社区摘要);查询分解把成本压在 检索侧(多轮 LLM)。默认都不开。
  • 图谱召回粒度粗。 GraphRAG 返回的是「一大段拼接文本」,不参与逐 chunk 重排,引用溯源到整块。
  • RAPTOR 摘要可能引入 LLM 幻觉。 摘要是 LLM 写的,若原簇噪声大,摘要可能失真;代码只做了 token 截断 和错误计数容错,不校验摘要事实性。
  • 分解深度硬顶。 查询分解靠 depth(默认 3)防失控,过深的多跳推理链可能被截断。
  • Psi 大库要分桶近似。 超过 psi_exact_max_leaves 后 Psi 树不再精确,靠 k-means 分桶近似,合并结构与 精确版有差。

8. 代码地图(导航索引)

主题文件路径符号名
RAPTOR 主类 / 建树入口rag/raptor.pyRecursiveAbstractiveProcessing4TreeOrganizedRetrieval__call__
经典树:最优簇数(BIC)rag/raptor.py_get_optimal_clusters_get_clusters_ahc
簇摘要 → 新 chunkrag/raptor.py_summarize_texts
Psi 合并树:节点 / 并查集rag/raptor.py_PsiTreeNode_PsiUnionFinduniontree
Psi 建树 / 分桶 / 重平衡rag/raptor.py_build_psi_structure_split_psi_buckets_rebalance_psi_tree_build_psi_layers
RAPTOR 常量 / 配置rag/utils/raptor_utils.pyRAPTOR_TREE_BUILDERPSI_TREE_BUILDERget_raptor_tree_builder
RAPTOR 后台编排 / 入库标记rag/svr/task_executor.pyrun_raptor_for_kbraptor_kwdhas_raptor_chunksdelete_raptor_chunks
GraphRAG 召回主类rag/graphrag/search.pyKGSearchretrieval
查询改写 / 三路召回rag/graphrag/search.pyquery_rewriteget_relevant_ents_by_keywordsget_relevant_relations_by_txtget_relevant_ents_by_types
社区报告召回rag/graphrag/search.py_community_retrieval_
GraphRAG 建图编排rag/graphrag/general/index.pyrun_graphrag_for_kbgenerate_subgraph
社区发现(Leiden)rag/graphrag/general/leiden.pyrun_compute_leiden_communities
实体消解rag/graphrag/entity_resolution.pyEntityResolutionis_similarity
抽取器(三档)rag/graphrag/{general,light,ner}/graph_extractor.pyGraphExtractor
树状查询分解rag/advanced_rag/tree_structured_query_decomposition_retrieval.pyTreeStructuredQueryDecompositionRetrievalresearch_research_retrieve_information
结构辅助召回rag/nlp/search.pyDealer.retrieval_by_tocDealer.retrieval_by_children
召回侧总装配api/db/services/dialog_service.pychat(reasoner.research / retrieval_by_toc / use_kg 分支,约 :702/:733/:742)
Agent 检索工具开关agent/tools/retrieval.pyuse_kgtoc_enhance(:231/:219)

相邻章节: RAGFlow — 全景与阅读地图 · DeepDoc 文档理解 · 入库写入线 · 混合检索与融合重排(本章的基础)