第12章:矛盾检测
定位:第四部分"时间维度"的中间章。分析 MemPalace 如何利用时态知识图谱检测归属冲突、过期信息和不一致日期——从 README 中的三个具体示例出发,推断实现机制,并讨论误报与漏报的工程权衡。
AI 自信地犯错
大语言模型有一个众所周知的特点:它们犯错时毫不犹豫。它们不会说"我不确定"或者"让我查一下"——它们会以和正确回答完全相同的语气,流畅地给出一个错误答案。
当 AI 充当你的记忆系统时,这个特点变得格外危险。如果你的 AI 助手记住了"Soren 负责 auth migration",而实际上 Maya 才是负责人,那么接下来所有基于这条错误信息做出的决策都是建立在沙子上的。更糟糕的是,你可能永远不会发现这个错误——因为你信任你的记忆系统,正如你信任你自己的记忆。
MemPalace 的矛盾检测机制正是为了应对这个问题。它不是试图让 AI 不犯错(这在当前技术条件下不可能),而是在 AI 即将犯错时拉响警报。
三个具体的矛盾场景
MemPalace 的 README 展示了三种不同类型的矛盾检测(README.md:262-273):
Input: "Soren finished the auth migration"
Output: AUTH-MIGRATION: attribution conflict -- Maya was assigned, not Soren
Input: "Kai has been here 2 years"
Output: KAI: wrong_tenure -- records show 3 years (started 2023-04)
Input: "The sprint ends Friday"
Output: SPRINT: stale_date -- current sprint ends Thursday (updated 2 days ago)
这三个例子看似简单,实际上代表了三种完全不同的检测逻辑。让我们逐一分析。
场景一:归属冲突
Input: "Soren finished the auth migration"
Output: AUTH-MIGRATION: attribution conflict -- Maya was assigned, not Soren
这条声明包含一个隐含的归属断言:auth migration 是 Soren 的工作。要检测这个矛盾,系统需要:
- 识别出声明中涉及的实体:
Soren(人)和auth-migration(项目/任务)。 - 识别出声明中的关系类型:某种"完成"或"负责"的关系。
- 查询知识图谱:auth-migration 实际上被分配给了谁?
从第 11 章分析的 knowledge_graph.py 来看,这种查询对应的操作是以 auth-migration 为实体查询 incoming 方向的关系:
kg.query_entity("auth-migration", direction="incoming")
或者直接查询特定关系类型:
kg.query_relationship("assigned_to")
知识图谱中存在的三元组可能是:
Maya → assigned_to → auth-migration (valid_from: 2026-01-15, valid_to: NULL)
当新的声明试图建立 Soren → completed → auth-migration 这条关系时,系统发现 auth-migration 的 assigned_to 关系指向的是 Maya 而非 Soren。两个不同的人被关联到同一个任务的责任归属上——这就是归属冲突。
关键在于:这种检测不需要理解自然语言的全部语义。它只需要做三件事——提取实体、识别关系类型、与已知事实交叉比对。知识图谱提供了比对的基准,时态信息确保比对的是当前有效的事实。
场景二:任期错误
Input: "Kai has been here 2 years"
Output: KAI: wrong_tenure -- records show 3 years (started 2023-04)
这个矛盾涉及动态计算。声明中的"2 years"不是一个可以直接存储在知识图谱中的静态值——它需要从 Kai 的入职日期和当前日期推算出来。
知识图谱中存储的三元组可能是:
Kai → started_at → Company (valid_from: 2023-04, valid_to: NULL)
检测逻辑大致如下:
- 从声明中提取实体
Kai和数值声明2 years。 - 查询
Kai的started_at或类似的入职关系。 - 从
valid_from(2023-04)到当前日期计算实际任期。 - 如果计算结果(约 3 年)与声明中的数值(2 年)不一致,触发警报。
这种检测的核心能力来自时态 KG 的 valid_from 字段。如果知识图谱只存储"Kai 在公司工作"这个静态事实,它无法判断任期声明是否正确。正是因为它存储了"Kai 从 2023 年 4 月开始在公司工作",系统才有了计算任期的基础数据。
注意输出中的 (started 2023-04) —— 系统不仅指出了矛盾,还给出了判断依据。这让用户可以决定:是知识图谱中的日期有误,还是声明中的数字不对。矛盾检测不做最终裁决,它只是把不一致呈现给人类。
场景三:日期过期
Input: "The sprint ends Friday"
Output: SPRINT: stale_date -- current sprint ends Thursday (updated 2 days ago)
这个场景检测的是一种更微妙的矛盾:声明本身可能在几天前是正确的,但现在已经过期了。
知识图谱中可能存在两条关于 sprint 结束日期的三元组:
Sprint → ends_on → Friday (valid_from: 2026-03-20, valid_to: 2026-03-23)
Sprint → ends_on → Thursday (valid_from: 2026-03-23, valid_to: NULL)
第一条三元组已经被 invalidate() 标记为结束(因为 sprint 的结束日期在两天前被更新了),第二条三元组是当前有效的。
当声明引用"Friday"时,系统用 as_of 参数查询当前有效的 sprint 结束日期,发现当前记录显示的是 Thursday,而不是 Friday。(updated 2 days ago) 这个附加信息来自第一条三元组的 valid_to 日期——它告诉你信息是什么时候变过期的。
这就是 invalidate() 方法(knowledge_graph.py:169-182)在矛盾检测中的价值。它不是在删除错误信息,而是在记录信息的生命周期。旧的事实变成了历史,新的事实取而代之,而系统可以精确地告诉你这个转变发生在什么时候。
实现机制推断
从三个场景中可以归纳出矛盾检测的通用流程:
输入声明
|
v
实体提取 —— 从声明中识别涉及的人、项目、时间、数值
|
v
关系映射 —— 推断声明中隐含的关系类型
|
v
知识图谱查询 —— 用 query_entity() 或 query_relationship() 获取已知事实
|
v
交叉比对 —— 将声明中的断言与已知事实对比
|
v
矛盾报告 —— 如果发现不一致,生成包含矛盾类型和依据的报告
其中,第一步(实体提取)和第二步(关系映射)是自然语言处理任务。从 MemPalace 的整体架构来看,这部分工作大概率由 LLM 完成——要么是用户正在对话的那个 LLM(通过 MCP 工具调用),要么是 MemPalace 内部集成的一个轻量级语言处理模块。
第三步(知识图谱查询)直接使用 KnowledgeGraph 类的查询方法。从第 11 章的分析可知,query_entity() 支持 as_of 时间过滤和方向控制,query_relationship() 支持按关系类型查询——这两个接口足以覆盖上述三种矛盾类型的查询需求。
第四步(交叉比对)是核心判断逻辑。它需要根据矛盾类型执行不同的比对策略:
- 归属冲突:检查同一任务/项目是否被分配给了不同的人。比对条件是同一个 object 实体存在多条不同 subject 的
assigned_to类关系。 - 数值不一致:从时间戳计算动态值(任期、年龄等),与声明中的数值比对。
- 日期过期:查询当前有效的日期类事实,与声明中引用的日期比对。
置信度的角色
知识图谱中每条三元组都有一个 confidence 字段(knowledge_graph.py:72),默认值为 1.0。这个字段在矛盾检测中扮演着重要角色。
当两条事实发生矛盾时,置信度提供了一种优先级判断:如果知识图谱中的事实置信度为 1.0(完全确信),而新声明来自一段随意的对话(可能的置信度较低),那么系统倾向于信任已有事实。反之,如果已有事实的置信度本来就不高,那么矛盾可能意味着新的声明提供了更准确的信息。
置信度不是矛盾检测的决策依据——系统仍然会报告矛盾——但它为矛盾报告提供了上下文。"知识图谱中有一条置信度 0.6 的记录与你的声明矛盾"和"知识图谱中有一条置信度 1.0 的基准事实与你的声明矛盾",这两种情况的严重程度是不同的。
source_closet 的溯源价值
当矛盾被检测到时,source_closet 字段(knowledge_graph.py:74)可以提供溯源能力。更准确地说:如果写入知识图谱时调用方提供了这个可选字段,系统不仅可以告诉你"Maya 被分配到了 auth migration",还可以告诉你这条信息来自哪个 closet——也就是继续追溯到原始的对话记录或文档。当前源码并不会在默认流程里自动补全这条链路。
这种溯源能力是 MemPalace 宫殿结构与知识图谱协同工作的接口体现。知识图谱负责快速的结构化查询("谁被分配到了这个任务"),宫殿结构负责深度的上下文检索("当时的对话是怎么说的")。两者在 schema 里通过 source_closet 字段连接;至于每条事实是否真的带着这根"线",取决于写入时是否提供了它。
矛盾分类
从 README 的三个示例中可以提取出一个矛盾分类体系。注意输出中使用了不同的严重级别标记——归属冲突被标记为红色(高严重度),任期错误和日期过期被标记为黄色(中等严重度)。
这种分级背后的逻辑是:
高严重度(归属冲突):某个人被错误地归属为某件事的负责人。这种错误的后果可能是很严重的——你可能因此在会议上感谢了错误的人,或者把后续任务分配给了错误的人。
中等严重度(数值不一致):任期、年龄等数值声明与记录不符。这种错误通常是无心的近似(记成"2 年"而实际是"3 年"),后果相对有限,但仍然值得纠正。
中等严重度(日期过期):引用了已经更新过的时间信息。这种错误通常是因为说话的人不知道信息已经变了,而不是因为他们记错了。
一个更完整的矛盾分类可能包括:
| 矛盾类型 | 严重度 | 检测方法 |
|---|---|---|
| 归属冲突 | 高 | 同一任务的不同归属者比对 |
| 数值不一致 | 中 | 从时间戳动态计算后比对 |
| 日期过期 | 中 | 当前有效事实与声明比对 |
| 状态矛盾 | 高 | 已结束的事实被引用为进行中 |
| 关系矛盾 | 中 | 不兼容的关系类型同时存在 |
误报与漏报
任何检测系统都面临误报(false positive)和漏报(false negative)的权衡。矛盾检测也不例外。
误报场景
同名不同实体。 如果团队中有两个 "Jordan",一个是设计师,一个是后端工程师。当声明提到"Jordan 完成了 UI 设计"时,系统可能会因为另一个 Jordan 是后端工程师而错误地触发归属冲突。
从 _entity_id() 的实现(knowledge_graph.py:92-93)可以看到,实体 ID 是通过简单的字符串标准化生成的——"jordan" 就是 "jordan",没有消歧机制。这意味着同名实体会被合并为一个节点,从而可能导致虚假的矛盾。
解决方案可能包括在实体注册时使用全名或加上限定词("Jordan Chen" vs "Jordan Kim"),但这要求上游的实体提取足够精确。
语义理解偏差。 "Soren helped with the auth migration" 与 "Soren finished the auth migration" 表达的是不同的关系——"帮忙"不等于"负责"。如果系统把"帮忙"也理解为归属关系,就会产生误报。
这类误报取决于关系映射阶段的精度。如果关系映射过于宽泛(把所有人-任务的关联都当作归属关系),误报率会上升;如果过于严格(只有明确的"负责"、"完成"才算归属关系),漏报率会上升。
时间粒度不匹配。 知识图谱中的 valid_from 使用日期字符串("2026-01-15"),而声明中可能使用更模糊的时间表达("上个月"、"去年年底")。如果这两者之间的转换不够准确,可能导致误报。
漏报场景
知识图谱不完整。 如果某条事实从未被录入知识图谱,系统就无法检测与它相关的矛盾。比如,如果知识图谱中没有记录 Maya 被分配到 auth migration,那么"Soren 完成了 auth migration"就不会触发任何警报——因为系统不知道应该是谁负责的。
这是最根本的漏报来源。矛盾检测只能在已知事实的范围内工作。知识图谱的覆盖度直接决定了矛盾检测的召回率。
隐含矛盾。 有些矛盾不是直接的事实冲突,而是逻辑推断才能发现的。比如,"Kai 上周一直在度假"和"Kai 上周审查了 12 个 PR"——这两条声明在表面上没有共享任何实体关系,但逻辑上是矛盾的(度假时不太可能审查 12 个 PR)。这种推断性矛盾超出了简单的三元组比对的能力范围。
渐变矛盾。 有些事实不是突然变成错误的,而是逐渐偏离现实的。比如,"我们团队有 5 个人"在三个月前是对的,但这三个月里陆续有人加入和离开,现在实际上是 7 个人。如果没有人显式地 invalidate() 旧的团队规模信息并录入新的,知识图谱会继续认为"5 个人"是有效的。
工程策略
面对误报和漏报的权衡,MemPalace 的设计选择倾向于宁可误报,不要漏报。理由很直接:一个误报只会让用户花几秒钟确认"哦,这次没问题";一个漏报可能让一个错误的事实在系统中存活数月,影响所有后续的回答和决策。
从 README 中的输出格式可以看到,矛盾报告附带了完整的判断依据("Maya was assigned, not Soren";"records show 3 years (started 2023-04)";"current sprint ends Thursday (updated 2 days ago)")。这让用户可以快速评估一个矛盾报告是真实的矛盾还是误报,从而降低误报对用户体验的负面影响。
矛盾检测的闭环
矛盾检测不是一个终点,而是一个闭环的起点。当矛盾被检测到后,有三种可能的处理路径:
路径一:修正声明。 用户承认声明有误。"哦对,确实是 Maya 在做 auth migration,不是 Soren。" 知识图谱不需要变化。
路径二:更新知识图谱。 用户确认声明是正确的,知识图谱需要更新。比如,auth migration 的负责人确实从 Maya 变成了 Soren。这时需要调用 invalidate() 结束 Maya 的 assigned_to 关系,然后用 add_triple() 创建 Soren 的新关系。
路径三:标记为需要调查。 用户不确定哪个版本是对的。这种情况下,矛盾本身就是有价值的信息——它标记了知识图谱中的一个不确定区域,提醒用户下次遇到相关话题时需要核实。
三种路径都比"让错误信息悄悄溜过去"要好。矛盾检测的核心价值不在于它的准确率是 90% 还是 99%,而在于它把"AI 可能在犯错"这个事实从隐性变成了显性。
深层设计思考
矛盾检测与时态 KG 的耦合
三种矛盾类型中,有两种(数值不一致、日期过期)直接依赖时态 KG 的能力。如果知识图谱没有 valid_from 和 valid_to 字段,任期就无法从起始日期动态计算,过期的日期就无法与当前有效的日期区分。
归属冲突检测在理论上可以在静态 KG 上实现(只要存储了归属关系),但在实践中,时态 KG 使得检测更加精确——它可以区分"Maya 现在负责 auth migration"和"Maya 曾经负责 auth migration(但已经转交给别人了)"。
这意味着矛盾检测不是一个独立的功能模块,而是时态知识图谱的自然延伸。有了时间维度,矛盾检测几乎是"免费"获得的——你只需要把新的声明与现有事实做时间感知的比对就够了。
矛盾检测的规模边界
当前的实现假设知识图谱的规模是可管理的——几百个实体,几千条三元组。在这个规模下,全量扫描 query_relationship() 的结果是完全可行的。
但如果知识图谱增长到百万级三元组(比如一个大型组织的完整知识库),逐条比对的策略就需要演进。可能的方向包括:为矛盾检测建立专门的索引(比如按实体对索引的归属关系表)、引入增量检测(只对新增三元组做矛盾检查,而不是每次全量比对)、或者使用规则引擎定义矛盾模式而不是硬编码检测逻辑。
不过,对于 MemPalace 的目标场景——个人或小团队的 AI 记忆系统——当前的实现足够了。知识图谱的规模增长速度受限于用户的对话量和事实提取率,几年内不太可能达到需要优化检测策略的量级。
这又是一个工程权衡:为当前的规模设计,而不是为想象中的未来规模过度设计。SQLite 处理几千条三元组的查询用时在毫秒级,矛盾检测的额外开销可以忽略不计。等到真的需要优化的那一天,再优化不迟。
小结
矛盾检测是 MemPalace 时态知识图谱最具实用价值的应用之一。它把 AI 记忆系统从"什么都记住"提升到了"记住,并且在记错的时候告诉你"。
三种矛盾类型——归属冲突、数值不一致、日期过期——各自代表了不同的检测逻辑,但它们共享同一个基础设施:带时间窗口的知识图谱。valid_from 和 valid_to 不仅使得历史查询成为可能,还使得过期检测和动态计算成为可能。
下一章,我们将看到时态知识图谱的另一个应用:时间线叙事。当你需要了解一个项目或一个人的完整历史时,timeline() 方法如何把离散的三元组编织成一个可读的编年史。