From b8743fc8906675bb66201e9dd23ca1b25d934f28 Mon Sep 17 00:00:00 2001 From: MrL <332199893@qq.com> Date: Wed, 4 Oct 2023 11:12:23 +0800 Subject: [PATCH 1/6] Changes --- .../werewolf_game/actions/common_actions.py | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index 84936ebde..e69de29bb 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -1,30 +0,0 @@ -from metagpt.actions import Action - -class Speak(Action): - """Action: Any speak action in a game""" - - PROMPT_TEMPLATE = """ - ## BACKGROUND - It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, - ## HISTORY - You have knowledge to the following conversation: - {context} - ## YOUR TURN - 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 100 words; - 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. - Your will say: - """ - - def __init__(self, name="Speak", context=None, llm=None): - super().__init__(name, context, llm) - - async def run(self, context: str, profile: str): - - prompt = self.PROMPT_TEMPLATE.format(context=context, profile=profile) - - rsp = await self._aask(prompt) - - return rsp - - From 17a0bd5de1b16a2f154998b9fcd07ac86b5cf71b Mon Sep 17 00:00:00 2001 From: MrL <332199893@qq.com> Date: Wed, 4 Oct 2023 11:14:16 +0800 Subject: [PATCH 2/6] Changes --- .../werewolf_game/actions/common_actions.py | 208 ++++++++++++++++++ .../werewolf_game/actions/werewolf_actions.py | 63 +++--- 2 files changed, 235 insertions(+), 36 deletions(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index e69de29bb..14e3cc0c0 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -0,0 +1,208 @@ +from metagpt.actions import Action +import json +from metagpt.const import WORKSPACE_ROOT + + +class Speak(Action): + """Action: Any speak action in a game""" + + # PROMPT_TEMPLATE = """ + # ## BACKGROUND + # It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, + # ## HISTORY + # You have knowledge to the following conversation: + # {context} + # ## YOUR TURN + # 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 100 words; + # 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. + # Your will say: + # """ + + PROMPT_TEMPLATE = """ + { + "BACKGROUND": "It's a Werewolf game, you are __profile__, say whatever possible to increase your chance of win" + ,"HISTORY": "You have knowledge to the following conversation: __context__" + ,"ATTENTION": "You can not VOTE a player who is NOT ALIVE now! And be careful of revealing your identity !" + ,"YOUR_TURN": "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. + " + ,"OUTPUT_FORMAT": + { + "ROLE": "Your role." + ,"NUMBER": "Your player number." + ,"IDENTITY": "You are? What is you identity? You are player1 or player2 or player3 or player4 or player5 or player6 or player7?" + ,"LIVING_PLAYERS": "List the players who is alive. Return a LIST datatype." + ,"THOUGHTS": "It is day time. Return the thinking steps of your decision of giving VOTE to other player from `LIVING_PLAYERS`. And return the reason why you choose to VOTE this player from `LIVING_PLAYERS`." + ,"SPEECH_OR_VOTE": "Follow the instruction of `YOUR_TURN` above and the `THOUGHTS` you have just now, give a speech or your vote." + } + + } + """ + + def __init__(self, name="Speak", context=None, llm=None): + super().__init__(name, context, llm) + + async def run(self, context: str, profile: str): + + # prompt = self.PROMPT_TEMPLATE.format(context=context, profile=profile) + prompt = self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile) + + rsp = await self._aask(prompt) + re_run = 2 + while re_run > 0: + try: + rsp_json = json.loads(rsp) + break + except: + re_run -= 1 + + with open(WORKSPACE_ROOT / 'speak.txt', 'a') as f: + f.write(rsp) + + return rsp_json['SPEECH_OR_VOTE'] + + +class NighttimeWhispers(Action): + """ + + Action: nighttime whispers with thinking processes + + Usage Example: + + class Hunt(NighttimeWhispers): + ROLE = "Werewolf" + ACTION = "KILL" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + + class Protect(NighttimeWhispers): + ROLE = "Guard" + ACTION = "PROTECT" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + + class Verify(NighttimeWhispers): + ROLE = "Seer" + ACTION = "VERIFY" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + + class Save(NighttimeWhispers): + ROLE = "Witch" + ACTION = "SAVE" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + + def subclass_renew_prompt(self, prompt_json): + 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"]["OUTPUT"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS." + + return prompt_json + + class Poison(NighttimeWhispers): + ROLE = "Witch" + ACTION = "POISON" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + + def subclass_renew_prompt(self, prompt_json): + prompt_json["OUTPUT_FORMAT"]["OUTPUT"] += "Or if you want to PASS, then return PASS." + return prompt_json + + """ + + ROLE = "Werewolf" + ACTION = "KILL" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True + PROMPT_TEMPLATE = """ + { + "ROLE": "__role__" + ,"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 __role__. Here's the game history:{__context__}." + ,"OUTPUT_FORMAT": + { + "ROLE": "Your role." + ,"NUMBER": "Your player number." + ,"IDENTITY": "You are? What is you identity? You are player1 or player2 or player3 or player4 or player5 or player6 or player7?" + ,"LIVING_PLAYERS": "List the players who is alive. 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 __role__, you should choose one living player from `LIVING_PLAYERS` to __action__ this night according to the THOUGHTS you have just now. Return the number of the player you choose and return this NUMBER ONLY." + } + } + """ + + def __init__(self, name="NightTimeWhispers", context=None, llm=None): + super().__init__(name, context, llm) + + def _renew_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): + + def replace_string(prompt_json: dict): + k: str + for k in prompt_json.keys(): + if isinstance(prompt_json[k], dict): + prompt_json[k] = replace_string(prompt_json[k]) + continue + prompt_json[k] = prompt_json[k].replace("__role__", role) + prompt_json[k] = prompt_json[k].replace("__action__", action) + + return prompt_json + + prompt_json = replace_string(prompt_json) + + prompt_json["BACKGROUND"] = prompt_json["BACKGROUND"].replace("__context__", context) + + return prompt_json + + def subclass_renew_prompt(self, prompt_json: dict): + return prompt_json + + async def run(self, context: str): + """ + Note: `final_prompt` could be undefined and will raise error if `IF_RENEW` is true and `IF_JSON_INPUT` is False + """ + + if not self.IF_RENEW: + final_prompt = self.PROMPT_TEMPLATE.replace("__context__", context) + rsp_content = await self._aask(final_prompt) + return rsp_content + + if self.IF_JSON_INPUT: + prompt_json = json.loads(self.PROMPT_TEMPLATE) + prompt_json = self._renew_prompt_json(prompt_json=prompt_json, role=self.ROLE, action=self.ACTION, + context=context) + prompt_json = self.subclass_renew_prompt(prompt_json) # can be defined in subclass + final_prompt = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False) + + rsp_content = await self._aask(final_prompt) + + with open(WORKSPACE_ROOT / f'{self.ACTION}.txt', 'a') as f: + f.write(rsp_content) + + if self.IF_JSON_OUTPUT: + re_run = 2 + while re_run > 0: + try: + rsp = json.loads(rsp_content) + break + except: + re_run -= 1 + return f"{self.ACTION} Player" + str(rsp["OUTPUT"]) + + return rsp_content + + diff --git a/examples/werewolf_game/actions/werewolf_actions.py b/examples/werewolf_game/actions/werewolf_actions.py index 410221cb0..cb30ca7eb 100644 --- a/examples/werewolf_game/actions/werewolf_actions.py +++ b/examples/werewolf_game/actions/werewolf_actions.py @@ -1,46 +1,37 @@ from metagpt.actions import Action -from examples.werewolf_game.actions.common_actions import Speak +from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispers -class Hunt(Action): - """Action: choose a villager to kill""" - - PROMPT_TEMPLATE = """ - It's a werewolf game and you are a werewolf, - this is game history: - {context}. - Attention: if your previous werewolf has chosen, follow its choice. - Format: "Kill PlayerX", where X is the player index. - Now, choose one to kill, you will: - """ - - def __init__(self, name="Hunt", 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 = "Kill Player 1" - - return rsp +class Hunt(NighttimeWhispers): + ROLE = "Werewolf" + ACTION = "KILL" + IF_RENEW = True + IF_JSON_INPUT = True + IF_JSON_OUTPUT = True class Impersonate(Speak): """Action: werewolf impersonating a good guy in daytime speak""" PROMPT_TEMPLATE = """ - ## BACKGROUND - It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, - ## HISTORY - You have knowledge to the following conversation: - {context} - ## ATTENTION: Try continuously impersonating a role with special ability, such as a Seer or a Witch, in order to mislead - other players, make them trust you, and thus hiding your werewolf identity - ## YOUR TURN - 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 100 words; - 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. - Your will say: + { + "BACKGROUND": "It's a Werewolf game, you are __profile__, say whatever possible to increase your chance of win" + ,"HISTORY": "You have knowledge to the following conversation: __context__" + ,"ATTENTION": "Try continuously impersonating a role with special ability, such as a Seer or a Witch, in order to mislead + other players, make them trust you, and thus hiding your werewolf identity. You can not VOTE a player who is NOT ALIVE now!" + ,"YOUR_TURN": "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. + " + ,"OUTPUT_FORMAT": + { + "ROLE": "Your role." + ,"NUMBER": "Your player number." + ,"IDENTITY": "You are? What is you identity? You are player1 or player2 or player3 or player4 or player5 or player6 or player7?" + ,"LIVING_PLAYERS": "List the players who is alive. Return a LIST datatype." + ,"THOUGHTS": "It is day time. Return the thinking steps of your decision of giving VOTE to other player from `LIVING_PLAYERS`. And return the reason why you choose to VOTE this player from `LIVING_PLAYERS`." + ,"SPEECH_OR_VOTE": "Follow the instruction of `YOUR_TURN` above and the `THOUGHTS` you have just now, give a speech or your vote. Remember, you are a WEREWOLF!!! But just keep it in mind, don't tell other players." + } + } """ def __init__(self, name="Impersonate", context=None, llm=None): From e694848b48ed63f74fba73d0deeeed480be811c1 Mon Sep 17 00:00:00 2001 From: MrL <332199893@qq.com> Date: Wed, 4 Oct 2023 11:31:05 +0800 Subject: [PATCH 3/6] make roles to speak their mind while they act --- examples/werewolf_game/actions/common_actions.py | 1 - examples/werewolf_game/actions/werewolf_actions.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index 14e3cc0c0..af477d397 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -64,7 +64,6 @@ class Speak(Action): return rsp_json['SPEECH_OR_VOTE'] - class NighttimeWhispers(Action): """ diff --git a/examples/werewolf_game/actions/werewolf_actions.py b/examples/werewolf_game/actions/werewolf_actions.py index cb30ca7eb..74905ff16 100644 --- a/examples/werewolf_game/actions/werewolf_actions.py +++ b/examples/werewolf_game/actions/werewolf_actions.py @@ -1,6 +1,7 @@ from metagpt.actions import Action from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispers + class Hunt(NighttimeWhispers): ROLE = "Werewolf" ACTION = "KILL" From 546c58aa6a02ba706e780bbf6ce90c1fb3330c98 Mon Sep 17 00:00:00 2001 From: MrL <332199893@qq.com> Date: Wed, 4 Oct 2023 11:35:51 +0800 Subject: [PATCH 4/6] make roles to speak their mind while they act --- examples/werewolf_game/actions/common_actions.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index af477d397..5f56806da 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -6,19 +6,6 @@ from metagpt.const import WORKSPACE_ROOT class Speak(Action): """Action: Any speak action in a game""" - # PROMPT_TEMPLATE = """ - # ## BACKGROUND - # It's a Werewolf game, you are {profile}, say whatever possible to increase your chance of win, - # ## HISTORY - # You have knowledge to the following conversation: - # {context} - # ## YOUR TURN - # 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 100 words; - # 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. - # Your will say: - # """ - PROMPT_TEMPLATE = """ { "BACKGROUND": "It's a Werewolf game, you are __profile__, say whatever possible to increase your chance of win" From 55794a572bca46077ce439dd987978fa2176f555 Mon Sep 17 00:00:00 2001 From: MrL <332199893@qq.com> Date: Wed, 4 Oct 2023 11:40:41 +0800 Subject: [PATCH 5/6] make roles to speak their mind while they act --- examples/werewolf_game/actions/common_actions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index 5f56806da..9a5e628eb 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -34,7 +34,6 @@ class Speak(Action): async def run(self, context: str, profile: str): - # prompt = self.PROMPT_TEMPLATE.format(context=context, profile=profile) prompt = self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile) rsp = await self._aask(prompt) From 010d11600baaefc24320ffe87efd58c0670e4c24 Mon Sep 17 00:00:00 2001 From: davidlee21 <36806941+davidlee21@users.noreply.github.com> Date: Sun, 8 Oct 2023 22:02:42 +0800 Subject: [PATCH 6/6] Update the proposal according to the reviewer's suggestions for improvement --- .../werewolf_game/actions/common_actions.py | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/examples/werewolf_game/actions/common_actions.py b/examples/werewolf_game/actions/common_actions.py index 9a5e628eb..d54b1957a 100644 --- a/examples/werewolf_game/actions/common_actions.py +++ b/examples/werewolf_game/actions/common_actions.py @@ -36,10 +36,10 @@ class Speak(Action): prompt = self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile) - rsp = await self._aask(prompt) re_run = 2 while re_run > 0: try: + rsp = await self._aask(prompt) rsp_json = json.loads(rsp) break except: @@ -85,14 +85,14 @@ class NighttimeWhispers(Action): IF_JSON_INPUT = True IF_JSON_OUTPUT = True - def subclass_renew_prompt(self, prompt_json): + def _construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): 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"]["OUTPUT"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS." - return prompt_json + return self._default_construct_prompt_json(prompt_json, role, action, context) class Poison(NighttimeWhispers): ROLE = "Witch" @@ -101,9 +101,9 @@ class NighttimeWhispers(Action): IF_JSON_INPUT = True IF_JSON_OUTPUT = True - def subclass_renew_prompt(self, prompt_json): + def _construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): prompt_json["OUTPUT_FORMAT"]["OUTPUT"] += "Or if you want to PASS, then return PASS." - return prompt_json + return self._default_construct_prompt_json(prompt_json, role, action, context) """ @@ -134,7 +134,7 @@ class NighttimeWhispers(Action): def __init__(self, name="NightTimeWhispers", context=None, llm=None): super().__init__(name, context, llm) - def _renew_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): + def _default_construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): def replace_string(prompt_json: dict): k: str @@ -153,12 +153,12 @@ class NighttimeWhispers(Action): return prompt_json - def subclass_renew_prompt(self, prompt_json: dict): - return prompt_json + def _construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str): + return self._default_construct_prompt_json(prompt_json, role, action, context) async def run(self, context: str): """ - Note: `final_prompt` could be undefined and will raise error if `IF_RENEW` is true and `IF_JSON_INPUT` is False + Note: `final_prompt` could be undefined and will raise error if `IF_RENEW` is True and `IF_JSON_INPUT` is False """ if not self.IF_RENEW: @@ -168,24 +168,23 @@ class NighttimeWhispers(Action): if self.IF_JSON_INPUT: prompt_json = json.loads(self.PROMPT_TEMPLATE) - prompt_json = self._renew_prompt_json(prompt_json=prompt_json, role=self.ROLE, action=self.ACTION, - context=context) - prompt_json = self.subclass_renew_prompt(prompt_json) # can be defined in subclass + prompt_json = self._construct_prompt_json(prompt_json=prompt_json, role=self.ROLE, action=self.ACTION, + context=context) # can be defined in subclass final_prompt = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False) - rsp_content = await self._aask(final_prompt) - - with open(WORKSPACE_ROOT / f'{self.ACTION}.txt', 'a') as f: - f.write(rsp_content) - if self.IF_JSON_OUTPUT: re_run = 2 while re_run > 0: try: + rsp_content = await self._aask(final_prompt) rsp = json.loads(rsp_content) break except: re_run -= 1 + + with open(WORKSPACE_ROOT / f'{self.ACTION}.txt', 'a') as f: + f.write(rsp_content) + return f"{self.ACTION} Player" + str(rsp["OUTPUT"]) return rsp_content