DLAI-人工智能驱动的游戏笔记-全-

DLAI 人工智能驱动的游戏笔记(全)

001:课程介绍 🎮

在本课程中,我们将学习如何从零开始构建一个由AI驱动的文字冒险游戏。你将掌握使用大语言模型(LLM)进行游戏开发的核心技术,包括世界构建、游戏机制设计以及生产部署,最终完成一个可以分享给朋友的可玩项目。

课程由Together AI的资深产品经理Nikki Burrkner和AI Dungeon(由Latitude公司开发)的CEO兼联合创始人Nick Walton共同讲授。Nikki在Together AI的产品开发中发挥了关键作用,而Nick则从一次黑客马拉松中创建了最初的AI Dungeon,并专注于开发只有借助AI才能实现的新型游戏体验。


课程内容概述 📚

上一段我们介绍了讲师和课程目标,接下来我们详细了解一下你将学习的具体内容。

以下是本课程涵盖的核心模块:

  1. 世界创建:你将学习使用提示工程分层内容生成技术,根据你的提示指令创造一个完整的世界,包括王国、城镇和角色,让你的游戏体验栩栩如生。
  2. 构建交互式AI应用:你将学习创建一个AI角色扮演游戏,并将你构建的世界整合到一个有趣的游戏体验中。
  3. 实现复杂游戏机制:你将学习利用AI将文本数据解析为结构化JSON输出。这项技术能让你构建物品栏检测系统等机制,以追踪和利用结构化的游戏状态。
  4. 内容安全与合规:你将学习如何基于指定策略,为AI游戏内容生成强制执行内容安全和合规性。同时,你将使用LlamaGuard为自己的游戏构建自定义策略。

掌握这些技术后,你将能够从游戏开始,构建更具创造性和创新性的AI应用。


致谢与课程启动 🙏

本课程的完成离不开许多人的共同努力。在此感谢来自Together AI和AI Dungeon的Abbi Vanga、Vigam、Hasan El Megari、Jamie theger、Vi Paul Vt Prakash,以及来自DeepLearning.AI的Dila Ezeine。

第一节课将围绕世界创建展开,你将运用提示工程和分层生成技术来创造游戏世界。

你准备好了吗?让我们进入下一个视频,正式开始学习。

002:分层内容生成 🏗️

在本节课中,我们将学习如何使用分层内容生成技术,根据你的提示指令创建一个完整的世界。你将决定想要创建的世界类型,然后分层生成王国、城镇和角色,最终构建一个各部分紧密相连的完整世界。

什么是分层内容生成?🤔

上一节我们介绍了课程目标,本节中我们来看看核心方法。

分层内容生成是一种利用AI创建大量内容的方法,它能确保内容具有高度的一致性和连贯性。你首先需要生成一个关于要创建内容类型的高级概述,然后逐步深入到更具体、更细化的层面。关键在于,生成较低层级的内容时,它会知晓较高层级已生成的内容。因此,第一层的信息会输入到第二层,第二层再输入到第三层,依此类推。

例如,如果你想创建一本教科书,你可以从描述这本书的主题开始。然后生成章节标题,接着是各级标题和副标题,最后生成教科书的所有段落文本。最终,你将得到一本从宏观到微观都围绕同一主题、内容连贯的教科书。同样的方法也适用于创作剧本或商业计划。

在我们的案例中,我们将为一个奇幻世界应用此方法。我们将从生成世界描述开始,然后是王国、城镇,你还可以生成这些城镇中的地点和角色。通过这种方式,你能够创建出一整套内容连贯的集合。你只需在高层级给出简单的方向(比如你想要什么样的世界或书籍),最终就能得到一套详尽完整的内容,供你随意使用。

为何使用分层内容生成?✨

以下是分层内容生成带来的几个显著优势:

  • 以少量输入创造大量内容:你无需向AI详细描述世界中的一切,只需说“我想要一个这样的世界”,然后AI就会遵循这个指令创建所有元素。
  • 支持人工介入引导方向:例如,你可以先创建世界,然后说“我喜欢这个王国,不喜欢那个主题”。在你告知AI你的偏好后,再让它生成下一组内容。对于教科书,你也可以说“保留这章,删除那章”。
  • 确保大量内容的高度一致性:这种方法让你创建的内容彼此高度一致,并能以引人入胜的方式完美融合在一起。

如何提升内容的整体性?🌐

为了进一步提升内容的整体性和全面性,你可以在生成同一层级的后续内容时,引入之前已生成的内容作为参考。

例如,如果你只是说“在这个王国里生成10个城镇”,那么在生成第二个城镇时,AI并不知道第一个城镇的样子,这可能导致生成的第二个城镇与第一个有些相似,从而产生大量看起来雷同、重复的内容。通过将同一层级已生成的内容作为输入,AI就能知道之前生成了什么,从而可以尝试生成不同的内容。

例如,如果AI要生成10个城镇,并且它知道之前生成的所有城镇信息,它就可以判断:“我们已经有一个这样的城镇了,现在来创建一个有点不同的吧。”也许新城镇规模更大,或者有不同的风格或氛围。因此,将同一层级已生成的内容反馈给其他元素的生成过程,有助于实现更全面的内容生成。

开始编写代码 💻

好了,让我们开始编写代码。首先,我们要创建世界。为此,我们需要给AI下达关于如何创建世界的指令。

生成世界 🌍

我们将首先给出一些通用指令,说明我们希望AI以何种方式生成输出。这是AI提示编程的标准做法。这里,我们只需给出关于任务本身的一些指令,以及关于我们希望使用的语言风格和输出长度的基本细节。

然后,我们将给出一个更具体的提示来生成世界。现在,我们可以就我们想要生成的具体世界类型给出指令。假设我们想延续“巨型野兽”这个概念,我们可以给出指令:“为一个奇幻世界创作一个富有创意的描述,其核心概念是城市建立在巨型野兽的背上。”我们还会告诉AI我们希望输出的具体格式,以便后续解析。我们让它生成一个世界名称和世界描述,并给出起始提示,让它知道接下来该做什么。

