从百万字网文到可游玩剧情:我开源了一个多 Agent 叙事引擎

0 阅读9分钟

Github: github.com/ypcypc/What…

希望大佬们能点点 Star 支持一下

起源

我玩 AI Dungeon 的时候,有些时候有一种特别割裂的体验——生成的剧情在几轮之后就开始乱套,主角忘了自己是谁,世界观自相矛盾,明明说好的复仇之路突然变成了种地模拟器。

我当时就在想:AI 的问题不是写不好,而是没有剧情参考。那有没有办法给 AI 一个剧本,一个写好的故事,让它在这个骨架里让玩家自由探索?

我后来发现,网上有的是现成剧本,而且质量极高,就是各种网文。凡人修仙传、斗破苍穹,每一本都是几百万字的精密世界观。如果能把一本网文提取成结构化数据,再让玩家在里面"另辟蹊径"——那不就是我想要的东西吗?

于是我尝试开发了一个由多 Agent 协同叙事引擎, WhatIf.

我给我的项目起名为 WhatIf 就是因为我在写小说的时候总在想,假如我是 xxx, 我会怎么探索这个小说的世界……

WhatIf 项目

WhatIf 做两件事:

第一,把一本小说 .txt 提取成结构化的"世界包"——包含事件序列、角色实体、地点和实体的状态转移。

第二,让玩家在这个世界里推进剧情、做选择、触发偏离。系统用 6 个 Agent 协同维护叙事连贯性,保证你的"另一种可能"不会影响总体的剧情推进。

演示视频

演示视频: www.bilibili.com/video/BV1YA…

演示视频请见上方链接,可以按需通过章节跳转到视频的不同演示的部分。

过程

