本节摘要:压轴章的收尾一节。决策智能缺最后一块拼图:决策之前的合规把关。
PolicyEngine(policy_engine.py,1043 行)把合规策略存为版本化图节点,用min_/max_/required_三前缀规则 DSL 对决策做合规评估——"拒贷必须给出三条理由"这类监管要求可以写成一条required_规则;不合规决策在入库前被拦截,合规记录经APPLIED_POLICY边永久留痕,例外走GRANTED_EXCEPTION审批链。后半段把全书配方做一次端到端演练:以 README 的贷款审批例子为脚本,走完注册决策→记录证据链→监管审计→先例对比四步,并回答压轴之问——决策智能的"为什么可信"。
内容来源:
semantica/context/policy_engine.py(1043 行)、decision_models.py(Decision/Policy 数据类)、decision_recorder.py(例外与审批链)、README.md(Decision Intelligence 与 Recipe: Audit Trail)、docs/guides/decision-intelligence.md(Gating 一节)
⚠️ 注意:
PolicyEngine.check_compliance的兜底行为是"异常即不合规"(第 398 行except ... return False)——策略节点缺失、规则求值抛错都会静默归入"违规"。fail-closed 对合规是正确默认,但调试时策略 ID 拼错与真实违规在返回值上不可区分,务必配合日志排查。
policy_id:version 节点 + VERSION_OF 版本链。min_/max_/required_ 三前缀规则 DSL 与 _evaluate_compliance 的求值顺序。PolicyEngine 的世界观与 6.1 节一脉相承:策略不该躺在配置文件里,而该是图上有版本、有历史、有引用关系的节点。Policy 数据类(decision_models.py 第 175 行)的核心字段:policy_id/name/rules/category/version。入库时节点 ID 直接编码版本(policy_engine.py 第 146 行):
node_id = f"{policy.policy_id}:{policy.version}" self.graph_store.add_node( node_id=node_id, node_type="Policy", content=policy.name or policy.policy_id, policy_id=policy.policy_id, name=policy.name, description=policy.description, rules=policy.rules, category=policy.category, version=policy.version, ...)
改策略不是覆盖而是出新版:update_policy(第 167—242 行)调 _generate_next_version 递增补丁号,新版本入库后连一条 VERSION_OF 边(Cypher 后端是 MERGE (old)-[:VERSION_OF]->(new),内存后端是 add_edge(..., edge_type="VERSION_OF", change_reason=...))——变更原因随之入图。get_applicable_policies(第 244 行)取"没有后继版本"的节点为现行版(Cypher 写法是 WHERE NOT (p)-[:VERSION_OF]->(:Policy)),并支持按实体范围收窄(_policy_matches_entities:metadata 里的 entities/entity_ids/applies_to_entities 与决策实体有交集才适用)。一条 Cypher 查询 get_policy_history(第 528 行)能拉出全版本时间线——监管问"当时生效的是哪版政策",答案就是决策引用的那个版本号。
策略的灵魂在 rules 字典。_evaluate_compliance(第 879—914 行)定义了一门极简但够用的规则语言:
for rule_key, rule_value in rules.items(): # min_X → metadata["X"] >= rule_value if rule_key.startswith("min_"): field = rule_key[4:] field_value = self._get_metadata_field(metadata, decision, field) if field_value is None: return False # 字段缺失即违规(fail-closed) if field_value < rule_value: return False # max_X → metadata["X"] <= rule_value elif rule_key.startswith("max_"): ... # required_X → metadata["X"] 包含列表所有项(或等于字符串) elif rule_key.startswith("required_"): field = rule_key[9:] field_value = self._get_metadata_field(metadata, decision, field) if field_value is None: return False if isinstance(rule_value, list): if not all(item in field_value for item in rule_value): return False ... # 三个特例键:min_confidence / allowed_outcomes / required_categories elif rule_key in {"min_confidence", "allowed_outcomes", "required_categories"}: decision_data = {"confidence": decision.confidence, "outcome": decision.outcome, "category": decision.category} if not self._check_compliance_with_rules(decision_data, {rule_key: rule_value}): return False
三个前缀覆盖了监管条款的高频句式:min_confidence: 0.85(置信度下限)、max_loan_amount: 500000(额度上限)、allowed_outcomes: ["approved", "rejected", "conditional"](结论白名单)。字段查找走 _get_metadata_field(第 862 行)三级回退:metadata 精确键 → 后缀匹配(规则 status 能命中 verification_status)→ 决策属性。
**"拒贷必须给出三条理由"**就落在 required_ 前缀上。给拒贷类决策配一条策略:
rules = { "required_reasons": ["dti_ratio_check", "credit_history_check", "income_verification"], "min_confidence": 0.85, }
求值时 required_ 取 metadata["reasons"](或后缀匹配 rejection_reasons 等),要求列表包含全部三项——少给一条理由,决策就过不了门禁。注意各规则是 AND 语义:任何一条不满足整体即不合规,return False 立即短路。
门禁的用法是决策入库前的 if 分支(指南 Gating 一节的完整示例):
engine.add_policy(Policy( policy_id = "lending_policy_v3", name = "Lending Compliance Policy v3", description = "Credit decisions require confidence >= 0.85 and documented reasoning", rules = {"min_confidence": 0.85, "requires_reasoning": True}, category = "loan_approval", version = "3.0", ...)) if engine.check_compliance(d, "lending_policy_v3"): loan_id = context.record_decision(...) engine.record_policy_application(d.decision_id, "lending_policy_v3", "3.0") else: # 拦截:不合规决策不入库 ...
放行路径上 record_policy_application(第 400 行)连一条 APPLIED_POLICY 边并在边上记 applied_at/policy_id/version——决策与"当时适用的策略版本"从此绑定,这就是 6.1 节"可追溯"在策略维度的落点。拦截不等于路堵死:紧急场景可以走例外审批,record_exception(第 446 行)创建 Exception 节点并连两条边:
MERGE (d)-[:GRANTED_EXCEPTION]->(e) MERGE (e)-[:OVERRIDDEN_POLICY]->(p)
重型版 DecisionRecorder.record_exception(decision_recorder.py 第 271 行)还要 approver/approval_method/justification 三要素——指南的安全示例里连"03:14 UTC 的 CISO 口头批准"都写进了 justification。策略变更的历史责任同样可查:get_affected_decisions(policy_id, from_version, to_version)(第 589 行)沿 APPLIED_POLICY 边反查"引用了旧版的全部决策",analyze_policy_impact(第 711 行)更进一步支持 what-if——拟议新规则与现行规则的 diff、受影响决策数、平均合规分、风险增幅一次算清,让改政策前先看冲击面。
现在把全书零件装成一台整机。以下配方以 README 的 Decision Intelligence 一节为蓝本(category/scenario 原样保留),四步走完:
第一步,注册决策链。 三笔决策、两条因果边:
from semantica.context import ContextGraph graph = ContextGraph(advanced_analytics=True) app_id = graph.record_decision( category="credit_application", scenario="Personal loan, $85k income, 31% DTI, 3yr employment", reasoning="Income meets threshold; employment stable; no adverse credit events", outcome="proceed_to_underwriting", confidence=0.88, metadata={"applicant_id": "A-7291"}) uw_id = graph.record_decision( category="loan_underwriting", scenario="Underwriting review for A-7291", reasoning="DTI within policy; clean 36-month credit history", outcome="approved", confidence=0.94) rate_id = graph.record_decision( category="interest_rate", scenario="Rate assignment for approved loan A-7291", outcome="rate_set_8.9pct", reasoning="Prime + 2.4% based on risk tier B2", confidence=0.99) graph.add_causal_relationship(app_id, uw_id, relationship_type="CAUSED") graph.add_causal_relationship(uw_id, rate_id, relationship_type="INFLUENCED")
每笔决策入库即过 6.1 节的校验与织图:决策节点、involves/belongs_to/made_by 边一步到位。
第二步,记录证据链。 申请人实体与决策挂接,溯源同步落账(README Recipe 的模式,接第 7 章的 PROV-O):
from semantica.provenance import ProvenanceManager from semantica.export import RDFExporter prov = ProvenanceManager(storage_path="./audit.db") graph.add_node("A-7291", "Person", name="Applicant A-7291") graph.add_edge(uw_id, "A-7291", "involves", confidence=0.94) prov.track_entity("A-7291", source="crm/applications/2025/A-7291.json", metadata={"extractor": "NamedEntityRecognizer"})
第三步,监管审计导出。 全图转 KG 字典后一键导出 W3C PROV-O:
kg = graph.to_kg_dict() RDFExporter().export(kg, "audit_trail.ttl", format="turtle") chain = graph.trace_decision_chain(rate_id) # 完整因果谱系 insights = graph.get_decision_insights() # 置信度/类别/结论统计
第四步,先例对比。 下一笔同类申请进来,先查历史:
similar = graph.find_similar_decisions( "personal loan approval, 31% DTI", max_results=5) compliant = graph.check_decision_rules( {"category": "loan_underwriting", "confidence": 0.94})
A-7291 的核保决策成为后继申请的先验;同口径场景从此同判。四步配方合计不到四十行,却覆盖了 SR 11-7 式模型治理审查要问的全部问题:谁在何时依据什么政策、基于哪些证据、参考哪些先例、做出了什么结论。
第 5—6 章合起来,Semantica 给出了一个值得背下来的可信公式:
确定性图谱 + 确定性推理 + 全链路溯源 = 可审计决策。
APPLIED_POLICY/因果边/溯源账本三重留痕,export_prov 一键交付监管格式。LLM 依然在管线里——但只负责理解语言与抽取结构,从不负责"要签字"的结论。正如 README 卷首语:Explainable, traceable, and trustworthy by design——可信不是事后补救,而是管线每一阶的设计属性。
💡 装配要点:PolicyEngine 的四张图要素——
policy_id:version节点、VERSION_OF版本链、APPLIED_POLICY应用留痕、GRANTED_EXCEPTION/OVERRIDDEN_POLICY例外链。规则 DSL 三前缀 AND 语义:min_/max_ 数值比较、required_ 列表包含(拒贷三理由的落点)、三特例键(min_confidence/allowed_outcomes/required_categories);字段查找带后缀回退。拦截闭环:check_compliance 放行 → record_policy_application 留痕;不合规 → 拦截或 record_exception 留痕例外。配方四步(注册决策链→证据链→审计导出→先例对比)可作为任何受监管决策场景的模板。
policy_id:version 节点 + VERSION_OF 链,update_policy 出新版不改史,get_applicable_policies 取无后继版本,实体范围可收窄。min_X(≥)、max_X(≤)、required_X(包含全部列表项/等于字符串);三特例键 min_confidence/allowed_outcomes/required_categories;字段缺失即违规,规则间 AND 短路;_get_metadata_field 三级回退(精确→后缀→属性)。required_reasons: [三项];每个决策连 APPLIED_POLICY 边绑定当时策略版本;例外走 GRANTED_EXCEPTION/OVERRIDDEN_POLICY,审批人与方法入图。get_affected_decisions 反查引用旧版的决策;analyze_policy_impact 做 what-if(规则 diff+合规分+风险增幅)。下一节:进入第 7 章《溯源与时态》——
01 PROV-O 溯源管理器:1486 行溯源管理器里的 Entity/Activity/Agent 三元模型与监管格式的审计轨迹导出。