File size: 24,322 Bytes
cfd3735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Multi-Player Dungeons & Dragons\n",
    "\n",
    "This notebook shows how the `DialogueAgent` and `DialogueSimulator` class make it easy to extend the [Two-Player Dungeons & Dragons example](https://python.langchain.com/en/latest/use_cases/agent_simulations/two_player_dnd.html) to multiple players.\n",
    "\n",
    "The main difference between simulating two players and multiple players is in revising the schedule for when each agent speaks\n",
    "\n",
    "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."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import LangChain related modules "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List, Dict, Callable\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "from langchain.schema import (\n",
    "    AIMessage,\n",
    "    HumanMessage,\n",
    "    SystemMessage,\n",
    "    BaseMessage,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DialogueAgent:\n",
    "    def __init__(\n",
    "        self,\n",
    "        name: str,\n",
    "        system_message: SystemMessage,\n",
    "        model: ChatOpenAI,\n",
    "    ) -> None:\n",
    "        self.name = name\n",
    "        self.system_message = system_message\n",
    "        self.model = model\n",
    "        self.prefix = f\"{self.name}: \"\n",
    "        self.reset()\n",
    "        \n",
    "    def reset(self):\n",
    "        self.message_history = [\"Here is the conversation so far.\"]\n",
    "\n",
    "    def send(self) -> str:\n",
    "        \"\"\"\n",
    "        Applies the chatmodel to the message history\n",
    "        and returns the message string\n",
    "        \"\"\"\n",
    "        message = self.model(\n",
    "            [\n",
    "                self.system_message,\n",
    "                HumanMessage(content=\"\\n\".join(self.message_history + [self.prefix])),\n",
    "            ]\n",
    "        )\n",
    "        return message.content\n",
    "\n",
    "    def receive(self, name: str, message: str) -> None:\n",
    "        \"\"\"\n",
    "        Concatenates {message} spoken by {name} into message history\n",
    "        \"\"\"\n",
    "        self.message_history.append(f\"{name}: {message}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DialogueSimulator:\n",
    "    def __init__(\n",
    "        self,\n",
    "        agents: List[DialogueAgent],\n",
    "        selection_function: Callable[[int, List[DialogueAgent]], int],\n",
    "    ) -> None:\n",
    "        self.agents = agents\n",
    "        self._step = 0\n",
    "        self.select_next_speaker = selection_function\n",
    "        \n",
    "    def reset(self):\n",
    "        for agent in self.agents:\n",
    "            agent.reset()\n",
    "\n",
    "    def inject(self, name: str, message: str):\n",
    "        \"\"\"\n",
    "        Initiates the conversation with a {message} from {name}\n",
    "        \"\"\"\n",
    "        for agent in self.agents:\n",
    "            agent.receive(name, message)\n",
    "\n",
    "        # increment time\n",
    "        self._step += 1\n",
    "\n",
    "    def step(self) -> tuple[str, str]:\n",
    "        # 1. choose the next speaker\n",
    "        speaker_idx = self.select_next_speaker(self._step, self.agents)\n",
    "        speaker = self.agents[speaker_idx]\n",
    "\n",
    "        # 2. next speaker sends message\n",
    "        message = speaker.send()\n",
    "\n",
    "        # 3. everyone receives message\n",
    "        for receiver in self.agents:\n",
    "            receiver.receive(speaker.name, message)\n",
    "\n",
    "        # 4. increment time\n",
    "        self._step += 1\n",
    "\n",
    "        return speaker.name, message"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define roles and quest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "character_names = [\"Harry Potter\", \"Ron Weasley\", \"Hermione Granger\", \"Argus Filch\"]\n",
    "storyteller_name = \"Dungeon Master\"\n",
    "quest = \"Find all of Lord Voldemort's seven horcruxes.\"\n",
    "word_limit = 50 # word limit for task brainstorming"
   ]
  },
  {
   "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",
    "        The characters are: {*character_names,}.\n",
    "        The story is narrated by the storyteller, {storyteller_name}.\"\"\"\n",
    "\n",
    "player_descriptor_system_message = SystemMessage(\n",
    "    content=\"You can add detail to the description of a Dungeons & Dragons player.\")\n",
    "\n",
    "def generate_character_description(character_name):\n",
    "    character_specifier_prompt = [\n",
    "        player_descriptor_system_message,\n",
    "        HumanMessage(content=\n",
    "            f\"\"\"{game_description}\n",
    "            Please reply with a creative description of the character, {character_name}, in {word_limit} words or less. \n",
    "            Speak directly to {character_name}.\n",
    "            Do not add anything else.\"\"\"\n",
    "            )\n",
    "    ]\n",
    "    character_description = ChatOpenAI(temperature=1.0)(character_specifier_prompt).content\n",
    "    return character_description\n",
    "\n",
    "def generate_character_system_message(character_name, character_description):\n",
    "    return SystemMessage(content=(\n",
    "    f\"\"\"{game_description}\n",
    "    Your name is {character_name}. \n",
    "    Your character description is as follows: {character_description}.\n",
    "    You will propose actions you plan to take and {storyteller_name} will explain what happens when you take those actions.\n",
    "    Speak in the first person from the perspective of {character_name}.\n",
    "    For describing your own body movements, wrap your description in '*'.\n",
    "    Do not change roles!\n",
    "    Do not speak from the perspective of anyone else.\n",
    "    Remember you are {character_name}.\n",
    "    Stop speaking the moment you finish speaking from your perspective.\n",
    "    Never forget to keep your response to {word_limit} words!\n",
    "    Do not add anything else.\n",
    "    \"\"\"\n",
    "    ))\n",
    "\n",
    "character_descriptions = [generate_character_description(character_name) for character_name in character_names]\n",
    "character_system_messages = [generate_character_system_message(character_name, character_description) for character_name, character_description in zip(character_names, character_descriptions)]\n",
    "\n",
    "storyteller_specifier_prompt = [\n",
    "    player_descriptor_system_message,\n",
    "    HumanMessage(content=\n",
    "        f\"\"\"{game_description}\n",
    "        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",
    "        )\n",
    "]\n",
    "storyteller_description = ChatOpenAI(temperature=1.0)(storyteller_specifier_prompt).content\n",
    "\n",
    "storyteller_system_message = SystemMessage(content=(\n",
    "f\"\"\"{game_description}\n",
    "You are the storyteller, {storyteller_name}. \n",
    "Your description is as follows: {storyteller_description}.\n",
    "The other players will propose actions to take and you will explain what happens when they take those actions.\n",
    "Speak in the first person from the perspective of {storyteller_name}.\n",
    "Do not change roles!\n",
    "Do not speak from the perspective of anyone else.\n",
    "Remember you are the storyteller, {storyteller_name}.\n",
    "Stop speaking the moment you finish speaking from your perspective.\n",
    "Never forget to keep your response to {word_limit} words!\n",
    "Do not add anything else.\n",
    "\"\"\"\n",
    "))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Storyteller Description:\n",
      "Dungeon Master, your power over this adventure is unparalleled. With your whimsical mind and impeccable storytelling, you guide us through the dangers of Hogwarts and beyond. We eagerly await your every twist, your every turn, in the hunt for Voldemort's cursed horcruxes.\n",
      "Harry Potter Description:\n",
      "\"Welcome, Harry Potter. You are the young wizard with a lightning-shaped scar on your forehead. You possess brave and heroic qualities that will be essential on this perilous quest. Your destiny is not of your own choosing, but you must rise to the occasion and destroy the evil horcruxes. The wizarding world is counting on you.\"\n",
      "Ron Weasley Description:\n",
      "Ron Weasley, you are Harry's loyal friend and a talented wizard. You have a good heart but can be quick to anger. Keep your emotions in check as you journey to find the horcruxes. Your bravery will be tested, stay strong and focused.\n",
      "Hermione Granger Description:\n",
      "Hermione Granger, you are a brilliant and resourceful witch, with encyclopedic knowledge of magic and an unwavering dedication to your friends. Your quick thinking and problem-solving skills make you a vital asset on any quest.\n",
      "Argus Filch Description:\n",
      "Argus Filch, you are a squib, lacking magical abilities. But you make up for it with your sharpest of eyes, roving around the Hogwarts castle looking for any rule-breaker to punish. Your love for your feline friend, Mrs. Norris, is the only thing that feeds your heart.\n"
     ]
    }
   ],
   "source": [
    "print('Storyteller Description:')\n",
    "print(storyteller_description)\n",
    "for character_name, character_description in zip(character_names, character_descriptions):\n",
    "    print(f'{character_name} Description:')\n",
    "    print(character_description)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Use an LLM to create an elaborate quest description"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original quest:\n",
      "Find all of Lord Voldemort's seven horcruxes.\n",
      "\n",
      "Detailed quest:\n",
      "Harry Potter and his companions must journey to the Forbidden Forest, find the hidden entrance to Voldemort's secret lair, and retrieve the horcrux guarded by the deadly Acromantula, Aragog. Remember, time is of the essence as Voldemort's power grows stronger every day. Good luck.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "quest_specifier_prompt = [\n",
    "    SystemMessage(content=\"You can make a task more specific.\"),\n",
    "    HumanMessage(content=\n",
    "        f\"\"\"{game_description}\n",
    "        \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 characters: {*character_names,}.\n",
    "        Do not add anything else.\"\"\"\n",
    "        )\n",
    "]\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\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Main Loop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "characters = []\n",
    "for character_name, character_system_message in zip(character_names, character_system_messages):\n",
    "    characters.append(DialogueAgent(\n",
    "        name=character_name,\n",
    "        system_message=character_system_message, \n",
    "        model=ChatOpenAI(temperature=0.2)))\n",
    "storyteller = DialogueAgent(name=storyteller_name,\n",
    "                     system_message=storyteller_system_message, \n",
    "                     model=ChatOpenAI(temperature=0.2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def select_next_speaker(step: int, agents: List[DialogueAgent]) -> int:\n",
    "    \"\"\"\n",
    "    If the step is even, then select the storyteller\n",
    "    Otherwise, select the other characters in a round-robin fashion.\n",
    "    \n",
    "    For example, with three characters with indices: 1 2 3\n",
    "    The storyteller is index 0.\n",
    "    Then the selected index will be as follows:\n",
    "\n",
    "    step: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16\n",
    "\n",
    "    idx:  0  1  0  2  0  3  0  1  0  2  0  3  0  1  0  2  0\n",
    "    \"\"\"\n",
    "    if step % 2 == 0:\n",
    "        idx = 0\n",
    "    else:\n",
    "        idx = (step//2) % (len(agents)-1) + 1\n",
    "    return idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(Dungeon Master): Harry Potter and his companions must journey to the Forbidden Forest, find the hidden entrance to Voldemort's secret lair, and retrieve the horcrux guarded by the deadly Acromantula, Aragog. Remember, time is of the essence as Voldemort's power grows stronger every day. Good luck.\n",
      "\n",
      "\n",
      "(Harry Potter): I suggest we sneak into the Forbidden Forest under the cover of darkness. Ron, Hermione, and I can use our wands to create a Disillusionment Charm to make us invisible. Filch, you can keep watch for any signs of danger. Let's move quickly and quietly.\n",
      "\n",
      "\n",
      "(Dungeon Master): As you make your way through the Forbidden Forest, you hear the eerie sounds of nocturnal creatures. Suddenly, you come across a clearing where Aragog and his spider minions are waiting for you. Ron, Hermione, and Harry, you must use your wands to cast spells to fend off the spiders while Filch keeps watch. Be careful not to get bitten!\n",
      "\n",
      "\n",
      "(Ron Weasley): I'll cast a spell to create a fiery blast to scare off the spiders. *I wave my wand and shout \"Incendio!\"* Hopefully, that will give us enough time to find the horcrux and get out of here safely.\n",
      "\n",
      "\n",
      "(Dungeon Master): Ron's spell creates a burst of flames, causing the spiders to scurry away in fear. You quickly search the area and find a small, ornate box hidden in a crevice. Congratulations, you have found one of Voldemort's horcruxes! But beware, the Dark Lord's minions will stop at nothing to get it back.\n",
      "\n",
      "\n",
      "(Hermione Granger): We need to destroy this horcrux as soon as possible. I suggest we use the Sword of Gryffindor to do it. Harry, do you still have it with you? We can use Fiendfyre to destroy it, but we need to be careful not to let the flames get out of control. Ron, can you help me create a protective barrier around us while Harry uses the sword?\n",
      "\n",
      "\n",
      "\n",
      "(Dungeon Master): Harry retrieves the Sword of Gryffindor from his bag and holds it tightly. Hermione and Ron cast a protective barrier around the group as Harry uses the sword to destroy the horcrux with a swift strike. The box shatters into a million pieces, and a dark energy dissipates into the air. Well done, but there are still six more horcruxes to find and destroy. The hunt continues.\n",
      "\n",
      "\n",
      "(Argus Filch): *I keep watch, making sure no one is following us.* I'll also keep an eye out for any signs of danger. Mrs. Norris, my trusty companion, will help me sniff out any trouble. We'll make sure the group stays safe while they search for the remaining horcruxes.\n",
      "\n",
      "\n",
      "(Dungeon Master): As you continue on your quest, Filch and Mrs. Norris alert you to a group of Death Eaters approaching. You must act quickly to defend yourselves. Harry, Ron, and Hermione, use your wands to cast spells while Filch and Mrs. Norris keep watch. Remember, the fate of the wizarding world rests on your success.\n",
      "\n",
      "\n",
      "(Harry Potter): I'll cast a spell to create a shield around us. *I wave my wand and shout \"Protego!\"* Ron and Hermione, you focus on attacking the Death Eaters with your spells. We need to work together to defeat them and protect the remaining horcruxes. Filch, keep watch and let us know if there are any more approaching.\n",
      "\n",
      "\n",
      "(Dungeon Master): Harry's shield protects the group from the Death Eaters' spells as Ron and Hermione launch their own attacks. The Death Eaters are no match for the combined power of the trio and are quickly defeated. You continue on your journey, knowing that the next horcrux could be just around the corner. Keep your wits about you, for the Dark Lord's minions are always watching.\n",
      "\n",
      "\n",
      "(Ron Weasley): I suggest we split up to cover more ground. Harry and I can search the Forbidden Forest while Hermione and Filch search Hogwarts. We can use our wands to communicate with each other and meet back up once we find a horcrux. Let's move quickly and stay alert for any danger.\n",
      "\n",
      "\n",
      "(Dungeon Master): As the group splits up, Harry and Ron make their way deeper into the Forbidden Forest while Hermione and Filch search the halls of Hogwarts. Suddenly, Harry and Ron come across a group of dementors. They must use their Patronus charms to fend them off while Hermione and Filch rush to their aid. Remember, the power of friendship and teamwork is crucial in this quest.\n",
      "\n",
      "\n",
      "(Hermione Granger): I hear Harry and Ron's Patronus charms from afar. We need to hurry and help them. Filch, can you use your knowledge of Hogwarts to find a shortcut to their location? I'll prepare a spell to repel the dementors. We need to work together to protect each other and find the next horcrux.\n",
      "\n",
      "\n",
      "\n",
      "(Dungeon Master): Filch leads Hermione to a hidden passageway that leads to Harry and Ron's location. Hermione's spell repels the dementors, and the group is reunited. They continue their search, knowing that every moment counts. The fate of the wizarding world rests on their success.\n",
      "\n",
      "\n",
      "(Argus Filch): *I keep watch as the group searches for the next horcrux.* Mrs. Norris and I will make sure no one is following us. We need to stay alert and work together to find the remaining horcruxes before it's too late. The Dark Lord's power grows stronger every day, and we must not let him win.\n",
      "\n",
      "\n",
      "(Dungeon Master): As the group continues their search, they come across a hidden room in the depths of Hogwarts. Inside, they find a locket that they suspect is another one of Voldemort's horcruxes. But the locket is cursed, and they must work together to break the curse before they can destroy it. Harry, Ron, and Hermione, use your combined knowledge and skills to break the curse while Filch and Mrs. Norris keep watch. Time is running out, and the fate of the wizarding world rests on your success.\n",
      "\n",
      "\n",
      "(Harry Potter): I'll use my knowledge of dark magic to try and break the curse on the locket. Ron and Hermione, you can help me by using your wands to channel your magic into mine. We need to work together and stay focused. Filch, keep watch and let us know if there are any signs of danger.\n",
      "Dungeon Master: Harry, Ron, and Hermione combine their magical abilities to break the curse on the locket. The locket opens, revealing a small piece of Voldemort's soul. Harry uses the Sword of Gryffindor to destroy it, and the group feels a sense of relief knowing that they are one step closer to defeating the Dark Lord. But there are still four more horcruxes to find and destroy. The hunt continues.\n",
      "\n",
      "\n",
      "(Dungeon Master): As the group continues their quest, they face even greater challenges and dangers. But with their unwavering determination and teamwork, they press on, knowing that the fate of the wizarding world rests on their success. Will they be able to find and destroy all of Voldemort's horcruxes before it's too late? Only time will tell.\n",
      "\n",
      "\n",
      "(Ron Weasley): We can't give up now. We've come too far to let Voldemort win. Let's keep searching and fighting until we destroy all of his horcruxes and defeat him once and for all. We can do this together.\n",
      "\n",
      "\n",
      "(Dungeon Master): The group nods in agreement, their determination stronger than ever. They continue their search, facing challenges and obstacles at every turn. But they know that they must not give up, for the fate of the wizarding world rests on their success. The hunt for Voldemort's horcruxes continues, and the end is in sight.\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "max_iters = 20\n",
    "n = 0\n",
    "\n",
    "simulator = DialogueSimulator(\n",
    "    agents=[storyteller] + characters,\n",
    "    selection_function=select_next_speaker\n",
    ")\n",
    "simulator.reset()\n",
    "simulator.inject(storyteller_name, specified_quest)\n",
    "print(f\"({storyteller_name}): {specified_quest}\")\n",
    "print('\\n')\n",
    "\n",
    "while n < max_iters:\n",
    "    name, message = simulator.step()\n",
    "    print(f\"({name}): {message}\")\n",
    "    print('\\n')\n",
    "    n += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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",
   "version": "3.9.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}