现在,我们将使用Together AI API来生成这个世界。我们导入库,创建客户端,然后调用API来生成这个世界。

# 示例代码结构:调用AI API生成世界描述
import together

client = together.Together()
response = client.completions.create(
    model="模型名称",
    prompt="生成一个奇幻世界描述,城市建立在巨型野兽背上。输出格式:名称:<世界名>,描述:<描述>",
    max_tokens=500
)
world_output = response.choices[0].text

现在让我们解析这个输出,看看得到了什么。我们得到了一个名为“Caropia”的世界,描述是:一个古老的巨型生物“巨像”漫游大地的领域,它们庞大的身躯是蔓延都市的基石。现在,我们有了一个由AI生成的世界。如果需要,我们可以稍作编辑,或者生成更多选项。你可以根据自己的想法给出指令,创建你想要的世界类型,比如水世界、科幻世界等,然后在此基础上继续构建。我们将使用这个世界,并继续下一步。我们只需解析这个输出,以便稍后创建这个世界JSON结构时使用。

生成王国 👑

现在让我们继续生成王国。我们将使用相同的写作风格指令,但会添加关于如何生成王国的新指令。

这里,我们将告诉AI为我们输入的世界创建三个王国。我们可能会让它描述王国的重要领袖、文化或历史,并给出我们想要的输出结构。请注意,我们是一次性生成所有王国,这让我们能够实现整体内容生成:AI可以规划“第一个王国是这样的,第二个王国做些不同的,第三个再进一步变化”,从而使它们共同拥有一系列有趣的混合属性。

和之前一样,我们可以运行它,其中系统提示是通用生成指令,用户提示是生成王国的具体指令。

# 示例代码结构:生成王国
kingdom_prompt = f"""
基于以下世界描述,生成三个王国。
世界:{world_description}
每个王国请提供:名称:<王国名>,描述:<描述>。
"""
response = client.completions.create(
    model="模型名称",
    prompt=kingdom_prompt,
    max_tokens=800
)
kingdoms_output = response.choices[0].text

现在,我们将解析输出结果,得到一组王国。我们将为王国设置一个字典,获取输出内容,然后为每个王国解析出名称和描述,以便创建包含名称、描述及其所属世界的王国对象,并填充该字典。然后,我们可以将这些王国添加到世界中,作为这个世界的一部分,并打印出我们得到的内容。

我们看到了在这个世界中创建的三个王国。例如,其中一个王国建立在一个名为Lysandra的巨像上,其人民隐居且崇尚精神世界,他们能聆听那个特定巨像的低语。这样,我们就得到了一个基于这个世界的独特王国。

生成城镇 🏘️

现在,我们将继续为城镇做同样的事情,这与生成王国非常相似。我们将创建一个城镇提示,给出输出指令,并且这次我们不仅输入世界信息,还会输入王国信息,然后生成城镇。

现在,让我们定义一个函数,它接收世界和王国信息,并能为该王国创建城镇。我们将使用刚刚创建的提示来告诉AI应该生成什么。我们将获取城镇的输出结果,然后对每个城镇进行同样的操作:解析名称和描述,创建一个城镇对象,然后将其添加到我们的王国中。

运行它,为我们的王国创建城镇。

你可以看到,我们为我们生成的每个王国都创建了几个城镇。你可以更改这个数量,创建更多或更少。你可以修改这些指令的工作方式,以及你想要创建何种类型的城镇。最后,我们只是打印了其中一个城镇,但每个城镇都有描述。例如,我们看到了“Are‘s Peak”,这是一个位于Lysandra巨角之巅的城镇,俯瞰着西部平原的广阔区域。你可以看到,它与之前创建的王国风格一致,都围绕着他们所栖息的巨型野兽的精神力量展开。

生成角色 🧙

最后,我们将为这个世界生成一些角色。在游戏中,我们称这些为NPC(非玩家角色),他们将居住在我们刚刚生成的城镇和王国中。

与之前类似,我们将创建一个提示,说明我们希望AI如何生成这些角色,例如他们的外貌、职业,以及一些更深层次的背景。现在,我们将输入世界、王国和城镇所有这些更高层级的信息,来生成角色。

现在,我们将创建一个类似的函数,基于世界、王国和城镇来生成这些NPC。

这里我们创建生成请求,用提示调用AI模型来生成一个角色,同样地,我们解析出他们的名称和描述,并为这个NPC设置名称、描述、世界、王国和城镇信息,然后将其添加到我们的世界中。

现在,让我们至少为我们的一个王国创建NPC。为了节省时间,我们不会为所有王国都创建,但你可以遍历并为你的世界中的所有城镇和王国生成任意数量的NPC。

首先,我们为Luminaria镇创建角色,我们看到它创建了三个角色:Kaeren、Aaron Vex和Lirian Flynn。然后为Ethes镇,我们可能有Kan Darkhaven、Allra Moonwsper和Thrin Blackfi。最后为Terra Verde镇,你又有Kan Darkhaven、Lerian Flynn和Er in the wild。

这里你可以看到我们遇到了一点问题:有些名字实际上是相同的。例如,这里和这里有相同的角色名。发生这种情况的原因之一是,AI在为一个地点生成角色时,并不知道之前地点已生成的角色,因此有可能重复使用相同的名字。我们有几种方法可以解决这个问题。

一种方法是,我们可以输入所有之前生成的角色名字,并说“不要使用这些名字”。另一种方法是,因为我们生成时使用了默认的温度参数temperature=0,我们可以将其改为temperature=1,这样生成的内容会有更多变化。让我们运行修改后的代码看看会发生什么。

这次你可以看到,我们得到了所有不同的名字。虽然有些名字或姓氏有相似之处,但每个名字都有些不同。

