This commit is contained in:
MrL 2023-10-04 11:14:16 +08:00
parent b8743fc890
commit 17a0bd5de1
2 changed files with 235 additions and 36 deletions

View file

@ -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

View file

@ -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):