叙事推理的目标不是单纯判断一句话是否正确,而是理解多个事件之间如何通过时间、因果和人物动机串成一条可信的链条。普通语言模型在续写故事时,往往只依赖局部上文,很容易出现前面角色已经掌握关键信息、后面却表现得毫不知情的情况。推理模型的做法是先抽取出故事中的事件、参与者和关系,再在结构层面上做推演,因此对长篇叙事的前后一致性更敏感。

把故事结构转成事件图
要让模型理解故事结构,第一步通常是把线性文本转换为结构化的事件图。故事语法理论认为,一个完整叙事可以由背景、目标、行动、结果等情节单元组成,而这些单元之间存在因果、时序、转折、解释等关系。事件图中的节点可以是一个动作、一个状态变化或一个关键对话,边则用来描述谁导致了谁、谁先于谁发生。
这种表示方式的好处是,它把隐含在自然语言中的逻辑关系显式化了。例如主角收到威胁信后决定离开城市,文本里可能隔着大段描写,但事件图会直接把收到威胁信和离开城市用一条因果边连起来。模型后续做推理时,不再需要从冗长文本中反复检索,只需要在图上沿着边做多跳传播。下面是一个用 Python 字典构建简化事件图的例子,实际系统中可以替换成 networkx 或图数据库。
# 构建简化故事事件图
nodes = [
{"id": 1, "event": "主角收到威胁信", "agent": "主角"},
{"id": 2, "event": "主角决定离开城市", "agent": "主角"},
{"id": 3, "event": "朋友劝主角留下", "agent": "朋友"},
{"id": 4, "event": "主角发现朋友隐瞒了真相", "agent": "主角"},
]
edges = [
(1, 2, "causes"),
(3, 2, "opposes"),
(4, 2, "explains"),
(4, 3, "conflicts"),
]
def get_neighbors(event_id, relation=None):
result = []
for src, tgt, rel in edges:
if src == event_id:
if relation is None or rel == relation:
result.append((tgt, rel))
elif tgt == event_id:
if relation is None or rel == relation:
result.append((src, rel))
return result
print(get_neighbors(2))
实际应用中,事件抽取本身就是一个难点。模型需要识别同一个角色的指代、判断动词是否构成情节推进,还要处理省略和隐含信息。不少系统会先使用命名实体识别和语义角色标注,再训练一个关系分类器来标注因果与时序边。抽取质量直接决定后续推理的上限,因此通常会配合人工标注数据做微调。
因果链推断与约束传播
有了事件图之后,推理模型可以沿着因果链做多跳推断。比如想知道主角为什么在第三章突然返回城市,模型需要在事件图上追溯返回城市这一事件的前置事件,再检查这些前置事件是否仍然有效。这个过程很像约束满足问题:每个人物的行动都必须有目标支撑,每个目标至少要有一个发起事件,冲突事件不能同时作为同一行动的直接原因。
约束传播的任务是把已知事实作为初始约束,在图上迭代更新相邻节点的合法性。如果某个事件的原因是另一个已经被否定的事件,那么这条因果边就会被剪掉。推理模型可以在这个过程中引入置信度,不直接删除低概率边,而是降低其权重,让后续判断更加平滑。下面这段简化代码演示了如何检查人物动机一致性:
# 检查每个人物的行动是否都有目标支撑
character_goals = {
"主角": ["保护家人", "查明真相"],
"朋友": ["隐藏秘密", "阻止主角离开"],
}
actions = [
{"agent": "主角", "action": "离开城市", "supports": "保护家人"},
{"agent": "朋友", "action": "劝主角留下", "supports": "阻止主角离开"},
]
def check_motivation(actions, goals):
violations = []
for act in actions:
agent = act["agent"]
support = act["supports"]
if support not in goals.get(agent, []):
violations.append(act)
return violations
print(check_motivation(actions, character_goals))
这种显式的目标支撑检查,在开放文本生成中常常被忽略。普通生成模型可能写出朋友一边想阻止主角离开、一边又主动提供离开路线的情节,因为它没有把人物目标当作硬约束。推理模型则可以把目标库作为外部知识注入,在解码每一步时增加一个逻辑合理性评分,从而过滤掉与当前目标集合冲突的续写候选。
情节发展预测与反事实推理
预测情节发展是叙事推理的另一个核心任务。给定故事开头和中间事件,模型需要判断下一步最可能发生什么,以及哪些发展虽然可能但不够合理。简单做法是在事件图上做链接预测,给候选事件与已有事件之间的因果和时序关系打分。更严格的做法是让模型生成一个候选事件,再验证它是否与人物目标、已有事实和常识约束一致。
反事实推理进一步要求模型回答如果某个事件没有发生会怎样。这需要临时修改事件图,把目标事件及其下游影响暂时屏蔽,然后观察因果链如何重新闭合。假如删除主角收到威胁信这一节点,离开城市就失去了直接原因,模型应该判断该行动要么不会发生,要么需要从其他事件借入新的支撑。下面是一个反事实剪枝的示意:
# 反事实移除一个事件后,检查哪些行动失去因果支撑
def remove_event(event_id, edges):
removed_edges = [e for e in edges if e[0] == event_id or e[1] == event_id]
remaining_edges = [e for e in edges if e[0] != event_id and e[1] != event_id]
return remaining_edges, removed_edges
remaining, removed = remove_event(1, edges)
print("被移除的因果边:", removed)
print("剩余边:", remaining)
反事实推理对评估模型是否真正理解故事逻辑很有价值。一个模型如果只是因为训练数据中威胁信和离开经常共现而给出预测,那么在反事实删除威胁信后,它可能仍然坚持预测离开。真正理解因果结构的模型会调整预测,而只会做表面共现匹配的模型则不会。这也是推理模型与单纯模式识别模型的重要区别。
叙事逻辑一致性的评估与训练
评估叙事推理能力不能只看文本流畅度。一个好的故事续写可能在语言上通顺,但在人物动机、时间线和事实细节上充满矛盾。常用的评估方式包括自动指标和人工评估。自动指标可以计算生成情节与参考情节之间的事件图编辑距离,或者检查生成文本中是否包含与已知事实冲突的陈述。人工评估则更关注整体连贯性、动机合理性和转折是否自然。
在训练层面,让模型学会做过程监督比只给最终答案更有效。可以把一次完整推理拆成事件抽取、关系判断、因果链构建、一致性检查等多个步骤,每一步都给出中间标签。这样模型不仅知道哪个故事合理,还知道为什么合理。另一个有效策略是构造负样本,让模型在对比中学习。比如保留同一个故事开头,但人为改变其中一个事件,使后续情节出现矛盾,然后训练模型识别矛盾位置。
推理模型在实际落地时,往往会与生成模型配合使用。生成模型负责产出候选情节,推理模型负责对候选情节做逻辑验证和排序。这种分工可以显著提升长故事的连贯性,因为生成模型可以继续发挥语言流畅性的优势,而推理模型补上了结构化约束的短板。随着过程监督和符号约束技术的成熟,叙事推理模型在自动写作、游戏剧情设计、交互式小说等场景中会越来越实用。
把故事结构显式地建模成事件图,并在此基础上做因果推断和一致性检查,是当前提升叙事推理能力的一条可行路径。模型不再只是记忆故事片段,而是能够推演情节之间的逻辑关系。未来更值得期待的是让模型从海量非结构化文本中自动构建更完整的叙事知识库,并在推理时动态融合常识与人物目标,从而真正理解一个好故事为什么能够成立。