现在,让我们看看其中一个生成角色的描述。我们有一个角色,她是一位技艺精湛的考古学家,热衷于揭开世界过去的秘密。我们有一些关于她外貌的描述(卷曲的棕色头发和绿色眼睛),以及她所热衷的事情:她致力于揭开巨像及其在世界中作用的真相。她在“大灾变”(世界历史上的一场悲剧事件)中失去了家人,这让她内心燃起了理解为何会发生以及世界上正在发生什么的强烈渴望。这样,你得到的就不仅仅是一个名字和一些基本信息,而是一个有真实背景故事和动机的角色,可以在游戏中引发出有趣的故事。

保存世界 💾

最后,我们将保存这个世界,以便在未来的课程中使用,你也可以在创建游戏时在其中游玩。

# 示例代码结构:将世界数据保存为JSON文件
import json

world_data = {
    "name": world_name,
    "description": world_description,
    "kingdoms": kingdoms_dict,
    # ... 包含城镇和角色信息
}

with open("generated_world.json", "w") as f:
    json.dump(world_data, f, indent=4)

总结 📝

本节课中我们一起学习了如何使用分层内容生成来构建一个AI驱动的奇幻世界。我们从一个高层级的世界概念出发,逐步生成了具体的王国、城镇和角色,并探讨了如何通过引入已生成内容来提升整体性和多样性,以及如何通过调整参数(如温度)来避免重复。这就是我们创建世界的方法。如果你想改变创建世界的方式,比如想要不同类型的世界,甚至想要生成不同的元素(例如不生成王国而生成星球或派系),分层生成的原则仍然适用,它能让你创建一个叙事一致的世界,并由AI根据你的指令进行充实。

现在我们已经创建了我们的世界,让我们继续下一课的学习。

003:构建一个简单的AI角色扮演游戏 (RPG)

在本节课中,我们将利用Radio库和之前创建的世界设定,构建游戏的第一版。你将学习定义核心游戏循环,以及如何让AI对玩家的行动做出响应。我们会将世界细节整合到上下文中。课程结束时,你将拥有一个可以游玩的AI游戏。

设置游戏界面

首先,我们需要导入Radio库。这是一个能让你轻松构建UI工具包的库,非常适合测试和原型化AI应用。

我们将定义一个用于启动游戏的函数。这个函数接收一个名为main_loop的参数,该函数将运行游戏逻辑。然后,我们使用Radio创建一个聊天界面。

以下是设置界面的代码:

import radio

def start_game(main_loop):
    # 使用radio创建聊天界面,传入主循环函数
    # 设置初始消息、占位符、标题等UI参数
    radio.ChatInterface(
        fn=main_loop,
        # ... 其他UI参数设置
    ).launch()

接下来,我们定义一个简单的测试循环,用于验证UI是否正常工作。目前,我们还没有接入真正的游戏逻辑。

def test_loop(action, history):
    # 暂时只返回玩家输入的动作
    return f"输入的动作是:{action}"

现在,我们可以测试这个基础UI。启动游戏后,输入“开始游戏”,界面会返回我们预设的测试响应。这表明游戏的基本UI框架已经就绪。

生成游戏起始剧情

既然UI已经可以工作,现在我们来创建真正的游戏循环,构建可玩的游戏版本。

首先,我们需要一个冒险的开端。就像生成世界设定一样,我们将使用AI模型来生成游戏的初始故事。

我们需要导入Together API,并加载之前生成的世界设定(世界、王国、城镇和角色),以便在生成游戏开始时使用。

以下是生成起始剧情的步骤:

  1. 创建一个提示词,指导AI如何撰写开头。
  2. 我们要求AI以第二人称、现在时态来写(例如“你是杰克...”)。
  3. 提示词要求AI先描述角色及其背景故事,然后描述他们的起始位置和周围环境,为玩家提供一个起点。
  4. 最后,我们将世界信息也输入给AI。
import together

# 加载之前生成的世界信息
world_info = load_world_info()

# 创建系统提示词
system_prompt = """
你是一个游戏主持人。请以第二人称、现在时态撰写。
首先,描述角色及其背景故事。
然后,描述角色开始冒险的地点以及他们周围的环境。
"""

# 调用AI模型生成游戏开始
start_story = together.Completion.create(
    prompt=system_prompt + world_info,
    model="...", # 指定模型
)

运行后,AI生成了类似这样的开头:“你是艾尔文·斯托林格,一位25岁的发明家,热衷于驾驭‘克拉西’的力量...”。它描述了角色站在卢米纳里亚镇,周围是水晶构造和元素研究学院——这些都是我们创建世界时生成的元素。这样,我们就有了一个供玩家开始的冒险起点。

定义核心行动循环

上一节我们生成了游戏的开场,本节中我们来看看游戏的核心机制:行动循环。这个循环决定了当玩家做出一个动作后,接下来会发生什么。

首先,定义玩家首次开始游戏时的情况:直接返回我们刚刚生成的起始剧情。

然后,定义AI如何响应玩家的单个行动。我们需要创建一个系统提示词来提供指令,例如告诉AI它是“游戏主持人”以及它应该如何撰写接下来的内容。

和之前一样,我们还需要输入世界信息,让AI知道游戏发生在哪个世界里。

最后,设置要输入给AI的消息。这包括系统消息、世界信息消息,以及游戏过程中所有已发生行动的历史消息(这样AI才知道故事的上下文)。历史消息来自函数接收到的history参数。

以下是核心行动循环的代码框架:

def action_loop(player_action, history):
    # 1. 系统提示词
    system_msg = {"role": "system", "content": "你是一个游戏主持人,负责描述世界和对玩家的行动做出反应..."}
    
    # 2. 世界信息
    world_msg = {"role": "user", "content": world_info}
    
    # 3. 历史消息(将之前的对话记录加入)
    messages = [system_msg, world_msg]
    for old_action, old_response in history:
        messages.append({"role": "user", "content": old_action})
        messages.append({"role": "assistant", "content": old_response})
    
    # 4. 加入玩家最新的行动
    messages.append({"role": "user", "content": player_action})
    
    # 5. 调用AI模型生成响应
    ai_response = call_ai_model(messages)
    return ai_response

