第 7 章 · 02 双时态知识图谱与 GDPR 撤回


第 7 章 · 02 双时态知识图谱与 GDPR 撤回

本节摘要:时间有两个轴,混用它们是知识系统最常见的静默错误。本节装配第二条纵贯能力线:时态。双时态模型把事实的 valid time(事实在世界里何时成立)与 recorded time(系统何时得知)独立建模——"去年就生效、上个月才录入"的合同与"上个月录入、当时即生效"的新闻,两条时间线分开管。state_at 提供时间点快照查询——图谱在任意历史时刻的状态一键重放;kg/temporal_query.py(1765 行)补齐时点/时段查询、一致性校验、演化分析与版本快照。压轴是 0.6.5 新增的 retraction/purge(CHANGELOG #957):撤回关闭事实的有效窗口但保留历史以解释旧决策,物理清除为 GDPR 第 17 条删除权而生、只留无内容的墓碑——两者语义差异正是"纠错"与"遗忘"的法律分界。change_management 的 MutationRecord 为全程留变更账。

内容来源:semantica/kg/temporal_model.py(BiTemporalFact)、semantica/kg/temporal_query.py(1765 行)、semantica/context/context_graph.py(state_at 第 2590 行、retract_node 第 1556 行、purge_node 第 1718 行)、semantica/change_management/change_log.py(MutationRecord)、CHANGELOG.md(#957)、README.md(Temporal Intelligence)

⚠️ 注意:purge 的作用域是本图purge_node 的 docstring 明说:AgentMemory 与绑定的向量库里留存的副本不会被触达,导出文件里的快照同样不在此列——purge 是删除工作流的一步,不是全部;完整 GDPR 落地还需要清理记忆层、向量库与备份。

学习目标

  1. 理解双时态模型:valid_from/valid_until 与 recorded_at/superseded_at 两轴独立的语义。
  2. 掌握 state_at 时间点快照的实现口径(节点与边的双重活动性判定)。
  3. 会用 TemporalGraphQuery 做时点/时段查询、一致性校验与演化分析。
  4. 掌握 retraction 与 purge 的语义差异:窗口关闭 vs 物理移除、可解释 vs 可遗忘。
  5. 理解 MutationRecord 变更账与两者的衔接。

一、双时态模型:两个时间轴独立行走

现实里的"事实什么时候成立"和"我们什么时候知道"经常错位:合同 2024 年 3 月 1 日生效,3 月 5 日才扫描入库;某高管 1 月已经离职,3 月才见于新闻。单时态系统里这两个时刻被压成一个,于是"回放当时我们相信什么"永远失真。双时态(bi-temporal)把两根轴拆开——BiTemporalFact(temporal_model.py 第 28—66 行):

class BiTemporalFact: valid_from: Optional[datetime] # 有效时间轴:何时开始成立 valid_until: Optional[datetime | TemporalBound] # 何时失效(OPEN=未定) recorded_at: datetime = field(default_factory=_default_recorded_at) # 记录时间轴 superseded_at: datetime | TemporalBound = TemporalBound.OPEN # 被取代时刻

类文档里有一句关键的设计声明:事实在图里继续以普通 relationship 字典存放,这个类只是归一化包装——老调用方继续读写 valid_from/valid_until,新机制不必迁移数据。缺省行为也有讲究:recorded_at 未给时回落到 valid_from 或当前时刻(第 56 行),"只知成立、不知何时录入"的事实不至于丢轴。README 的例子一句道破两轴分工:

fact = BiTemporalFact( valid_from=datetime(2024, 3, 1), # 事实在世界里何时为真 valid_until=datetime(2025, 1, 1), recorded_at=datetime(2024, 3, 5), # 你何时得知它 )

两轴独立后,四类查询全部可答:现在有效的事实(valid 轴)、某时刻图谱相信什么(recorded 轴)、"当时就知道且当时已生效"的交集、以及"过去的事实现在才被知道"的迟到录入——第 3 章管线里 ingest 的时间戳从此有了精确语义。

二、state_at:把图谱拨回任意时刻

ContextGraph.state_at(timestamp)(context_graph.py 第 2590 行起)是时间旅行的入口:

def state_at(self, timestamp: Union[str, int, float, datetime]) -> Dict[str, Any]: """Return a serializable snapshot of graph state valid at the given time.""" at_time = self._normalize_timestamp(timestamp) with self._lock: active_nodes = [node for node in self.nodes.values() if node.is_active(at_time)] active_node_ids = {node.node_id for node in active_nodes} active_edges = [ edge for edge in self.edges if edge.is_active(at_time) and edge.source_id in active_node_ids # 两端点必须同时在场 and edge.target_id in active_node_ids ]

口径有三条:节点按 is_active(at_time) 判定(有效期窗口含住查询时刻即活);边要多过一道——两端节点必须同时在活集里,防悬空边;整个过程持锁执行并输出可序列化字典。README 的用法 graph.state_at("2024-01-01") 一行即得"那一刻的图谱快照",且是重放而非重算——历史从未被覆盖,只是被窗口住了。这依赖于 6.1 节的铺垫:add_node/add_edge 起就接受 valid_from/valid_until,决策记录也自带双时态有效期——图谱从第一天起就是按时间维度存的。

三、temporal_query.py:时态查询全家桶

kg/temporal_query.py(1765 行)在快照之上提供查询语言级的时态能力,三大组件:

TemporalGraphQuery(第 41 行起)——查询引擎。query_at_time(时点过滤:只留该时刻有效的关系)、reconstruct_at_time(时刻重建)、query_time_range(时段重叠/覆盖三种匹配模式,_relationship_overlaps_range/_relationship_covers_range)、query_temporal_pattern(时序模式)、find_temporal_paths(时序因果路径,_path_respects_causal_order 保证路径上事件先后有序——"果不能走在因前面")、analyze_evolution(跨周期演化分析)、validate_temporal_consistency(一致性体检,产出 TemporalConsistencyReport:errors+warnings 分列)。自然语言时间短语("last quarter")由 TemporalNormalizer 归一化为起止区间后才进入这套引擎。README 的组合用法演示了从快照到时段查询的完整接缝:

kg = graph.to_kg_dict() # 官方适配器:{"entities", "relationships"} 形态 tq = TemporalGraphQuery() facts_in_window = tq.query_time_range( kg, query="valid_facts", start_time="2024-01-01", end_time="2024-12-31") norm = TemporalNormalizer() start, end = norm.normalize("last quarter") # 自然语言 → (start, end)

TemporalPatternDetector(第 918 行起)——在带时间戳的边上挖反复出现的序列(_find_sequences 按最小频率)与环(_find_cycles),周报式的"每季度末审批量激增"模式自动浮现。

TemporalVersionManager(第 1149 行起)——版本/快照管理。create_version 存版本、compare_versions 做版本 diff、create_snapshot 出全量快照、apply_revision 把修订套到快照上重放历史。它与 6.2 节 AgentContext.checkpoint/diff_checkpoints 一脉相承——决策状态的版本化快照,最终都落到这套管理器上。

四、retraction:纠错但不失忆(0.6.5,#957)

CHANGELOG #957 的一句话抓住了新增能力的动机:此前 ContextGraph 没有任何办法移除一个节点或边(除非 clear() 全弃)。0.6.5 补上了一对语义相反的操作,先看温和的 retract_node(第 1556—1660 行):

def retract_node(self, node_id, reason=None, at=None, cascade=True) -> bool: """Retract a node: no longer active, but still visible in history. Closes the node's validity window rather than deleting it, so state_at before ``at`` still returns the node and any decision recorded against it remains explainable.""" ... node.valid_until = _closing_valid_until(node.valid_until, at_iso) record = {"entity_id": node_id, "entity_kind": "node", "retracted_at": at_iso, "reason": reason} self._retractions[("node", node_id)] = record

撤回的语义是关窗不是删除:把 valid_until 拨到撤回时刻,节点从此从 find_active_nodes 与未来的 state_at 里消失,但撤回之前的所有 state_at 依旧能查到它——当年基于这条事实做出的决策,其解释链完好无损。单条边有对应的 retract_edge(edge_id, reason, at)(第 1662 行),端点节点不受牵连,同样遵守"窗口只关不扩"。三个语义细节都被源码注释钉死:

  • 窗口只关不扩_closing_valid_until(第 191—208 行)取"既有上界与撤回时刻的较早者"——一个本来就已在过去失效的实体,不会被后来的撤回拨成"活到撤回那天"。
  • 幂等:重复撤回返回 False 而非报错,且保留首次的 reason 不被覆盖。
  • 级联默认开cascade=True 连带关闭所有触及该节点的边(活跃视图的一致性要求),源码注释还解释了一个并发陷阱——循环内不能查活的 _retractions 字典做去重(内容派生的 edge_id 可能重复),必须循环前快照。

撤回事件走审计通道时发的是 UPDATE_NODE/UPDATE_EDGE——改的是有效期,不是存在性,这正是它与 purge 在变更账上的分野。

五、purge:GDPR 的"被遗忘权"

purge_node(第 1718 行起)是破坏性孪生兄弟:

"""Permanently remove a node; history no longer contains it. Unlike :meth:`retract_node` this is destructive: the node disappears from :meth:`state_at` as well as from the active view. Only a tombstone remains, recording that a purge happened and why -- deliberately without the purged content, since retaining it would defeat the point."""

删除是彻底的:从活动视图和全部历史中消失,连 state_at 回放到删除前也查不到它。留下的只有一个墓碑(tombstone)——{"entity_id", "entity_kind", "purged_at", "reason"},经 get_tombstone()/list_tombstones() 可查,刻意不含被删内容(存了内容就违背了删除的本意)。CHANGELOG 把适用场景点名:erasure obligations retraction alone cannot satisfy (e.g. GDPR Article 17)——数据主体要求删除其个人数据的法定权利。级联同样默认开:节点连同所有触及边一起清除,跨图链接的标记节点一并拆除;审计通道发的则是 REMOVE_NODE/REMOVE_EDGE。与 ProvenanceManager.invalidate(7.1 节的"无效化留痕")合起来看,Semantica 形成了撤销光谱的三档:关窗(retraction,纠错保解释)→ 无效化(invalidate,账本标记不删除)→ 物理清除(purge,法定遗忘)——按法律与业务要求选档。

六、MutationRecord:变更的流水账

撤回与清除的每一步都要留痕,记账的格式定义在 change_management/change_log.py(第 114—130 行):

@dataclass class MutationRecord: """ Granular record of a single change to a graph entity (node or edge). """ timestamp: str # ISO 8601 变更时刻 operation: str # ADD_NODE / UPDATE_NODE / REMOVE_NODE / # ADD_EDGE / UPDATE_EDGE / REMOVE_EDGE entity_id: str # 受影响节点或边 payload: Dict[str, Any] # 变更后的实体状态 version_label: Optional[str] = None # 关联的快照版本

CHANGELOG #957 明确记载了复用方式:retraction 发 UPDATE_*、purge 发 REMOVE_*,与 MutationRecord 既有的操作词表严丝合缝——撤回/清除没有为此新造任何审计格式。并发安全也在同一处注释里:payload 在锁内快照、回调在锁外触发,回调里再改图(甚至 clear())也观察不到半截记录。change_management 的其余三件(managers.pyontology_version_manager.pyversion_storage.py)提供本体版本管理与快照存储,与 TemporalVersionManager 共同构成"图谱的 Git":每个变更是一条 MutationRecord,每个版本是一次快照,任意两个时刻可 diff——这让 6.3 节配方里的"审计导出"又多了一层底账。

💡 装配要点:本阶是叠加在①→⑧全线上的时态层。心智模型:每条事实带两根轴(valid 世界轴、recorded 系统轴),state_at 按窗口重放(边还要求两端点同活),TemporalGraphQuery 把时点/时段/时序路径/演化做成查询算子。撤销三档要按法律语义选:retraction 关 valid_until 保历史解释(审计发 UPDATE)、purge 物理移除只留无内容墓碑(审计发 REMOVE、GDPR Article 17)、invalidate 是溯源账本侧的中间档。MutationRecord 六操作词表是变更审计的通用格式,payload 锁内快照防并发。落地 GDPR 记得 purge 只管本图——记忆层、向量库、备份要另行清理。

本节要点回顾

  • 双时态四字段:valid_from/valid_until(世界轴)+ recorded_at/superseded_at(系统轴)独立建模;BiTemporalFact 是归一化包装,事实仍以普通字典存图;recorded_at 缺省回落 valid_from 或当前时刻。
  • state_at 三条口径:节点按 is_active(at_time)、边要求两端点同在活集、持锁输出可序列化快照——历史重放而非重算。
  • temporal_query.py 三组件:TemporalGraphQuery(时点/时段/时序路径/演化/一致性体检)、TemporalPatternDetector(序列+环挖掘)、TemporalVersionManager(版本/快照/diff)。
  • retraction:关窗不删除——出 find_active_nodes 与未来 state_at,历史 state_at 仍可查(旧决策可解释);窗口只关不扩、幂等、级联默认开、审计发 UPDATE_*。
  • purge:连历史一起移除,只留无内容墓碑(get_tombstone/list_tombstones),对应 GDPR Article 17;审计发 REMOVE_*;作用域仅本图。
  • 撤销光谱三档:retraction(纠错保解释)→ invalidate(账本无效化)→ purge(法定遗忘);MutationRecord 六操作为通用变更账,payload 锁内快照。

下一节:进入第 8 章《Polyglot 存储抽象》——溯源与时态数据同样要落到合适的存储后端:内存、SQLite、FAISS、Neo4j 各守其位。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U