本节摘要:第 5—6 章的推理与决策之所以"敢签字",是因为背后有账本。本章装配第一条纵贯①→⑧全管线的能力线:溯源。
provenance/模块(6 个文件、约 3800 行)以 W3C PROV-O 标准为骨架——**Entity(东西)/Activity(过程)/Agent(责任方)**三元模型,让每个事实都能回答三问:从哪来(source)、谁产生(agent)、怎么产生(activity)。本节精读 1486 行的ProvenanceManager:track_entity的原子事务与版本归档、get_lineage的 BFS 血缘链与校验和完整性、audit_log的三种审计格式与export_prov的 RDF 导出;最后看它与决策智能的衔接——决策的证据链就是溯源链。
内容来源:
semantica/provenance/(manager.py1486 行、schemas.py495 行、storage.py1039 行、integrity.py236 行、bridge_axiom.py435 行)、semantica/context/decision_recorder.py(_track_decision_provenance)、README.md(Recipe: Audit Trail)
⚠️ 注意:PROV-O 是 W3C 的溯源本体标准(PROVENANCE ONTOLOGY),
ProvenanceManager的存储模型与它对齐但并非逐字实现——内存/SQLite 存储存的是扁平ProvenanceEntry,三元组形态只在export_prov()导出时现场构建(需要 rdflib)。读源码时别把存储层当成 RDF 库。
ProvenanceEntry 的字段分组:PROV 核心、代理类型、审计级源引用、时态与版本。track_entity 的原子事务(#807)、旧版本归档与 previous_version_id/derived_from_id 语义区分。get_lineage/trace_lineage/revision_history 追血缘,用 audit_log/export_prov 出审计包。溯源要回答的三个问题,恰好对应 PROV-O 的三个核心类:
三个类再用几条标准关系缝成网:wasDerivedFrom(此物派生自彼物)、used(活动使用了哪些实体)、wasAttributedTo(实体归于哪个代理)、generatedAtTime(何时生成)。落到 Semantica,这张网有一个统一载体——ProvenanceEntry(schemas.py 第 35 行起,约 60 个字段按组排布):
# W3C PROV-O core entities entity_id: str # prov:Entity entity_type: str # entity / chunk / relationship / property / decision ... activity_id: str # prov:Activity agent_id: str = "semantica" # prov:Agent agent_type: str = "software_agent" # "person" | "software_agent" | "organization" is_automated: bool = True role: Optional[str] = None # prov:hadRole:"generator"/"approver"/"reviewer" # Audit-grade source tracking source_document: str = "" # DOI、文件路径、URL source_location: Optional[str] = None # 页码、图号、字符区间 source_quote: Optional[str] = None # 原文直接引用(审计铁证) confidence: float = 1.0 checksum: str = ... # SHA-256 完整性校验 # Temporal & versioning first_seen / last_updated / valid_from / valid_until parent_entity_id: ... # prov:wasDerivedFrom used_entities: List[str] # prov:used
source_quote 值得单独划线:不只记"来自文档 X 第 3 页",还把原文那句话存下来——监管对质时直接出示引文,不用翻库。agent_type/is_automated/role 三字段则是问责粒度的关键(issue #825):出问题时能区分"算法自动干的"还是"某人审批的"。
track_entity(manager.py 第 263—415 行)是全模块的枢纽方法,120 行里装了三重设计。
第一重,原子事务(issue #807):
with self._get_or_create_transaction(_conn) as conn: existing = self.storage._retrieve_with_conn(conn, entity_id) ... self._save_entry(entry, _conn=conn, _raise_on_error=True)
读取现有记录、归档旧版、写入新条目共用一个连接一个事务(BEGIN IMMEDIATE 使并发调用在读取阶段即串行化),任何一步抛异常整体回滚——溯源账本不会出现"归档了但没写入"的半截状态。
第二重,父链自动解析(第 307—320 行):parent_entity_id(对应 wasDerivedFrom)有三条来源——显式 kwarg、metadata["derived_from"],以及一个巧妙的启发式:若 source 参数本身是已登记的实体 ID,就把它当父实体——"从实体 A 抽取出实体 B"这类管线衔接自动成链。
第三重,版本归档而非覆盖(第 325—348 行):实体更新时,旧条目被深拷贝后改挂 "{entity_id}:v:{last_updated}" 的版本化 ID 入库,新条目的 previous_version_id 指向它。源码注释特意解释了归档时为何原样保留校验和:归档只是改名不是篡改,重算校验和反而会让后续哈希链断出"幻影缺口",让 verify_chain() 误报篡改。第 381 行起的注释再划一道语义界线:
# previous_version_id always captures "this corrects a prior # version of the same fact" ... # derived_from_id captures "this fact was derived from a # different source entity" — only set when a parent was # explicitly resolved ...
修正同一事实的旧版本(previous_version_id)与从别的实体派生(derived_from_id)是两种血缘,混为一谈审计就失真。
血缘查询是 Manager 的第二序列。get_lineage(第 748—813 行)沿 trace_lineage(storage 层 BFS,entries[0] 恒为被查实体本身,其后是父、祖……)聚合出一份血缘档案:
return { "entity_id": entity_id, "lineage_chain": chain_dicts, # 完整链条(BFS 序) "source_documents": list(set( # 血缘上出现过的全部来源文档 e.source_document for e in lineage_entries if e.source_document)), "first_seen": min(...), "last_updated": max(...), "entity_count": len(lineage_entries), "metadata": aggregated_metadata, # 祖先先铺、自己后盖:当前值胜出 "integrity_verified": all(verify_checksum(e) for e in lineage_entries), }
两处细节:metadata 聚合按 BFS 逆序合并(第 777 行注释)——祖先的属性先铺底、被查实体自己的最后覆盖,冲突时"最新者胜";integrity_verified 对整条链逐条 verify_checksum(integrity.py 的 SHA-256 校验),返回布尔让调用方一眼确认血缘没被篡改。配套查询还有 trace_descendants/get_descendants(血缘反向)、revision_history(版本时间线)、query_recorded_between(时段过滤)、invalidate(无效化而非删除:归档旧态后追记 invalidated/invalidated_at_time/invalidation_reason——"证明一个事实存在过、被审过、又被撤回")。
账本记了,还要能交给监管。两个出口对应两种受众:
audit_log(第 1151—1199 行)给人工审阅,三种格式:
if format == "json": return [e.to_dict() for e in entries] elif format == "csv": lines = ["entity_id,entity_type,activity_id,agent_id,timestamp"] ... else: # table:对齐的文本表格,流水对账用
按时间排序、since 参数过滤时段,CSV 表头五列直指审计要素(什么实体/什么类型/什么活动/谁干的/何时)。
export_prov(第 1203 行起)给机器互操作——把全部条目现场构建成 W3C PROV-O 的 RDF 图:
PROV = Namespace("http://www.w3.org/ns/prov#") EX = Namespace(base_uri or DEFAULT_BASE_URI) # https://semantica.dev/ns# for e in self.storage.retrieve_all(): ent_uri = uri(e.entity_id) g.add((ent_uri, RDF.type, PROV.Entity)) g.add((ent_uri, PROV.generatedAtTime, Literal(e.timestamp, datatype=XSD.dateTime))) ...
导出里有两处标准对齐的巧思:_AGENT_TYPE_PROV_CLASS(第 1245 行)把自家代理类型映射到 PROV-O 的规范子类——"person"→prov:Person、"software_agent"→prov:SoftwareAgent、"organization"→prov:Organization;DEFAULT_BASE_URI 与 RDFExporter 的命名空间表共用同一 URI 前缀(issue #825),保证图谱导出的实体与溯源导出的实体在 RDF 层同 URI 合流。格式支持 turtle/ntriples/jsonld,README 的监管提交配方用的正是 turtle。
纵向能力线的价值在与第 6 章的接缝处显现。对接点在 DecisionRecorder._track_decision_provenance(decision_recorder.py 第 587—618 行):决策入库时同步写两条溯源——决策节点登记为 Entity(entity_type="decision"、agent_id=decision_maker、source_documents 全带),决策活动登记为 Activity(activity_type="decision_making"、used_entities=[decision_id]、起止时间取决策时间戳)。于是一笔拒贷决策的完整解释链在账本上闭合:
原始文档(source_document+quote,Entity) ← NER 抽取活动(Activity,software_agent) ← 图谱实体(Entity,wasDerivedFrom 文档) ← 推理结论(Entity,used 推理前提) ← 决策(Entity+Activity,agent=decision_maker) → export_prov() 输出 .ttl 提交监管
README 的 Recipe: Audit Trail 演示了最短接线:ContextGraph 记决策链、ProvenanceManager(storage_path="./audit.db") 记证据、to_kg_dict() 转 KG 字典后 RDFExporter().export(kg, "audit_trail.ttl", format="turtle")——十几行,一个可提交的审计包。进阶件 bridge_axiom.py 还管一种特殊血缘:跨域换算公式的系数出处(如"1% 生物量增长→0.346% 旅游收入"的 DOI+页码+引文),保证"翻译链"每一环都可回查——对 Blue Finance、临床诊断这类跨域高风险场景,这是溯源的最后一块拼图。
💡 装配要点:本阶是与全管线正交的能力层。心智模型:ProvenanceManager = 一张 PROV-O 对齐的追加式账本(Entry 即账页),
track_entity/track_relationship/track_chunk/track_property_source四个记账口,读取侧get_lineage(BFS 血缘+完整性)、revision_history(版本线)、query_recorded_between(时段),导出侧audit_log(人读)与export_prov(机读 RDF)。三个装配纪律:账本只追加不覆盖(更新=归档旧版+新条目)、校验和构成防篡改哈希链、previous_version_id(修正)与derived_from_id(派生)不可混用。生产必传storage_path(SQLite),否则内存存储重启即失忆。
ProvenanceEntry 约 60 字段分四组(PROV 核心/代理类型/审计级源引用/时态版本)。source_quote 存原文引文,agent_type+is_automated+role 支撑问责粒度(自动 vs 人工审批)。track_entity 三重设计:BEGIN IMMEDIATE 原子事务(#807)、父链三级解析(kwarg→derived_from→source 即父的启发式)、版本归档制(旧版挂 entity_id:v:时间戳,校验和原样保留防哈希链幻影缺口);previous_version_id=修正旧版,derived_from_id=跨实体派生。get_lineage:BFS 链+来源文档集合+metadata 逆序聚合(最新者胜)+integrity_verified 全链校验和核验;invalidate 无效化留痕不删记录。audit_log(table/csv/json,人读)+ export_prov(turtle/ntriples/jsonld,PROV 命名空间、代理子类映射、与 RDFExporter 同 base URI 合流)。_track_decision_provenance 把决策记为 Entity+Activity;文档→抽取→实体→推理→决策全链在账本闭合,export_prov 交付监管。下一节:
02 双时态知识图谱与 GDPR 撤回——valid time 与 recorded time 的独立建模、state_at时间点快照、retraction/purge 的语义差异与 GDPR 删除权。