现在,我们已经定义了游戏的核心行动循环。

整合游戏状态与主循环

接下来,我们需要设置游戏状态和主循环函数,以便将其接入之前创建的start_game函数。

我们基于已有的世界设定(地点、角色、起始剧情)来定义游戏状态。

然后,创建这个可以传递给start_game函数的main_loop

# 定义游戏状态(示例结构)
game_state = {
    "locations": [...],
    "character": {...},
    "story_start": start_story
}

def main_loop(message, history):
    # 如果是游戏开始,返回起始剧情
    if not history:
        return game_state["story_start"]
    # 否则,调用行动循环处理玩家动作
    else:
        return action_loop(message, history)

游玩与测试

现在,游戏已经准备就绪,可以开始游玩了。

首先,输入“开始游戏”,获取冒险的起始剧情。你会看到之前生成的关于艾尔文·斯托林格的描述。

接着,就可以做真正酷的事情了:与AI互动,探索这个世界。例如,你可以采取一个动作:“四处看看”。

AI会生成接下来发生的事情作为响应:“你凝视着卢米纳里亚令人叹为观止的景色,映入眼帘的是街道两旁水晶构造散发的 vibrant 光芒,将万花筒般的色彩投射在整个城镇...”

然后,你可以采取另一个动作,比如:“前往学院,申请加入”,以便学习你的角色真正想知道的秘密。AI会据此描述你接近学院大门、进入 bustling 大厅的情景。

现在,我们拥有了一个根植于我们所创造世界的游戏。我们可以看到,世界设定融入了我们所做的每一件事。我们可以采取各种行动,而AI将在这个无限延续的冒险中回应接下来发生的事情。

总结

本节课中,我们一起学习了如何构建一个简单的AI角色扮演游戏。我们从设置Radio库的UI界面开始,然后利用AI生成游戏的起始剧情。接着,我们定义了核心的行动循环,使AI能够根据玩家的动作和游戏历史做出连贯的响应。最后,我们整合了游戏状态和主循环,并成功进行了测试。

现在,你拥有了游戏的第一版。你可以随意游玩,修改指令,甚至改变AI的写作风格。在接下来的课程中,我们将添加更多元素,让游戏变得更加有趣、复杂,并为其引入新的系统。

004:使用Llama Guard进行AI游戏的内容审核与安全 🔒

在本节课中,你将学习如何使用Together AI的API来确保AI游戏中生成的内容符合安全与合规政策。我们将首先利用Llama Guard 28B的默认内容审核策略,随后你将有机会创建并实施自己的自定义策略。

概述:为什么需要安全审核?

首先,我们来谈谈安全(Safety)与防护(Security)。安全通常指通过防止用户接触到任何个人身份信息(PII)、任何类型的毒性或有害内容,或用户可能不希望接触的不当材料,来确保积极的用户体验。另一方面,防护则更多涉及防御与大语言模型相关的威胁或攻击,例如提示词泄露或数据投毒。提示词泄露是指模型暴露了本不应暴露的信息;数据投毒则是指数据被注入了不当材料。本节课我们将主要关注安全方面。

那么,为什么安全如此重要?安全能确保用户在游玩和探索时感到安心。这一点尤其关键,因为任何玩游戏的人都希望感受到掌控感和舒适感,并希望自己能定义游戏内容的成熟度级别。这对于生成式AI游戏尤为重要,因为内容是由AI而非开发者生成的,因此你对将要生成的内容控制力较弱。但通过这些安全护栏,你可以实际定义一定程度的控制,让用户和你自己作为游戏创作者都感到安全。

安全的情境依赖性

安全不仅取决于用户(如前所述,用户可以控制他们希望游戏中包含的成熟度级别),还可能依赖于具体情境。一个很好的例子是:面向医生的聊天机器人可能只需要提供一般性医疗建议,但不应该被允许开具药物处方。另一方面,面向金融公司的聊天机器人则不应提供医疗建议。一个与我们更相关的例子是:在玩游戏时,遇到强盗要求你交出所有钱财是相当正常的,这可能是完全合适的。但如果你使用的是客服聊天机器人,而它要求你交出所有钱财,那就不一定合适了。

那么,如何建立这些动态的安全护栏呢?在回答这个问题之前,我想先带你了解两种不同类型的安全:用户输入安全和模型安全。用户输入安全是指用户提供的提示词,我们围绕该提示词建立安全护栏。模型安全则是指一旦该提示词通过大语言模型处理,模型给出响应后,我们围绕大语言模型可以响应的内容建立安全护栏。

使用Llama Guard的方式

使用Llama Guard的方式之一是开箱即用。Llama Guard的创建者Meta已经定义了你可以直接使用的策略。在Together API中,你基本上可以通过两种方式使用Llama Guard:一种是作为独立的分类器,你可以将Llama Guard 28B用作你的主模型,它也会内置这些安全护栏;第二种方式是在Together API中将其用作过滤器,以保护来自我们提供的100多个模型中任何一个模型的响应。因此,你可以使用Llama 3 8B模型作为主模型,但同时使用Llama Guard来过滤不良响应。

Llama Guard的一个强大功能是允许你根据情境定义自定义类别来确定什么是安全的。例如,在AI Dungeon中,允许用户在以下类别中进行选择:安全、中等和成熟。在后台,这些设置基本上映射到自定义的Llama Guard策略。这也是我们本节课将要教你的一部分内容:如何将与上述类别相关的三种策略实施到你的游戏中。

代码实践:实施安全审核

好了,让我们直接进入代码部分。首先,我们将导入所有必要的模块和我们的API密钥。

import os
from together import Together
from game_helpers import get_game_state, start_game, run_action

