跳到主要内容

第 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:78)。SearchType 枚举共 17 种(cognee/modules/search/types/SearchType.py),主要几类:

SearchType召回器干什么
GRAPH_COMPLETION(默认)GraphCompletionRetriever图三元组 + LLM 回答
CHUNKSChunksRetriever纯向量召回文本块
SUMMARIESSummariesRetriever召回预生成的摘要
RAG_COMPLETIONCompletionRetriever传统向量 RAG + LLM
HYBRID_COMPLETIONHybridRetriever块 + 实体混合
TEMPORALTemporalRetriever时序图检索
CYPHERCypherSearchRetriever直接跑 Cypher 查询

默认是 GRAPH_COMPLETION(search()query_type: SearchType = SearchType.GRAPH_COMPLETION,cognee/api/v1/search/search.py:31)。本章聚焦它。

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:101-119)。

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:217),由 GraphCompletionRetriever.get_triplets 调用(graph_completion_retriever.py:152-186)。它做的事:

  1. 收集要查的向量集合。 _get_vector_index_collections 遍历所有 DataPoint 子类,凡声明了 index_fields 的字段都对应一个向量集合,如 Entity_nameDocumentChunk_text(graph_completion_retriever.py:87-99)。这就是第 2 章 index_fields 的用途。
  2. 向量找种子。 用查询向量在这些集合里搜出最相关的节点 id(wide_search_top_k 控制范围,默认 100;extract_relevant_node_ids,brute_force_triplet_search.py:153-154)。
  3. 投影内存子图。 get_memory_fragment 把图库里(被种子 id 过滤的)节点和边拉进一个内存 CogneeGraph,默认投影 id/description/name/type/text/importance_weight 这些属性(brute_force_triplet_search.py:50-110)。
  4. 把向量距离映射到图元素,再算三元组重要性(map_vector_distances_to_graph_nodes/_edgescalculate_top_triplet_importances,brute_force_triplet_search.py:203-210)。

3.6 三元组怎么打分(精华)

打分逻辑在 _calculate_query_top_triplet_importances(cognee/modules/graph/cognee_graph/CogneeGraph.py:470)。一条三元组(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:535)。
  • _effective_distance(distance, feedback_weight) —— 反馈混合:把用户/系统反馈权重按 feedback_influence 比例掺进归一化后的距离里,让「被点赞过的」记忆排更前(CogneeGraph.py:480-502)。只对合法的 cosine 距离 [0,2] 生效,fallback 惩罚值保持不变,确保「缺失的元素」永远排在有效匹配之后(CogneeGraph.py:486-488)。
  • heapq.nsmallest(k, self.edges, key=score) —— 取分数最小(最相关)的 k 条三元组(CogneeGraph.py:541)。

一句话直觉: 一条三元组的「相关度」= 它的两个端点和这条边各自有多贴近查询,再用「这东西有多重要」和「以前的反馈」微调。

3.7 渲染成上下文 + 生成答案

选出的三元组(Edge 列表)由 resolve_edges_to_text 渲染成人类可读文本(GraphCompletionRetriever.resolve_edges_to_text → cognee/modules/graph/utils/resolve_edges_to_text,graph_completion_retriever.py:135-146),拼进 prompt(默认模板 graph_context_for_question.txt),交给 LLM 生成最终答案(get_completion_from_context,graph_completion_retriever.py:316)。

可选增强:

  • 全局上下文前言:include_global_context_index=True 时,会先拼上「根文本 + top 摘要」当背景(_build_global_context_prelude,graph_completion_retriever.py:262-272)。
  • 引用证据:include_references=True 时,把答案文本反过来当查询去 chunk 索引里找,附一段 Evidence 说明答案「落在语料哪里」(_append_graph_evidence,graph_completion_retriever.py:298-307)。
  • 会话缓存:开了 CACHING 且非批量时,走 SessionManager.generate_completion_with_session 带会话历史回答(graph_completion_retriever.py:336-356)。

3.8 边界

  • 图空就直接返回空:get_retrieved_objects 先检查 graph.is_empty(),空图直接 warn + 返回 [](graph_completion_retriever.py:118-124)——所以没跑过 cognify 就 search 会拿到空结果。
  • neighborhood 模式需要向量种子:neighborhood_depth 设了但没有种子 id 会直接报错(get_memory_fragment,brute_force_triplet_search.py:91-96)。
  • 打分要求每个图元素的 vector_distance 是「长度足够的 list」,否则抛 ValueError(CogneeGraph.py:519-526)——批量查询时按 query_index 取对应那一列。

3.9 代码地图

主题文件路径符号名
检索类型路由cognee/modules/search/methods/get_search_type_retriever_instance.pyget_search_type_retriever_instance
默认召回器cognee/modules/retrieval/graph_completion_retriever.pyGraphCompletionRetriever, get_triplets
召回三步契约cognee/modules/retrieval/base_retriever.pyBaseRetriever, get_completion
混合召回核心cognee/modules/retrieval/utils/brute_force_triplet_search.pybrute_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.pySearchType