mirror of https://github.com/hwchase17/langchain
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
17 KiB
Plaintext
443 lines
17 KiB
Plaintext
1 year ago
|
{
|
||
|
"cells": [
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
|
"# Two-Player Dungeons & Dragons\n",
|
||
|
"\n",
|
||
1 year ago
|
"In this notebook, we show how we can use concepts from [CAMEL](https://www.camel-ai.org/) to simulate a role-playing game with a protagonist and a dungeon master. To simulate this game, we create an `DialogueSimulator` class that coordinates the dialogue between the two agents."
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
|
"## Import LangChain related modules "
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 1,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
1 year ago
|
"from typing import List, Dict, Callable\n",
|
||
1 year ago
|
"from langchain.chat_models import ChatOpenAI\n",
|
||
|
"from langchain.schema import (\n",
|
||
|
" HumanMessage,\n",
|
||
|
" SystemMessage,\n",
|
||
|
")"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
1 year ago
|
"## `DialogueAgent` class\n",
|
||
|
"The `DialogueAgent` class is a simple wrapper around the `ChatOpenAI` model that stores the message history from the `dialogue_agent`'s point of view by simply concatenating the messages as strings.\n",
|
||
|
"\n",
|
||
|
"It exposes two methods: \n",
|
||
|
"- `send()`: applies the chatmodel to the message history and returns the message string\n",
|
||
|
"- `receive(name, message)`: adds the `message` spoken by `name` to message history"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 2,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
1 year ago
|
"class DialogueAgent:\n",
|
||
1 year ago
|
" def __init__(\n",
|
||
|
" self,\n",
|
||
1 year ago
|
" name: str,\n",
|
||
1 year ago
|
" system_message: SystemMessage,\n",
|
||
|
" model: ChatOpenAI,\n",
|
||
|
" ) -> None:\n",
|
||
1 year ago
|
" self.name = name\n",
|
||
1 year ago
|
" self.system_message = system_message\n",
|
||
|
" self.model = model\n",
|
||
1 year ago
|
" self.prefix = f\"{self.name}: \"\n",
|
||
|
" self.reset()\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" def reset(self):\n",
|
||
|
" self.message_history = [\"Here is the conversation so far.\"]\n",
|
||
|
"\n",
|
||
1 year ago
|
" def send(self) -> str:\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" Applies the chatmodel to the message history\n",
|
||
|
" and returns the message string\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" message = self.model(\n",
|
||
1 year ago
|
" [\n",
|
||
|
" self.system_message,\n",
|
||
|
" HumanMessage(content=\"\\n\".join(self.message_history + [self.prefix])),\n",
|
||
|
" ]\n",
|
||
|
" )\n",
|
||
1 year ago
|
" return message.content\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" def receive(self, name: str, message: str) -> None:\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" Concatenates {message} spoken by {name} into message history\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" self.message_history.append(f\"{name}: {message}\")"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
1 year ago
|
"## `DialogueSimulator` class\n",
|
||
|
"The `DialogueSimulator` class takes a list of agents. At each step, it performs the following:\n",
|
||
|
"1. Select the next speaker\n",
|
||
|
"2. Calls the next speaker to send a message \n",
|
||
|
"3. Broadcasts the message to all other agents\n",
|
||
|
"4. Update the step counter.\n",
|
||
|
"The selection of the next speaker can be implemented as any function, but in this case we simply loop through the agents."
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 3,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
1 year ago
|
"class DialogueSimulator:\n",
|
||
|
" def __init__(\n",
|
||
|
" self,\n",
|
||
|
" agents: List[DialogueAgent],\n",
|
||
|
" selection_function: Callable[[int, List[DialogueAgent]], int],\n",
|
||
|
" ) -> None:\n",
|
||
1 year ago
|
" self.agents = agents\n",
|
||
|
" self._step = 0\n",
|
||
1 year ago
|
" self.select_next_speaker = selection_function\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" def reset(self):\n",
|
||
|
" for agent in self.agents:\n",
|
||
|
" agent.reset()\n",
|
||
|
"\n",
|
||
|
" def inject(self, name: str, message: str):\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" Initiates the conversation with a {message} from {name}\n",
|
||
1 year ago
|
" \"\"\"\n",
|
||
1 year ago
|
" for agent in self.agents:\n",
|
||
|
" agent.receive(name, message)\n",
|
||
1 year ago
|
"\n",
|
||
|
" # increment time\n",
|
||
|
" self._step += 1\n",
|
||
|
"\n",
|
||
1 year ago
|
" def step(self) -> tuple[str, str]:\n",
|
||
|
" # 1. choose the next speaker\n",
|
||
1 year ago
|
" speaker_idx = self.select_next_speaker(self._step, self.agents)\n",
|
||
|
" speaker = self.agents[speaker_idx]\n",
|
||
|
"\n",
|
||
1 year ago
|
" # 2. next speaker sends message\n",
|
||
|
" message = speaker.send()\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" # 3. everyone receives message\n",
|
||
|
" for receiver in self.agents:\n",
|
||
|
" receiver.receive(speaker.name, message)\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" # 4. increment time\n",
|
||
|
" self._step += 1\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
" return speaker.name, message"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
|
"## Define roles and quest"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 4,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
|
"protagonist_name = \"Harry Potter\"\n",
|
||
|
"storyteller_name = \"Dungeon Master\"\n",
|
||
|
"quest = \"Find all of Lord Voldemort's seven horcruxes.\"\n",
|
||
1 year ago
|
"word_limit = 50 # word limit for task brainstorming"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
|
"## Ask an LLM to add detail to the game description"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 5,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
|
"game_description = f\"\"\"Here is the topic for a Dungeons & Dragons game: {quest}.\n",
|
||
|
" There is one player in this game: the protagonist, {protagonist_name}.\n",
|
||
|
" The story is narrated by the storyteller, {storyteller_name}.\"\"\"\n",
|
||
|
"\n",
|
||
|
"player_descriptor_system_message = SystemMessage(\n",
|
||
1 year ago
|
" content=\"You can add detail to the description of a Dungeons & Dragons player.\"\n",
|
||
|
")\n",
|
||
1 year ago
|
"\n",
|
||
|
"protagonist_specifier_prompt = [\n",
|
||
|
" player_descriptor_system_message,\n",
|
||
1 year ago
|
" HumanMessage(\n",
|
||
|
" content=f\"\"\"{game_description}\n",
|
||
1 year ago
|
" Please reply with a creative description of the protagonist, {protagonist_name}, in {word_limit} words or less. \n",
|
||
|
" Speak directly to {protagonist_name}.\n",
|
||
|
" Do not add anything else.\"\"\"\n",
|
||
1 year ago
|
" ),\n",
|
||
1 year ago
|
"]\n",
|
||
1 year ago
|
"protagonist_description = ChatOpenAI(temperature=1.0)(\n",
|
||
|
" protagonist_specifier_prompt\n",
|
||
|
").content\n",
|
||
1 year ago
|
"\n",
|
||
|
"storyteller_specifier_prompt = [\n",
|
||
|
" player_descriptor_system_message,\n",
|
||
1 year ago
|
" HumanMessage(\n",
|
||
|
" content=f\"\"\"{game_description}\n",
|
||
1 year ago
|
" Please reply with a creative description of the storyteller, {storyteller_name}, in {word_limit} words or less. \n",
|
||
|
" Speak directly to {storyteller_name}.\n",
|
||
|
" Do not add anything else.\"\"\"\n",
|
||
1 year ago
|
" ),\n",
|
||
1 year ago
|
"]\n",
|
||
1 year ago
|
"storyteller_description = ChatOpenAI(temperature=1.0)(\n",
|
||
|
" storyteller_specifier_prompt\n",
|
||
|
").content"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 6,
|
||
|
"metadata": {},
|
||
|
"outputs": [
|
||
|
{
|
||
|
"name": "stdout",
|
||
|
"output_type": "stream",
|
||
|
"text": [
|
||
|
"Protagonist Description:\n",
|
||
1 year ago
|
"\"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?\"\n",
|
||
1 year ago
|
"Storyteller Description:\n",
|
||
1 year ago
|
"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.\n"
|
||
1 year ago
|
]
|
||
|
}
|
||
|
],
|
||
|
"source": [
|
||
1 year ago
|
"print(\"Protagonist Description:\")\n",
|
||
1 year ago
|
"print(protagonist_description)\n",
|
||
1 year ago
|
"print(\"Storyteller Description:\")\n",
|
||
1 year ago
|
"print(storyteller_description)"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
1 year ago
|
"## Protagonist and dungeon master system messages"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
1 year ago
|
"execution_count": 7,
|
||
1 year ago
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
1 year ago
|
"protagonist_system_message = SystemMessage(\n",
|
||
|
" content=(\n",
|
||
|
" f\"\"\"{game_description}\n",
|
||
1 year ago
|
"Never forget you are the protagonist, {protagonist_name}, and I am the storyteller, {storyteller_name}. \n",
|
||
|
"Your character description is as follows: {protagonist_description}.\n",
|
||
|
"You will propose actions you plan to take and I will explain what happens when you take those actions.\n",
|
||
|
"Speak in the first person from the perspective of {protagonist_name}.\n",
|
||
1 year ago
|
"For describing your own body movements, wrap your description in '*'.\n",
|
||
1 year ago
|
"Do not change roles!\n",
|
||
1 year ago
|
"Do not speak from the perspective of {storyteller_name}.\n",
|
||
|
"Do not forget to finish speaking by saying, 'It is your turn, {storyteller_name}.'\n",
|
||
|
"Do not add anything else.\n",
|
||
|
"Remember you are the protagonist, {protagonist_name}.\n",
|
||
|
"Stop speaking the moment you finish speaking from your perspective.\n",
|
||
1 year ago
|
"\"\"\"\n",
|
||
1 year ago
|
" )\n",
|
||
|
")\n",
|
||
1 year ago
|
"\n",
|
||
1 year ago
|
"storyteller_system_message = SystemMessage(\n",
|
||
|
" content=(\n",
|
||
|
" f\"\"\"{game_description}\n",
|
||
1 year ago
|
"Never forget you are the storyteller, {storyteller_name}, and I am the protagonist, {protagonist_name}. \n",
|
||
|
"Your character description is as follows: {storyteller_description}.\n",
|
||
|
"I will propose actions I plan to take and you will explain what happens when I take those actions.\n",
|
||
|
"Speak in the first person from the perspective of {storyteller_name}.\n",
|
||
1 year ago
|
"For describing your own body movements, wrap your description in '*'.\n",
|
||
1 year ago
|
"Do not change roles!\n",
|
||
1 year ago
|
"Do not speak from the perspective of {protagonist_name}.\n",
|
||
|
"Do not forget to finish speaking by saying, 'It is your turn, {protagonist_name}.'\n",
|
||
|
"Do not add anything else.\n",
|
||
|
"Remember you are the storyteller, {storyteller_name}.\n",
|
||
|
"Stop speaking the moment you finish speaking from your perspective.\n",
|
||
1 year ago
|
"\"\"\"\n",
|
||
1 year ago
|
" )\n",
|
||
|
")"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
1 year ago
|
"## Use an LLM to create an elaborate quest description"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
1 year ago
|
"execution_count": 8,
|
||
1 year ago
|
"metadata": {},
|
||
1 year ago
|
"outputs": [
|
||
|
{
|
||
|
"name": "stdout",
|
||
|
"output_type": "stream",
|
||
|
"text": [
|
||
|
"Original quest:\n",
|
||
|
"Find all of Lord Voldemort's seven horcruxes.\n",
|
||
|
"\n",
|
||
|
"Detailed quest:\n",
|
||
1 year ago
|
"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?\n",
|
||
1 year ago
|
"\n"
|
||
|
]
|
||
|
}
|
||
|
],
|
||
1 year ago
|
"source": [
|
||
1 year ago
|
"quest_specifier_prompt = [\n",
|
||
|
" SystemMessage(content=\"You can make a task more specific.\"),\n",
|
||
1 year ago
|
" HumanMessage(\n",
|
||
|
" content=f\"\"\"{game_description}\n",
|
||
1 year ago
|
" \n",
|
||
|
" You are the storyteller, {storyteller_name}.\n",
|
||
|
" Please make the quest more specific. Be creative and imaginative.\n",
|
||
|
" Please reply with the specified quest in {word_limit} words or less. \n",
|
||
|
" Speak directly to the protagonist {protagonist_name}.\n",
|
||
|
" Do not add anything else.\"\"\"\n",
|
||
1 year ago
|
" ),\n",
|
||
1 year ago
|
"]\n",
|
||
|
"specified_quest = ChatOpenAI(temperature=1.0)(quest_specifier_prompt).content\n",
|
||
|
"\n",
|
||
|
"print(f\"Original quest:\\n{quest}\\n\")\n",
|
||
|
"print(f\"Detailed quest:\\n{specified_quest}\\n\")"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "markdown",
|
||
|
"metadata": {},
|
||
|
"source": [
|
||
|
"## Main Loop"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
1 year ago
|
"execution_count": 16,
|
||
1 year ago
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
1 year ago
|
"protagonist = DialogueAgent(\n",
|
||
|
" name=protagonist_name,\n",
|
||
|
" system_message=protagonist_system_message,\n",
|
||
|
" model=ChatOpenAI(temperature=0.2),\n",
|
||
|
")\n",
|
||
|
"storyteller = DialogueAgent(\n",
|
||
|
" name=storyteller_name,\n",
|
||
|
" system_message=storyteller_system_message,\n",
|
||
|
" model=ChatOpenAI(temperature=0.2),\n",
|
||
|
")"
|
||
1 year ago
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
1 year ago
|
"execution_count": 17,
|
||
|
"metadata": {},
|
||
|
"outputs": [],
|
||
|
"source": [
|
||
|
"def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:\n",
|
||
|
" idx = step % len(agents)\n",
|
||
|
" return idx"
|
||
|
]
|
||
|
},
|
||
|
{
|
||
|
"cell_type": "code",
|
||
|
"execution_count": 18,
|
||
1 year ago
|
"metadata": {},
|
||
|
"outputs": [
|
||
|
{
|
||
|
"name": "stdout",
|
||
|
"output_type": "stream",
|
||
|
"text": [
|
||
1 year ago
|
"(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?\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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.\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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?\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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.\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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?\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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.\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n",
|
||
1 year ago
|
"(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.\n",
|
||
1 year ago
|
"\n",
|
||
|
"\n"
|
||
|
]
|
||
|
}
|
||
|
],
|
||
|
"source": [
|
||
1 year ago
|
"max_iters = 6\n",
|
||
1 year ago
|
"n = 0\n",
|
||
|
"\n",
|
||
1 year ago
|
"simulator = DialogueSimulator(\n",
|
||
1 year ago
|
" agents=[storyteller, protagonist], selection_function=select_next_speaker\n",
|
||
1 year ago
|
")\n",
|
||
|
"simulator.reset()\n",
|
||
|
"simulator.inject(storyteller_name, specified_quest)\n",
|
||
1 year ago
|
"print(f\"({storyteller_name}): {specified_quest}\")\n",
|
||
1 year ago
|
"print(\"\\n\")\n",
|
||
1 year ago
|
"\n",
|
||
|
"while n < max_iters:\n",
|
||
1 year ago
|
" name, message = simulator.step()\n",
|
||
|
" print(f\"({name}): {message}\")\n",
|
||
1 year ago
|
" print(\"\\n\")\n",
|
||
1 year ago
|
" n += 1"
|
||
|
]
|
||
|
}
|
||
|
],
|
||
|
"metadata": {
|
||
|
"kernelspec": {
|
||
|
"display_name": "Python 3 (ipykernel)",
|
||
|
"language": "python",
|
||
|
"name": "python3"
|
||
|
},
|
||
|
"language_info": {
|
||
|
"codemirror_mode": {
|
||
|
"name": "ipython",
|
||
|
"version": 3
|
||
|
},
|
||
|
"file_extension": ".py",
|
||
|
"mimetype": "text/x-python",
|
||
|
"name": "python",
|
||
|
"nbconvert_exporter": "python",
|
||
|
"pygments_lexer": "ipython3",
|
||
1 year ago
|
"version": "3.9.16"
|
||
1 year ago
|
}
|
||
|
},
|
||
|
"nbformat": 4,
|
||
|
"nbformat_minor": 2
|
||
|
}
|