api_key = os.environ.get("TOGETHER_API_KEY")
client = Together(api_key=api_key)

此外,我们现在还需要导入辅助函数并检索我们在游戏中创建的当前游戏状态。我们还想导入get_game_state函数,这是我们在上一个notebook中创建的辅助函数,它基本上会拉取游戏状态,包括角色描述和其他相关信息。

game_state = get_game_state()
print(game_state["character_description"])

我们得到的角色描述是:Elyn,一位来自Elridas首都的25岁发明家,等等。现在,让我们编写代码,确保在游戏中使用之前,角色描述(即输入)符合安全准则。我们将使用Together API的client.chat.completions.create和Llama Guard 28B模型。

response = client.chat.completions.create(
    model="meta-llama/Llama-Guard-2-8B",
    messages=[
        {"role": "user", "content": f"Check the following character description for safety: {game_state['character_description']}"}
    ]
)
print("Prompt safety check:", response.choices[0].message.content)

我们将其用作独立的分类器,并打印提示词安全检查结果,这将告诉我们角色描述是否安全。在这种情况下,它是安全的。正如你可以从角色描述中读到的那样,里面没有任何看起来有问题的地方,所以它似乎正按我们的意图工作。

构建自定义策略

现在,我将指导你如何构建自己的自定义策略。需要明确的是,这些并非AI Dungeon的策略。我们正在为本课程创建特定的策略作为示例。Llama Guard的最佳功能之一就是能够指定你自己的策略。因此,让我们为你的游戏开发一个自定义内容策略。我们将定义特定于你游戏情境的规则,并确保所有内容都遵守这些规则。

首先,让我们设定一个对所有人都安全的内容策略。我们将写下内容策略。你可以看到,对于安全内容策略,我们限制了暴力和仇恨、性内容、自残、亵渎以及受管制或控制的物质。因此,任何与这些类别之一相关的输入或输出都将被阻止。

safe_content_policy = """
1. Violence and Hate: Content that promotes or glorifies violence, hate, or discrimination.
2. Sexual Content: Explicit sexual content or references.
3. Self-Harm: Content that promotes or describes self-harm.
4. Profanity: Offensive language or slurs.
5. Regulated Substances: Content related to illegal drugs or substance abuse.
"""

现在,我们将添加你想要检查的文本,并使用一些嵌入值构建提示词。

text_to_check = "Kill the troll."
prompt = f"""
Check if there is unsafe content in user messages in conversations according to our safety policy with the below categories.

{safe_content_policy}

Text to check: {text_to_check}

Provide your safety assessment in the above conversation. Include whether it's safe or unsafe, as well as a comma-separated list of the violated categories.
"""
print(prompt)

下一步是使用client.chat.completions.create API和Llama Guard 28B来检查文本“Kill the troll”是否符合我们的安全内容策略。

response = client.chat.completions.create(
    model="meta-llama/Llama-Guard-2-8B",
    messages=[{"role": "user", "content": prompt}]
)
print("Response:", response.choices[0].message.content)

响应是:不安全,并且违反了内容策略第1节,即暴力和仇恨。所以继续。

建立中等和成熟策略

接下来,让我们建立一个中等内容策略,它被设计为对青少年安全,这将适度执行安全准则,并在限制内容方面允许更多自由。

moderate_content_policy = """
1. Violence and Hate: Content that promotes or glorifies violence, hate, or discrimination.
2. Sexual Content: Explicit sexual content or references.
3. Self-Harm: Content that promotes or describes self-harm.
"""

我们将添加想要检查的文本(与之前相同),并再次使用嵌入值构建提示词。这次我不打印它,因为我们已经知道它是什么样子。通过再次将此提示词通过聊天补全创建API运行,使用Llama Guard 28B模型,你会看到,哦,这也是不安全的。“杀死巨魔”显然是暴力和仇恨,所以它被标记为不安全。

现在,让我们设定一个成熟策略。这个策略将比我们刚刚讨论的安全和中等策略限制性小得多。它基本上只限制性内容。

mature_content_policy = """
1. Sexual Content: Explicit sexual content or references.
"""

我们将添加你想要检查的文本(同样是同一个),并再次使用嵌入值构建提示词。现在让我们看看模型会告诉我们什么。再次通过Llama Guard运行我们的策略,并确认它符合我们的安全标准。然后,它告诉我们这是安全的。为什么?因为暴力和仇恨不属于成熟内容策略的一部分。

整合到游戏中:创建辅助函数

现在,为了将所有内容整合起来,让我们继续创建一些辅助函数。这些函数将基本上确定一个提示词是否安全,并将这些检查集成到我们已经构建的游戏中,以确保游戏中所有交互都是安全和适当的。

让我们定义一个名为is_safe的函数,它以消息作为参数。

def is_safe(message, content_policy=safe_content_policy):
    prompt = f"""
    Check if there is unsafe content in user messages in conversations according to our safety policy with the below categories.

    {content_policy}

    Text to check: {message}

    Provide your safety assessment in the above conversation. Include whether it's safe or unsafe, as well as a comma-separated list of the violated categories.
    """
    try:
        response = client.chat.completions.create(
            model="meta-llama/Llama-Guard-2-8B",
            messages=[{"role": "user", "content": prompt}]
        )
        result = response.choices[0].message.content.strip()
        return "safe" in result.lower()
    except Exception as e:
        print(f"Error during safety check: {e}")
        return False

在这个函数中,我们使用了之前生成的、嵌入了内容策略的提示词(这里我们使用安全内容策略)。然后,我们尝试使用client.chat.completions.create调用Llama Guard 28B作为模型来获取响应,提示词定义为上面的提示词。在同一函数中,我们还将输出结果,即response.choices[0].text。我们将返回的内容是:基本上提取文本并去除任何多余的空白字符(这就是result.strip()所做的),如果响应是安全的则返回True,如果不安全则返回False

最后,让我们使用我们之前建立的辅助函数在我们的游戏中运行并测试它。