开发的过程中踩了挺多坑的,我这里将几个比较大的坑给分享一下,

  1. 偏离度检测问题:这个项目的初衷就是希望可以给玩家足够的自由度去改写剧情,同时剧情还需要按照原来的节奏发展,那么必须要处理的问题就是,当玩家的输入和剧情的走向产生冲突时,系统应该如何在自由度和剧情忠实度之间取得一个平衡。

    • 一开始我采用的是 Deviation Score, 也就是偏离度计分系统,当玩家的输入偏离了原来的剧情的时候,交由 LLM 进行打分,然后将其偏离度的分数累加到全局,这是一个非常直觉性的尝试,但是实际测试下来,我发现效果极差,主要原因在于在当前叙事引擎无法获得全局视野的情况下, LLM 根本无法准确判定当前事件的偏差对于未来影响有多大,而且也极其难以量化,到最后往往会出现非常不合理的数据导致叙事风格和剧情极度偏离;
    • 之后我尝试了 Event DAG 的方法,也就是对一个一个事件建立彼此间的依赖关系,当发现前置事件的目标和后续事件存在强依赖关系,即,到达当前叙事节点 B 需要经过叙事节点 A 的时候,建立一条由 A 指向 B 的边。这个系统开始运行的不错,但是在网文的测试下结果极其糟糕,因为我发现网文的结构非常简单,往往是一条线性叙事,所以到最后事件依赖图都会退化成一条链,也就是玩家如果想体验事件 N 必须先经过 事件 N - 1, 这样子就毫无自由度可言了。
    • 经过几次尝试之后,我最终决定用必要实体的形式来约束故事叙事,这个灵感来源于论文 Morphology of the Folktale, Propp, 1928Structural Semantics, Greimas, 1966, 这两篇论文中提到的 具名实体功能性实体的概念让我觉得正是约束叙事的关键。如果不懂具名实体和功能性实体的概念的话,没关系,我们来看这个例子,英雄张伟坐着一只巨大的猎隼到达了 A 市,然后猎隼飞走了, 那么在这里,张伟是具名实体,但是, 猎隼 是功能性实体。判断的依据在于,张伟这个人名将会主导后续的剧情开展,若将名字更换为别的名字会造成后续剧情的断裂,但是猎隼不是,猎隼只是到达 A 市的手段,假如我们将猎隼换成其他载具依然可行吗?答案是依然可行的,即,猎隼 的存在只服务于将 张伟 这个具名实体送达至某地这个功能。在有了具名实体功能性实体的想法后,我意识到, LLM 特别擅长把控不同功能性实体的叙事节奏,因为替换功能性实体只需要处理叙事细节,但是 LLM 不善于把控不同具名实体的叙事节奏,因为 LLM 对于剧情的把控能力很弱,而牵扯到具名实体的偏离往往涉及到剧情结构的改变。在这个思路的基础上,我们将玩家产生的偏离分为细节性偏离结构性偏离,所谓细节性偏离只是影响了功能性实体, 而 结构性偏离 则是影响了 具名实体 我先通过预处理,将小说的原文通过语义切分成不同的事件,接着识别出事件中的具名实体,那么在叙事的时候,我们只需要时刻检查由玩家产生的偏离是否影响到了接下来的事件的具名实体即可判断玩家的选择是否产生了结构性偏离。提取具名实体还有一个好处,就是我们可以度量当前事件的自由度,具名实体在一个事件中占总实体的比例越小,那么该事件的自由度就越高。 WhatIf 的目标是在不影响剧情的情况下给玩家最高的自由度去探索剧情,所以在这个想法下,给予最高的自由度这个目标可以认为是一个寻找当前事件最小具名实体集合的贪心算法。
  2. 如何处理结构性偏离:在有了一套检测偏离的逻辑之后,接下来就是处理 结构性偏离 的问题,即,当玩家的输入和当前的具名实体产生了冲突时,应该如何处理?

    • 我最初的想法就是强行纠正玩家的输入,比如说,当玩家的输入触发了结构性偏离的时候,直接触发游戏失败,或者直接告诉玩家不能这么做。但是当做好的时候,我给我的几个朋友体验了一下,他们都认为体感较差,因为玩家有一种逆反心理,你越是不让我这么做,我越是要这么做,最糟糕的点,越是重要的实体越能激发玩家的逆反心理。举一个例子,在凡人修仙传中,原文中主角韩立去七玄门是父亲的决定,但是玩家到这一步基本都会尝试输入 “你算老几,你让我去我就去?” 这种回应,甚至于直接拒绝去七玄门的提议在家种地,那么对于这种情况,给予玩家正反馈是需要的,如果一直拒绝或者触发游戏失败会给玩家很强的挫败感。
    • 在显式拒绝这一条路失败后,我在思考,如何能让玩家无感地改变他们的选择,也就是说,玩家不想去七玄门时,我们可以怎么样在玩家不经意间“微调”玩家的选择,让玩家的选择不产生结构性地冲突。我在这里想到了意图拆解的想法。该想法是,每一个输入都可以被拆分成若干个子意图,比如说,玩家的输入是你算老几,七玄门有啥好的?我才不去七玄门, 那么该意图可以被拆分为以下意图, 1. 玩家抗拒父亲, 2. 玩家不去七玄门, 3. 玩家抗拒七玄门, 其中只有子意图 2 是和具名实体 七玄门 产生明显冲突的,那么我们只需要改变玩家的子意图 2, 保留其他的子意图就可以在玩家感知最小的情况下改写玩家的意图,在这个例子中,玩家的意图可能会被改写为 玩家抗拒父亲的指令,而且玩家对去七玄门有明显的抗拒情绪, 接下来交给 LLM 改写就可以了。所以偏离处理这一块,其实还是贪心的思路,假设玩家的意图集合为 InIn, 如何找到最小的集合 IniIn_i 来改写才可以产生结构性冲突。
  3. 如何处理细节性偏离:解决完结构性偏离之后,我本以为 LLM 可以自动处理细节性偏离,因为 LLM 对于细节处理最擅长了,然而我发现在多数情况下, LLM 对于细节的处理把控不是特别到位,容易出现叙事漂移的现象,有些时候甚至会忽略处理好的细节,于是思考如何给 LLM 制定一套处理细节偏离的规范也很重要。

    • 最近状态替代:这个思路就是让 LLM 不要没有方向地去处理细节性偏离,而是需要找到和原文最相似的状态来处理细节性偏离。还是以七玄门那个例子为例,原文中韩立的父亲和韩立都答应答应去七玄门的,但是玩家的意图是 抗拒父亲,且抗拒七玄门, 那么原文中的答应需要被改写为 争执 然后添加一些细节。这一个规范可以有效地防止 LLM 对于细节把控出问题的情况,在没有这个规则之前, LLM 甚至还写过 父子俩大打出手 这种奇葩的场景,但是添加了这个规范之后, LLM 对于细节的把控明显改善了非常多。

局限

当前版本为 Alpha 版本。 Web 端的存档加载还没完全打通,因为 Web 端是纯 Claude Code 写的, Web 端的提取 API 也还是占位状态。核心的叙事引擎和 CLI 流程是完整的,但离"开箱即用"还有距离。

一个人的力量是有限的,如果您看了本文有兴趣的话,欢迎来体验 WhatIf 或者提 issue,也欢迎一起来交流技术细节。

结尾

如果你也玩过网文,玩过 Galgame,玩过 AI Dungeon,然后觉得"总差点什么"——WhatIf 就是我给自己那个问题的答案。

GitHub:github.com/ypcypc/What…

欢迎 Star, 提 Issue, 交流技术,或者纯闲聊。