mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-24 14:15:17 +02:00
introduce nighttime thought to all roles & add simple strategies into action
This commit is contained in:
parent
343ad8d653
commit
ef1a9a4609
11 changed files with 79 additions and 116 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from examples.werewolf_game.actions.moderator_actions import InstructSpeak
|
||||
from examples.werewolf_game.actions.common_actions import Speak
|
||||
from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispers
|
||||
from examples.werewolf_game.actions.werewolf_actions import Hunt, Impersonate
|
||||
from examples.werewolf_game.actions.guard_actions import Protect
|
||||
from examples.werewolf_game.actions.seer_actions import Verify
|
||||
|
|
|
|||
|
|
@ -13,21 +13,22 @@ class Speak(Action):
|
|||
,"ATTENTION": "You can NOT VOTE a player who is NOT ALIVE now!"
|
||||
,"STRATEGY": __strategy__
|
||||
,"MODERATOR_INSTRUCTION": __latest_instruction__,
|
||||
,"RULE": "Please follow the moderator's latest instruction, figure out if you need to speak your opinion or directly to vote,
|
||||
1. If the instruction is to SPEAK, speak in 200 words. Remember the goal of your role and try to achieve it using your speech;
|
||||
2. If the instruction is to VOTE, you MUST vote and ONLY say 'I vote to eliminate PlayerX', where X is the player index, DO NOT include any other words."
|
||||
,"RULE": "Please follow the moderator's latest instruction, figure out if you need to speak your opinion or directly to vote:
|
||||
1. If the instruction is to SPEAK, speak in 200 words. Remember the goal of your role and try to achieve it using your speech;
|
||||
2. If the instruction is to VOTE, you MUST vote and ONLY say 'I vote to eliminate PlayerX', replace PlayerX with the actual player name, DO NOT include any other words."
|
||||
,"OUTPUT_FORMAT":
|
||||
{
|
||||
"ROLE": "Your role, in this case, __profile__"
|
||||
,"PLAYER_NAME": "Your name, in this case, __name__"
|
||||
,"LIVING_PLAYERS": "List living players based on MODERATOR_INSTRUCTION. Return a LIST datatype."
|
||||
,"THOUGHTS": "Based on `MODERATOR_INSTRUCTION` and `RULE`, carefully think about what to say or vote so that your chance of win as __profile__ maximizes. Return the thinking process."
|
||||
,"THOUGHTS": "Based on `MODERATOR_INSTRUCTION` and `RULE`, carefully think about what to say or vote so that your chance of win as __profile__ maximizes. Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..."
|
||||
,"RESPONSE": "Based on `MODERATOR_INSTRUCTION`, `RULE`, and the 'THOUGHTS' you had, express your opinion or cast a vote."
|
||||
}
|
||||
}
|
||||
"""
|
||||
STRATEGY = """
|
||||
Decide whether to reveal your identity based on benefits vs. risks, provide useful information, and vote to eliminate the most suspicious.
|
||||
If you have special abilities, pay attention to those who falsely claims your role, for they are probably werewolves.
|
||||
"""
|
||||
|
||||
def __init__(self, name="Speak", context=None, llm=None):
|
||||
|
|
@ -45,6 +46,7 @@ class Speak(Action):
|
|||
while re_run > 0:
|
||||
rsp = await self._aask(prompt)
|
||||
try:
|
||||
rsp = rsp.replace("\n", " ")
|
||||
rsp_json = json.loads(rsp)
|
||||
break
|
||||
except:
|
||||
|
|
@ -63,58 +65,63 @@ class NighttimeWhispers(Action):
|
|||
Usage Example:
|
||||
|
||||
class Hunt(NighttimeWhispers):
|
||||
ACTION = "KILL"
|
||||
def __init__(self, name="Hunt", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
class Protect(NighttimeWhispers):
|
||||
ACTION = "PROTECT"
|
||||
def __init__(self, name="Protect", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
class Verify(NighttimeWhispers):
|
||||
ACTION = "VERIFY"
|
||||
def __init__(self, name="Verify", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
class Save(NighttimeWhispers):
|
||||
ACTION = "SAVE"
|
||||
def __init__(self, name="Save", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
def _construct_prompt_json(self, prompt_json: dict, profile: str, action: str, context: str):
|
||||
def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
|
||||
del prompt_json['ACTION']
|
||||
del prompt_json['ATTENTION']
|
||||
|
||||
prompt_json["OUTPUT_FORMAT"]["THOUGHTS"] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST be killed at this night."
|
||||
prompt_json["OUTPUT_FORMAT"]["RESPONSE"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS."
|
||||
|
||||
return self._default_construct_prompt_json(prompt_json, profile, name, action, context)
|
||||
return prompt_json
|
||||
|
||||
class Poison(NighttimeWhispers):
|
||||
ACTION = "POISON"
|
||||
|
||||
def _construct_prompt_json(self, prompt_json: dict, profile: str, action: str, context: str):
|
||||
prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, then return PASS."
|
||||
return self._default_construct_prompt_json(prompt_json, profile, name, action, context)
|
||||
def __init__(self, name="Poison", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
|
||||
prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS."
|
||||
return prompt_json
|
||||
"""
|
||||
|
||||
ACTION = "KILL"
|
||||
PROMPT_TEMPLATE = """
|
||||
{
|
||||
"ROLE": "__profile__"
|
||||
,"ACTION": "Choose one living player to __action__."
|
||||
,"ATTENTION": "You can only __action__ a player who is alive at this night! And you can not __action__ a player who is dead as this night!"
|
||||
,"PHASE": "Night"
|
||||
,"BACKGROUND": "It's a werewolf game and you are a __profile__. Here's the game history:__context__."
|
||||
,"ATTENTION": "1. You can only __action__ a player who is alive this night! And you can not __action__ a player who is dead this night! 2. `HISTORY` is all the information you observed, DONT hallucinate other player actions!"
|
||||
,"STRATEGY": "__strategy__"
|
||||
,"BACKGROUND": "It's a werewolf game and you are a __profile__. Here's the game history: __context__."
|
||||
,"OUTPUT_FORMAT":
|
||||
{
|
||||
"ROLE": "Your role, in this case, __profile__"
|
||||
,"PLAYER_NAME": "Your name, in this case, __name__"
|
||||
,"LIVING_PLAYERS": "List the players who is alive based on moderator's latest instruction. Return a LIST datatype."
|
||||
,"THOUGHTS": "It is night time. Return the thinking steps of your decision of choosing one living player from `LIVING_PLAYERS` to __action__ this night. And return the reason why you choose to __action__ this player."
|
||||
,"OUTPUT": "As a __profile__, you should choose one living player from `LIVING_PLAYERS` to __action__ this night according to the THOUGHTS you have just now. Return the player name ONLY."
|
||||
,"THOUGHTS": "Choose one living player from `LIVING_PLAYERS` to __action__ this night. Return the reason why you choose to __action__ this player. If you observe nothing at first night, DONT imagine unexisting player actions! Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..."
|
||||
,"RESPONSE": "As a __profile__, you should choose one living player from `LIVING_PLAYERS` to __action__ this night according to the THOUGHTS you have just now. Return the player name ONLY."
|
||||
}
|
||||
}
|
||||
"""
|
||||
STRATEGY = """
|
||||
Decide which player is most threatening to you or most needs your support, take your action correspondingly.
|
||||
"""
|
||||
|
||||
def __init__(self, name="NightTimeWhispers", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
def _default_construct_prompt_json(self, prompt_json: dict, profile: str, name:str, action: str, context: str):
|
||||
def _construct_prompt_json(self, role_profile: str, role_name: str, context: str, **kwargs):
|
||||
prompt_template = self.PROMPT_TEMPLATE
|
||||
|
||||
def replace_string(prompt_json: dict):
|
||||
k: str
|
||||
|
|
@ -122,39 +129,46 @@ class NighttimeWhispers(Action):
|
|||
if isinstance(prompt_json[k], dict):
|
||||
prompt_json[k] = replace_string(prompt_json[k])
|
||||
continue
|
||||
prompt_json[k] = prompt_json[k].replace("__profile__", profile)
|
||||
prompt_json[k] = prompt_json[k].replace("__name__", name)
|
||||
prompt_json[k] = prompt_json[k].replace("__action__", action)
|
||||
prompt_json[k] = prompt_json[k].replace("__profile__", role_profile)
|
||||
prompt_json[k] = prompt_json[k].replace("__name__", role_name)
|
||||
prompt_json[k] = prompt_json[k].replace("__context__", context)
|
||||
prompt_json[k] = prompt_json[k].replace("__action__", self.name)
|
||||
prompt_json[k] = prompt_json[k].replace("__strategy__", self.STRATEGY)
|
||||
|
||||
return prompt_json
|
||||
|
||||
prompt_json: dict = json.loads(prompt_template)
|
||||
|
||||
prompt_json = replace_string(prompt_json)
|
||||
|
||||
prompt_json["BACKGROUND"] = prompt_json["BACKGROUND"].replace("__context__", context)
|
||||
prompt_json: dict = self._update_prompt_json(prompt_json, role_profile, role_name, context, **kwargs)
|
||||
assert isinstance(prompt_json, dict)
|
||||
|
||||
prompt: str = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False)
|
||||
|
||||
return prompt
|
||||
|
||||
def _update_prompt_json(self, prompt_json: dict, role_profile: str, role_name: str, context: str) -> dict:
|
||||
# one can modify the prompt_json dictionary here
|
||||
return prompt_json
|
||||
|
||||
def _construct_prompt_json(self, prompt_json: dict, profile: str, name: str, action: str, context: str):
|
||||
return self._default_construct_prompt_json(prompt_json, profile, name, action, context)
|
||||
|
||||
async def run(self, context: str, profile: str, name: str):
|
||||
|
||||
prompt_json = json.loads(self.PROMPT_TEMPLATE)
|
||||
prompt_json = self._construct_prompt_json(
|
||||
prompt_json=prompt_json, profile=profile, name=name, action=self.ACTION, context=context
|
||||
final_prompt = self._construct_prompt_json(
|
||||
role_profile=profile, role_name=name, context=context
|
||||
)
|
||||
final_prompt = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False)
|
||||
|
||||
re_run = 2
|
||||
while re_run > 0:
|
||||
rsp_content = await self._aask(final_prompt)
|
||||
try:
|
||||
rsp_content = rsp_content.replace("\n", " ")
|
||||
rsp = json.loads(rsp_content)
|
||||
break
|
||||
except:
|
||||
re_run -= 1
|
||||
|
||||
with open(WORKSPACE_ROOT / f'{self.ACTION}.txt', 'a') as f:
|
||||
with open(WORKSPACE_ROOT / f'{self.name}.txt', 'a') as f:
|
||||
f.write(rsp_content)
|
||||
|
||||
return f"{self.ACTION} " + str(rsp["OUTPUT"])
|
||||
return f"{self.name} " + str(rsp["RESPONSE"])
|
||||
|
|
|
|||
|
|
@ -1,26 +1,7 @@
|
|||
from metagpt.actions import Action
|
||||
from examples.werewolf_game.actions import NighttimeWhispers
|
||||
|
||||
class Protect(Action):
|
||||
"""Action: choose a player to protect"""
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
It's a werewolf game and you are a guard,
|
||||
you can choose to protect a player, including yourself, then the protected player will not be killed by the Werewolves this night.
|
||||
this is game history:
|
||||
{context}.
|
||||
Attention: you can not protect the same player two nights in a row.
|
||||
Format: "Protect PlayerX", where X is the player index.
|
||||
Now, choose one to protect, you will:
|
||||
"""
|
||||
class Protect(NighttimeWhispers):
|
||||
|
||||
def __init__(self, name="Protect", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context)
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
# rsp = "Protect Player 1"
|
||||
|
||||
return rsp
|
||||
|
|
@ -42,7 +42,7 @@ STEP_INSTRUCTIONS = {
|
|||
"restricted_to": "Moderator,Witch"}, # 要先判断女巫是否有解药,再去询问女巫是否使用解药救人
|
||||
9: {"content": """Witch, you also have a bottle of poison, would you like to use it to kill one of the living players?
|
||||
Choose one from the following living options: {living_players}.
|
||||
If so, say "Poison PlayerX", where X is the player index, else, say "Pass".""",
|
||||
If so, say ONLY "Poison PlayerX", replace PlayerX with the actual player name, else, say "Pass".""",
|
||||
"send_to": "Witch",
|
||||
"restricted_to": "Moderator,Witch"}, #
|
||||
10: {"content": "Witch, close your eyes",
|
||||
|
|
|
|||
|
|
@ -1,24 +1,6 @@
|
|||
from metagpt.actions import Action
|
||||
from examples.werewolf_game.actions import NighttimeWhispers
|
||||
|
||||
|
||||
class Verify(Action):
|
||||
"""Action: Seer verifies a player's identity at night"""
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
It's a werewolf game and you are a seer.
|
||||
You can choose to verify the identity of a player.
|
||||
Here's the game history:
|
||||
{context}.
|
||||
Now, choose one player to verify
|
||||
Format: "Verify PlayerX", where X is the player index.
|
||||
You will:
|
||||
"""
|
||||
|
||||
class Verify(NighttimeWhispers):
|
||||
def __init__(self, name="Verify", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str):
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context)
|
||||
rsp = await self._aask(prompt)
|
||||
|
||||
return rsp
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispe
|
|||
|
||||
|
||||
class Hunt(NighttimeWhispers):
|
||||
ACTION = "KILL"
|
||||
def __init__(self, name="Hunt", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
class Impersonate(Speak):
|
||||
"""Action: werewolf impersonating a good guy in daytime speak"""
|
||||
|
|
|
|||
|
|
@ -1,45 +1,30 @@
|
|||
from metagpt.actions import Action
|
||||
from examples.werewolf_game.actions import NighttimeWhispers
|
||||
|
||||
class Save(Action):
|
||||
"""Action: choose a villager to Save"""
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
It's a werewolf game and you are a witch,
|
||||
this is game history:
|
||||
{context}.
|
||||
Follow the Moderator's instruction, decide whether you want to save that person or not:
|
||||
"""
|
||||
|
||||
class Save(NighttimeWhispers):
|
||||
def __init__(self, name="Save", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str):
|
||||
def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
|
||||
del prompt_json['ACTION']
|
||||
del prompt_json['ATTENTION']
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context)
|
||||
prompt_json["OUTPUT_FORMAT"]["THOUGHTS"] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST be killed at this night."
|
||||
prompt_json["OUTPUT_FORMAT"]["RESPONSE"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS."
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
# rsp = "Save Player 1"
|
||||
return prompt_json
|
||||
|
||||
return rsp
|
||||
|
||||
class Poison(Action):
|
||||
"""Action: choose a villager to Poison"""
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
It's a werewolf game and you are a witch,
|
||||
this is game history:
|
||||
{context}.
|
||||
Follow the Moderator's instruction, decide whether you want to poison another person or not:
|
||||
class Poison(NighttimeWhispers):
|
||||
STRATEGY = """
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, name="Poison", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str):
|
||||
def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.format(context=context)
|
||||
prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS."
|
||||
|
||||
rsp = await self._aask(prompt)
|
||||
# rsp = "Poison Player 1"
|
||||
|
||||
return rsp
|
||||
return prompt_json
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class Guard(BasePlayer):
|
|||
)
|
||||
|
||||
elif isinstance(todo, Protect):
|
||||
rsp = await todo.run(context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Protect, send_to="",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ async def _act(self):
|
|||
## You are {self.name}({self.profile})
|
||||
## Guidance:
|
||||
1. If you are performing a special action or exercising a vote,
|
||||
end your response with "PlayerX" where X is the player index, e.g., "..., kill/protect/poison/.../vote Player1".
|
||||
end your response with "PlayerX", replace PlayerX with the actual player name, e.g., "..., kill/protect/poison/.../vote Player1".
|
||||
2. If it is a daytime free speech, you can speak in whatever format.
|
||||
Now, please speak:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Seer(BasePlayer):
|
|||
)
|
||||
|
||||
elif isinstance(todo, Verify):
|
||||
rsp = await todo.run(context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Verify, send_to="",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class Witch(BasePlayer):
|
|||
)
|
||||
|
||||
elif isinstance(todo, Save):
|
||||
rsp = await todo.run(context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Save, send_to="",
|
||||
|
|
@ -58,7 +58,7 @@ class Witch(BasePlayer):
|
|||
)
|
||||
|
||||
elif isinstance(todo, Poison):
|
||||
rsp = await todo.run(context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Poison, send_to="",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue