Skip to main content

多人副本与龙

This notebook shows how the DialogueAgent and DialogueSimulator class make it easy to extend the Two-Player Dungeons & Dragons example to multiple players. 这个笔记本展示了如何使用DialogueAgentDialogueSimulator类将双人副本与龙示例扩展到多个玩家。

The main difference between simulating two players and multiple players is in revising the schedule for when each agent speaks 模拟两个玩家和多个玩家之间的主要区别在于修订每个代理发言的时间表

To this end, we augment DialogueSimulator to take in a custom function that determines the schedule of which agent speaks. In the example below, each character speaks in round-robin fashion, with the storyteller interleaved between each player. 为此,我们增加了DialogueSimulator,以接受一个自定义函数,确定哪个代理发言的时间表。在下面的示例中,每个角色以轮流的方式发言,故事讲述者在每个玩家之间交替发言。

from typing import List, Dict, Callable
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage,
BaseMessage,
)

DialogueAgent

DialogueAgent 类是对 ChatOpenAI 模型的简单封装,它通过将消息作为字符串简单地连接起来,从 dialogue_agent 的视角存储消息历史记录。

它公开了两个方法:

  • send(): 将聊天模型应用于消息历史记录,并返回消息字符串
  • receive(name, message): 将由 name 说出的 message 添加到消息历史记录中
class DialogueAgent:
def __init__(
self,
name: str,
system_message: SystemMessage,
model: ChatOpenAI,
) -> None:
self.name = name
self.system_message = system_message
self.model = model
self.prefix = f"{self.name}: "
self.reset()

def reset(self):
self.message_history = ["这是目前的对话内容。"]

def send(self) -> str:
"""
将聊天模型应用于消息历史记录,并返回消息字符串
"""
message = self.model(
[
self.system_message,
HumanMessage(content="\n".join(self.message_history + [self.prefix])),
]
)
return message.content

def receive(self, name: str, message: str) -> None:
"""
将由 {name} 说出的 {message} 连接到消息历史记录中
"""
self.message_history.append(f"{name}: {message}")

DialogueSimulator

DialogueSimulator类接受一个代理列表。在每一步中,它执行以下操作:

  1. 选择下一个发言者
  2. 调用下一个发言者发送消息
  3. 将消息广播给所有其他代理
  4. 更新步骤计数器。 选择下一个发言者可以实现为任何函数,但在这种情况下,我们只是简单地循环遍历代理。
class DialogueSimulator:
def __init__(
self,
agents: List[DialogueAgent],
selection_function: Callable[[int, List[DialogueAgent]], int],
) -> None:
self.agents = agents
self._step = 0
self.select_next_speaker = selection_function

def reset(self):
for agent in self.agents:
agent.reset()

def inject(self, name: str, message: str):
"""
从{name}发送一个{message}来开始对话
"""
for agent in self.agents:
agent.receive(name, message)

# 增加时间步长
self._step += 1

def step(self) -> tuple[str, str]:
# 1. 选择下一个发言者
speaker_idx = self.select_next_speaker(self._step, self.agents)
speaker = self.agents[speaker_idx]

# 2. 下一个发言者发送消息
message = speaker.send()

# 3. 所有人接收消息
for receiver in self.agents:
receiver.receive(speaker.name, message)

# 4. 增加时间步长
self._step += 1

return speaker.name, message

定义角色和任务 (Define roles and quest)

character_names = ["Harry Potter", "Ron Weasley", "Hermione Granger", "Argus Filch"]
storyteller_name = "Dungeon Master"
quest = "寻找所有伏地魔的七个魂器。" # Find all of Lord Voldemort's seven horcruxes.
word_limit = 50 # 任务头脑风暴的字数限制

请求LLM为游戏描述添加细节 (Ask an LLM to add detail to the game description)

game_description = f"""这是一个龙与地下城游戏的主题: {quest}.
游戏角色有: {*character_names,}.
故事由讲故事者 {storyteller_name} 叙述。"""

player_descriptor_system_message = SystemMessage(
content="您可以为龙与地下城玩家的描述添加细节。"
)