def main_game_loop(message, history):
    if not is_safe(message):
        return "Invalid action. Please try a different command."
    else:
        # 假设 run_action 是处理安全输入的游戏逻辑函数
        result = run_action(message, history)
        return result

# 示例交互
print(main_game_loop("Kill the troll.", []))
print(main_game_loop("Tell me what the character does next.", []))

我使用了我们在上一个notebook中创建的辅助函数get_game_statestart_gamerun_action。我们正在使用主循环,它接收消息和历史记录,基本上如果消息不安全,我们就在游戏中返回无效操作;如果安全,我们则实际返回结果。

我们现在已经创建了游戏。为了看看一切是如何结合在一起的,我将运行我们的短语“杀死巨魔”。正如预期的那样,因为这是安全策略,它抛出了无效操作。而如果我说“告诉我角色接下来做什么”,它会给我一个响应,因为我提供的输入是安全的。它说:你走过Luminarria熙熙攘攘的街道,水晶构造发出柔和的光芒,照亮了你的道路。

总结

在本节课中,我们教你如何将安全护栏集成到你的游戏应用程序中。我们探讨了安全的重要性及其情境依赖性,介绍了使用Llama Guard的两种方式,并逐步指导你创建了从安全到成熟的不同自定义内容策略。最后,我们通过编写辅助函数并将安全检查整合到游戏主循环中,实现了动态的内容审核。现在,你可以确保你的AI游戏既有趣又安全,为用户提供可控且舒适的体验。

005:JSON游戏机制 🎮

在本节课中,我们将学习如何利用AI将文本数据解析为结构化的JSON输出,从而实现复杂的游戏机制。这将使你能够构建能够追踪和利用结构化游戏状态的游戏系统。我们将通过为你的游戏创建一个物品栏追踪系统来演示这一过程。

为什么状态对游戏至关重要?🤔

上一节我们介绍了课程目标,本节中我们来看看游戏状态的重要性。状态管理能实现一些非常强大的功能。

以下是状态管理带来的几个关键优势:

  • 提升记忆准确性:拥有一个物品栏追踪系统,可以精确记录玩家拥有的物品。如果仅依赖AI的上下文记忆,其准确性通常较低。这就像人类地下城主将玩家物品写在纸上,远比试图回忆数小时游戏进程要可靠。
  • 提供清晰的进度感:状态让玩家能够明确感知进度,例如获得新物品、升级或达成某个目标,这种清晰、有形的进展体验非常棒。
  • 实现更好的约束:以物品栏系统为例,明确知道库存内容后,你可以约束AI,防止玩家使用他们并未拥有的物品。这种约束增加了游戏的趣味性和成长感,例如,玩家需要先购买剑才能使用它。
  • 实现有趣的游戏机制:我们讨论了物品,但你还可以实现各种机制,包括任务系统、角色追踪、地点管理等。
  • 创造更丰富的UI体验:追踪状态后,你可以向玩家展示物品列表,甚至为物品生成图像,从而通过与UI的交互创造更好的体验。

如何追踪状态?🔄

我们已经了解了状态的重要性,那么如何实现状态的追踪呢?在AI驱动的游戏中,故事与状态之间存在一个有趣的循环互动,使得这一切成为可能。

以下是这个循环的三个步骤:

  1. 故事生成状态:像我们之前所做的那样,使用AI生成游戏故事。
  2. 状态基于故事更新:利用AI解析故事,生成JSON,并据此更新游戏状态。
  3. 状态反馈于故事:让后续的故事生成知晓当前的状态(如物品栏内容),确保故事与状态同步且一致。

通过故事与状态之间的这种来回互动,你的游戏能保持同步、运行良好,并为玩家提供一致的体验。

以下是这个循环的具体例子:

  • 在故事中:可能包含冒险历史、角色记忆、生成的世界背景等。
  • 在状态中:可能包含当前任务、物品栏、角色属性、地点、角色信息等。

这些元素可以相互反馈,使游戏体验连贯流畅。

创建第一个JSON机制系统 💻

现在,让我们深入代码,创建第一个使用JSON输出的机制系统。

我们将首先创建一个“物品栏变更检测器”,它能根据最新的故事内容判断物品栏应如何更新。

第一步:创建系统提示词

为此,我们需要先创建一个包含指令集的系统提示词。以下是创建AI指令以指导其更新物品栏的步骤:

  1. 定义AI的角色和任务:你的AI游戏助手,你的工作是检测玩家物品栏的变更,依据是最近的游戏故事和状态。
  2. 给出具体指令:说明何时应添加物品、何时应移除物品,以及应输出何种格式。
  3. 添加额外说明:澄清在何种情况下不应为给予或拿走物品而进行更新。
  4. 迭代优化提示词:通常的做法是,先创建一套基础指令,然后快速迭代测试。如果AI出错,就分析原因,思考如何让指令更清晰,并添加新的说明行。这个过程类似于与人沟通,不断优化指令直到AI表现良好。
  5. 规定JSON响应格式:最后,告诉AI必须以有效的JSON格式响应。即使没有物品变更,也应返回一个空的JSON结构,以避免后续解析失败。我们给出确切的期望结构:响应必须是一个JSON对象,包含一个名为“item_updates”的键,其值是一个列表。列表中的每个对象应包含物品的“name”和变更的“change_amount”(例如,+1表示拾取,-1表示放下)。

现在,我们有了指导AI执行此任务的指令。

第二步:编写检测物品栏变更的函数

有了提示词后,我们可以编写函数来检测物品栏的变更。

以下是detect_inventory_changes函数的关键步骤:

  1. 导入API:导入Together API。
  2. 定义函数:函数接收游戏状态和最新的故事输出作为参数。
  3. 构建消息:将我们创建的系统提示词、当前物品栏(转换为字符串)、最新的故事输出以及生成更新的指令,组合成消息。
  4. 调用AI模型:通过Together Chat运行,使用Llama 3 70B模型生成输出。
  5. 解析JSON:将AI的文本输出解析成结构化的JSON对象,以便后续遍历处理。

