MetaGPT Werewolf Game Implementation: Framework Analysis

Hello everyone, I am Student Xiao Zhang, continuously learning C++ advanced knowledge and practical cases of AI large models. I will keep sharing, and welcome everyone to like + follow for mutual learning and progress.

This article will explore a practical case of MetaGPT – the Werewolf game. The source code for this case can be found on MetaGPT GitHub repository (https://github.com/geekan/MetaGPT/tree/main/examples/werewolf_game).

0. Werewolf Game Rules

If you are unfamiliar, please refer to the game rules below to better understand the content that follows.

The Werewolf game is a social deduction game involving multiple participants, with roles divided into three main categories: werewolves, villagers, and special roles.

Basic Rules

  1. 1. Role Assignment: Before the game starts, each player is randomly assigned a role, including werewolves, ordinary villagers, and villagers with special abilities (e.g., seer, witch, hunter, etc.).

  2. 2. Game Process: The game is divided into two phases: night and day. At night, werewolves open their eyes and kill one player; during the day, all players discuss and vote to execute one player. This process continues until a victory condition is met.

  3. 3. Victory Conditions: The victory conditions are divided into werewolf camp victory and villager camp victory.

  • Werewolf Victory: The werewolf camp wins when the number of werewolves equals the number of villagers.

  • Villager Victory: The villager camp wins when all werewolves are identified and executed.

Special Role Introductions

  • Seer: Can check the identity of one player each night.

  • Witch: Has a potion to save a player; has a poison to kill one player. Usually can only use the antidote on the first night.

  • Hunter: Can shoot and take one player with them when executed.

  • Guard: Can protect one player each night from being killed by werewolves.

The overall game process can be represented in the flowchart below:

MetaGPT Werewolf Game Implementation: Framework Analysis

1. Let’s Run It and See the Effect

Following my steps, the first thing to do is to run the program and see what it looks like.

Run it directly in the source code by executing .
MetaGPT
eexamples
ewerewolf_gameestart_game.py

1.1 The Effect After Running

(1) The game starts, role assignment

MetaGPT Werewolf Game Implementation: Framework Analysis

(2) The host proceeds, night, the guard speaks

MetaGPT Werewolf Game Implementation: Framework Analysis

(3) Werewolves kill

MetaGPT Werewolf Game Implementation: Framework Analysis
MetaGPT Werewolf Game Implementation: Framework Analysis

(4) Similar processes continue until the game ends.

Special Reminder: Before running this game, please modify the n_round in the main function to a smaller value, otherwise your wallet may not be safe!!!

def main(
    investment: float = 20.0,
    n_round: int = 16, # Modify here! Modify here! Modify here!
    shuffle: bool = True,
    add_human: bool = False,
    use_reflection: bool = True,
    use_experience: bool = False,
    use_memory_selection: bool = False,
    new_experience_version: str = "",
):

1.2 Errors Encountered During Running

1.2.1 Various No Module Named ‘llama_index.xxxxxx’

Install the missing packages, for example, I was missing the following packages:

pip install llama-index-vector-stores-postgres -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-vector-stores-chroma -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-vector-stores-elasticsearch -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-vector-stores-faiss -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-retrievers-bm25 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-embeddings-azure-openai -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-embeddings-gemini -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install llama-index-embeddings-ollama -i https://pypi.tuna.tsinghua.edu.cn/simple

To install the missing packages, you can either search online or find out how to install llama-index related packages here: https://llamahub.ai/?tab=retrievers

2. Case Breakdown

This case is relatively complex, with a lot of code and many details, so this article will first break down the overall framework, and more code and implementation details will be covered in dedicated follow-up articles.

For multi-agent programs based on MetaGPT, the core generally involves three parts: Role, Action, and Environment. Once you identify these three parts, the overall framework should become clear.

Role represents the agent’s identity, Action corresponds to the actions of that role, and Environment connects the messages of various Roles to facilitate information exchange among multiple agents.

2.1 Role Settings and Corresponding Action Settings

2.1.0 Required Roles and Role Framework – BasePlayer

2.1.0.1 Required Roles

First, let’s look at the role settings. According to the rules of the Werewolf game, we need the following types of roles:

  • • Moderator

  • • Guard

  • • Villager

  • • Werewolf

  • • Seer

  • • Witch

Thus, the code first defines these types of roles:

class RoleType(Enum):
    VILLAGER = "Villager" # Villager
    WEREWOLF = "Werewolf" # Werewolf
    GUARD = "Guard" # Guard
    SEER = "Seer" # Seer
    WITCH = "Witch" # Witch
    MODERATOR = "Moderator" # Moderator

2.1.0.2 Role Framework – BasePlayer

The code includes a BasePlayer class that encapsulates the basic behaviors and attributes of roles, from which all roles inherit and derive. Its basic attributes and initialization are as follows:

class BasePlayer(Role):
    name: str = "PlayerXYZ"
    profile: str = "BasePlayer"
    special_action_names: list[str] = []
    use_reflection: bool = True
    use_experience: bool = False
    use_memory_selection: bool = False
    new_experience_version: str = ""
    status: RoleState = RoleState.ALIVE

    special_actions: list[SerializeAsAny[Action]] = Field(default=[], validate_default=True)
    experiences: list[RoleExperience] = []

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Skills and listening configuration
        self._watch([InstructSpeak])  # Listen for Moderator instructions to take action
        special_actions = [ACTIONS[action_name] for action_name in self.special_action_names]
        capable_actions = [Speak] + special_actions
        self.set_actions(capable_actions)  # Assign action skills to the role
        self.special_actions = special_actions

        if not self.use_reflection and self.use_experience:
            logger.warning("You must enable use_reflection before using experience")
            self.use_experience = False

(1) First, all roles need to listen for messages generated by the InstructSpeak action: self._watch([InstructSpeak])

(2) Role behavior settings: self.set_actions(capable_actions), including the incoming special_actions and Speak Action.

(3) The meanings of other parameters will be explored later. For now, you just need to understand how each role’s behavior is derived.

2.1.0.3 Common Action for All Roles – Speak

As we saw above, there is a Speak Action in BasePlayer, which all derived roles from BasePlayer possess by default.

The core idea is that each role assembles a prompt based on their information and inputs it to the large model to obtain the model’s output.

MetaGPT Werewolf Game Implementation: Framework Analysis

Next, let’s take a look at the design of each role.

2.1.1 Moderator – Moderator

.
MetaGPT ext
ewerewolf
oles
ewoderator.py

2.1.1.1 Role Setting

The role setting for the moderator is as follows:

class Moderator(BasePlayer):
    name: str = RoleType.MODERATOR.value
    profile: str = RoleType.MODERATOR.value

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._watch([UserRequirement, InstructSpeak, ParseSpeak])
        self.set_actions([InstructSpeak, ParseSpeak, AnnounceGameResult])
  • • Actions it receives: UserRequirement user input, InstructSpeak, ParseSpeak

  • • Actions it needs to execute: InstructSpeak, ParseSpeak, AnnounceGameResult

  • • Of course, it also inherits from BasePlayer, so it also has the Speak Action that needs to be executed.

2.1.1.2 Role Action: InstructSpeak

The code is as follows:

class InstructSpeak(Action):
    name: str = "InstructSpeak"

    async def run(self, step_idx, living_players, werewolf_players, player_hunted, player_current_dead):
        instruction_info = STEP_INSTRUCTIONS.get(
            step_idx, {"content": "Unknown instruction.", "send_to": {}, "restricted_to": {}}
        )
        content = instruction_info["content"]
        if "{living_players}" in content and "{werewolf_players}" in content:
            content = content.format(
                living_players=living_players, werewolf_players=werewolf_players, werewolf_num=len(werewolf_players)
            )
        if "{living_players}" in content:
            content = content.format(living_players=living_players)
        if "{werewolf_players}" in content:
            content = content.format(werewolf_players=werewolf_players)
        if "{player_hunted}" in content:
            content = content.format(player_hunted=player_hunted)
        if "{player_current_dead}" in content:
            player_current_dead = "No one" if not player_current_dead else player_current_dead
            content = content.format(player_current_dead=player_current_dead)

        return content, instruction_info["send_to"], instruction_info["restricted_to"]

The purpose of this action is to host the entire game process, with a parameter step_idx indicating which step the game is currently at. Based on this step, it retrieves the information for this step from the pre-set process. A brief look at the preset process steps (too many to display all, just a portion to give you an idea):

MetaGPT Werewolf Game Implementation: Framework Analysis

These steps are preset prompts and messages indicating who should speak next based on the process outlined at the beginning of the article.

2.1.1.3 Role Action: ParseSpeak

This action is used to parse the information spoken by roles.

2.1.1.4 Role Action: AnnounceGameResult

Announces the end of the game, the game result, and the reason for the game’s conclusion.

class AnnounceGameResult(Action):
    async def run(self, winner: str, win_reason: str):
        return f"Game over! {win_reason}. The winner is the {winner}"

2.1.1.5 Observational Action: UserRequirement

Used to start the game.

2.1.1.6 Observational Action: InstructSpeak

Receives messages from InstructSpeak; if the message needs to be handled by the moderator, it processes that message.

For example, from round 0 to round 1, after the moderator says “night falls, please close your eyes,” they will continue to say, “please guard open your eyes.” The message for InstructSpeak in round 0 is directed to the moderator. So, the code states:

"send_to": {RoleType.MODERATOR.value},  # for moderator to continue speaking

2.1.1.7 Observational Action: ParseSpeak

After parsing the role speeches, the moderator continues speaking to host the process.

2.1.1.8 Summary

Thus, the main responsibilities of the Moderator are: starting the game, hosting the process, parsing role speeches, and announcing game results.

2.1.2 Villager – Villager

class Villager(BasePlayer):
    name: str = RoleType.VILLAGER.value
    profile: str = RoleType.VILLAGER.value
    special_action_names: list[str] = []

The villager inherits from BasePlayer, so they possess the Speak behavior; besides that, they have no special abilities and can only Speak.

2.1.3 Werewolf – Werewolf

2.1.3.1 Role Setting

class Werewolf(BasePlayer):
    name: str = RoleType.WEREWOLF.value
    profile: str = RoleType.WEREWOLF.value
    special_action_names: list[str] = ["Hunt"]

    async def _think(self):
        """When the werewolf speaks during the day, they need to disguise themselves, which is different from other roles, so _think needs to be overridden"""
        await super()._think()
        if isinstance(self.rc.todo, Speak):
            self.rc.todo = Impersonate()

(1) In addition to being able to Speak (inherited from BasePlayer), the werewolf possesses the special skill Hunt.

(2) During the day, the werewolf must disguise themselves as a good person while speaking, which requires an additional Action: Impersonate

The werewolf has two actions: one is to kill at night, and the other is to disguise themselves as a good person during the day.

2.1.3.2 Role Action: Hunt

This action is when the werewolf decides who to kill at night.

class Hunt(NighttimeWhispers):
    name: str = "Hunt"

2.1.3.3 Role Action: Impersonate

Inherits from Speak, mainly used to disguise as a good person while speaking.

class Impersonate(Speak):
    """Action: werewolf impersonating a good guy in daytime speak"""

    STRATEGY: str = """
    Try continuously impersonating a role, such as Seer, Guard, Villager, etc., in order to mislead
    other players, make them trust you, and thus hiding your werewolf identity. However, pay attention to what your werewolf partner said, 
    DONT claim the same role as your werewolf partner. Remmber NOT to reveal your real identity as a werewolf!
    """

    name: str = "Impersonate"

2.1.4 Guard – Guard

2.1.4.1 Role Setting

class Guard(BasePlayer):
    name: str = RoleType.GUARD.value
    profile: str = RoleType.GUARD.value
    special_action_names: list[str] = ["Protect"]

The guard’s special skill is Protect, which is to protect someone.

2.1.4.2 Role Action: Protect

class Protect(NighttimeWhispers):
    name: str = "Protect"

2.1.5 Seer – Seer

2.1.5.1 Role Setting

class Seer(BasePlayer):
    name: str = RoleType.SEER.value
    profile: str = RoleType.SEER.value
    special_action_names: list[str] = ["Verify"]

The seer’s special skill is Verify, which verifies the identity of other roles.

2.1.5.2 Role Action: Verify

class Verify(NighttimeWhispers):
    name: str = "Verify"

2.1.6 Witch – Witch

2.1.6.1 Role Setting

class Witch(BasePlayer):
    name: str = RoleType.WITCH.value
    profile: str = RoleType.WITCH.value
    special_action_names: list[str] = ["Save", "Poison"]

The witch has two special skills: Save and Poison, which are to save and poison.

2.1.6.2 Role Action: Save

class Save(NighttimeWhispers):
    name: str = "Save"

2.1.6.3 Role Action: Poison

class Poison(NighttimeWhispers):
    STRATEGY: str = """
    Only poison a player if you are confident he/she is a werewolf. Don't poison a player randomly or at first night.
    If someone claims to be the witch, poison him/her, because you are the only witch, he/she can only be a werewolf.
    """

    name: str = "Poison"

2.1.7 Common Action at Night – NighttimeWhispers

From the Action code above, you may have noticed that most Actions inherit from NighttimeWhispers. This Action is set for quiet thinking and speaking during the night.

2.2 Environment – WerewolfGame, WerewolfEnv

The environment is used for message passing between roles. Additionally, round_cnt controls the maximum number of interactions. WerewolfExtEnv also updates the game and the states of each role. Let’s take a general look at the environment encapsulation:

class WerewolfGame(Team):
    """Use the "software company paradigm" to hold a werewolf game"""

    env: Optional[WerewolfEnv] = None

    def __init__(self, context: Context = None, **data: Any):
        super(Team, self).__init__(**data)
        ctx = context or Context()
        if not self.env:
            self.env = WerewolfEnv(context=ctx)
        else:
            self.env.context = ctx  # The `env` object is allocated by deserialization
class WerewolfEnv(WerewolfExtEnv, Environment):
    round_cnt: int = Field(default=0)

class WerewolfExtEnv(ExtEnv):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    players_state: dict[str, tuple[str, RoleState]] = Field(
        default_factory=dict, description="the player's role type and state by player_name"
    )

    round_idx: int = Field(default=0)  # the current round
    step_idx: int = Field(default=0)  # the current step of current round
    eval_step_idx: list[int] = Field(default=[])
    per_round_steps: int = Field(default=len(STEP_INSTRUCTIONS))

    # game global states
    game_setup: str = Field(default="", description="game setup including role and its num")
    special_role_players: list[str] = Field(default=[])
    winner: Optional[str] = Field(default=None)
    win_reason: Optional[str] = Field(default=None)
    witch_poison_left: int = Field(default=1, description="should be 1 or 0")
    witch_antidote_left: int = Field(default=1, description="should be 1 or 0")

    # game current round states, a round is from closing your eyes to the next time you close your eyes
    round_hunts: dict[str, str] = Field(default_factory=dict, description="nighttime wolf hunt result")
    round_votes: dict[str, str] = Field(
        default_factory=dict, description="daytime all players vote result, key=voter, value=voted one"
    )
    player_hunted: Optional[str] = Field(default=None)
    player_protected: Optional[str] = Field(default=None)
    is_hunted_player_saved: bool = Field(default=False)
    player_poisoned: Optional[str] = Field(default=None)
    player_current_dead: list[str] = Field(default=[])

3. Summary

This article concludes the breakdown. In summary, we have looked at the basic rules of the Werewolf game, from which we understand the basic process and required roles, as well as the skill settings for each role.

Understanding the basic process, the most fundamental process of organizing agents in MetaGPT is established (SOP). However, the SOP here is expressed differently from what we’ve seen before. Previously, I believe the SOP was implicit, meaning it is hidden within each role, connecting the entire SOP through the role’s set_actions and watch. In this game, in addition to this implicit SOP, there is also an explicit SOP process, which is the moderator’s role in stringing together the process. The code also clearly provides the complete process through STEP_INSTRUCTIONS. This is another way of thinking about SOP that can be referenced: when the process is too complex, it might be helpful to write the SOP in this way, retrieving the next step based on the step number.

With the SOP established, the framework of MetaGPT consists of three elements: roles, actions, and environment. We know the roles and their actions based on the game rules. The environment mainly provides a channel for communication between roles, and implementing the encapsulation of the environment in this direction is sufficient.

In conclusion, this article covers the overall framework of this case. With the framework in place, what remains are the details of the actions and interactions within it, which are the most challenging to implement. We will continue to conduct detailed breakdowns in separate articles.

If you find this article helpful, please give it a like and follow ~~~ Click the official account above to follow ↑↑↑

  • • Hello everyone, I am Student Xiao Zhang, daily sharing AI knowledge and practical cases.

  • • Welcome like + follow 👏, continuous learning, continuous output of valuable content.

  • • +v: jasper_8017 Let’s communicate 💬 and progress together 💪.

Overview of articles in the official account

MetaGPT Werewolf Game Implementation: Framework Analysis

Leave a Comment