跨文档同名实体消歧:替换原文之前先保护字符偏移

发布时间:2026/10/25 17:17:53
跨文档同名实体消歧:替换原文之前先保护字符偏移

同一份长文档里出现两个“张伟”,可能是同一个人的不同描述,也可能是两家公司的人。给它们加命名空间有助于检索,但在讨论聚类模型之前,有一个更朴素的错误值得先排除:替换后的字符串还能不能对上原始位置?

假设第一处姓名被扩展成“张伟【甲公司】”,后面的字符位置都会移动。只在每个实体簇里倒序替换还不够;不同簇之间也共享同一条字符串,必须把全部编辑按原始位置统一处理。

编辑计划先于字符串修改

把消歧器的输出表示为起点、终点、替代文本、实体ID,先检查范围和重叠,再从右向左应用。原文保持不变,修改后的文本另行保存。这样语义模型即使产生坏位置,执行层也能拒绝,而不是悄悄污染其他实体。

from dataclasses import dataclass @dataclass(frozen=True) class Edit: start: int end: int replacement: str def apply_edits(text, edits): ordered = sorted(edits, key=lambda e: (e.start, e.end)) previous_end = 0 for e in ordered: if not (0 <= e.start < e.end <= len(text)): raise ValueError('invalid span') if e.start < previous_end: raise ValueError('overlapping edits') previous_end = e.end result = text for e in reversed(ordered): result = result[:e.start] + e.replacement + result[e.end:] return result original = '张伟审阅,李明复核,张伟签字。' edits = [Edit(0, 2, '张伟【甲】'), Edit(11, 13, '张伟【乙】')] # Obtain the second span from the untouched original, never from edited text. pos = original.rindex('张伟') edits[1] = Edit(pos, pos + 2, '张伟【乙】') result = apply_edits(original, edits) assert result == '张伟【甲】审阅,李明复核,张伟【乙】签字。' assert original == '张伟审阅,李明复核,张伟签字。' try: apply_edits(original, [Edit(0, 3, 'x'), Edit(2, 4, 'y')]) except ValueError: pass else: raise AssertionError('overlap was accepted') print('global reverse edits, unchanged source, overlap rejection: PASS')

别让命名空间制造新的事实

图里的ID只表示区分,不证明两次出现一定属于不同的人。真正的消歧还需要文档身份、段落、所属组织和时间。相似度阈值只能生成候选;证据不足时,保留“尚未确认是否同人”比强行拆成两个实体可靠。

10 月21日的Kimi Code更新把插件Skill调用改成限定名,避免同名技能互相遮蔽。这是另一个层面的命名空间问题,不能当作文本消歧算法,但它说明名字相同不意味着身份相同。官方更新

这个例子已在本地验证跨位置扩长、原文不变和重叠拒绝。它没有运行NER、嵌入聚类或共指模型,也没有得出消歧准确率。若要测效果,应该分别标注“同名不同人”和“异名同一人”,并检查重写是否引入错误归属。只统计字符串替换成功,不等于语义消歧成功。