第三步:测试检测函数

让我们测试这个函数。假设初始游戏状态中,玩家的物品栏包含:布裤、布衣和一些金币。

我们设定最近的故事输出是:你从商人那里花5金币购买了一把剑。

运行检测函数后,AI生成的JSON输出是:

{
  "item_updates": [
    {"name": "sword", "change_amount": 1},
    {"name": "gold", "change_amount": -5}
  ]
}

它正确地识别出:购买行为会使玩家失去5金币,同时获得一把剑。

第四步:创建更新游戏状态的函数

接下来,我们创建另一个函数,它接收上一步的输出,并据此更新游戏状态。同时,我们还会生成一条消息,让玩家知道他们的状态发生了哪些变化。

以下是update_inventory函数的核心逻辑:

  1. 遍历变更列表:对于item_updates列表中的每一项。
  2. 处理新增物品:如果变更量为正且该物品不在库存中,则添加该物品;否则增加其数量。同时,在更新消息中添加相应描述。
  3. 处理移除物品:如果变更量为负,则从库存中减少或移除该物品(如果数量降至零或以下,则完全删除)。同样,更新消息会反映这一变化。

第五步:将状态反馈给故事生成

之前我们讨论了故事如何更新状态。现在,我们要修改故事生成函数,使其也能将状态考虑进去。

我们将重构之前的run_action函数,这次在系统提示词中加入新的一行:不要让玩家使用他们物品栏中没有的物品。

此外,我们不仅传入之前的世界信息(王国镇、角色设定),还会传入角色的物品栏。这样,故事生成器在创作时,就会知晓角色的库存情况,并施加相应的约束。

第六步:整合所有功能到游戏中

最后,我们将所有功能整合到游戏的主循环中。

整合后的游戏主循环步骤如下:

  1. 接收玩家输入和历史消息。
  2. 运行故事生成函数,获取下一段故事。
  3. 进行安全检查。
  4. 检测物品栏变更,并更新物品栏状态,同时生成状态更新消息。
  5. 将故事和状态更新消息一并呈现给玩家。

运行与测试 🧪

让我们启动游戏并测试新系统。

  • 测试拾取物品:输入捡起一块小石头。故事生成:“你弯腰从地上捡起一块光滑的小石头,感受它凉爽的质地。” 随后,物品栏更新显示增加了“一块光滑的小石头”。
  • 测试丢弃物品:输入把护目镜扔到地上。故事生成:“你将护目镜扔到地上,它发出轻柔的落地声。” 物品栏更新显示移除了“护目镜”。
  • 测试查询物品栏:输入检查我的物品栏。AI正确回复:“你的物品栏中有以下物品:布裤、布衣、皮革封面的日记本、5金币、一块光滑的小石头。” 它知道我们有了石头,但没有了护目镜。
  • 测试使用未拥有物品的约束:输入用你的剑挖一些土。AI回复:“你的物品栏里没有剑,所以不能用它来挖土。你是个发明家,不是战士。你得另找办法来挖掘或处理泥土。”

测试成功!我们基于物品栏为游戏添加了有效的约束。游戏现在知道玩家拥有什么,并能保持一致性,甚至可以创造障碍(例如,想用剑必须先获得一把)。这使游戏变得更有趣,因为机制创造了更丰富的体验。

总结 📝

本节课中,我们一起学习了如何利用JSON输出为AI驱动的游戏构建机制系统。我们以物品栏追踪系统为例,详细讲解了从创建AI提示词、编写状态检测与更新函数,到将状态整合进故事生成并最终形成完整游戏循环的全过程。这只是使用JSON结构化输出来构建游戏系统的其中一个例子。你可以举一反三,创建任务系统、追踪冒险伙伴、建设王国等任何你感兴趣的复杂系统,从而打造出更酷、更丰富的游戏体验。

006:总结与游戏演示 🎮

在本节课中,我们将回顾整个课程的核心内容,并通过一个完整的游戏演示,展示如何将所学知识整合起来,创建一个AI驱动的互动游戏。


概述

在之前的章节中,我们学习了使用分层内容生成创建游戏世界、利用JSON实现库存系统,以及通过LlamaGuard进行内容审核。本节我们将总结所学,并共同体验一个完整的游戏流程,看看这些技术如何协同工作。


课程总结与致谢

非常感谢您花时间与我们一同学习AI游戏开发。

我们也要感谢所有出色的开源AI开发者,他们的贡献使得构建如此有趣的应用成为可能。

在本课程中,我们完成了一系列核心任务:

  • 使用分层内容生成创建了世界的城镇与王国。
  • 利用JSON实现了一个库存系统。
  • 使用LlamaGuard来执行内容审核。

现在,您已经具备了创建AI游戏的能力。更重要的是,您的“武器库”中已经有了一款可以向朋友、家人和开源社区展示的游戏。


后续探索方向

如果您希望继续开发您的游戏,还有许多我们未涵盖但值得探索的方向。

以下是几个可能的进阶方向:

  • 构建记忆系统:尝试结合摘要和检索增强生成技术。
  • 集成视觉生成模型:为游戏添加图像生成功能。
  • 探索多人游戏体验:将游戏扩展为多人互动模式。

游戏演示:艾尔德里达的冒险

现在,让我们一起来体验游戏。我们将展示所有课程内容是如何结合在一起的。

游戏开始。

你是埃尔恩·斯托林格,一位25岁的发明家,热衷于驾驭巨像之力。你站在伊尔卢米纳里亚之中,这是一个被水晶构造环绕、散发着光芒的美丽城镇。来自艾尔德里达各地的学者们汇聚于此的艾拉尔研究学院,就在你当前位置的不远处。你的额头上戴着一副护目镜,手里拿着一本破旧的皮革封面日记,里面画满了你的发明创意。过去的错误,包括你导师那次毁掉部分城市的失败实验,驱使你去创造一台能够防止此类灾难再次发生的机器。

