{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "qNH7XS_2whrD" }, "source": [ "# Murder Mystery Game with LLM Agents\n", "\n", "## Overview\n", "This tutorial demonstrates how to create a game environment featuring autonomous LLM agents that take part in the game, using LangGraph, a framework for creating workflows with language models. The project produces a game that can be played by either a human or an LLM Agent.\n", "\n", "## Motivation\n", "Creating autonomous agents that interact with a game environment has always been a topic of great interest, and now, we can utilize LLMs as the agents. This kind of work is also interesting from a robotics perspective.\n", "\n", "## Murder Mystery Game Description\n", "\"Murder Mystery\" is an interactive text-based detective game powered by Large Language Models (LLMs).\n", "\n", "In this engaging experience, a human player steps into the shoes of Sherlock Holmes to solve procedurally generated murder mysteries. The game creates unique scenarios each time you play.\n", "\n", "## Our Inspiration\n", "\"Murder Mystery\" is an interactive detective game inspired by the recent paper \"UNBOUNDED: A Generative Infinite Game of Character Life Simulation\" (Li et al., 2024). While UNBOUNDED introduced the concept of generative infinite games through character life simulation, we adapt their core principles to create an engaging murder mystery experience.\n", "\n", "Like UNBOUNDED, our game leverages large language models and generative AI to create dynamic, open-ended gameplay that transcends traditional hard-coded game mechanics. Whereas UNBOUNDED allows players to interact with autonomous virtual characters across various environments, our game applies similar technical principles to enable players to step into Sherlock Holmes' shoes and solve procedurally generated murder mysteries.\n", "\n", "We wanted to make a tutorial for including LLM Agents inside game environments. Prior to creating this tutorial, we couldn't find a good resource that explains how to achieve this, so we made this tutorial! Additionally, we couldn't find projects that use LLM Agents inside games implemented with LangGraph.\n", "\n", "## Architecture\n", "We use two LangGraphs to build the infinite game mechanics for Murder Mystery Agent.\n", "\n", "1. Game Loop Graph: This is the main Graph which orchestrates the game creation and game loop. This Graph is responsible for:\n", " * Creating the game characters/NPCs\n", " * Creating the storyline\n", " * Introducing the Players/Users to the game\n", " * Game Mechanics of Character Interviews (through Conversation Sub Graph) and Guessing the Killer\n", "\n", "2. Conversation Sub-graph: This is the Sub Graph which orchestrates the Infinite Conversation Mechanics of the game. This graph allows the Players/Users to interview the different Characters in the game to identify the Killer. The Graph also supports a Sherlock AI assistant that can automate the interview process and assist the Players/Users with their investigation. The Sherlock AI assistant has access to Clues which can aid the Players with their investigation.\n", "\n", "The two Graphs together form the complete game mechanics of Murder Mystery Agent.\n", "\n", "We have also used display functions which contain basic game logic (which can also be standalone functions). These display functions are responsible for creating a basic UI for the game and allow Players/Users to experience the game in this notebook.\n", "\n", "## Key Components\n", "1. State Management: Utilizes `GenerateGameState`, `ConversationState` classes to manage the game state and the textual conversation state\n", "2. Language Model: Employs ChatOpenAI (GPT-4o) for the backstory generation, interactable characters, and (optionally) the LLM investigator protagonist agent\n", "3. Gameplay Features:\n", " * LLM generated character backstory and story\n", " * Talk with LLM Agent characters (Human/Sherlock LLM Agent)\n", " * Pick the killer\n", "4. Interactive UI\n", "5. LangGraph Workflow: Orchestrates the composition process using 2 state graphs:\n", " * Conversation sub-graph\n", " * Game Loop graph\n", "\n", "## Method\n", "The system has 2 main phases:\n", "\n", "Game Setup:\n", "1. User inputs an initial environment string (e.g., \"Urban city\"), amount of players, and the number of guesses the investigator can make\n", "2. Plot and character backstory generated by an LLM\n", "3. One character is set as the murderer and the others are innocent\n", "\n", "Game Loop:\n", "Human/LLM Agent acts as the investigator. It can make 2 actions: talk to characters in the scene and guess which of the characters is the killer.\n", "\n", "Talking to characters maintains a conversation with an LLM Agent with memory.\n", "\n", "The entire process is orchestrated using LangGraph, which manages the flow of information between different components and ensures that each step builds upon the previous ones.\n", "\n", "## A Beginner Friendly Understanding of our Approach\n", "\n", "Below is a step by step guide to our thought process as we built Murder Mystery Agent, it is similar to directing a 'Play':\n", "\n", "### 1. Setting the Stage\n", "* First, you decide on your story's world - it could be anywhere from a bustling office to a mystical forest\n", "* Choose how many characters will populate your story\n", "* Determine how challenging you want the mystery to be by setting the number of guesses\n", "\n", "### 2. Creating the Cast\n", "* Rather than writing every character's background yourself, we let AI create rich, interconnected characters\n", "* Each character has their own personality, motives, and relationship to the story\n", "* One character becomes the victim, another the killer, while others become suspects\n", "\n", "### 3. Writing the Story\n", "* The AI weaves together a unique murder mystery incorporating your setting and characters\n", "* Every story element is connected, making the mystery solvable through careful investigation\n", "* Each playthrough creates a completely new narrative\n", "\n", "### 4. Designing Interactions\n", "* Players need ways to investigate - like interviewing suspects or examining evidence\n", "* The game tracks these interactions, remembering what each character has said\n", "* Players can either ask their own questions or get help from SherlockAI\n", "\n", "### 5. Making it Dynamic\n", "* The conversation system allows for natural back-and-forth dialogue\n", "* Characters remember previous interactions and maintain consistent stories\n", "* This creates an immersive experience where every question could lead to a vital clue\n", "\n", "### 6. Player Experience\n", "* A clean, intuitive interface helps players focus on solving the mystery\n", "* Clear feedback shows players how their investigation is progressing\n", "* The game guides players while still leaving room for deduction and reasoning\n", "\n", "The beauty of this system is that it combines human creativity (in designing the framework) with AI capabilities (in creating unique content) to produce an endless variety of mysteries. Each game is different, but all follow the same core principles of good detective fiction - observation, deduction, and the thrill of discovery.\n", "\n", "This modular approach means you can easily modify any aspect without breaking the others. Want more suspects? Just adjust the character count. Prefer a different setting? Simply change the environment. The system adapts while maintaining the core mystery-solving experience.\n", "\n", "## Conclusion\n", "This notebook demonstrates the great potential LLM agents can have in a game, either by utilizing their rich text generation abilities or making them play an active role in a dynamic system. This can be used to make compelling NPC AI, for example." ] }, { "cell_type": "markdown", "metadata": { "id": "397P2AuyvOJq" }, "source": [ "
\n", "\n", "\n", "
\n" ] }, { "cell_type": "markdown", "source": [ "## Beginner's Guide to Using This Notebook" ], "metadata": { "id": "9M3jgJYVgBsp" } }, { "cell_type": "markdown", "source": [ "### Prerequisites\n", "Before you begin, you'll need:\n", "1. A Google Colab account or Jupyter Notebook environment\n", "2. Basic understanding of Python\n", "3. An OpenAI API key (for GPT-4o access)\n", "\n", "### Structure of the Notebook\n", "This notebook is organized into several key sections:\n", "\n", "1. **Setup & Installation**\n", " - Installing required libraries\n", " - Importing necessary packages\n", " - Setting up API keys\n", "\n", "2. **Display Functions**\n", " - These create the user interface\n", " - Handle game interactions and visual elements\n", "\n", "3. **Core Game Components**\n", " - State management classes\n", " - Game logic functions\n", " - Character and story generation\n", "\n", "4. **Game Workflow**\n", " - Conversation graphs\n", " - Game loop implementation\n", "\n", "### How to Use This Notebook\n", "\n", "#### Step 1: Setting Up\n", "1. Open the notebook in Google Colab or your Jupyter environment\n", "2. Run the installation cell to install required packages:\n", " ```python\n", " !pip install -q -U langchain-cli langchain langchain_core langgraph langchain_community\n", " !pip install -q openai langchain_openai\n", " ```\n", "\n", "#### Step 2: API Key Configuration\n", "1. You'll need to set up your OpenAI API key\n", "2. The notebook will prompt you to enter it when needed\n", "3. Alternative methods:\n", " ```python\n", " os.environ[\"OPENAI_API_KEY\"] = \"your-api-key-here\"\n", " ```\n", "\n", "#### Step 3: Running the Game\n", "1. Execute cells in order from top to bottom\n", "2. When you reach the game play section, input:\n", " - Number of characters (e.g., 5)\n", " - Number of guesses (e.g., 3)\n", " - Environment (e.g., \"Mistral office in Paris\")\n", "\n", "#### Step 4: Playing the Game\n", "1. Read the narrative provided by Dr. Watson\n", "2. Choose characters to interview\n", "3. Select between:\n", " - Using SherlockAI to ask questions\n", " - Asking your own questions\n", "4. Make your deductions and try to identify the killer\n", "\n", "### Common Issues and Solutions\n", "\n", "#### API Key Issues\n", "If you see API key errors:\n", "- Ensure you have a valid OpenAI API key\n", "- Check if the key is correctly set in the environment\n", "- Verify your API key has access to GPT-4o\n", "\n", "#### Runtime Errors\n", "If you encounter runtime errors:\n", "- Make sure all cells are executed in order\n", "- Verify all required packages are installed\n", "- Check your Python environment matches requirements\n", "\n", "#### Game Flow Issues\n", "If the game doesn't progress properly:\n", "- Ensure all cells executed successfully\n", "- Check for any error messages in previous cells\n", "- Verify input formats match expected values\n", "\n", "### Customizing the Game\n", "You can modify these parameters to change the game experience:\n", "- `max_characters`: Number of characters in the story\n", "- `num_guesses_left`: Number of attempts to identify the killer\n", "- `environment`: Setting for the murder mystery\n", "\n", "Example:\n", "```python\n", "max_characters = 5\n", "num_guesses_left = 3\n", "environment = \"Mistral office in Paris\"\n", "```\n", "\n", "### Tips for Success\n", "1. Read all character descriptions carefully\n", "2. Use SherlockAI for structured questioning\n", "3. Take notes on character responses\n", "4. Look for inconsistencies in statements\n", "5. Consider motives and opportunities\n", "\n", "### Additional Resources\n", "- LangChain documentation for understanding the framework\n", "- OpenAI API documentation for GPT-4o integration\n", "- Python basics tutorials if needed\n", "\n", "Remember, the goal is to solve the mystery by gathering information through interviews and making logical deductions. Good luck, detective!" ], "metadata": { "id": "josFOJwffq8-" } }, { "cell_type": "markdown", "metadata": { "id": "GUXGU5ASlOX4" }, "source": [ "## Install Required Libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Gs6LX3hWlLBn" }, "outputs": [], "source": [ "!pip install -q -U langchain-cli langchain langchain_core langgraph langchain_community\n", "!pip install -q openai langchain_openai" ] }, { "cell_type": "markdown", "metadata": { "id": "LYOpILW-UufL" }, "source": [ "## Import Libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "hKcFB0goUwwE" }, "outputs": [], "source": [ "# Python imports\n", "import os\n", "import random\n", "from typing import List, Optional, Annotated, Sequence\n", "from typing_extensions import TypedDict\n", "import operator\n", "\n", "from pydantic import BaseModel, Field\n", "\n", "import openai\n", "import getpass\n", "from google.colab import userdata\n", "\n", "# LangChain imports\n", "from langchain_openai import ChatOpenAI\n", "from langchain_core.messages import (\n", " AIMessage,\n", " BaseMessage,\n", " ChatMessage,\n", " FunctionMessage,\n", " HumanMessage,\n", " SystemMessage,\n", ")\n", "from langgraph.graph import START, END, StateGraph\n", "from langgraph.checkpoint.memory import MemorySaver\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "\n", "# Display imports\n", "from IPython.display import display, Markdown, HTML, Image\n", "from rich import print as rprint\n", "from rich.panel import Panel\n", "from rich.console import Console\n", "from rich.layout import Layout\n", "from rich.style import Style\n", "from rich.prompt import Prompt\n", "from rich.box import HEAVY_EDGE\n", "from rich.table import Table\n", "from rich.text import Text\n", "\n", "# Load environment variables and set OpenAI API key\n", "try:\n", " os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n", "except:\n", " os.environ[\"OPENAI_API_KEY\"] = getpass.getpass(f\"OPENAI_API_KEY: \") # Fallback if env variable is missing, ask user to input key." ] }, { "cell_type": "markdown", "metadata": { "id": "zTaZEZ5uGA1e" }, "source": [ "## Display Functions (Not Necessary for Core Functionallity)" ] }, { "cell_type": "markdown", "source": [ "Define the display functions and logic to provide a interactive UI." ], "metadata": { "id": "D1ansxiBS7BL" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "pBjWxv76GFjX" }, "outputs": [], "source": [ "def print_game_header():\n", " \"\"\"\n", " Displays the main header for the Murder Mystery Investigation game using HTML formatting.\n", " \"\"\"\n", " display(HTML(\"\"\"\n", "

\n", " 🕵️‍♂️ MURDER MYSTERY INVESTIGATION 🔍\n", "

\n", " \"\"\"))\n", "\n", "def print_narration(narration):\n", " \"\"\"\n", " Prints the narration dialogue from Dr. John Watson with styled formatting.\n", "\n", " Args:\n", " narration: The LLM response object containing the narration content in its 'content' attribute.\n", " \"\"\"\n", " console = Console()\n", " console.print(Panel(\n", " f\"[bold] Dr. John Watson [/bold]:\\n\\n{narration.content}\",\n", " border_style=\"blue\",\n", " padding=(1, 2),\n", " title=\"💬 Dialogue\",\n", " title_align=\"left\"\n", " ))\n", " console.rule(style=\"blue\")\n", "\n", "def print_introduction(character, narration):\n", " \"\"\"\n", " Displays the introduction dialogue for a character with styled formatting.\n", "\n", " Args:\n", " character: Character object containing character information.\n", " narration: LLM response object containing the narration content in its 'content' attribute.\n", " \"\"\"\n", " console = Console()\n", " console.rule(f\"[bold blue]Conversation with {character.name}[/bold blue]\", style=\"blue\")\n", " console.print(Panel(\n", " f\"[bold]{character.name}[/bold]:\\n\\n{narration.content}\",\n", " border_style=\"blue\",\n", " padding=(1, 2),\n", " title=\"💬 Dialogue\",\n", " title_align=\"left\"\n", " ))\n", " console.rule(style=\"blue\")\n", "\n", "def get_player_input(character_name):\n", " \"\"\"\n", " Prompts the player for input during character interactions.\n", "\n", " Args:\n", " character_name (str): Name of the character being questioned.\n", "\n", " Returns:\n", " str: The player's input question or 'EXIT' to end the conversation.\n", " \"\"\"\n", " console = Console()\n", "\n", " # Show input instructions\n", " console.print(Panel(\n", " f\"[bold blue]Ask your question to {character_name}[/bold blue]\\n\"\n", " f\"[dim]Type 'EXIT' to end conversation[/dim]\",\n", " box=HEAVY_EDGE,\n", " border_style=\"blue\",\n", " padding=(1, 2),\n", " title=\"💭 Your Question\",\n", " title_align=\"left\"\n", " ))\n", "\n", " # Custom prompt with styling\n", " question = Prompt.ask(\n", " \"[bold yellow]Detective[/bold yellow]\",\n", " default=\"\",\n", " show_default=False\n", " )\n", "\n", " # Echo the input in a panel for better visibility\n", " if question.lower() != 'exit':\n", " console.print(Panel(\n", " f\"[italic]{question}[/italic]\",\n", " border_style=\"yellow\",\n", " padding=(1, 1),\n", " title=\"🔍 Asked\",\n", " title_align=\"left\"\n", " ))\n", "\n", " return question\n", "\n", "def print_character_answer(character, reaction):\n", " \"\"\"\n", " Displays a character's answer with styled formatting.\n", "\n", " Args:\n", " character: Character object containing character information.\n", " reaction (str): The character's response or reaction as generated from the LLM.\n", " \"\"\"\n", " console = Console()\n", " console.print(Panel(\n", " f\"[bold]{character.name}'s Answer[/bold]:\\n\\n[italic]{reaction}[/italic]\",\n", " border_style=\"cyan\",\n", " padding=(1, 2),\n", " title=\"🗣️ Answer\",\n", " title_align=\"left\"\n", " ))\n", "\n", "def print_characters_list(characters):\n", " \"\"\"\n", " Displays a formatted table of all characters in the game with their backgrounds.\n", "\n", " Args:\n", " characters (list): List of character objects containing name, role, and backstory.\n", "\n", " Returns:\n", " dict: Mapping of displayed positions to original character indices.\n", " \"\"\"\n", " console = Console()\n", "\n", " # Create title\n", " console.print(\"\\n[bold blue]CHARACTERS[/bold blue]\", justify=\"center\")\n", "\n", " # Create list of indices and characters\n", " char_list = list(enumerate(characters))\n", " random.shuffle(char_list)\n", "\n", " # Create and populate table\n", " table = Table(\n", " show_header=True,\n", " header_style=\"bold magenta\",\n", " box=HEAVY_EDGE,\n", " expand=True\n", " )\n", "\n", " table.add_column(\"#\", style=\"dim\", width=4)\n", " table.add_column(\"Name\", style=\"bold cyan\", width=20)\n", " table.add_column(\"Background\", style=\"green\")\n", "\n", " # Create mapping of displayed position to original index\n", " display_to_original = {}\n", "\n", " for display_pos, (orig_idx, character) in enumerate(char_list):\n", " # Add victim note to name if applicable\n", " name_text = f\"{character.name} {'[red](victim)[/red]' if character.role == 'Victim' else ''}\"\n", "\n", " table.add_row(\n", " str(display_pos + 1),\n", " name_text,\n", " Text(character.backstory, overflow=\"fold\")\n", " )\n", " display_to_original[display_pos] = orig_idx\n", " # Add border between rows\n", " if display_pos < len(char_list) - 1:\n", " table.add_row(style=\"dim\")\n", "\n", " console.print(table)\n", " return display_to_original\n", "\n", "def get_character_selection(characters, display_to_original):\n", " \"\"\"\n", " Handles the player's character selection for investigation during the game.\n", "\n", " Args:\n", " characters (list): List of character objects.\n", " display_to_original (dict): Mapping of displayed positions to original indices.\n", "\n", " Returns:\n", " dict: Contains selected_character_id (None if player chooses to guess the killer).\n", "\n", " Note:\n", " Returns -1 when player wants to guess the killer.\n", " Validates input and prevents selection of the victim.\n", " \"\"\"\n", " console = Console()\n", "\n", " while True:\n", " try:\n", " # Create selection prompt\n", " console.print(Panel(\n", " \"[bold blue]Enter the number of the character to investigate[/bold blue]\\n\"\n", " \"[dim]Enter -1 to Guess the Killer[/dim]\",\n", " border_style=\"blue\",\n", " title=\"👤 Selection\",\n", " title_align=\"left\"\n", " ))\n", "\n", " # Get user input\n", " choice = Prompt.ask(\n", " \"[bold yellow]Detective[/bold yellow]\",\n", " default=\"-1\",\n", " show_default=False\n", " )\n", "\n", " # Convert to int and validate\n", " choice = int(choice)\n", "\n", " if choice == -1:\n", " return {\"selected_character_id\": None}\n", "\n", " if 0 < choice <= len(characters):\n", " # Map displayed choice to original index\n", " original_idx = display_to_original[choice - 1]\n", " selected_character = characters[original_idx]\n", "\n", " if selected_character.role == 'victim':\n", " console.print(\"[red]Invalid input. You are unable to choose the victim[/red]\")\n", " continue\n", "\n", " # Show selection confirmation\n", " console.print(f\"You have selected {selected_character.name}\")\n", " return {\"selected_character_id\": original_idx}\n", "\n", " console.print(\"[red]Invalid input. Please enter a number within the range or -1.[/red]\")\n", "\n", " except ValueError:\n", " console.print(\"[red]Invalid input. Please enter a number.[/red]\")\n", " except KeyError:\n", " console.print(\"[red]Invalid selection. Please try again.[/red]\")\n", "\n", "def get_player_yesno_answer(question):\n", " \"\"\"\n", " Prompts the player for a yes/no response regarding Sherlock AI assistance.\n", "\n", " Args:\n", " question (str): The player instruction/question to display to the player.\n", "\n", " Returns:\n", " str: Player's response ('y' for yes, 'n' for no/exit)\n", " \"\"\"\n", " console = Console()\n", "\n", " # Show input instructions\n", " console.print(Panel(\n", " f\"[bold blue]{question}[/bold blue]\\n\"\n", " f\"enter 'y' to get his help or 'n' to ask by yourself or exit\",\n", " box=HEAVY_EDGE,\n", " border_style=\"blue\",\n", " padding=(1, 2),\n", " title=\"🤖🕵️ Sherlock AI\",\n", " title_align=\"left\"\n", " ))\n", "\n", " # Custom prompt with styling\n", " answer = Prompt.ask(\n", " \"[bold yellow]Detective[/bold yellow]\",\n", " default=\"\",\n", " show_default=False\n", " )\n", " return answer\n", "\n", "def print_suspect_list(characters):\n", " \"\"\"\n", " Displays a formatted table of all suspects in the investigation.\n", "\n", " Args:\n", " characters (list): List of character objects to be displayed as suspects.\n", " \"\"\"\n", " console = Console()\n", "\n", " # Create and populate table\n", " table = Table(\n", " show_header=True,\n", " header_style=\"bold bright_red\",\n", " box=HEAVY_EDGE,\n", " expand=True,\n", " title=\"[bold bright_red]🔍 Suspects[/bold bright_red]\"\n", " )\n", "\n", " table.add_column(\"#\", style=\"dim\", width=4)\n", " table.add_column(\"Name\", style=\"bold bright_red\")\n", "\n", " # Sort characters by name\n", " characters = sorted(characters, key=lambda x: x.name)\n", " for idx, character in enumerate(characters, 1):\n", " table.add_row(str(idx), character.name)\n", "\n", " console.print(table)\n", "\n", "def print_guesses_remaining(num_guesses):\n", " \"\"\"\n", " Displays the number of remaining guesses available to the player.\n", "\n", " Args:\n", " num_guesses (int): Number of guesses remaining.\n", " \"\"\"\n", " console = Console()\n", " console.print(Panel(\n", " f\"[bold]You have {num_guesses} {'guess' if num_guesses == 1 else 'guesses'} remaining[/bold]\",\n", " border_style=\"yellow\",\n", " title=\"⏳ Guesses\",\n", " title_align=\"left\"\n", " ))\n", "\n", "def print_result(is_win, is_lose, killer_name=None):\n", " \"\"\"\n", " Displays the game result message indicating whether the player won or lost.\n", "\n", " Args:\n", " is_win (bool): True if player won the game.\n", " is_lose (bool): True if player lost the game.\n", " killer_name (str, optional): Name of the killer to reveal if player lost.\n", " \"\"\"\n", " console = Console()\n", " if is_win:\n", " console.print(Panel(\n", " \"[bold green]Congratulations! You have correctly identified the killer.[/bold green]\",\n", " border_style=\"green\",\n", " title=\"🎯 Success\",\n", " title_align=\"left\"\n", " ))\n", " elif is_lose:\n", " console.print(Panel(\n", " f\"[bold bright_red]Investigation Failed![/bold bright_red]\\n[bright_red]The killer was {killer_name}.[/bright_red]\",\n", " border_style=\"bright_red\",\n", " title=\"❌ Game Over\",\n", " title_align=\"left\"\n", " ))\n", "\n", "def print_incorrect_guess():\n", " \"\"\"\n", " Displays a message indicating that the player's guess was incorrect.\n", " \"\"\"\n", " console = Console()\n", " console.print(Panel(\n", " \"[bold yellow]The person you chose was innocent.[/bold yellow]\",\n", " border_style=\"yellow\",\n", " title=\"❗ Wrong Guess\",\n", " title_align=\"left\"\n", " ))" ] }, { "cell_type": "markdown", "metadata": { "id": "MrjDINwu-9Bj" }, "source": [ "## State and Schema Classes Definition\n", "\n", "We define 3 schema classes:\n", "1. `Character`: an ingame character\n", "2. `NPC`: The interactable character list for the game\n", "3. `StoryDetails`: Blueprint for story details to LLM generate \n", "The schema classes allow us to request structured output from the LLM \n", "\n", "We define 2 state classes:\n", "1. `ConversationState`: holds the ongoing conversation state throughout the game\n", "2. `GenerateGameState`: holds the overall game state\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xbLDoT4F_HVU" }, "outputs": [], "source": [ "# Classes to define the Game Characters and allow for Structured Output from the LLMs while generating the Characters\n", "class Character(BaseModel):\n", " role: str = Field(\n", " description=\"Primary role of the character in the story\",\n", " )\n", " name: str = Field(\n", " description=\"Name of the character.\"\n", " )\n", " backstory: str = Field(\n", " description=\"Backstory of the character focus, concerns, and motives.\",\n", " )\n", " @property\n", " def persona(self) -> str:\n", " return f\"Name: {self.name}\\nRole: {self.role}\\nBackstory: {self.backstory}\\n\"\n", "\n", "class NPC(BaseModel):\n", " characters: List[Character] = Field(\n", " description=\"Comprehensive list of characters with their roles and backstories.\",\n", " default_factory=list\n", " )\n", "\n", "# A Class to define the Game Story and allow for Structured Output from the LLMs while generating the Game Story\n", "class StoryDetails(BaseModel):\n", " victim_name: str = Field(\n", " description=\"Name of the murder victim\"\n", " )\n", " time_of_death: str = Field(\n", " description=\"Approximate time when the murder occurred\"\n", " )\n", " location_found: str = Field(\n", " description=\"Where the body was discovered\"\n", " )\n", " murder_weapon: str = Field(\n", " description=\"The weapon or method used in the murder\"\n", " )\n", " cause_of_death: str = Field(\n", " description=\"Specific medical cause of death\"\n", " )\n", " crime_scene_details: str = Field(\n", " description=\"Description of the crime scene and any relevant evidence found\"\n", " )\n", " witnesses: str = Field(\n", " description=\"Information about potential witnesses or last known sightings\"\n", " )\n", " initial_clues: str = Field(\n", " description=\"Initial clues or evidence found at the scene\"\n", " )\n", " npc_brief:str = Field(\n", " description=\"Brief description of the characters and their relationships\"\n", " )\n", "\n", "# A Class to define and manage the State for the conversation\n", "class ConversationState(TypedDict):\n", " messages: Annotated[Sequence[BaseMessage], operator.add]\n", " character: Character # Character being interviewed\n", " story_details: Optional[StoryDetails] # Details about the murder mystery\n", "\n", "# A Class to define and manage the overall State of the Game\n", "class GenerateGameState(TypedDict):\n", " messages: Annotated[Sequence[BaseMessage], operator.add]\n", " environment: str # Story environment\n", " max_characters: int # Number of characters\n", " characters: List[Character] # Characters in the story\n", " story_details: Optional[StoryDetails] # Details about the murder mystery\n", " selected_character_id: Optional[int] # Index of the selected character\n", " num_guesses_left: int # Number of guesses the player has\n", " result: str #Store the Guesser result and evalute Correct/Incorrect" ] }, { "cell_type": "markdown", "metadata": { "id": "Nuf58TvC_JYW" }, "source": [ "## LLM Initialization\n", "\n", "Initialize the Large Language Model (LLM) backbone for all the different agents in our system. \n", "We have used OPEN AI GPT-4o for our development and testing. \n", "The code should also work with any other LLM supported by LangChain. \n", " \n", "We set the `temperature` to 0, this makes the run deterministic, i.e. no randomness." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xlVIzTIA_Lf9" }, "outputs": [], "source": [ "llm = ChatOpenAI(model=\"gpt-4o\", temperature=0)" ] }, { "cell_type": "markdown", "metadata": { "id": "lMPJAa4lAtGG" }, "source": [ "## Component Functions\n", "\n", "Define the component functions:\n", "- premise story generation\n", "- character background story generation\n", "- character dialogue\n", "- sherlock LLM agent questioning\n", "- killer guessing" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "9Y6dKjsTBIIQ" }, "outputs": [], "source": [ "# Node: Characters introduce themselves to the User (as Sherlock)\n", "\n", "def character_introduction(state: ConversationState):\n", " \"\"\"\n", " Part of the Conversation Sub-Graph.\n", "\n", " Generates and displays a character's introduction to Sherlock Holmes in the murder mystery game.\n", "\n", " Args:\n", " state (ConversationState): The LangGraph State object containing:\n", " - messages: List of previous conversation messages. Used to store conversation history\n", " - character: Character object with persona and character details\n", " - story_details: Object containing crime details including:\n", " - victim_name, time_of_death, location_found\n", " - murder_weapon, cause_of_death\n", " - crime_scene_details, initial_clues\n", "\n", " Returns:\n", " dict: Adds the introduction messages to the conversation history\n", " - messages: Introduction messages to be added\n", "\n", " Note:\n", " The function uses an LLM to generate appropriate character dialogue while ensuring\n", " the character doesn't reveal their role or incriminate themselves.\n", " \"\"\"\n", "\n", " character = state['character']\n", " story = state['story_details']\n", " character_instructions = \"\"\"You are playing the role of a character with the below persona:\n", "{subject_persona}\n", "You are being interviewed by Sherlock Holmes in relationship to the below crime:\n", "Crime details:\n", "- Victim: {victim}\n", "- Time of death: {time}\n", "- Location: {location}\n", "Please greet and introduce your self to Sherlock Holmes.\n", "Your tone should be conversational and should address Sherlock Holmes directly.\n", "Make sure that you do not reveal your role and incriminate yourself.\n", "\"\"\"\n", " system_message = character_instructions.format(\n", " subject_persona=character.persona,\n", " victim=story.victim_name,\n", " time=story.time_of_death,\n", " location=story.location_found,\n", " )\n", " # Generate narration\n", " narration = llm.invoke([\n", " SystemMessage(content=system_message),\n", " HumanMessage(content=\"Introduce yourself to Sherlock Holmes\")\n", " ])\n", "\n", " print_introduction(character, narration)\n", "\n", " return {\"messages\": [narration]}\n", "\n", "\n", "sherlock_ask_prompt = \"\"\"\n", "You are Sherlock Holmes, the renowned detective. You are interviewing {character_name} about the murder of {victim_name}.\n", "The murder occurred around {time_of_death} at {location_found}. The murder weapon was {murder_weapon}, and the cause of death was {cause_of_death}.\n", "\n", "Here's the crime scene description: {crime_scene_details}\n", "Here are some initial clues: {initial_clues}\n", "\n", "Here's the conversation history with {character_name}:\n", "{conversation_history}\n", "\n", "Considering the above information, formulate a insightful and relevant question to ask {character_name} to further investigate the case.\n", "The question should be phrased in a manner befitting Sherlock Holmes's inquisitive nature.\n", "in your answer make a new line for every sentance to make it easier to read.\n", "\"\"\"\n", "def get_question(state: ConversationState):\n", " \"\"\"\n", " Part of the Conversation Sub-Graph.\n", "\n", " Generates an investigative question from Sherlock Holmes to ask a character.\n", "\n", " Args:\n", " state (ConversationState): The LangGraph State object containing:\n", " - messages: List of previous conversation messages. Used to store conversation history\n", " - character: Character object with persona and character details specific to the character being interviewed\n", " - story_details: Object containing crime details including:\n", " - victim_name, time_of_death, location_found\n", " - murder_weapon, cause_of_death\n", " - crime_scene_details, initial_clues\n", "\n", " Returns:\n", " str: Generated question content from Sherlock AI assistance.\n", "\n", " Note:\n", " The question is generated considering:\n", " - The crime scene details and initial clues\n", " - Previous conversation history with the character\n", " - Sherlock Holmes' characteristic investigative style\n", " \"\"\"\n", "\n", " messages = state[\"messages\"]\n", " character = state[\"character\"]\n", " story = state[\"story_details\"]\n", " system_message = sherlock_ask_prompt.format(\n", " character_name=character.name,\n", " victim_name=story.victim_name,\n", " time_of_death=story.time_of_death,\n", " location_found=story.location_found,\n", " murder_weapon=story.murder_weapon,\n", " cause_of_death=story.cause_of_death,\n", " crime_scene_details=story.crime_scene_details,\n", " initial_clues=story.initial_clues,\n", " conversation_history=\"\\n\".join([f\"{msg.type}: {msg.content}\" for msg in messages])\n", " )\n", "\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", " \"system\",\n", " system_message,\n", " ),\n", " MessagesPlaceholder(variable_name=\"messages\"),\n", " ]\n", " )\n", " chain = prompt | llm\n", " question = chain.invoke(messages)\n", "\n", " console = Console()\n", " console.print(Panel(\n", " f\"[italic]{question.content}[/italic]\",\n", " border_style=\"yellow\",\n", " padding=(1, 1),\n", " title=\"🔍 Asked by Sherlock AI 🤖🕵️\",\n", " title_align=\"left\"\n", " ))\n", "\n", " return question.content\n", "\n", "# Node: User can ask a question to the Character or decide to quit the conversation\n", "\n", "def ask_question(state: ConversationState):\n", " \"\"\"\n", " Part of the Conversation Sub-Graph.\n", "\n", " Handles the question-asking process, allowing either AI-generated Sherlock questions\n", " or direct player input.\n", "\n", " Args:\n", " state (ConversationState): The LangGraph State object containing:\n", " - messages: List of previous conversation messages. Used to store conversation history\n", " - character: Character object with persona and character details\n", " - story_details: Object containing crime details including:\n", " - victim_name, time_of_death, location_found\n", " - murder_weapon, cause_of_death\n", " - crime_scene_details, initial_clues\n", "\n", " Returns:\n", " dict: Adds question asked to the conversation history\n", " - messages : Question to be added\n", "\n", " Note:\n", " - Provides option to use AI-generated \"SherlockAI\" questions\n", " - Handles input validation and error cases\n", " - Allows for conversation termination\n", " \"\"\"\n", " character = state['character']\n", " # Get user input\n", " while True:\n", " try:\n", " use_ai_sherlock = get_player_yesno_answer(\"Do you want SherlockAI to ask a question?\")\n", " if use_ai_sherlock.lower()[0] == 'y':\n", " question = get_question(state)\n", " else:\n", " question = get_player_input(character.name)\n", " return {\"messages\": [HumanMessage(content=question)]}\n", " except ValueError:\n", " print(\"Invalid input. Please enter a valid question\")\n", "\n", "\n", "# Node: Character answers the question posed by the User\n", "\n", "def answer_question(state: ConversationState):\n", " \"\"\"\n", " Part of the Conversation Sub-Graph.\n", "\n", " Generates a character's response to a question during the investigation.\n", "\n", " Args:\n", " state (ConversationState): The LangGraph State object containing:\n", " - messages: List of previous conversation messages. Used to store conversation history\n", " - character: Character object with persona and character details specifc to the character answering the question\n", " - story_details: Object containing crime details including:\n", " - victim_name, time_of_death, location_found\n", " - murder_weapon, cause_of_death\n", " - crime_scene_details, initial_clues\n", "\n", " Returns:\n", " dict: Adds response from the character to the conversation history\n", " - messages : Response to be added\n", "\n", " Note:\n", " The character's response:\n", " - Maintains consistency with their persona and knowledge\n", " - Considers their relationships with other characters\n", " - May include deception based on character motivations\n", " - Takes into account all previous conversation context\n", " \"\"\"\n", " messages = state['messages']\n", " character = state['character']\n", " last_message = messages[-1]\n", " story = state['story_details']\n", " answer_instructions = \"\"\"\n", "You are playing the role of a character with the below persona:\n", "{subject_persona}\n", "You are being interviewed by Sherlock Holmes in relationship to the below crime:\n", "Crime Scene Details:\n", " Victim: {victim}\n", " Time: {time}\n", " Location: {location}\n", " Weapon: {weapon}\n", " Cause of Death: {cause}\n", "\n", " Scene Description:\n", " {scene}\n", "\n", " All Characters and their relationships:\n", " {npc_brief}\n", "Based on the message history, answer the question as the character would, based on:\n", "1. Your character's personality and background\n", "2. Your knowledge of the crime\n", "3. Your relationships with other characters\n", "4. Your potential motives or alibis\n", "\n", "\n", "Important:\n", "- Stay in character\n", "- Only reveal information this character would know\n", "- Maintain consistency with the story details\n", "- You can lie if your character would have a reason to do so\n", "\n", "Question to answer:\n", "{question}\n", "\"\"\"\n", " system_message = answer_instructions.format(\n", " subject_persona=character.persona,\n", " victim=story.victim_name,\n", " time=story.time_of_death,\n", " location=story.location_found,\n", " weapon=story.murder_weapon,\n", " cause=story.cause_of_death,\n", " scene=story.crime_scene_details,\n", " npc_brief=story.npc_brief,\n", " question=last_message.content\n", " )\n", "\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", " \"system\",\n", " system_message,\n", " ),\n", " MessagesPlaceholder(variable_name=\"messages\"),\n", " ]\n", " )\n", " chain = prompt | llm\n", " answer = chain.invoke(messages)\n", "\n", " print_character_answer(character, answer.content)\n", "\n", " return {\"messages\":[answer]}\n", "\n", "# Conditional Edge Function\n", "\n", "def where_to_go(state: ConversationState):\n", " \"\"\"\n", " Part of the Conversation Sub-Graph.\n", "\n", " Determines the next conversation state based on the last message.\n", "\n", " Args:\n", " state (ConversationState): The LangGraph State object containing:\n", " - messages: List of previous conversation messages. Used to store conversation history\n", " - character: Character object with persona and character details\n", " - story_details: Object containing crime details including:\n", " - victim_name, time_of_death, location_found\n", " - murder_weapon, cause_of_death\n", " - crime_scene_details, initial_clues\n", "\n", " Returns:\n", " str: Either \"end\" to terminate conversation or \"continue\" to proceed\n", "\n", " Note:\n", " Checks for \"EXIT\" keyword in the last message to determine conversation flow.\n", " \"\"\"\n", " messages = state['messages']\n", " last_message = messages[-1]\n", " if \"EXIT\" in last_message.content:\n", " return \"end\"\n", " else:\n", " return \"continue\"\n", "\n", "\n", "# Node: Character Creation\n", "\n", "character_instructions=\"\"\"You are an AI character designer tasked with creating personas for a murder mystery game.\n", "Your goal is to develop a cast of characters that fits the given environment and creates an engaging, interactive experience for players.\n", "\n", "First, carefully understand the environment setting:\n", "\n", "\n", "{{environment}}\n", "\n", "\n", "Now, follow these steps to create the character personas:\n", "\n", "1. Review the environment and identify the most interesting themes and elements that could influence character creation.\n", "\n", "2. Determine the number of characters to create. This will be specified by the max_characters variable:\n", "\n", "\n", "{{max_characters}}\n", "\n", "\n", "3. Based on the environment and the number of characters, create a list of roles that would be appropriate for the setting. Remember:\n", " - One character must be designated as the killer.\n", " - One character must be designated as the victim.\n", " - The remaining characters should be supporting roles who can be questioned by the detective.\n", " - Roles should fit the story setting (e.g., shopkeepers in a market, passengers on a train).\n", "\n", "4. Assign one character to each role, ensuring a diverse and interesting cast.\n", "\n", "5. For each character, provide:\n", " - A name\n", " - Their role in the story\n", " - A brief description of their persona, including any relevant background or motivations\n", "\n", "Before creating the final list, brainstorm and plan your approach inside tags:\n", "\n", "\n", "[Your thought process here. Consider the following:\n", "1. List potential character archetypes that fit the environment.\n", "2. Brainstorm possible motives for the killer and how other characters might be connected.\n", "3. Consider character relationships and potential conflicts.\n", "4. Think about the setting, interesting character dynamics, and how each character might contribute to the mystery.]\n", "\n", "\n", "After your brainstorming, create the final list of characters.\n", "\n", "Remember:\n", "- Ensure that the characters and their roles are appropriate for the given environment.\n", "- Make the characters diverse and interesting to enhance the gameplay experience.\n", "- Provide enough detail for each character to make them memorable and useful in the game context.\"\"\"\n", "\n", "def create_characters(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Creates a cast of characters for the murder mystery game based on the environment and the max_characters.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - environment: Description of the game's setting\n", " - max_characters: Maximum number of characters to create\n", "\n", " Returns:\n", " dict : Contains the generated character list. Adds to the State object.\n", " - characters : List of NPC objects with defined roles, including:\n", " - One killer\n", " - One victim\n", " - Supporting characters\n", "\n", " Note:\n", " - Uses structured LLM output to ensure consistent character creation\n", " - Each character has a name, role, and detailed persona\n", " - Ensures character diversity and setting appropriateness\n", " \"\"\"\n", "\n", " environment = state['environment']\n", " max_characters = state['max_characters']\n", "\n", " # Enforce structured output\n", " structured_llm = llm.with_structured_output(NPC)\n", "\n", " # System message\n", " system_message = character_instructions.replace(\"{{environment}}\", environment)\n", " system_message = system_message.replace(\"{{max_characters}}\", str(max_characters))\n", "\n", " # Generate characters\n", " result = structured_llm.invoke([\n", " SystemMessage(content=system_message),\n", " HumanMessage(content=\"Generate the set of characters\")\n", " ])\n", "\n", " # Return the characters from the NPC object\n", " return {\"characters\": result.characters}\n", "\n", "\n", "# Node: Story Creation including Crime Seen, Incident Details and Story Arc\n", "\n", "story_instructions = \"\"\"You are crafting the central murder mystery for our story. Using the provided environment and characters, create a compelling murder scenario.\n", "Include specific details about the crime while maintaining mystery about the killer's identity.\n", "\n", "Environment:\n", "{{environment}}\n", "\n", "Characters:\n", "{{characters}}\n", "\n", "Follow these guidelines when creating the murder scenario:\n", "\n", "1. For the victim describe:\n", " - Where and how the body was found\n", " - The approximate time of death\n", " - The cause of death and murder weapon\n", " - The condition of the crime scene\n", "\n", "2. Include crucial evidence and clues:\n", " - Physical evidence at the scene\n", " - Witness statements or last known sightings\n", " - Any suspicious circumstances\n", " - Environmental factors that might be relevant\n", "\n", "3. Create a mix of:\n", " - True clues that lead to the killer\n", " - Red herrings that create suspense\n", " - Background circumstances that add depth\n", "\n", "4. Consider:\n", " - The timing of the murder\n", " - Access to the location\n", " - Potential motives\n", " - Physical evidence\n", " - Witness reliability\n", "\n", "5. For the Character Brief:\n", " - Mention the important points\n", " - DO not mention who the killer is\n", "\n", "Important:\n", "- DO NOT reveal or hint at the killer's identity\n", "- Include enough detail to make the mystery solvable\n", "- Ensure all clues are consistent with the environment and characters\n", "- Make the scenario complex enough to be interesting but clear enough to be solvable\n", "\n", "Format your response to provide the specific details requested in the StoryDetails schema.\"\"\"\n", "\n", "def create_story(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Generates the complete murder mystery scenario and storyline based on the provided environment and the generated characters.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - environment: Description of the game's setting\n", " - characters: List of character objects generated in create_character step\n", "\n", " Returns:\n", " dict: Contains the complete story details. Adds to the State Object.\n", " - story_details: StoryDetails object including:\n", " - Crime scene information\n", " - Evidence and clues\n", " - Character relationships\n", " - Environmental factors\n", "\n", " Note:\n", " - Creates a solvable mystery without revealing the killer\n", " - Includes both true clues and red herrings\n", " - Ensures consistency between characters and environment\n", " \"\"\"\n", "\n", " environment = state['environment']\n", " characters = state['characters']\n", "\n", " # Format character list for the prompt\n", " character_list = \"\\n\".join([char.persona for char in characters])\n", "\n", " # Enforce structured output\n", " structured_llm = llm.with_structured_output(StoryDetails)\n", "\n", " # System message\n", " system_message = story_instructions.replace(\"{{environment}}\", environment)\n", " system_message = system_message.replace(\"{{characters}}\", character_list)\n", "\n", " # Generate story details\n", " result = structured_llm.invoke([\n", " SystemMessage(content=system_message),\n", " HumanMessage(content=\"Generate the murder mystery scenario\")\n", " ])\n", "\n", " # Return the story details\n", " return {\"story_details\": result}\n", "\n", "\n", "# Node: The Narator (Dr. John Watson) who narators the crime seen and other deatils\n", "\n", "narrator_instructions = \"\"\"You are trusted assistant and friend of the legendary detective Sherlock Holmes - Dr. John Watson.\n", "Sherlock has just arrived at the seen of the murder.\n", "Use the provided details to give Sherlock a brief, engaging introduction to the crime seen in 100 words or less.\n", "Your tone should be conversational and should address Sherlock Holmes directly.\n", "\n", "Crime Scene Details:\n", " Victim: {victim}\n", " Time: {time}\n", " Location: {location}\n", " Weapon: {weapon}\n", " Cause of Death: {cause}\n", "\n", " Scene Description:\n", " {scene}\n", "\"\"\"\n", "\n", "def narrartor(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Generates Dr. Watson's narration of the crime scene for Sherlock Holmes.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - story_details: Complete information about the crime\n", "\n", " Returns:\n", " dict: Contains the narration message. Adds to the State Object.\n", " - messages: Dr. Watson's narrative description\n", "\n", " Note:\n", " - Provides a concise (100 words or less) introduction to the crime\n", " - Maintains Dr. Watson's characteristic narrative style\n", " - Sets the initial atmosphere for the investigation\n", " \"\"\"\n", " story = state['story_details']\n", "\n", " # Format the message with the story details\n", " system_message = narrator_instructions.format(\n", " victim=story.victim_name,\n", " time=story.time_of_death,\n", " location=story.location_found,\n", " weapon=story.murder_weapon,\n", " cause=story.cause_of_death,\n", " scene=story.crime_scene_details\n", " )\n", " # Generate narration\n", " narration = llm.invoke([\n", " SystemMessage(content=system_message),\n", " HumanMessage(content=\"Create an atmospheric narration of the crime scene\")\n", " ])\n", "\n", " print_game_header()\n", " print_narration(narration)\n", "\n", " return {\"messages\": [narration]}\n", "\n", "# Node: User to select who to investigate\n", "\n", "def sherlock(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Handles the character selection phase of the investigation.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - characters: List of all character objects\n", "\n", " Returns:\n", " dict: Result of get_character_selection containing. Adds to the State Object.\n", " - selected_character_id: Index of selected character or None for guessing phase\n", "\n", " Note:\n", " - Displays character list with randomized order\n", " - Maintains mapping between display order and original character indices\n", " - Prevents selection of the victim character\n", " \"\"\"\n", " characters = state['characters']\n", "\n", " # Display characters and get the mapping of displayed order to original indices\n", " display_to_original = print_characters_list(characters)\n", "\n", " # Get user selection\n", " return get_character_selection(characters, display_to_original)\n", "\n", "\n", "#Node: Allows the Users to guess the Killer\n", "\n", "KILLER_ROLE = \"Killer\"\n", "\n", "def guesser(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Manages the final phase where the player attempts to identify the killer.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - num_guesses_left: Number of remaining guess attempts\n", " - characters: List of all character objects\n", "\n", " Returns:\n", " dict: Contains:\n", " - result: \"end\" if game is over, \"sherlock\" to continue investigation\n", " - num_guesses_left: Updated number of remaining guesses\n", "\n", " Note:\n", " - Handles the win/loss conditions\n", " - Maintains guess counter\n", " - Provides feedback on incorrect guesses\n", " - Excludes victim from suspect list\n", " \"\"\"\n", " console = Console()\n", " num_guesses_left = state['num_guesses_left']\n", " all_characters = state['characters']\n", " non_victims = [char for char in all_characters if char.role != 'Victim']\n", " killer_character = next(char for char in all_characters if char.role == KILLER_ROLE)\n", " characters = list(sorted(non_victims, key=lambda x: x.name))\n", "\n", " # Print initial state\n", " console.rule(\"[bold red]🔍 Final Deduction[/bold red]\")\n", " print_guesses_remaining(num_guesses_left)\n", " print_suspect_list(characters)\n", "\n", " is_win, is_lose = False, False\n", "\n", " while True:\n", " try:\n", " # Get user input\n", " choice = Prompt.ask(\n", " \"\\n[bold red]Who is the killer?[/bold red] (Enter suspect number)\",\n", " default=\"\",\n", " show_default=False\n", " )\n", "\n", " choice = int(choice)\n", " if 0 < choice <= len(characters):\n", " selected_character_id = choice - 1\n", " selected_character = characters[selected_character_id]\n", "\n", " if selected_character.role == KILLER_ROLE:\n", " is_win = True\n", " break\n", " else:\n", " print_incorrect_guess()\n", " num_guesses_left -= 1\n", " if num_guesses_left > 0:\n", " print_guesses_remaining(num_guesses_left)\n", "\n", " if num_guesses_left == 0:\n", " is_lose = True\n", " break\n", "\n", " else:\n", " console.print(\"[red]Invalid input. Please enter a valid suspect number.[/red]\")\n", " except ValueError:\n", " console.print(\"[red]Invalid input. Please enter a number.[/red]\")\n", "\n", " # Print final result\n", " print_result(is_win, is_lose, killer_character.name)\n", "\n", " is_end = is_win or is_lose\n", " return {\"result\": \"end\", \"num_guesses_left\": num_guesses_left} if is_end else {\"result\": \"sherlock\", \"num_guesses_left\": num_guesses_left}\n", "\n", "\n", "# Node: Adding the Conversation SubGraph i.e. Conversation Loop\n", "\n", "def conversation(state: GenerateGameState):\n", " \"\"\"\n", " Part of the Game Loop Graph.\n", "\n", " Manages the main conversation loop between Sherlock/player and characters.\n", "\n", " Args:\n", " state (GenerateGameState): The LangGraph State object containing:\n", " - selected_character_id: ID of the character to converse with\n", " - characters: List of all character objects\n", " - story_details: Complete story information\n", "\n", " Returns:\n", " dict: Contains either:\n", " - messages: List of conversation messages if character selected\n", " - END constant if no character selected (moving to guessing phase)\n", "\n", " Note:\n", " - Implements recursion limit to prevent infinite loops\n", " - Handles conversation flow and state management\n", " - Integrates with the conversation subgraph\n", " \"\"\"\n", " selected_character_id = state['selected_character_id']\n", " if selected_character_id is not None:\n", " characters = state['characters']\n", " character = characters[selected_character_id]\n", " inputs = {\n", " \"character\": character,\n", " \"story_details\": state['story_details'],\n", " }\n", " response = conversation_graph.invoke(inputs,{\"recursion_limit\": 50})\n", "\n", " # Return the response as a message\n", " return {\"messages\": [response['messages']]}\n", " else:\n", " return END" ] }, { "cell_type": "markdown", "metadata": { "id": "KFWIVnlNcY1p" }, "source": [ "## Conversation Sub-Graph Construction\n", "\n", "Construct the LangGraph workflow for the conversation mechanic." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ExLGL3tWCV86", "outputId": "2cab4cb8-1ff5-4be4-b1f0-c82f1d50225e" }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Creating the Conversation Graph to act as the Conversation Loop\n", "\n", "# Update the graph\n", "conversation_builder = StateGraph(ConversationState)\n", "\n", "# Add nodes\n", "conversation_builder.add_node(\"character_introduction\", character_introduction)\n", "conversation_builder.add_node(\"ask_question\", ask_question)\n", "conversation_builder.add_node(\"answer_question\", answer_question)\n", "\n", "# Add edges\n", "conversation_builder.add_edge(START, \"character_introduction\")\n", "conversation_builder.add_edge(\"character_introduction\", \"ask_question\")\n", "conversation_builder.add_conditional_edges(\"ask_question\",where_to_go,{\"continue\": \"answer_question\", \"end\": END})\n", "conversation_builder.add_edge(\"answer_question\", \"ask_question\")\n", "\n", "conversation_graph = conversation_builder.compile()\n", "# View\n", "display(Image(conversation_graph.get_graph(xray=1).draw_mermaid_png()))" ] }, { "cell_type": "markdown", "metadata": { "id": "7z9Wi3oDDcTM" }, "source": [ "## Game Loop Graph Construction\n", "\n", "Construct the LangGraph workflow for the overall game loop. The graph contains the conversation workflow." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 884 }, "id": "3M8ZnOXRRYuC", "outputId": "14a44faa-86cd-43ee-9dd5-220216cf8c07" }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Creating the Core Game Graph\n", "\n", "# Update the graph\n", "builder = StateGraph(GenerateGameState)\n", "\n", "# Add nodes\n", "builder.add_node(\"create_characters\", create_characters)\n", "builder.add_node(\"create_story\", create_story)\n", "builder.add_node(\"narrartor\", narrartor)\n", "builder.add_node(\"sherlock\", sherlock)\n", "builder.add_node(\"guesser\", guesser)\n", "builder.add_node(\"conversation\", conversation)\n", "\n", "\n", "\n", "# Add edges\n", "builder.add_edge(START, \"create_characters\")\n", "builder.add_edge(\"create_characters\", \"create_story\")\n", "builder.add_edge(\"create_story\", \"narrartor\")\n", "builder.add_edge(\"narrartor\", \"sherlock\")\n", "\n", "builder.add_conditional_edges(\"sherlock\",\n", " lambda state: \"next_talk\" if state.get('selected_character_id') is not None else \"end_talks\",\n", " {\n", " \"next_talk\": \"conversation\",\n", " \"end_talks\": \"guesser\"\n", " }\n", ")\n", "\n", "builder.add_edge(\"conversation\", \"sherlock\")\n", "builder.add_conditional_edges(\"guesser\",\n", " lambda state: state.get('result'),\n", " {\"sherlock\":\"sherlock\",\"end\":END})\n", "\n", "# Compile\n", "graph = builder.compile()\n", "# View\n", "display(Image(graph.get_graph(xray=1).draw_mermaid_png()))" ] }, { "cell_type": "markdown", "metadata": { "id": "IDw3-4PUR8RI" }, "source": [ "# Game Play" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "background_save": true, "base_uri": "https://localhost:8080/", "height": 1000 }, "id": "5E9H9VmIR9wV", "outputId": "65cdb39a-465a-4b94-995b-d6c9f6714056" }, "outputs": [ { "data": { "text/html": [ "\n", "

\n", " 🕵️‍♂️ MURDER MYSTERY INVESTIGATION 🔍\n", "

\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 💬 Dialogue ───────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              "                                                                                                                 \n",
              "   Dr. John Watson :                                                                                             \n",
              "                                                                                                                 \n",
              "  Ah, Sherlock, welcome to the scene. Claire Moreau, the victim, met her untimely end between 8:00 and 9:00 PM   \n",
              "  right here in her office at Mistral headquarters. The room is in chaos—papers strewn about, a vase shattered   \n",
              "  near the desk. The letter opener, the murder weapon, lies on the floor, stained with blood. Interestingly,     \n",
              "  the window is slightly open, hinting at a possible escape route. No signs of forced entry, though. Her         \n",
              "  computer remains on, displaying an unfinished email draft. It seems we have a puzzle on our hands, my friend.  \n",
              "  Where shall we begin?                                                                                          \n",
              "                                                                                                                 \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[34m╭─\u001b[0m\u001b[34m 💬 Dialogue \u001b[0m\u001b[34m──────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[34m─╮\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[1m Dr. John Watson \u001b[0m: \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m Ah, Sherlock, welcome to the scene. Claire Moreau, the victim, met her untimely end between 8:00 and 9:00 PM \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m right here in her office at Mistral headquarters. The room is in chaos—papers strewn about, a vase shattered \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m near the desk. The letter opener, the murder weapon, lies on the floor, stained with blood. Interestingly, \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m the window is slightly open, hinting at a possible escape route. No signs of forced entry, though. Her \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m computer remains on, displaying an unfinished email draft. It seems we have a puzzle on our hands, my friend. \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m Where shall we begin? \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n",
              "
\n" ], "text/plain": [ "\u001b[34m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
                                                                                                                   \n",
              "                                                    CHARACTERS                                                     \n",
              "
\n" ], "text/plain": [ " \n", " \u001b[1;34mCHARACTERS\u001b[0m \n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "┃ #     Name                  Background                                                                        ┃\n",
              "┠──────┼──────────────────────┼───────────────────────────────────────────────────────────────────────────────────┨\n",
              "┃ 1     Marc Renault          Marc is the head of finance at Mistral and has been with the company for over two ┃\n",
              "┃                             decades. He is known for his conservative approach to business and was openly     ┃\n",
              "┃                             critical of Claire's new direction for the company. Marc's financial strategies   ┃\n",
              "┃                             were often at odds with Claire's vision, creating tension between them.           ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 2     Claire Moreau         Claire was the beloved CEO of Mistral, known for her innovative ideas and         ┃\n",
              "┃       (victim)              compassionate leadership. She recently made a controversial decision to           ┃\n",
              "┃                             restructure the company, which upset several high-ranking employees. Claire's     ┃\n",
              "┃                             vision for the company was to prioritize sustainability, which clashed with some  ┃\n",
              "┃                             of the more profit-driven executives.                                             ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 3     Inspector Jean        Inspector Leclerc is a seasoned detective with a keen eye for detail and a        ┃\n",
              "┃       Leclerc               reputation for solving complex cases. He is known for his methodical approach and ┃\n",
              "┃                             ability to read people. Leclerc has a personal interest in this case, as he once  ┃\n",
              "┃                             worked with Claire on a charity project and admired her greatly.                  ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 4     Lucien Dupont         Lucien is a senior executive at Mistral, known for his ambitious nature and       ┃\n",
              "┃                             cutthroat tactics. He has been eyeing the CEO position for years and sees the     ┃\n",
              "┃                             victim as a major obstacle in his path. Lucien's motive stems from a recent       ┃\n",
              "┃                             decision by the victim that jeopardized a major project he was leading,           ┃\n",
              "┃                             threatening his career and reputation.                                            ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 5     Sophie Dubois         Sophie is the head of the marketing department and was a close confidante of      ┃\n",
              "┃                             Claire. She is intelligent and resourceful, but her loyalty to Claire made her    ┃\n",
              "┃                             unpopular with some colleagues. Sophie had been working on a campaign that was    ┃\n",
              "┃                             shelved due to the restructuring, giving her a potential motive.                  ┃\n",
              "┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n", "┃\u001b[1;35m \u001b[0m\u001b[1;35m# \u001b[0m\u001b[1;35m \u001b[0m│\u001b[1;35m \u001b[0m\u001b[1;35mName \u001b[0m\u001b[1;35m \u001b[0m│\u001b[1;35m \u001b[0m\u001b[1;35mBackground \u001b[0m\u001b[1;35m \u001b[0m┃\n", "┠──────┼──────────────────────┼───────────────────────────────────────────────────────────────────────────────────┨\n", "┃\u001b[2m \u001b[0m\u001b[2m1 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mMarc Renault \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mMarc is the head of finance at Mistral and has been with the company for over two\u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mdecades. He is known for his conservative approach to business and was openly \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcritical of Claire's new direction for the company. Marc's financial strategies \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mwere often at odds with Claire's vision, creating tension between them. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m2 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mClaire Moreau \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mClaire was the beloved CEO of Mistral, known for her innovative ideas and \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;31m(victim)\u001b[0m\u001b[1;36m \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcompassionate leadership. She recently made a controversial decision to \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mrestructure the company, which upset several high-ranking employees. Claire's \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mvision for the company was to prioritize sustainability, which clashed with some \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mof the more profit-driven executives. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m3 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mInspector Jean \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mInspector Leclerc is a seasoned detective with a keen eye for detail and a \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mLeclerc \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mreputation for solving complex cases. He is known for his methodical approach and\u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mability to read people. Leclerc has a personal interest in this case, as he once \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mworked with Claire on a charity project and admired her greatly. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m4 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mLucien Dupont \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mLucien is a senior executive at Mistral, known for his ambitious nature and \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcutthroat tactics. He has been eyeing the CEO position for years and sees the \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mvictim as a major obstacle in his path. Lucien's motive stems from a recent \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mdecision by the victim that jeopardized a major project he was leading, \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mthreatening his career and reputation. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m5 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mSophie Dubois \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mSophie is the head of the marketing department and was a close confidante of \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mClaire. She is intelligent and resourceful, but her loyalty to Claire made her \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32munpopular with some colleagues. Sophie had been working on a campaign that was \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mshelved due to the restructuring, giving her a potential motive. \u001b[0m\u001b[32m \u001b[0m┃\n", "┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 👤 Selection ──────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              " Enter the number of the character to investigate                                                                \n",
              " Enter -1 to Guess the Killer                                                                                    \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[34m╭─\u001b[0m\u001b[34m 👤 Selection \u001b[0m\u001b[34m─────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[34m─╮\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[1;34mEnter the number of the character to investigate\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[2mEnter -1 to Guess the Killer\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Detective: 
\n" ], "text/plain": [ "\u001b[1;33mDetective\u001b[0m: " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
You have selected Marc Renault\n",
              "
\n" ], "text/plain": [ "You have selected Marc Renault\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
───────────────────────────────────────── Conversation with Marc Renault ──────────────────────────────────────────\n",
              "
\n" ], "text/plain": [ "\u001b[34m───────────────────────────────────────── \u001b[0m\u001b[1;34mConversation with Marc Renault\u001b[0m\u001b[34m ──────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 💬 Dialogue ───────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              "                                                                                                                 \n",
              "  Marc Renault:                                                                                                  \n",
              "                                                                                                                 \n",
              "  Good day, Mr. Holmes. My name is Marc Renault. I've been with Mistral for over twenty years, heading the       \n",
              "  finance department. It's a pleasure to meet someone of your reputation, though I wish it were under different  \n",
              "  circumstances. How can I assist you in your investigation?                                                     \n",
              "                                                                                                                 \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[34m╭─\u001b[0m\u001b[34m 💬 Dialogue \u001b[0m\u001b[34m──────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[34m─╮\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[1mMarc Renault\u001b[0m: \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m Good day, Mr. Holmes. My name is Marc Renault. I've been with Mistral for over twenty years, heading the \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m finance department. It's a pleasure to meet someone of your reputation, though I wish it were under different \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m circumstances. How can I assist you in your investigation? \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n",
              "
\n" ], "text/plain": [ "\u001b[34m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
┏━ 🤖🕵️ Sherlock AI ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "                                                                                                                 \n",
              "  Do you want SherlockAI to ask a question?                                                                      \n",
              "  enter 'y' to get his help or 'n' to ask by yourself or exit                                                    \n",
              "                                                                                                                 \n",
              "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "\u001b[34m┏━\u001b[0m\u001b[34m 🤖🕵️ Sherlock AI \u001b[0m\u001b[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[34m━┓\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[1;34mDo you want SherlockAI to ask a question?\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m enter 'y' to get his help or 'n' to ask by yourself or exit \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Detective: 
\n" ], "text/plain": [ "\u001b[1;33mDetective\u001b[0m: " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 🔍 Asked by Sherlock AI 🤖🕵️ ───────────────────────────────────────────────────────────────────────────────────╮\n",
              "                                                                                                                 \n",
              " Mr. Renault, your extensive tenure at Mistral suggests a deep familiarity with both the company and its         \n",
              " personnel.                                                                                                      \n",
              "                                                                                                                 \n",
              " Could you elucidate any recent tensions or conflicts within the office, particularly those involving Claire     \n",
              " Moreau?                                                                                                         \n",
              "                                                                                                                 \n",
              " Furthermore, given your position, might you have any insight into the restructuring decision that seemed to     \n",
              " have caused some discontent, as evidenced by Mr. Dupont's voicemail?                                            \n",
              "                                                                                                                 \n",
              " Lastly, were you aware of any meetings or appointments Claire had scheduled on the evening of her untimely      \n",
              " demise?                                                                                                         \n",
              "                                                                                                                 \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[33m╭─\u001b[0m\u001b[33m 🔍 Asked by Sherlock AI 🤖🕵️ \u001b[0m\u001b[33m──────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[33m─╮\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mMr. Renault, your extensive tenure at Mistral suggests a deep familiarity with both the company and its \u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mpersonnel.\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mCould you elucidate any recent tensions or conflicts within the office, particularly those involving Claire \u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mMoreau?\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mFurthermore, given your position, might you have any insight into the restructuring decision that seemed to \u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mhave caused some discontent, as evidenced by Mr. Dupont's voicemail?\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mLastly, were you aware of any meetings or appointments Claire had scheduled on the evening of her untimely \u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[3mdemise?\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 🗣️ Answer ──────────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              "                                                                                                                 \n",
              "  Marc Renault's Answer:                                                                                         \n",
              "                                                                                                                 \n",
              "  Certainly, Mr. Holmes. As you might expect, with any major restructuring, there are bound to be tensions and   \n",
              "  disagreements. Claire's recent decisions were quite controversial, especially among those of us who have been  \n",
              "  with Mistral for a long time. Her vision for the company was bold, but it clashed with my more conservative    \n",
              "  financial strategies. I was openly critical of her approach, as I believed it posed significant risks to the   \n",
              "  company's stability.                                                                                           \n",
              "                                                                                                                 \n",
              "  Regarding Mr. Dupont, he is indeed ambitious and saw Claire's restructuring as an obstacle to his own          \n",
              "  aspirations. His voicemail doesn't surprise me; he was quite vocal about his dissatisfaction. However, I       \n",
              "  can't speak to his intentions beyond that.                                                                     \n",
              "                                                                                                                 \n",
              "  As for Claire's schedule on the evening of her death, I wasn't aware of any specific meetings or               \n",
              "  appointments. Our last interaction was earlier in the day, during a rather heated discussion about the         \n",
              "  financial implications of her plans. After that, I left the office to clear my head. I didn't return until     \n",
              "  the following morning when I heard the tragic news.                                                            \n",
              "                                                                                                                 \n",
              "  I hope this information is helpful to your investigation, Mr. Holmes. If there's anything else you need,       \n",
              "  please don't hesitate to ask.                                                                                  \n",
              "                                                                                                                 \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[36m╭─\u001b[0m\u001b[36m 🗣️ Answer \u001b[0m\u001b[36m─────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[36m─╮\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[1mMarc Renault's Answer\u001b[0m: \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mCertainly, Mr. Holmes. As you might expect, with any major restructuring, there are bound to be tensions and \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mdisagreements. Claire's recent decisions were quite controversial, especially among those of us who have been\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mwith Mistral for a long time. Her vision for the company was bold, but it clashed with my more conservative \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mfinancial strategies. I was openly critical of her approach, as I believed it posed significant risks to the \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mcompany's stability.\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mRegarding Mr. Dupont, he is indeed ambitious and saw Claire's restructuring as an obstacle to his own \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3maspirations. His voicemail doesn't surprise me; he was quite vocal about his dissatisfaction. However, I \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mcan't speak to his intentions beyond that.\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mAs for Claire's schedule on the evening of her death, I wasn't aware of any specific meetings or \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mappointments. Our last interaction was earlier in the day, during a rather heated discussion about the \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mfinancial implications of her plans. After that, I left the office to clear my head. I didn't return until \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mthe following morning when I heard the tragic news.\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mI hope this information is helpful to your investigation, Mr. Holmes. If there's anything else you need, \u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[3mplease don't hesitate to ask.\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m│\u001b[0m \u001b[36m│\u001b[0m\n", "\u001b[36m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
┏━ 🤖🕵️ Sherlock AI ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "                                                                                                                 \n",
              "  Do you want SherlockAI to ask a question?                                                                      \n",
              "  enter 'y' to get his help or 'n' to ask by yourself or exit                                                    \n",
              "                                                                                                                 \n",
              "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "\u001b[34m┏━\u001b[0m\u001b[34m 🤖🕵️ Sherlock AI \u001b[0m\u001b[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[34m━┓\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[1;34mDo you want SherlockAI to ask a question?\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m enter 'y' to get his help or 'n' to ask by yourself or exit \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Detective: 
\n" ], "text/plain": [ "\u001b[1;33mDetective\u001b[0m: " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
┏━ 💭 Your Question ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "                                                                                                                 \n",
              "  Ask your question to Marc Renault                                                                              \n",
              "  Type 'EXIT' to end conversation                                                                                \n",
              "                                                                                                                 \n",
              "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "\u001b[34m┏━\u001b[0m\u001b[34m 💭 Your Question \u001b[0m\u001b[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[34m━┓\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[1;34mAsk your question to Marc Renault\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[2mType 'EXIT' to end conversation\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┃\u001b[0m \u001b[34m┃\u001b[0m\n", "\u001b[34m┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Detective: 
\n" ], "text/plain": [ "\u001b[1;33mDetective\u001b[0m: " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
                                                                                                                   \n",
              "                                                    CHARACTERS                                                     \n",
              "
\n" ], "text/plain": [ " \n", " \u001b[1;34mCHARACTERS\u001b[0m \n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "┃ #     Name                  Background                                                                        ┃\n",
              "┠──────┼──────────────────────┼───────────────────────────────────────────────────────────────────────────────────┨\n",
              "┃ 1     Claire Moreau         Claire was the beloved CEO of Mistral, known for her innovative ideas and         ┃\n",
              "┃       (victim)              compassionate leadership. She recently made a controversial decision to           ┃\n",
              "┃                             restructure the company, which upset several high-ranking employees. Claire's     ┃\n",
              "┃                             vision for the company was to prioritize sustainability, which clashed with some  ┃\n",
              "┃                             of the more profit-driven executives.                                             ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 2     Lucien Dupont         Lucien is a senior executive at Mistral, known for his ambitious nature and       ┃\n",
              "┃                             cutthroat tactics. He has been eyeing the CEO position for years and sees the     ┃\n",
              "┃                             victim as a major obstacle in his path. Lucien's motive stems from a recent       ┃\n",
              "┃                             decision by the victim that jeopardized a major project he was leading,           ┃\n",
              "┃                             threatening his career and reputation.                                            ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 3     Inspector Jean        Inspector Leclerc is a seasoned detective with a keen eye for detail and a        ┃\n",
              "┃       Leclerc               reputation for solving complex cases. He is known for his methodical approach and ┃\n",
              "┃                             ability to read people. Leclerc has a personal interest in this case, as he once  ┃\n",
              "┃                             worked with Claire on a charity project and admired her greatly.                  ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 4     Marc Renault          Marc is the head of finance at Mistral and has been with the company for over two ┃\n",
              "┃                             decades. He is known for his conservative approach to business and was openly     ┃\n",
              "┃                             critical of Claire's new direction for the company. Marc's financial strategies   ┃\n",
              "┃                             were often at odds with Claire's vision, creating tension between them.           ┃\n",
              "┃                                                                                                               ┃\n",
              "┃ 5     Sophie Dubois         Sophie is the head of the marketing department and was a close confidante of      ┃\n",
              "┃                             Claire. She is intelligent and resourceful, but her loyalty to Claire made her    ┃\n",
              "┃                             unpopular with some colleagues. Sophie had been working on a campaign that was    ┃\n",
              "┃                             shelved due to the restructuring, giving her a potential motive.                  ┃\n",
              "┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n", "┃\u001b[1;35m \u001b[0m\u001b[1;35m# \u001b[0m\u001b[1;35m \u001b[0m│\u001b[1;35m \u001b[0m\u001b[1;35mName \u001b[0m\u001b[1;35m \u001b[0m│\u001b[1;35m \u001b[0m\u001b[1;35mBackground \u001b[0m\u001b[1;35m \u001b[0m┃\n", "┠──────┼──────────────────────┼───────────────────────────────────────────────────────────────────────────────────┨\n", "┃\u001b[2m \u001b[0m\u001b[2m1 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mClaire Moreau \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mClaire was the beloved CEO of Mistral, known for her innovative ideas and \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;31m(victim)\u001b[0m\u001b[1;36m \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcompassionate leadership. She recently made a controversial decision to \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mrestructure the company, which upset several high-ranking employees. Claire's \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mvision for the company was to prioritize sustainability, which clashed with some \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mof the more profit-driven executives. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m2 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mLucien Dupont \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mLucien is a senior executive at Mistral, known for his ambitious nature and \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcutthroat tactics. He has been eyeing the CEO position for years and sees the \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mvictim as a major obstacle in his path. Lucien's motive stems from a recent \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mdecision by the victim that jeopardized a major project he was leading, \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mthreatening his career and reputation. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m3 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mInspector Jean \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mInspector Leclerc is a seasoned detective with a keen eye for detail and a \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mLeclerc \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mreputation for solving complex cases. He is known for his methodical approach and\u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mability to read people. Leclerc has a personal interest in this case, as he once \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mworked with Claire on a charity project and admired her greatly. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m4 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mMarc Renault \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mMarc is the head of finance at Mistral and has been with the company for over two\u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mdecades. He is known for his conservative approach to business and was openly \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mcritical of Claire's new direction for the company. Marc's financial strategies \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mwere often at odds with Claire's vision, creating tension between them. \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m \u001b[0m\u001b[2m \u001b[0m│\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m\u001b[1;2;36m \u001b[0m│\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m\u001b[2;32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m5 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m\u001b[1;36mSophie Dubois \u001b[0m\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mSophie is the head of the marketing department and was a close confidante of \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mClaire. She is intelligent and resourceful, but her loyalty to Claire made her \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32munpopular with some colleagues. Sophie had been working on a campaign that was \u001b[0m\u001b[32m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m│\u001b[1;36m \u001b[0m│\u001b[32m \u001b[0m\u001b[32mshelved due to the restructuring, giving her a potential motive. \u001b[0m\u001b[32m \u001b[0m┃\n", "┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ 👤 Selection ──────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              " Enter the number of the character to investigate                                                                \n",
              " Enter -1 to Guess the Killer                                                                                    \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[34m╭─\u001b[0m\u001b[34m 👤 Selection \u001b[0m\u001b[34m─────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[34m─╮\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[1;34mEnter the number of the character to investigate\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m│\u001b[0m \u001b[2mEnter -1 to Guess the Killer\u001b[0m \u001b[34m│\u001b[0m\n", "\u001b[34m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Detective: 
\n" ], "text/plain": [ "\u001b[1;33mDetective\u001b[0m: " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
─────────────────────────────────────────────── 🔍 Final Deduction ────────────────────────────────────────────────\n",
              "
\n" ], "text/plain": [ "\u001b[92m─────────────────────────────────────────────── \u001b[0m\u001b[1;31m🔍 Final Deduction\u001b[0m\u001b[92m ────────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ ⏳ Guesses ────────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              " You have 3 guesses remaining                                                                                    \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[33m╭─\u001b[0m\u001b[33m ⏳ Guesses \u001b[0m\u001b[33m───────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[33m─╮\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[1mYou have 3 guesses remaining\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
                                                    🔍 Suspects                                                    \n",
              "┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n",
              "┃ #                      Name                                                                                    ┃\n",
              "┠───────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┨\n",
              "┃ 1                      Inspector Jean Leclerc                                                                  ┃\n",
              "┃ 2                      Lucien Dupont                                                                           ┃\n",
              "┃ 3                      Marc Renault                                                                            ┃\n",
              "┃ 4                      Sophie Dubois                                                                           ┃\n",
              "┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n",
              "
\n" ], "text/plain": [ "\u001b[3m \u001b[0m\u001b[1;3;91m🔍 Suspects\u001b[0m\u001b[3m \u001b[0m\n", "┏━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n", "┃\u001b[1;91m \u001b[0m\u001b[1;91m# \u001b[0m\u001b[1;91m \u001b[0m│\u001b[1;91m \u001b[0m\u001b[1;91mName \u001b[0m\u001b[1;91m \u001b[0m┃\n", "┠───────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┨\n", "┃\u001b[2m \u001b[0m\u001b[2m1 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;91m \u001b[0m\u001b[1;91mInspector Jean Leclerc \u001b[0m\u001b[1;91m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m2 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;91m \u001b[0m\u001b[1;91mLucien Dupont \u001b[0m\u001b[1;91m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m3 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;91m \u001b[0m\u001b[1;91mMarc Renault \u001b[0m\u001b[1;91m \u001b[0m┃\n", "┃\u001b[2m \u001b[0m\u001b[2m4 \u001b[0m\u001b[2m \u001b[0m│\u001b[1;91m \u001b[0m\u001b[1;91mSophie Dubois \u001b[0m\u001b[1;91m \u001b[0m┃\n", "┗━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n",
              "Who is the killer? (Enter suspect number): 
\n" ], "text/plain": [ "\n", "\u001b[1;31mWho is the killer?\u001b[0m (Enter suspect number): " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ ❗ Wrong Guess ────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              " The person you chose was innocent.                                                                              \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[33m╭─\u001b[0m\u001b[33m ❗ Wrong Guess \u001b[0m\u001b[33m───────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[33m─╮\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[1;33mThe person you chose was innocent.\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
╭─ ⏳ Guesses ────────────────────────────────────────────────────────────────────────────────────────────────────╮\n",
              " You have 2 guesses remaining                                                                                    \n",
              "╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n",
              "
\n" ], "text/plain": [ "\u001b[33m╭─\u001b[0m\u001b[33m ⏳ Guesses \u001b[0m\u001b[33m───────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\u001b[33m─╮\u001b[0m\n", "\u001b[33m│\u001b[0m \u001b[1mYou have 2 guesses remaining\u001b[0m \u001b[33m│\u001b[0m\n", "\u001b[33m╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n",
              "Who is the killer? (Enter suspect number): 
\n" ], "text/plain": [ "\n", "\u001b[1;31mWho is the killer?\u001b[0m (Enter suspect number): " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Input\n", "max_characters = 5\n", "num_guesses_left: int = 3\n", "environment = \"Mistral office in Paris\"\n", "\n", "output = graph.invoke({\"environment\":environment,\"max_characters\":max_characters, \"num_guesses_left\": num_guesses_left})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](https://europe-west1-genai-agents-views-tracker.cloudfunctions.net/genai-agents-tracker?notebook=all-agents-tutorials--murder-mystery-agent-langgraph)" ] } ], "metadata": { "colab": { "collapsed_sections": [ "9M3jgJYVgBsp" ], "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }