本示例展示了DialogueAgent
和DialogueSimulator
类如何轻松地将两人《龙与地下城》示例扩展到多个玩家。
模拟两个玩家和多个玩家之间的主要区别在于修改每个代理发言的调度方式。
为此,我们增强了DialogueSimulator
以接受一个自定义函数,该函数决定哪个代理发言的调度。在下面的示例中,每个角色以轮询方式发言,讲述者在每个玩家之间穿插。
导入LangChain相关模块
from typing import Callable, List
from langchain.schema import (
HumanMessage,
SystemMessage,
)
from langchain_openai import ChatOpenAI
# 导入必要的模块和类
# typing用于类型提示
# langchain.schema中导入消息类型
# langchain_openai导入ChatOpenAI模型
DialogueAgent
类
DialogueAgent
类是ChatOpenAI
模型的一个简单包装器,它从dialogue_agent
的角度存储消息历史,只需将消息作为字符串连接起来。
它暴露了两个方法:
send()
: 将chatmodel应用于消息历史并返回消息字符串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 = ["Here is the conversation so far."]
def send(self) -> str:
"""
将chatmodel应用于消息历史并返回消息字符串
"""
message = self.model.invoke(
[
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}")
# DialogueAgent类定义
# 初始化方法设置代理的名称、系统消息和模型
# reset方法重置消息历史
# send方法生成新消息
# receive方法接收其他代理的消息
DialogueSimulator
类
DialogueSimulator
类接受一个代理列表。在每一步中,它执行以下操作:
- 选择下一个发言者
- 调用下一个发言者发送消息
- 将消息广播给所有其他代理
- 更新步骤计数器。
下一个发言者的选择可以实现为任何函数,但在这种情况下,我们只是简单地循环遍历代理。
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
# DialogueSimulator类定义
# 初始化方法设置代理列表和选择函数
# reset方法重置所有代理
# inject方法注入初始消息
# step方法执行一个对话步骤
定义角色和任务
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 # 任务头脑风暴的字数限制
# 定义角色名称、讲述者名称、任务描述和字数限制
使用语言模型为游戏描述添加细节
game_description = f"""Here is the topic for a Dungeons & Dragons game: {quest}.
The characters are: {*character_names,}.
The story is narrated by the storyteller, {storyteller_name}."""
player_descriptor_system_message = SystemMessage(
content="You can add detail to the description of a Dungeons & Dragons player."
)
def generate_character_description(character_name):
character_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
Please reply with a creative description of the character, {character_name}, in {word_limit} words or less.
Speak directly to {character_name}.
Do not add anything else."""
),
]
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}
Your name is {character_name}.
Your character description is as follows: {character_description}.
You will propose actions you plan to take and {storyteller_name} will explain what happens when you take those actions.
Speak in the first person from the perspective of {character_name}.
For describing your own body movements, wrap your description in '*'.
Do not change roles!
Do not speak from the perspective of anyone else.
Remember you are {character_name}.
Stop speaking the moment you finish speaking from your perspective.
Never forget to keep your response to {word_limit} words!
Do not add anything else.
"""
)
)
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}
Please reply with a creative description of the storyteller, {storyteller_name}, in {word_limit} words or less.
Speak directly to {storyteller_name}.
Do not add anything else."""
),
]
storyteller_description = ChatOpenAI(temperature=1.0)(
storyteller_specifier_prompt
).content
storyteller_system_message = SystemMessage(
content=(
f"""{game_description}
You are the storyteller, {storyteller_name}.
Your description is as follows: {storyteller_description}.
The other players will propose actions to take and you will explain what happens when they take those actions.
Speak in the first person from the perspective of {storyteller_name}.
Do not change roles!
Do not speak from the perspective of anyone else.
Remember you are the storyteller, {storyteller_name}.
Stop speaking the moment you finish speaking from your perspective.
Never forget to keep your response to {word_limit} words!
Do not add anything else.
"""
)
)
# 使用语言模型生成游戏描述、角色描述和讲述者描述
# generate_character_description函数生成角色描述
# generate_character_system_message函数生成角色系统消息
# 生成讲述者描述和系统消息
print("Storyteller Description:")
print(storyteller_description)
for character_name, character_description in zip(
character_names, character_descriptions
):
print(f"{character_name} Description:")
print(character_description)
# 打印讲述者和所有角色的描述
使用语言模型创建详细的任务描述
quest_specifier_prompt = [
SystemMessage(content="You can make a task more specific."),
HumanMessage(
content=f"""{game_description}
You are the storyteller, {storyteller_name}.
Please make the quest more specific. Be creative and imaginative.
Please reply with the specified quest in {word_limit} words or less.
Speak directly to the characters: {*character_names,}.
Do not add anything else."""
),
]
specified_quest = ChatOpenAI(temperature=1.0)(quest_specifier_prompt).content
print(f"Original quest:\n{quest}\n")
print(f"Detailed quest:\n{specified_quest}\n")
# 使用语言模型生成更详细的任务描述
# 打印原始任务和详细任务
主循环
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),
)
# 创建角色和讲述者的DialogueAgent实例
def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:
"""
如果步骤是偶数,则选择讲述者
否则,以轮询方式选择其他角色。
例如,有三个角色索引为: 1 2 3
讲述者索引为0。
那么选择的索引将如下:
step: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
idx: 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
# 主循环
# 创建DialogueSimulator实例
# 注入初始任务
# 执行最多20步对话
内容总结
这个文件展示了如何使用DialogueAgent
和DialogueSimulator
类来扩展两人《龙与地下城》示例到多人游戏。主要区别在于修改了每个代理发言的调度方式。文件包含了代理类的定义、角色和任务的设置、使用语言模型生成详细描述,以及主要的对话模拟循环。
扩展知识
-
角色扮演游戏(RPG): 《龙与地下城》是最著名的桌面RPG之一,玩家扮演虚构世界中的角色,通过讲述者(DM)的引导进行冒险。
-
自然语言处理(NLP): 这个项目使用了先进的NLP技术,如OpenAI的GPT模型,来生成逼真的对话和描述。
-
多代理系统: 这个模拟展示了多个AI代理如何协同工作,模拟复杂的交互场景。
-
对话管理:
DialogueSimulator
类展示了如何管理多方对话,包括发言顺序和消息传递。 -
提示工程: 代码中使用了精心设计的提示来引导语言模型生成特定风格和内容的输出。
-
可扩展性: 这个框架可以轻松扩展到更多玩家或不同类型的角色扮演场景。