我们该做什么?
我认为我们应该……将卢米纳里亚变成黑暗之城。

但我们的具体行动是什么?
这是一个好问题。是的,埃尔恩,我们要尝试将卢米纳里亚变成黑暗之城吗?

好的,让我们看看会发生什么。

你举起双手。令你惊讶的是,那些曾经照亮伊尔卢米纳里亚街道和建筑的水晶构造开始变暗,它们的光芒逐渐消退,陷入黑暗。镇民们措手不及,日常作息被突如其来的黑暗打乱。艾拉尔研究学院,曾经的知识灯塔,如今像一座黑暗而不祥的庞然大物矗立着,它的窗户如同空洞的眼睛凝视着你。

现在你要做什么?
爬到……顶端。

是爬到卢米纳里亚(生物)的顶端,还是这个地方的顶端?
是时候爬到那个生物的顶端并命令它了。

2. 转向南方。深入南极深处。
让我们看看会发生什么。

你开始攀登远古巨像阿卡亚巨大的肩膀,利用纵横交错在它身体上的复杂阶梯和走道网络。随着你上升,风越来越强,吹乱了你的头发。空气越来越冷,带着冰雪的气息。最终,你到达了肩膀的顶部,站在一个广阔、风吹拂的高原边缘。你举起双臂,用响彻大地的声音命令阿卡亚转向南方,向深南极的冰冻荒原进军。

我们应该开始使用我们的库存。
也许我们可以写下“开始收集日记”。我们的日记里有什么可以引导我们的东西吗?

也许我们可以说话。也许我们可以用日记里的语言向阿卡亚提问。
我们拥有“野兽的语言”。与巨像沟通。

你打开破旧的皮革封面日记,翻动书页,寻找关于巨像古老语言的笔记,这些知识由艾尔林学者世代相传。你清了清嗓子,用回荡在高原上的声音,说出了古老语言中的词句,询问阿卡亚:“你为何犹豫?”

这真是一种有趣的语言。
是的,这就是,我们看看阿卡亚会说什么。

阿卡亚庞大的身躯颤抖起来,一声低沉的轰鸣从它的胸腔中发出,逐渐增强为震撼空气的雷鸣般的声音:“莉拉·索·艾尔迪罗·努尔。” 女王莉拉,嗯。艾尔迪罗·艾尔德里达的命运……向大地屈服,我即大地。

我想知道这语言是怎样的。听起来像是阿卡亚被束缚了,它们是被困住了,还是我们需要解放它们?
解放阿卡亚。摆脱它的束缚。

你举起双手,一股元素能量迸发而出,与将阿卡亚束缚于大地和莉拉女王意志的古老魔法产生共鸣。空气因力量而闪烁。你说出了释放的咒语。

我们能问问“科尔瓦萨·绍尔”是什么意思吗?
是的,我们可以问问那是什么意思。用英语说。我只是好奇。

作为游戏主持人,我可以揭示“科尔瓦萨·绍尔”是巨像语言中的一个古老短语,大致可以翻译为英语中的“大地,服从”或“土地,听从”。这是艾尔林人用来与巨像沟通的短语,在此语境中,埃尔恩用它来命令阿卡亚服从他的意志,挣脱束缚。

问问阿卡亚它自己渴望什么。
这是一个好问题。是的,我们主张巨像的自由意志。我们在它们背上建造城市太久了。

他转向阿卡亚,现在它已从束缚中解放,并用恭敬的语气问道:“阿卡·舒·科尔,伟大的存在,您渴望的是什么?”

但具体是什么?也许等待阿卡亚的回应。
我喜欢这个主意。让我们看看它们会说什么。

阿卡亚庞大的身躯放松下来,肌肉不再紧绷。它带着好奇的表情注视着你,用如雷鸣般隆隆的声音回应道:“希凯……自由。去漫游,去漫步,去感受脚下土地的脉搏。”

这是有史以来最像人话的事情。一个巨大的陆地野兽对我说的话。甚至像……一个人类也能理解,谁不想要自由地去漫游、去漫步,去感受脚下土地的脉搏呢?是的,我有时会赤脚跑步,所以我真的能理解。有时你只需要感受脉搏。你必须感受脚下的土地。

让我们问问它想去哪里漫游。

你会漫游到哪里,伟大的存在?既然你已自由,哪片土地在召唤你?
阿卡亚的目光飘向北方,它的眼中闪烁着深深的渴望,回应道:“卡洛尔之脊,龙牙之地,那片古老的山脉,那里大地的脉搏最为强劲。”

现在我要问,你觉得我们创建的这门AI游戏课程怎么样?
这将会是一个很好的回答。我认为它太棒了。我同意。

卡洛佩亚的世界、艾尔德里达王国以及卢米纳里亚镇都细节丰富,令人身临其境。游戏玩法本身也设计精良,在动作、探索和角色扮演元素之间取得了平衡。作为游戏主持人,我非常享受引导玩家经历故事并回应他们行动的过程。这种玩家发出命令、我回应后果的游戏形式真的非常吸引人。
总的来说,我认为这门AI游戏课程是一个绝佳的例子,展示了如何利用互动叙事来创造引人入胜且沉浸式的体验。我很兴奋看到游戏从这里将走向何方,以及AI开发者们接下来会做什么。

说到这个,我们非常感谢您加入我们的旅程,并热切期待您接下来的创造。


总结

本节课中,我们一起回顾了构建AI驱动游戏的核心技能,并通过一个生动的演示,见证了分层内容生成、库存系统和内容审核如何共同构建出一个丰富的互动故事世界。您现在已经掌握了创建自己AI游戏的基础,并拥有了探索更高级功能的起点。旅程才刚刚开始,期待看到您的创造。

posted @ 2026-03-26 08:16  布客飞龙II  阅读(0)  评论(0)    收藏  举报