数据截至 (上游 commit b948f88d48be)
第 3 章:召回 —— 向量种子 + 图扩展 + 三元组打分
这是 Cognee 比传统 RAG 强的地方。本章拆解默认召回器
GraphCompletionRetriever的三段式:用向量找入口,用图扩展上下文,用打分排出最相关的三元组。
3.1 它要解决的小问题
传统向量 RAG 召回的是「字面最像查询的文本块」。但很多问题要的是关系:「这个项目的负责人还参与了哪些项目?」——答案散落在多条边上,单个文本块装不下。
Cognee 的召回:向量只用来找入口节点,真正的上下文是从这些入口在图上扩展出来的三元组(节点-边-节点)。
3.2 检索类型一览(SearchType)
search() 第一件事是按 query_type 路由到一个 Retriever(get_search_type_retriever_instance,cognee/modules/search/methods/get_search_type_retriever_instance.py:95)。SearchType 枚举共 17 种(cognee/modules/search/types/SearchType.py),主要几类:
| SearchType | 召回器 | 干什么 |
|---|---|---|
GRAPH_COMPLETION(默认) | GraphCompletionRetriever | 图三元组 + LLM 回答 |
CHUNKS | ChunksRetriever | 纯向量召回文本块 |
SUMMARIES | SummariesRetriever | 召回预生成的摘要 |
RAG_COMPLETION | CompletionRetriever | 传统向量 RAG + LLM |
HYBRID_COMPLETION | HybridRetriever | 块 + 实体混合 |
TEMPORAL | TemporalRetriever | 时序图检索 |
CYPHER | CypherSearchRetriever | 直接跑 Cypher 查询 |
默认是 GRAPH_COMPLETION(search() 的 query_type: SearchType = SearchType.GRAPH_COMPLETION,cognee/api/v1/search/search.py:41)。本章聚焦它。
3.3 三段式:Retriever 的统一契约
所有召回器都实现 BaseRetriever 的三步(cognee/modules/retrieval/base_retriever.py):
get_retrieved_objects → get_context_from_objects → get_completion_from_context
(从图/向量库拿原始数据) (整理成给 LLM 的文本上下文) (调 LLM 生成最终答案)
这套契约让所有检索模式「形状一致」:换一个检索策略只要换这三步的实现。get_completion 把三步按顺序串起来(base_retriever.py:118-136)。
3.4 图示:GraphCompletion 怎么召回
怎么读:左边是查询,中间向量库给入口,右边图库给关系,最后汇成上下文。
query "Einstein 出生在哪个国家?"
│
▼ ① 向量搜索(brute_force_triplet_search)
[向量库] ── 找最近的若干节点 ──▶ 种子节点 {Einstein, Ulm, ...}
│
▼ ② 图投影(project_graph_from_db)
[图库] ── 以种子为锚,投影出内存子图 CogneeGraph,扩展出三元组 ──▶
(Einstein)-[born_in]->(Ulm), (Ulm)-[located_in]->(德国) ...
│
▼ ③ 打分排序(calculate_top_triplet_importances)
按「节点+边的向量距离」给每条三元组打分,取 top-k
│
▼ ④ 渲染 + 回答
resolve_edges_to_text → 拼成上下文 → LLM 生成答案
3.5 核心:brute_force_triplet_search
第 ①②③ 步都在 brute_force_triplet_search(cognee/modules/retrieval/utils/brute_force_triplet_search.py:225),由 GraphCompletionRetriever.get_triplets 调用(graph_completion_retriever.py:160-201)。它做的事:
- 收集要查的向量集合。
_get_vector_index_collections遍历所有DataPoint子类,凡声明了index_fields的字段都对应一个向量集合,如Entity_name、DocumentChunk_text(graph_completion_retriever.py:95-107)。这就是第 2 章index_fields的用途。 - 向量找种子。 用查询向量在这些集合里搜出最相关的节点 id(
wide_search_top_k控制范围,默认 100;extract_relevant_node_ids,brute_force_triplet_search.py:156-157)。 - 投影内存子图。
get_memory_fragment把图库里(被种子 id 过滤的)节点和边拉进一个内存CogneeGraph,默认投影id/description/name/type/text/importance_weight这些属性(brute_force_triplet_search.py:50-110)。 - 把向量距离映射到图元素,再算三元组重要性(
map_vector_distances_to_graph_nodes/_edges→calculate_top_triplet_importances,brute_force_triplet_search.py:206-218)。
3.6 三元组怎么打分(精华)
打分逻辑在 _calculate_query_top_triplet_importances(cognee/modules/graph/cognee_graph/CogneeGraph.py:417)。一条三元组(node1 - edge - node2)的分数是三个元素各自距离之和,越小越好:
# 示意,提炼自 _calculate_query_top_triplet_importances 的 score()
def score(edge):
total = 0.0
for element in (edge.node1, edge.node2, edge): # 两端节点 + 边本身
distance = element.vector_distance # 该元素到查询的向量距离
importance = element.importance_weight # 0~1,越重要值越大
distance = (2 - importance) * distance # 重要的节点「拉近」距离
total += blend_with_feedback(distance, element.feedback_weight) # 再掺入反馈
return total
return heapq.nsmallest(k, edges, key=score) # 取分数最小的 k 条
真实代码对应:
distance = (2 - importance_weight) * distance—— 重要性加权:importance_weight越大,有效距离越小,越容易被选中(CogneeGraph.py:499)。_effective_distance(distance, feedback_weight)—— 反馈混合:把用户/系统反馈权重按feedback_influence比例掺进归一化后的距离里,让「被点赞过的」记忆排更前(CogneeGraph.py:428-450)。只对合法的 cosine 距离 [0,2] 生效,fallback 惩罚值保持不变,确保「缺失的元素」永远排在有效匹配之后(CogneeGraph.py:434-436)。heapq.nsmallest(k, self.edges, key=score)—— 取分数最小(最相关)的 k 条三元组(CogneeGraph.py:506)。
一句话直觉: 一条三元组的「相关度」= 它的两个端点和这条边各自有多贴近查询,再用「这东西有多重要」和「以前的反馈」微调。
3.7 渲染成上下文 + 生成答案
选出的三元组(Edge 列表)由 resolve_edges_to_text 渲染成人类可读文本(GraphCompletionRetriever.resolve_edges_to_text → cognee/modules/graph/utils/resolve_edges_to_text,graph_completion_retriever.py:143-154),拼进 prompt(默认模板 graph_context_for_question.txt),交给 LLM 生成最终答案(get_completion_from_context,graph_completion_retriever.py:355)。
可选增强:
- 全局上下文前言:
include_global_context_index=True时,会先拼上「根文本 + top 摘要」当背景(_build_global_context_prelude,graph_completion_retriever.py:278-288)。 - 引用证据:
include_references=True时,把答案文本反过来当查询去 chunk 索引里找,附一段 Evidence 说明答案「落在语料哪里」(_append_graph_evidence,graph_completion_retriever.py:323-346)。 - 会话缓存:开了
CACHING且非批量时,走SessionManager.generate_completion_with_session带会话历史回答(graph_completion_retriever.py:380-400)。
3.8 边界
- 图空就直接返回空:
get_retrieved_objects先检查graph.is_empty(),空图直接 warn + 返回[](graph_completion_retriever.py:126-132)——所以没跑过 cognify 就 search 会拿到空结果。 - neighborhood 模式需要向量种子:
neighborhood_depth设了但没有种子 id 会直接报错(get_memory_fragment,brute_force_triplet_search.py:91-96)。 - 打分要求每个图元素的
vector_distance是「长度足够的 list」,否则抛ValueError(CogneeGraph.py:483-490)——批量查询时按query_index取对应那一列。
3.9 代码地图
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 检索类型路由 | cognee/modules/search/methods/get_search_type_retriever_instance.py | get_search_type_retriever_instance |
| 默认召回器 | cognee/modules/retrieval/graph_completion_retriever.py | GraphCompletionRetriever, get_triplets |
| 召回三步契约 | cognee/modules/retrieval/base_retriever.py | BaseRetriever, get_completion |
| 混合召回核心 | cognee/modules/retrieval/utils/brute_force_triplet_search.py | brute_force_triplet_search, get_memory_fragment |
| 三元组打分 | cognee/modules/graph/cognee_graph/CogneeGraph.py | _calculate_query_top_triplet_importances, calculate_top_triplet_importances |
| 检索类型枚举 | cognee/modules/search/types/SearchType.py | SearchType |