数据截至 (上游 commit b948f88d48be)
第 4 章:DataPoint 统一模型与三库分工
本章讲底座:为什么图里所有东西(实体、文本块、文档)都是同一个
DataPoint,以及关系库 / 图库 / 向量库三者各管什么、怎么协作落库。
4.1 一个模型统治所有节点:DataPoint
Cognee 里凡是要进图的东西都继承 DataPoint(cognee/infrastructure/engine/models/DataPoint.py):Entity、EntityType、DocumentChunk、TextSummary、Event…… 全是它的子类。这带来一致性:id 生成、嵌入字段声明、provenance 字段、版本,所有节点共享一套规则。
DataPoint 的关键字段(DataPoint.py:48-75):
# 真实源码节选
class DataPoint(BaseModel):
id: UUID = Field(default_factory=uuid4) # 默认随机;声明 identity_fields 后变确定性
version: int = 1
topological_rank: int | None = 0 # 流水线第几步产出(可视化用)
metadata: MetaData = {"index_fields": []} # 哪些字段要做向量嵌入
type: str = Field(default_factory=lambda: DataPoint.__name__)
# provenance:这条数据从哪来(第 1 章盖的戳)
source_pipeline: str | None = None
source_task: str | None = None
source_node_set: str | None = None
source_user: str | None = None
source_content_hash: str | None = None
feedback_weight: float = 0.5 # 反馈权重(第 3 章打分用)
importance_weight: float | None = 0.5 # 重要性权重(第 3 章打分用)
两个 metadata 子字段是全局关节:
| 字段 | 作用 | 在哪用到 |
|---|---|---|
index_fields | 列出要做向量嵌入的字段 | 第 3 章召回的向量集合名 {类名}_{字段} |
identity_fields | 列出算确定性 id的字段 | 第 2 章的去重/合并 |
4.2 确定性 id 回顾(为什么是去重底座)
第 2 章讲过 id_for;这里强调它的默认 vs 显式对比,因为这是新手最容易踩的点:
| 情况 | id 怎么来 | 后果 |
|---|---|---|
没声明 identity_fields | default_factory=uuid4,随机 | 每次都是新节点,不会合并,无法按内容回查 |
声明了 identity_fields(如 Entity) | uuid5(NAMESPACE_OID, "Entity:name") | 同名 → 同 id → 自动合并/幂等 |
DataPoint.py:48-53 的注释直说了这个「随机 uuid 陷阱」:随机 id 的节点「没有稳定身份,跨运行不去重、也无法用重算 id 的方式查回」。所以自定义节点模型想被合并,必须主动声明 identity_fields。
4.3 三类数据库分工
Cognee 把存储拆成三块,各管一摊(CLAUDE.md:160-168「Interface-Based Database Adapters」):
| 库 | 存什么 | 默认实现 | 接口文件 |
|---|---|---|---|
| 关系库 | Dataset / Data / User / 权限等元数据 | SQLite | cognee/infrastructure/databases/relational/ |
| 图库 | 节点 + 边(知识图谱本体) | Ladybug | .../graph/graph_db_interface.py |
| 向量库 | 各 index_fields 字段的嵌入 | LanceDB | .../vector/vector_db_interface.py |
默认图库是 ladybug(cognee/infrastructure/databases/graph/config.py:45,graph_database_provider: str = Field("ladybug", ...)),可换 Kuzu / Neo4j / Neptune / Postgres(.../graph/ 下各 driver 目录)。向量默认 LanceDB,可换 pgvector(add.py docstring「VECTOR_DB_PROVIDER」)。
怎么读这张分工: 同一个 Entity 节点,它的「身份和关系」在图库,它的「name 嵌入向量」在向量库,而「它属于哪个 dataset、哪个用户能看」在关系库。三库通过节点 id 串起来。
4.4 落库那一步:add_data_points
cognify 流水线的第 ④ 步 add_data_points(cognee/tasks/storage/add_data_points.py:30)负责把内存里的 DataPoint 写进图库和向量库。它做的事:
- 从每个
DataPoint用get_graph_from_model抽出节点和边; deduplicate_nodes_and_edges按 id 去重(add_data_points.py:9-12的 import);upsert_nodes/upsert_edges写图库;index_data_points/index_graph_edges把index_fields字段嵌入并写向量库(add_data_points.py:14-16)。
因为节点 id 是确定性的(4.2),upsert 天然幂等:重复跑同一份数据不会产生重复节点。
4.5 统一引擎
召回侧不直接碰三个库,而是通过 get_unified_engine() 拿一个「统一存储引擎」,它把 .graph / .vector 包在一起(GraphCompletionRetriever.get_retrieved_objects 里 self._unified_engine = await get_unified_engine(),graph_completion_retriever.py:123;接口在 cognee/infrastructure/databases/unified/)。这层抽象让检索代码不关心底层用的是 Ladybug 还是 Neo4j。
4.6 节点 id 的另一个工具:generate_node_id
除了类带命名空间的 id_for,还有一个更轻的 generate_node_id(cognee/infrastructure/engine/utils/generate_node_id.py:4),用同样的归一化规则但不带类名前缀:
# 真实源码
def generate_node_id(node_id: str) -> UUID:
return uuid5(NAMESPACE_OID, node_id.lower().replace(" ", "_").replace("'", ""))
区别:id_for 用 f"{cls.__name__}:{values}" 保证不同节点类型不撞 id;generate_node_id 是裸字符串。新代码里实体类节点应优先走 id_for(类名命名空间更安全)。
4.7 边界
- 改了
metadata别丢identity_fields:子类覆盖metadata时若漏掉父类的identity_fields,_get_identity_fields会沿 MRO 检查并打 warning,但不会自动补——节点会退回随机 id(DataPoint.py:93-113)。 - 时间戳是毫秒整数(
created_at用int(... timestamp() * 1000),DataPoint.py:54-59),不是 datetime 对象。
4.8 代码地图
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 统一节点基类 | cognee/infrastructure/engine/models/DataPoint.py | DataPoint, id_for, _get_identity_fields |
| 轻量节点 id | cognee/infrastructure/engine/utils/generate_node_id.py | generate_node_id |
| 落库任务 | cognee/tasks/storage/add_data_points.py | add_data_points |
| 图库接口 + 默认 | cognee/infrastructure/databases/graph/ | graph_db_interface, config(graph_database_provider) |
| 向量库接口 | cognee/infrastructure/databases/vector/ | vector_db_interface |
| 统一引擎 | cognee/infrastructure/databases/unified/ | get_unified_engine |