A CUI-based simple werewolf game with langchain
and langgraph
-
LLM service API keys
- OpenAI API Key
- Groq API Key
- Gemini API Key
-
Python >= 3.10
-
Graphviz
git clone https://github.com/hmasdev/langchain_werewolf.git
cd langchain_werewolf
python -m pip install .
If you have uv
, uv sync
is also available to install langchain_werewolf
instead of python -m pip install .
.
You can also install langchain_werewolf
directly from the repository.
python -m pip install git+https://github.com/hmasdev/langchain_werewolf.git
-
Create
.env
file -
Set
OPENAI_API_KEY
,GROQ_API_KEY
orGOOGLE_API_KEY
in the.env
file as follows:OPENAI_API_KEY=HERE_IS_YOUR_API_KEY GROQ_API_KEY=HERE_IS_YOUR_API_KEY GOOGLE_API_KEY=HERE_IS_YOUR_API_KEY
In your command line interface like bash
,
python -m langchain_werewolf {HERE_IS_YOUR_FAVORITE_OPTIONS}
On the other hand, you can also enjoy langchain_werewolf
in python environment
$ python
Python 3.10.11 (main, Sep 20 2024, 18:41:54) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from langchain_werewolf.main import main
>>> main(**HERE_IS_YOUR_FAVORITE_OPTIONS)
$ python -m langchain_werewolf --help
Usage: python -m langchain_werewolf [OPTIONS]
Options:
-n, --n-players INTEGER The number of players. Default is 4.
--n-werewolf INTEGER The number of players with role='werewolf'.
Default is 1.
--n-villager INTEGER The number of players with role='villager'.
Default is 0.
--n-knight INTEGER The number of players with role='knight'.
Default is 0.
--n-fortuneteller INTEGER The number of players with
role='fortuneteller'. Default is 0.
-o, --output TEXT The output file. Defaults to "".
-l, --system-output-level TEXT The output type of the CLI. ['all',
'public', 'off'] and player names are valid.
Default is All.
--system-output-interface TEXT The system interface. Default is
EInputOutputType.standard.
--system-formatter TEXT The system formatter. The format should not
include anything other than "{name}",
"{timestamp}", "{message}",
"{participants}", "{template}".
-c, --config TEXT The configuration file. Defaults to "". Note
that you can specify CLI arguments in this
config file but the config file overwrite
the CLI arguments.
--seed INTEGER The random seed. Defaults to -1.
--model TEXT The model to use. Default is gpt-4o-mini.
--recursion-limit INTEGER The recursion limit. Default is 1000.
--debug Enable debug mode.
--verbose Enable verbose mode.
--help Show this message and exit.
You can also another options in the configuration json file like the followings:
{
"general": {
"n_players": 5,
"n_players_by_role": {
"werewolf": 2,
"fortuneteller": 1,
"knight": 2
}
},
"game": {
"daytime_chat_kwargs": {
"n_turns_per_day": 2,
"select_speaker": "round_robin"
},
"nighttime_chat_kwargs": {
"n_turns_per_day": 2,
"select_speaker": "random"
}
}
}
{
"general": {
"n_players": 5,
"n_players_by_role": {
"werewolf": 2,
"fortuneteller": 1,
"knight": 2
},
"system_output_interface": "none"
},
"game": {
"daytime_chat_kwargs": {
"n_turns_per_day": 2,
"select_speaker": "round_robin"
},
"nighttime_chat_kwargs": {
"n_turns_per_day": 2,
"select_speaker": "random"
}
},
"players": [
{
"name": "Me",
"player_input_interface": "click",
"player_output_interface": "click",
"language": "Japanese"
}
]
}
Then, the configuration file can be specified by -c
or --config
option.
See config.py for more details like the schema of the configuration json file.
You can see a more detailed grpah in .pics/langchain_werewolf_game_graph.png.
How to Generate the Drawing of the Graphs
Python 3.10.11 (main, Sep 20 2024, 18:41:54) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dotenv import load_dotenv
>>> load_dotenv()
True
>>> from langchain_werewolf.game.main import create_game_graph
>>> from langchain_werewolf.main import DEFAULT_CONFIG
>>> from langchain_werewolf.setup import generate_players
>>> players = generate_players(DEFAULT_CONFIG.general.n_players, DEFAULT_CONFIG.general.n_werewolves, DEFAULT_CONFIG.general.n_knights, DEFAULT_CONFIG.general.n_fortune_tellers, seed=DEFAULT_CONFIG.general.seed, custom_players=DEFAULT_CONFIG.players)
>>> graph = create_game_graph(players)
>>> graph.get_graph(xray=False).draw_png('pics/langchain_werewolf_game_graph_simple.png')
>>> graph.get_graph(xray=True).draw_png('pics/langchain_werewolf_game_graph.png')
In langchain_werewolf
, you can add your custom roles by creating a new class with a decorator.
This section walks you through creating and registering a new role in langchain_werewolf.
Follow the checklist first, then consult the detailed steps and code samples.
-
Subclass
BaseGamePlayerRole
- One Side mixin:
VillagerSideMixin
,WerewolfSideMixin
, or your own subclass ofBasePlayerSideMixin
-
Mandatory class attributes
role: ClassVar[str] = "YourCustomRole" night_action: ClassVar[str] = "YourCustomRole Night Action Description Here"
If you want to implement a custom night action, override
act_in_night
method. -
Registration
-
Add
@PlayerRoleRegistry.register
above your class. -
Import the module in
langchain_werewolf/game_players/player_roles/__init__.py
from .your_custom_role import YourCustomRole # add this line
-
-
Create a new module in
langchain_werewolf/game_players/player_roles/
directory, e.g.your_custom_role.py
. -
Implement your custom role class in the module. The class must inherits from the above 2 classes and have
role
andnight_action
attributes asClassVar[str]
. You can implement your own night action method by overridingact_in_night
method.Here are existing role classes in "langchain_werewolf/game_players/player_roles" for example, one is without
act_in_night
and the other is withact_in_night
method overridden.Villager Implementation
from typing import ClassVar from ..base import BaseGamePlayerRole from ..player_sides import VillagerSideMixin from ..registry import PlayerRoleRegistry @PlayerRoleRegistry.register class Villager(BaseGamePlayerRole, VillagerSideMixin): role: ClassVar[str] = 'villager' night_action: ClassVar[str] = 'No night action'
FortuneTeller Implementation
import json from typing import ClassVar, Iterable from langchain_core.exceptions import OutputParserException from pydantic import Field from ..base import BaseGamePlayer, BaseGamePlayerRole from ..player_sides import VillagerSideMixin from ..registry import PlayerRoleRegistry from ..utils import is_werewolf_role from ...const import GAME_MASTER_NAME from ...llm_utils import extract_name from ...models.state import ( MsgModel, StateModel, create_dict_to_record_chat, ) from ..utils import find_player_by_name @PlayerRoleRegistry.register class FortuneTeller(BaseGamePlayerRole, VillagerSideMixin): role: ClassVar[str] = 'fortuneteller' night_action: ClassVar[str] = 'Check whether a player is a werewolf or not' # noqa question_to_decide_night_action: str = Field( default='Who do you want to check whether he/she is a werewolf or not?', # noqa title="the question to decide the night action of the player", ) def act_in_night( self, players: Iterable[BaseGamePlayer], messages: Iterable[MsgModel], state: StateModel, ) -> dict[str, object]: target_player_name_raw = self.generate_message( prompt=self.question_to_decide_night_action, system_prompt=json.dumps([m.model_dump() for m in messages]), ) try: target_player_name = extract_name( target_player_name_raw.message, [p.name for p in players if p.name in state.alive_players_names], # noqa context=f'Extract the valid name of the player as the answer to "{self.question_to_decide_night_action}"', # noqa chat_model=self.runnable, ) except OutputParserException: return create_dict_to_record_chat( # type: ignore # noqa self.name, [GAME_MASTER_NAME], 'Failed to decide the target player.', ) try: target_player = find_player_by_name(target_player_name, players) return create_dict_to_record_chat( # type: ignore # noqa self.name, [GAME_MASTER_NAME], f'{target_player.name} is a werewolf' if is_werewolf_role(target_player) else f'{target_player.name} is not a werewolf' ) except ValueError: return create_dict_to_record_chat( # type: ignore # noqa self.name, [GAME_MASTER_NAME], f'Failed to find the target player: {target_player_name}' )
-
Finally, add the module name into
langchain_werewolf/game_players/player_roles/__init__.py
file.from .fortune_teller import FortuneTeller from .knight import Knight from .villager import Villager from .werewolf import Werewolf from .your_custom_role import YourCustomRole __all__ = [ FortuneTeller.__name__, Knight.__name__, Villager.__name__, Werewolf.__name__, ]
-
Test your custom role applied to the game by check the CLI help message.
python -m langchain_werewolf --help
TBD
-
Fork the repository: https://github.com/hmasdev/langchain_werewolf
-
Clone the repository
git clone https://github.com/{YOURE_NAME}/langchain_werewolf cd langchain_werewolf
-
Create a virtual environment and install the required packages
python -m venv venv source venv/bin/activate python -m pip install -e .[dev]
or
uv sync --extra dev
if you uses
uv
. -
Checkout your working branch
git checkout -b your-working-branch
-
Make your changes
-
Test your changes
pytest flake8 langchain_werewolf tests mypy langchain_werewolf tests
or
uv run pytest uv run flake8 langchain_werewolf tests uv run mypy langchain_werewolf tests
Note that the above commands run only unit tests. It is recommended to run integration tests with
uv run pytest -m integration
. -
Commit your changes
git add . git commit -m "Your commit message"
-
Push your changes
git push origin your-working-branch
-
Create a pull request: https://github.com/hmasdev/langchain_werewolf/compare