原文:
towardsdatascience.com/how-i-built-an-llm-based-game-from-scratch-86ac55ec7a10
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e5c7a4111b7b1ddc1127863de5ba81a1.png
由 Dalle 3 生成的 LLM 游戏主页
引言
几个月前,我和一个朋友就大型语言模型(LLM)在游戏设计中的应用进行了辩论。
我的朋友认为,大型语言模型(LLM)在游戏中使用时太不可预测,不应持续用于“实时机制”,而我认为如果通过正确的框架进行控制,它们可以提出创新体验。
这场讨论促使我开始了一个新的副项目,这个项目已经成为我最自豪的项目。
这一系列文章旨在提供一个高级概述,涵盖了我过去几个月所做的工作以及我至今仍需面对的挑战。
下面是一个展示截至 2024 年 2 月的游戏的简短视频。
cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FqCoJuEt2OsU%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DqCoJuEt2OsU&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FqCoJuEt2OsU%2Fhqdefault.jpg&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&schema=youtube
在这篇文章中,我们关注游戏的核心概念,并详细介绍了我想象出的架构,以解决我面临的一些最大问题,例如信息泄露和幻觉。
灵感和概念
我并不是第一个尝试利用 AI 来设计游戏的人。自从 GPT 发布以来,我们可以看到围绕 AI 的新体验的激增。
在游戏方面,我个人遇到了AI Dungeon,这是一个由 AI 驱动的基于文本的游戏,这成为了我自己概念的灵感来源。
我认为这个想法非常有趣,尽管我认为它有一个主要缺陷:冒险是在路上即兴创作的,这可以提供美好且意外的体验,但正如所有即兴创作一样,生成的故事缺乏一致性和复杂性。
当我阅读一本书或玩电子游戏时,我喜欢冒险有一个清晰的开始和结束,以及一个连贯的事件序列,从开始到结束。这是我想要融入我对基于 AI 的游戏的看法,这个想法成为我提出不同事物的最主要动机。
我基于 AI 的游戏背后的愿景
在开始编码或原型设计之前,我花了一些时间想象我的理想基于 AI 的游戏会是什么样子。像 AI Dungeon 一样,它将是一个基于文本的游戏,用户可以写消息并获得回应。
从角色扮演游戏(RPG)中汲取灵感,一个 AI 代理将作为游戏主持人,将玩家的信息转换成游戏内的动作。游戏主持人将执行事先设计好的故事。
玩家还可以与非玩家角色(NPC)互动,这些角色由其他 AI 代理控制,每个角色都有自己独特的个性和对环境的了解。宇宙将会有规则,保持连贯,玩家的任务将或多或少是预定的。想法是复制真实的 RPG,同时将玩家交互委托给 AI 代理。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2effc4b838b59ea870915c9c3fcbc36e.png
游戏引擎基本原理的基本示意图,作者插画
成功标准
在开始证明概念之前,我定义了一系列成功标准,以备在准备游戏 v0 版本时参考。这些标准将帮助验证我的概念是否合理,以及它是否可以在未来进一步发展。这些标准指导我定义了游戏及其用户界面的一些最重要的功能。
主要标准是:
叙述:每个冒险都应该有一个明确的开始和一个明确的结束。
因果关系:为了到达终点,玩家需要执行由因果关系连接的动作(除非先执行了另一个动作,否则无法执行某个动作)。
非线性:虽然一些动作由因果关系事件链接着,但其他动作则不是,这允许玩家以不同的顺序执行一些动作,同时保持故事连贯。
交互:玩家可以与 NPC 进行简单的讨论,或者通过游戏主持人与环境互动。
自由感:虽然冒险有开始和结束,但玩家应该感觉沉浸在游戏宇宙中。
游戏主题
一旦我有了成功标准清单,我需要创建证明概念中心的故事。这个故事必须简单,避免不必要的复杂性,这样我就可以专注于设计验证我的观点的功能。
经过几次迭代后,我决定采用简单的基于文本的逃脱游戏。玩家将被锁在一个神秘的房间里,必须通过与环境的各种元素互动找到出路。在互动中:找到钥匙、打开舱门、观察周围寻找线索、与隐藏元素互动。可以召唤一个幽灵来在玩家的任务中提供线索。游戏在玩家成功找到出口时结束。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3b60c89b169cb43c4f76c377581bb1c7.png
神秘房间的第一幅艺术作品,使用 Stable Diffusion 完成,作者插画
选择这种设计是因为它将允许我测试我想验证的大部分要点,特别是在非线性方面。
游戏中 LLM 的固有问题
与所有一切都是确定性的经典游戏不同,基于 LLM 的游戏以其不可预测性挑战我们。与经典视频游戏不同,你无法(也不希望)控制 100%的输出,因为这正是使这项技术有趣的原因。
尽管如此,为了为最终用户提供良好的体验,我们希望尽可能控制这种不可预测性,将其限制在狭窄的范围内(在我们的案例中,是故事及其事件链)。
幻觉
当代理对用户的查询回答不准确或开始编造不存在的信息时,就会发生幻觉。这有两个原因可能造成问题:首先,它可能会误导玩家。如果玩家问,“桌子上有什么?”而 AI 回答“一把钥匙”,玩家会尝试使用这把钥匙。但如果钥匙不存在,这将会产生挫败感。
这些幻觉也可能导致故事偏离,尤其是如果上下文被 AI 代理的答案自我修改。例如,如果你在制定下一个答案之前将玩家和 AI 之间的对话历史(包含幻觉)传递给代理,它将使用这些信息并在“幻觉循环”中自我强化,从而引导玩家走向错误的方向。
剧透
剧透是指 LLM 代理在应该向玩家透露信息之前透露信息。如果信息以某种方式在错误的时间对代理可用,存在泄露的风险,玩家可以获取他们不应该知道的信息。
这可能发生在静态预提示的情况下。如果你将所有故事作为上下文传递给代理以供参考,信息将随时可用,并且可以轻易泄露。这也可能发生在使用简单的检索增强生成(RAG)的动态预提示中。玩家的消息可能会查询远程向量数据库,意外地泄露或透露应该在之后才透露的秘密或信息。
定制冒险与鲁棒性之间的权衡
解决上述问题花了我相当长的时间,最后我决定牺牲一点“定制体验”(代理完美适应用户的提示)以换取更多的“鲁棒性”(通过一个一致的故事最小化幻觉和剧透,使其能够从 A 到 Z 流畅过渡)。
在经典的“讲故事游戏”中,用户通过从预定义的列表中选择一个选项来从一个场景移动到另一个场景或从对话移动到对话,每个选项都会导致下一个确定性的对话或行动。这种设置是鲁棒的,因为每个行动都会确定性地导致下一个,但定制体验是最小的。
另一方面,像 AI Dungeon 这样的游戏具有高度的可定制性,没有两次冒险看起来是相同的。缺点是故事无法控制,可能会很快变得不一致或无聊。
通过结合两种方法,我提出了一些新的想法,这将提供足够的鲁棒性和定制性,从而变得有趣。这个想法是通过因果图引导玩家在他们的冒险中,根据玩家已经执行的动作,将上下文注入到智能体中。玩家提出一个交互,有时,这种交互会在游戏的后端解锁新的上下文,以及玩家可以访问的新交互。
下面的插图显示了游戏中因果图的一个示例。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7c5bd62853a9f9d47ebe964ea87f03ce.png
简单因果图的示例,作者插图
在这个例子中,绿色动作(“打破罐子”、“使用钥匙打开舱门”、“玩家按下按钮”和“检查画作”)已经被玩家执行,向玩家揭示了新的上下文(画作和舱门后面的秘密按钮)。
橙色圆圈显示了玩家在此时此刻可以使用的“影响性”动作,但他尚未触发。
最后,“玩家逃脱”尚未可用,因为玩家没有激活两个按钮,并且只要剩余的动作未完成,这个动作就不会在叙述者的上下文中传递。
在因果图中添加了一个占位符,用于任何其他动作,以便游戏主持人向玩家指示他的动作是不相关的。
在游戏上下文中,这个因果图完全由一个由分类智能体辅助的确定性后端控制和修改,该分类智能体确定玩家的消息是否匹配一个“影响性”动作。
实际中的因果图
在本节中,我们将更深入地探讨如何为游戏构建自定义的因果图。
利用“函数”功能
函数功能允许一个 LLM 调用自定义函数。这最初由 OpenAI 模型在 2023 年实现,对于创建与后端/游戏引擎的交互特别有用。它通过向模型传递函数模式来实现,然后模型会响应它想要调用的函数和参数以获取更多上下文。后端可以处理函数调用,检索新信息,并将其以新的上下文传递给另一个 AI 智能体。
我正在使用这个机制来构建一个自定义的智能体,它充当一个“影响性”分类器,并将玩家提示和因果图中的可用动作关联起来。
简单的因果图架构
让我们看看这个在实际中是如何工作的一个简单案例。
为了使我们的因果图概念发挥作用,我们需要一些东西:
游戏文件
游戏状态:一个存储玩家已经完成的动作的文件。这将由后端使用,以构建与下一个动作的动态上下文,过滤掉已经完成/尚未可访问的内容。
因果图:一个包含编码完整冒险的节点列表的文件。每个节点代表故事中的一个有影响力的动作,具有 action_id、描述、动作的后果以及激活节点所需的先前 action_id 列表。
代理
有影响力的代理:此代理充当分类器,输入一个包含在特定时间点可用的预过滤动作列表的自定义预提示,以及玩家的消息。它检查玩家的消息是否匹配重要动作,并利用功能特性将相应的 action_id 传递给后端以检索额外上下文。
游戏大师代理:此代理对玩家可见。它基于后端构建的自定义上下文工作,反映有影响力动作(如果有)的后果,并根据玩家的消息定制其答案。如果动作不是有影响力的,则使用标准通用预提示解释玩家的动作在当前上下文中没有意义。
后端函数
filter_game_state:此函数接受当前游戏状态作为输入,过滤因果图以构建一个“上下文提示”,包含在特定时间点对玩家可用的所有 action_id 及其描述。过滤掉已执行且不可访问的动作,防止泄露。
get_action_consequence:如果有影响力的代理识别到玩家的提示与可用的有影响力动作匹配,它将调用 get_action_consequence,根据提供的 action_id 查询因果图,并添加更多关于玩家动作影响的上下文。
update_game_state:为了跟踪游戏当前状态,此函数使用玩家触发的新 action_id 更新游戏状态。这刷新了下一次交互中可用的动作集。
下面的图表展示了基于链式代理和后端调用来生成答案的完整过程。
https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/333330a3518286fd3990583960100311.png
AI 代理生成答案的详细视图,作者插图
这是一个非常基本的架构,可以通过添加更多中间步骤进行扩展,例如,添加新角色、新环境等。
我们将在另一篇文章中详细审查并使用 Python 代码实现此架构。
如何通过示例说明其工作原理
让我们通过上面的因果图和游戏状态来举一个例子。
因果图中的一个节点示例
因果图中的每个节点代表一个有影响力的动作。它包含与我们的自定义框架有效接口所需的所有信息。这包括:
一个独特的 action_id 用于识别节点。此 action_id 可用于跟踪玩家的进度。
“分类器代理”使用的动作描述,以确定玩家的消息是否与该动作匹配。
如果触发这个特定动作,将传递给“游戏大师代理”的动作后果。
需要激活此节点的 action_ids 列表,由后端用于过滤玩家在任何给定时间可用的动作。
可选,一个名字以帮助游戏设计师识别每个节点。
让我们看看上面展示的因果图中“阅读桌子上的笔记”节点。这个节点通常会看起来像这样:
#Example of causal graph[{...},{"id":4,"description":"The player reads the note on the table","consequence":"As the player read the note,the air become coldeandsuddenly the lights switch off.After a moment everythingisback to normal,but thereisnow a ghostly figure standinginthe middle of the room""needs":[0]},{...},]注意:Action_id 0 代表游戏的开始,并在游戏开始时默认激活。
这种“描述”和“后果”之间的分离非常重要。它确保没有剧透会泄露给玩家,因为“后果”消息永远不会添加到任何上下文提示中,除非触发动作“阅读桌子上的笔记”。
游戏状态
游戏状态仅包含玩家已经完成的动作列表。在上述示例中,它将是一个包含已触发的动作 id 的列表:
#Game State[0,1,2,3,5]三种玩家消息的示例
让我们看看当玩家尝试与游戏大师进行交互时的完整流程。在游戏的这个阶段,filter_game_state函数将始终返回同一块预提示,看起来像这样:
-action4:"The player reads the note on the table"-action6:"The player pushes the button found behind the painting"示例 1: “跳上桌子”
在这种情况下,消息将与从filter_game_state传递的动作列表一起传递给“分类代理”。
#Classifier agent#PrepromptYou are the classifier agent,your roleisto determineifthe actionfromthe user matches on of the actionfromthelistbelow.If no matchingisfound,call List of actions:-action4:"The player reads the note on the table"-action6:"The player pushes the button found behind the painting"If no matchesisfoundinthelistabove,uses"action X: no match found"#User messageJump on the table在这种情况下,模型将返回“动作 X”,因为没有找到匹配项。然后后端将处理这个特殊情况,通过传递给“游戏大师代理”的通用消息来处理:
# Game Master Agent#PrepromptThe player has made an action without impactinthe game.You must explains to the player that his action has no consequence.#User messageJump on the table示例 2: “我检查桌子上的笔记”
在这种情况下,“分类代理”将玩家的消息与动作 4 匹配。id 4 将被添加到游戏状态中,并将“后果”传递给“游戏大师代理”。这次,提示将看起来像这样:
# Game Master Agent#PrepromptThe player has made an impactful actioninthe game.Thisisthe consequence of its action:"As the player read the note,the air become coldeandsuddenly the lights switch off.After a moment everythingisback to normal,but thereisnow a ghostly figure standinginthe middle of the room"#User messageI check the note on the table这次,游戏大师将正确地以玩家的动作后果进行回应,并将游戏状态更新以进一步探索因果图。
示例 3: “房间里有没有鬼?”
这个例子强调了动作“描述”与其“后果”之间分离的重要性。
多亏了这种清晰的分离和中间的后端层,"游戏大师代理"在玩家实际阅读笔记之前,永远不会收到房间里有鬼的信息。
#Classifier agent#PrepromptYou are the classifier agent,your roleisto determineifthe actionfromthe user matches on of the actionfromthelistbelow.If no matchingisfound,call List of actions:-action4:"The player reads the note on the table"-action6:"The player pushes the button found behind the painting"If no matchesisfoundinthelistabove,uses"action X: no match found"#User messageIs there a ghostinthe room ?如果再次落在“动作 X”上,将导致“游戏大师代理”的标准答案
# Game Master Agent#PrepromptThe player has made an action without impactinthe game.You must explains to the player that his action has no consequence.#User messageIs there a ghostinthe room ?结论及其他考虑因素
在这篇文章中,我们讨论了我基于 LLM 的游戏的基本想法,该游戏旨在为玩家提供更大的自由度,同时仍然提供令人兴奋、受控的场景。我们探讨了与 LLM 合作时的挑战,特别是它们不可预测的响应,可能会使玩家偏离主要场景或无意中破坏体验。
我们专注于利用形式为因果图的确定性后端,以确保 LLMs 为玩家提供一致且有方向的答案。我们详细描述了该框架的简化版本,该版本结合了大多数 LLM 提供商的“函数”功能、两个专业代理以及包含整个因果图和玩家已触发的所有事件的两个文件。
这个因果图通过控制在生成玩家响应之前发送给代理的信息,有效地解决了剧透问题。至于幻觉,这个框架在引导故事进展(从动作 ID 到动作 ID)方面有很大帮助,但在这个阶段,模型偶尔仍可能提供错误答案,这可能会误导玩家。
在本系列的下一篇文章中,我们将仔细研究预提示设计以及进一步控制幻觉的策略。我们还将讨论成本和准确率之间的权衡,探讨如何通过更小(且更具成本效益)的模型实现更好的结果。此外,我们还将回顾在游戏背景下正确评估整个管道性能的策略。
感谢阅读!