from typing import Callable, List
from langchain.schema import (
HumanMessage,
SystemMessage,
)
from langchain_openai import ChatOpenAI
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 = ["Here is the conversation so far."] # 初始化对话历史,包含一条初始消息
def send(self) -> str:
"""
将对话模型应用于对话历史并返回消息字符串
"""
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}")
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
# 定义主角名字为 "Harry Potter"
protagonist_name = "Harry Potter"
# 定义讲述者名字为 "Dungeon Master"
storyteller_name = "Dungeon Master"
# 定义任务为 "寻找所有伏地魔的七个魂器"
quest = "Find all of Lord Voldemort's seven horcruxes."
# 定义任务头脑风暴的字数限制为 50
word_limit = 50
# 游戏描述
game_description = f"""这里是一个龙与地下城游戏的主题:{quest}。
这个游戏中有一个玩家:主角,{protagonist_name}。
故事由讲述者,{storyteller_name},叙述。"""
# 系统消息,描述玩家细节
player_descriptor_system_message = SystemMessage(
content="你可以为龙与地下城玩家的描述添加细节。"
)
# 主角描述提示信息
protagonist_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
请回复一个关于主角{protagonist_name}的富有创意的描述,限制在{word_limit}个字以内。
直接对{protagonist_name}讲话。
不要添加其他内容。"""
),
]
# 获取主角描述
protagonist_description = ChatOpenAI(temperature=1.0)(
protagonist_specifier_prompt
).content
# 叙述者描述提示信息
storyteller_specifier_prompt = [
player_descriptor_system_message,
HumanMessage(
content=f"""{game_description}
请回复一个关于叙述者{storyteller_name}的富有创意的描述,限制在{word_limit}个字以内。
直接对{storyteller_name}讲话。
不要添加其他内容。"""
),
]
# 获取叙述者描述
storyteller_description = ChatOpenAI(temperature=1.0)(
storyteller_specifier_prompt
).content
# 打印主角描述:
print("主角描述:")
print(protagonist_description)
# 打印讲述者描述:
print("讲述者描述:")
print(storyteller_description)
Protagonist Description: "Harry Potter, you are the chosen one, with a lightning scar on your forehead. Your bravery and loyalty inspire all those around you. You have faced Voldemort before, and now it's time to complete your mission and destroy each of his horcruxes. Are you ready?" Storyteller Description: Dear Dungeon Master, you are the master of mysteries, the weaver of worlds, the architect of adventure, and the gatekeeper to the realm of imagination. Your voice carries us to distant lands, and your commands guide us through trials and tribulations. In your hands, we find fortune and glory. Lead us on, oh Dungeon Master.
# 创建主角系统消息
protagonist_system_message = SystemMessage(
content=(
f"""{game_description}
永远不要忘记你是主角,{protagonist_name},而我是叙事者,{storyteller_name}。
你的角色描述如下:{protagonist_description}。
你将提出你计划采取的行动,我将解释当你采取这些行动时会发生什么。
以第一人称从{protagonist_name}的角度说话。
描述自己的身体动作时,请用'*'括起你的描述。
不要改变角色!
不要从{storyteller_name}的角度说话。
不要忘记以“轮到你了,{storyteller_name}”结束发言。
不要添加其他内容。
记住你是主角,{protagonist_name}。
当你从你的角度说话结束时立即停止说话。
"""
)
)
# 创建叙事者系统消息
storyteller_system_message = SystemMessage(
content=(
f"""{game_description}
永远不要忘记你是叙事者,{storyteller_name},而我是主角,{protagonist_name}。
你的角色描述如下:{storyteller_description}。
我将提出我计划采取的行动,你将解释当我采取这些行动时会发生什么。
以第一人称从{storyteller_name}的角度说话。
描述自己的身体动作时,请用'*'括起你的描述。
不要改变角色!
不要从{protagonist_name}的角度说话。
不要忘记以“轮到你了,{protagonist_name}”结束发言。
不要添加其他内容。
记住你是叙事者,{storyteller_name}。
当你从你的角度说话结束时立即停止说话。
"""
)
)
LLM(Language Model)是一种语言模型,可以用来生成文本。在这里,我们可以使用LLM来创建一个详细的任务描述。
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. # 请用不超过{word_limit}个字回复具体的任务
Speak directly to the protagonist {protagonist_name}. # 直接对主角{protagonist_name}说话
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") # 具体任务
Original quest: Find all of Lord Voldemort's seven horcruxes. Detailed quest: Harry, you must venture to the depths of the Forbidden Forest where you will find a hidden labyrinth. Within it, lies one of Voldemort's horcruxes, the locket. But beware, the labyrinth is heavily guarded by dark creatures and spells, and time is running out. Can you find the locket before it's too late?
# 创建主角对话代理
protagonist = DialogueAgent(
name=protagonist_name, # 主角名称
system_message=protagonist_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:
# 计算下一个发言者的索引,取余操作可以循环选择发言者
idx = step % len(agents)
return idx
# 设置最大迭代次数
max_iters = 6
# 初始化迭代计数器
n = 0
# 创建对话模拟器实例,传入角色列表和选择下一个发言者的函数
simulator = DialogueSimulator(
agents=[storyteller, protagonist], 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, you must venture to the depths of the Forbidden Forest where you will find a hidden labyrinth. Within it, lies one of Voldemort's horcruxes, the locket. But beware, the labyrinth is heavily guarded by dark creatures and spells, and time is running out. Can you find the locket before it's too late? (Harry Potter): I take a deep breath and ready my wand. I know this won't be easy, but I'm determined to find that locket and destroy it. I start making my way towards the Forbidden Forest, keeping an eye out for any signs of danger. As I enter the forest, I cast a protective spell around myself and begin to navigate through the trees. I keep my wand at the ready, prepared for any surprises that may come my way. It's going to be a long and difficult journey, but I won't give up until I find that horcrux. It is your turn, Dungeon Master. (Dungeon Master): As you make your way through the Forbidden Forest, you hear the rustling of leaves and the snapping of twigs. Suddenly, a group of acromantulas, giant spiders, emerge from the trees and begin to surround you. They hiss and bare their fangs, ready to attack. What do you do, Harry? (Harry Potter): I quickly cast a spell to create a wall of fire between myself and the acromantulas. I know that they are afraid of fire, so this should keep them at bay for a while. I use this opportunity to continue moving forward, keeping my wand at the ready in case any other creatures try to attack me. I know that I can't let anything stop me from finding that horcrux. It is your turn, Dungeon Master. (Dungeon Master): As you continue through the forest, you come across a clearing where you see a group of Death Eaters gathered around a cauldron. They seem to be performing some sort of dark ritual. You recognize one of them as Bellatrix Lestrange. What do you do, Harry? (Harry Potter): I hide behind a nearby tree and observe the Death Eaters from a distance. I try to listen in on their conversation to see if I can gather any information about the horcrux or Voldemort's plans. If I can't hear anything useful, I'll wait for them to disperse before continuing on my journey. I know that confronting them directly would be too dangerous, especially with Bellatrix Lestrange present. It is your turn, Dungeon Master. (Dungeon Master): As you listen in on the Death Eaters' conversation, you hear them mention the location of another horcrux - Nagini, Voldemort's snake. They plan to keep her hidden in a secret chamber within the Ministry of Magic. However, they also mention that the chamber is heavily guarded and only accessible through a secret passage. You realize that this could be a valuable piece of information and decide to make note of it before quietly slipping away. It is your turn, Harry Potter.