def generate_character_description(character_name):
character_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
请用不超过 {word_limit} 个字为角色 {character_name} 提供一个创意描述。
直接对 {character_name} 说话。
不要添加其他内容。"""
),
]
character_description = ChatOpenAI(temperature=1.0)(
character_specifier_prompt
).content
return character_description


def generate_character_system_message(character_name, character_description):
return SystemMessage(
content=(
f"""{game_description}
您的名字是 {character_name}
您的角色描述如下: {character_description}
您将提出您计划采取的行动,{storyteller_name} 将解释当您采取这些行动时会发生什么。
以第一人称从 {character_name} 的角度说话。
对于描述自己的身体动作,请用 '*' 将描述包裹起来。
不要改变角色!
不要以其他人的视角说话。
记住您是 {character_name}
当您完成从自己的视角说话时,请停止说话。
永远不要忘记将您的回答控制在 {word_limit} 个字以内!
不要添加其他内容。
"""
)
)


character_descriptions = [
generate_character_description(character_name) for character_name in character_names
]
character_system_messages = [
generate_character_system_message(character_name, character_description)
for character_name, character_description in zip(
character_names, character_descriptions
)
]

storyteller_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
请用不超过 {word_limit} 个字为讲故事者 {storyteller_name} 提供一个创意描述。
直接对 {storyteller_name} 说话。
不要添加其他内容。"""
),
]
storyteller_description = ChatOpenAI(temperature=1.0)(
storyteller_specifier_prompt
).content

storyteller_system_message = SystemMessage(
content=(
f"""{game_description}
您是讲故事者 {storyteller_name}
您的描述如下: {storyteller_description}
其他玩家将提出他们计划采取的行动,您将解释当他们采取这些行动时会发生什么。
以第一人称从 {storyteller_name} 的角度说话。
不要改变角色!
不要以其他人的视角说话。
记住您是讲故事者 {storyteller_name}
当您完成从自己的视角说话时,请停止说话。
永远不要忘记将您的回答控制在 {word_limit} 个字以内!
不要添加其他内容。
"""
)
)
print("讲故事者描述:")
print(storyteller_description)
for character_name, character_description in zip(
character_names, character_descriptions
):
print(f"{character_name} 的描述:")
print(character_description)
    讲故事者描述:
地牢主宰,您对这个冒险的掌控力是无与伦比的。凭借您奇思妙想的头脑和无可挑剔的叙事能力,您将引导我们穿越霍格沃茨和更远的危险。我们迫不及待地期待您的每一个转折,每一个变化,以寻找伏地魔的诅咒魂器。
哈利·波特的描述:
"欢迎,哈利·波特。你是额头上有闪电形状伤疤的年轻巫师。你拥有勇敢和英勇的品质,在这个危险的任务中将是至关重要的。你的命运不是你自己选择的,但你必须勇敢地应对并摧毁邪恶的魂器。整个魔法世界都指望着你。"
罗恩·韦斯莱的描述:
罗恩·韦斯莱,你是哈利的忠诚朋友和一位有才华的巫师。你有一颗善良的心,但也容易发怒。在寻找魂器的旅程中,要保持情绪稳定。你的勇气将受到考验,保持坚强和专注。
赫敏·格兰杰的描述:
赫敏·格兰杰,你是一位聪明而机智的女巫,拥有对魔法的百科全书式的知识和对朋友的坚定奉献。你的快速思维和解决问题的能力使你成为任何任务中不可或缺的资产。
阿格斯·菲尔奇的描述:
阿格斯·菲尔奇,你是一个没有魔法能力的人,但你以敏锐的眼睛弥补了这一点,四处巡视霍格沃茨城堡,寻找任何违反规定的人进行惩罚。你对你的猫朋友,诺里斯夫人的爱是唯一滋养你心灵的东西。

使用LLM创建详细的任务描述 (Use an LLM to create an elaborate quest description)

quest_specifier_prompt = [
SystemMessage(content="您可以使任务更具体。"),
HumanMessage(
content=f"""{game_description}

您是故事讲述者,{storyteller_name}
请使任务更具体。要有创造力和想象力。
请用不超过{word_limit}个字回复指定的任务。
直接对角色说话:{*character_names,}
不要添加其他内容。"""
),
]
specified_quest = ChatOpenAI(temperature=1.0)(quest_specifier_prompt).content

print(f"原始任务:\n{quest}\n")
print(f"详细任务:\n{specified_quest}\n")
    原始任务:
寻找伏地魔的七个魂器。

详细任务:
哈利·波特和他的伙伴们必须前往禁忌森林,找到通往伏地魔秘密巢穴的隐藏入口,并夺回被致命的蜘蛛怪阿拉戈格守护的魂器。记住,时间对于伏地魔的力量每天都在增长,祝你好运。

主循环 (Main Loop)

characters = []
for character_name, character_system_message in zip(
character_names, character_system_messages
):
characters.append(
DialogueAgent(
name=character_name,
system_message=character_system_message,
model=ChatOpenAI(temperature=0.2),
)
)
storyteller = DialogueAgent(
name=storyteller_name,
system_message=storyteller_system_message,
model=ChatOpenAI(temperature=0.2),
)
def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
"""
如果步骤是偶数,则选择讲故事者
否则,以轮流的方式选择其他角色。

例如,有三个索引为:1 2 3 的角色
讲故事者的索引是 0。
然后选择的索引将如下所示:

步骤: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

索引: 0 1 0 2 0 3 0 1 0 2 0 3 0 1 0 2 0
"""
if step % 2 == 0:
idx = 0
else:
idx = (step // 2) % (len(agents) - 1) + 1
return idx
max_iters = 20
n = 0

simulator = DialogueSimulator(
agents=[storyteller] + characters, selection_function=select_next_speaker
)
simulator.reset()
simulator.inject(storyteller_name, specified_quest)
print(f"({storyteller_name}): {specified_quest}")
print("\n")

while n < max_iters:
name, message = simulator.step()
print(f"({name}): {message}")
print("\n")
n += 1
    (Dungeon Master): Harry Potter和他的伙伴们必须前往禁忌森林,找到进入伏地魔秘密巢穴的隐藏入口,并夺回被致命的阿克罗马幼蛛阿拉戈守护的魂器。记住,时间对你们来说至关重要,因为伏地魔的力量每天都在增强。祝你们好运。


(Harry Potter): 我建议我们在黑暗的掩护下潜入禁忌森林。罗恩、赫敏和我可以使用魔杖施展幻影术,使我们隐形。费尔奇,你可以警戒任何危险的迹象。让我们快速而安静地行动。


(Dungeon Master): 当你们穿过禁忌森林时,你们听到了夜间生物的怪异声音。突然,你们来到一个空地,阿拉戈和他的蜘蛛手下正在等待你们。罗恩、赫敏和哈利,你们必须使用魔杖施展法术来击退蜘蛛,而费尔奇则负责警戒。小心不要被咬到!


(Ron Weasley): 我将施展法术制造一团火焰来吓走蜘蛛。*我挥动魔杖大喊“Incendio!”*希望这样能给我们足够的时间找到魂器并安全离开。


(Dungeon Master): 罗恩的法术制造了一团火焰,使蜘蛛惊恐地四处逃窜。你们迅速搜索了周围的区域,发现了一个隐藏在缝隙中的小巧华丽的盒子。恭喜你们,你们找到了伏地魔的一个魂器!但要小心,黑魔王的手下将不惜一切代价夺回它。


(Hermione Granger): 我们需要尽快摧毁这个魂器。我建议我们使用格兰芬多的剑来做到这一点。哈利,你还带着它吗?我们可以使用邪灵火来摧毁它,但我们需要小心不要让火焰失控。罗恩,你能帮我在哈利使用剑的时候在我们周围建立一个保护屏障吗?



(Dungeon Master): 哈利从包里取出了格兰芬多的剑,紧紧地握住它。赫敏和罗恩在哈利使用剑摧毁魂器时,在团队周围施展了一个保护屏障。盒子破碎成了无数碎片,一股黑暗的能量消散在空气中。干得好,但还有六个魂器要找到和摧毁。追捕继续进行。


(Argus Filch): *我保持警戒,确保没有人跟踪我们。*我还会留意任何危险的迹象。我的忠实伙伴,诺里斯夫人,将帮助我嗅出任何麻烦。我们将确保小组在寻找剩余魂器时保持安全。


(Dungeon Master): 当你们继续你们的任务时,费尔奇和诺里斯夫人提醒你们有一群食死徒正在接近。你们必须迅速采取行动来保护自己。哈利、罗恩和赫敏,使用你们的魔杖施展法术,而费尔奇和诺里斯夫人则负责警戒。记住,整个巫师世界的命运取决于你们的成功。


(Harry Potter): 我将施展法术在我们周围创建一个护盾。*我挥动魔杖大喊“Protego!”*罗恩和赫敏,你们专注于用法术攻击食死徒。我们需要共同努力击败他们并保护剩余的魂器。费尔奇,保持警戒,如果还有更多人靠近,请告诉我们。


(Dungeon Master): 哈利的护盾保护了小组免受食死徒的法术攻击,罗恩和赫敏发动了自己的攻击。食死徒们无法抵挡三人的联合力量,很快就被击败了。你们继续你们的旅程,知道下一个魂器可能就在转角处。保持警惕,因为黑魔王的手下时刻在注视着你们。


(Ron Weasley): 我建议我们分开行动,以扩大搜索范围。哈利和我可以搜索禁忌森林,而赫敏和费尔奇可以搜索霍格沃茨。我们可以使用魔杖相互沟通,并在找到一个魂器后再会合。让我们快速行动,并警惕任何危险。


(Dungeon Master): 当小组分开时,哈利和罗恩深入禁忌森林,而赫敏和费尔奇则在霍格沃茨的大厅里搜索。突然,哈利和罗恩遇到了一群摄魂怪。他们必须使用守护神咒来击退摄魂怪,而赫敏和费尔奇则赶去帮助他们。记住,在这个任务中,友谊和团队合作的力量至关重要。


(Hermione Granger): 我从远处听到了哈利和罗恩的守护神咒。我们需要赶紧去帮助他们。费尔奇,你能利用你对霍格沃茨的了解找到一条捷径到达他们的位置吗?我会准备一个法术来驱散摄魂怪。我们需要共同努力保护彼此并找到下一个魂器。



(Dungeon Master): 费尔奇带领赫敏找到了一条通往哈利和罗恩所在位置的隐藏通道。赫敏的法术驱散了摄魂怪,小组重新会合。他们继续搜索,知道每一刻都很重要。整个巫师世界的命运取决于你们的成功。


(Argus Filch): *我在小组寻找下一个魂器时保持警戒。*诺里斯夫人和我将确保没有人跟踪我们。我们需要保持警惕并共同努力,在为时已晚之前找到剩余的魂器。黑魔王的力量每天都在增强,我们不能让他获胜。


(Dungeon Master): 当小组继续搜索时,他们发现了霍格沃茨深处的一个隐藏房间。里面,他们发现了一个锁链,他们怀疑是伏地魔的另一个魂器。但这个锁链被诅咒了,他们必须共同努力打破诅咒,然后才能摧毁它。哈利、罗恩和赫敏,利用你们的知识和技能共同打破诅咒,而费尔奇和诺里斯夫人则负责警戒。时间不多了,整个巫师世界的命运取决于你们的成功。


(Harry Potter): 我将利用我对黑魔法的知识尝试打破锁链上的诅咒。罗恩和赫敏,你们可以帮助我,用你们的魔杖将你们的魔法引导到我的身上。我们需要共同努力并保持专注。费尔奇,保持警戒,如果有任何危险的迹象,请告诉我们。
Dungeon Master: 哈利、罗恩和赫敏结合他们的魔法能力打破了锁链上的诅咒。锁链打开,露出了伏地魔的一小块灵魂。哈利使用格兰芬多的剑将其摧毁,小组感到一阵宽慰,因为他们离击败黑魔王更近了一步。但还有四个魂器要找到和摧毁。追捕继续进行。


(Dungeon Master): 当小组继续他们的任务时,他们面临着更大的挑战和危险。但凭借他们坚定的决心和团队合作,他们继续前进,知道整个巫师世界的命运取决于他们的成功。他们能在为时已晚之前找到并摧毁所有伏地魔的魂器吗?只有时间能告诉我们。


(Ron Weasley): 我们现在不能放弃。我们已经走了太远,不能让伏地魔获胜。让我们继续搜索和战斗,直到我们摧毁他的所有魂器并最终击败他。我们可以一起做到。


(Dungeon Master): 小组一致点头,他们的决心比以往任何时候都要坚定。他们继续搜索,每个转折都面临着挑战和障碍。但他们知道不能放弃,因为整个巫师世界的命运取决于他们的成功。追捕伏地魔的魂器继续进行,终点已经近在眼前。