mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-03 21:02:38 +02:00
add more instruction in speak prompt & simplify nighttimewhisper
This commit is contained in:
parent
0c238d2b0d
commit
343ad8d653
8 changed files with 78 additions and 124 deletions
|
|
@ -10,36 +10,41 @@ class Speak(Action):
|
|||
{
|
||||
"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,
|
||||
,"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.
|
||||
"
|
||||
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."
|
||||
"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."
|
||||
,"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.
|
||||
"""
|
||||
|
||||
def __init__(self, name="Speak", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
async def run(self, context: str, profile: str):
|
||||
async def run(self, profile: str, name: str, context: str, latest_instruction: str):
|
||||
|
||||
prompt = self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile)
|
||||
prompt = (
|
||||
self.PROMPT_TEMPLATE.replace("__context__", context).replace("__profile__", profile)
|
||||
.replace("__name__", name).replace("__latest_instruction__", latest_instruction)
|
||||
.replace("__strategy__", self.STRATEGY)
|
||||
)
|
||||
|
||||
re_run = 2
|
||||
while re_run > 0:
|
||||
rsp = await self._aask(prompt)
|
||||
try:
|
||||
rsp = await self._aask(prompt)
|
||||
rsp_json = json.loads(rsp)
|
||||
break
|
||||
except:
|
||||
|
|
@ -48,7 +53,7 @@ class Speak(Action):
|
|||
with open(WORKSPACE_ROOT / 'speak.txt', 'a') as f:
|
||||
f.write(rsp)
|
||||
|
||||
return rsp_json['SPEECH_OR_VOTE']
|
||||
return rsp_json['RESPONSE']
|
||||
|
||||
class NighttimeWhispers(Action):
|
||||
"""
|
||||
|
|
@ -58,75 +63,50 @@ class NighttimeWhispers(Action):
|
|||
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 _construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str):
|
||||
def _construct_prompt_json(self, prompt_json: dict, profile: 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."
|
||||
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, role, action, context)
|
||||
return self._default_construct_prompt_json(prompt_json, profile, name, action, context)
|
||||
|
||||
class Poison(NighttimeWhispers):
|
||||
ROLE = "Witch"
|
||||
ACTION = "POISON"
|
||||
IF_RENEW = True
|
||||
IF_JSON_INPUT = True
|
||||
IF_JSON_OUTPUT = True
|
||||
|
||||
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 self._default_construct_prompt_json(prompt_json, role, action, context)
|
||||
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)
|
||||
|
||||
"""
|
||||
|
||||
ROLE = "Werewolf"
|
||||
ACTION = "KILL"
|
||||
IF_RENEW = True
|
||||
IF_JSON_INPUT = True
|
||||
IF_JSON_OUTPUT = True
|
||||
PROMPT_TEMPLATE = """
|
||||
{
|
||||
"ROLE": "__role__"
|
||||
"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 __role__. Here's the game history:{__context__}."
|
||||
,"BACKGROUND": "It's a werewolf game and you are a __profile__. 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."
|
||||
"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 __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."
|
||||
,"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."
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
|
@ -134,7 +114,7 @@ class NighttimeWhispers(Action):
|
|||
def __init__(self, name="NightTimeWhispers", context=None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
def _default_construct_prompt_json(self, prompt_json: dict, role: str, action: str, context: str):
|
||||
def _default_construct_prompt_json(self, prompt_json: dict, profile: str, name:str, action: str, context: str):
|
||||
|
||||
def replace_string(prompt_json: dict):
|
||||
k: str
|
||||
|
|
@ -142,7 +122,8 @@ 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("__role__", role)
|
||||
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)
|
||||
|
||||
return prompt_json
|
||||
|
|
@ -153,40 +134,27 @@ class NighttimeWhispers(Action):
|
|||
|
||||
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)
|
||||
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):
|
||||
"""
|
||||
Note: `final_prompt` could be undefined and will raise error if `IF_RENEW` is True and `IF_JSON_INPUT` is False
|
||||
"""
|
||||
async def run(self, context: str, profile: str, name: str):
|
||||
|
||||
if not self.IF_RENEW:
|
||||
final_prompt = self.PROMPT_TEMPLATE.replace("__context__", context)
|
||||
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 = json.dumps(prompt_json, indent=4, separators=(',', ': '), ensure_ascii=False)
|
||||
|
||||
re_run = 2
|
||||
while re_run > 0:
|
||||
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._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)
|
||||
|
||||
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
|
||||
try:
|
||||
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} " + str(rsp["OUTPUT"])
|
||||
|
|
|
|||
|
|
@ -3,36 +3,15 @@ from examples.werewolf_game.actions.common_actions import Speak, NighttimeWhispe
|
|||
|
||||
|
||||
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. 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."
|
||||
}
|
||||
}
|
||||
STRATEGY = """
|
||||
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. However, pay attention to what your werewolf partner said,
|
||||
if your werewolf partner has claimed to be a Seer or Witch, DONT claim to be the same role. Remmber NOT to reveal your real identity as a werewolf!
|
||||
"""
|
||||
|
||||
def __init__(self, name="Impersonate", context=None, llm=None):
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ class BasePlayer(Role):
|
|||
**kwargs,
|
||||
):
|
||||
super().__init__(name, profile, **kwargs)
|
||||
self._init_actions([Speak])
|
||||
self._watch([InstructSpeak])
|
||||
# 通过 set_status() 更新状态。
|
||||
self.status = 0 # 0代表活着,1代表死亡
|
||||
|
||||
|
|
@ -60,6 +58,9 @@ class BasePlayer(Role):
|
|||
memories = [f"{m.sent_from}: {re.sub(time_stamp_pattern, '', m.content)}" for m in memories] # regex去掉时间戳
|
||||
memories = "\n".join(memories)
|
||||
return memories
|
||||
|
||||
def get_latest_instruction(self) -> str:
|
||||
return self._rc.important_memory[-1].content # 角色监听着Moderator的InstructSpeak,是其重要记忆,直接获取即可
|
||||
|
||||
def set_status(self, new_status):
|
||||
self.status = new_status
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ class Guard(BasePlayer):
|
|||
todo = self._rc.todo
|
||||
logger.info(f"{self._setting}: ready to {str(todo)}")
|
||||
|
||||
# 可以用这个函数获取该角色的全部记忆
|
||||
# 可以用这个函数获取该角色的全部记忆和最新的instruction
|
||||
memories = self.get_all_memories()
|
||||
latest_instruction = self.get_latest_instruction()
|
||||
# print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10)
|
||||
|
||||
# 根据自己定义的角色Action,对应地去run,run的入参可能不同
|
||||
if isinstance(todo, Speak):
|
||||
rsp = await todo.run(profile=self.profile, context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Speak, send_to="", restricted_to="",
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ class Seer(BasePlayer):
|
|||
todo = self._rc.todo
|
||||
logger.info(f"{self._setting}: ready to {str(todo)}")
|
||||
|
||||
# 可以用这个函数获取该角色的全部记忆和最新的instruction
|
||||
memories = self.get_all_memories()
|
||||
latest_instruction = self.get_latest_instruction()
|
||||
# print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10)
|
||||
|
||||
# 基于todo的类型,调用不同的action
|
||||
if isinstance(todo, Speak):
|
||||
rsp = await todo.run(profile=self.profile, context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Speak, send_to="", restricted_to="",
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ class Villager(BasePlayer):
|
|||
todo = self._rc.todo
|
||||
logger.info(f"{self._setting}: ready to {todo}")
|
||||
|
||||
# 可以用这个函数获取该角色的全部记忆
|
||||
# 可以用这个函数获取该角色的全部记忆和最新的instruction
|
||||
memories = self.get_all_memories()
|
||||
latest_instruction = self.get_latest_instruction()
|
||||
# print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10)
|
||||
|
||||
# 根据自己定义的角色Action,对应地去run
|
||||
rsp = await todo.run(profile=self.profile, context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
|
||||
# 返回消息,注意给Moderator发送的加密消息需要用restricted_to="Moderator"
|
||||
msg = Message(
|
||||
|
|
|
|||
|
|
@ -18,21 +18,22 @@ class Werewolf(BasePlayer):
|
|||
todo = self._rc.todo
|
||||
logger.info(f"{self._setting}: ready to {str(todo)}")
|
||||
|
||||
# 可以用这个函数获取该角色的全部记忆
|
||||
# 可以用这个函数获取该角色的全部记忆和最新的instruction
|
||||
memories = self.get_all_memories()
|
||||
latest_instruction = self.get_latest_instruction()
|
||||
# print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10)
|
||||
|
||||
# 根据自己定义的角色Action,对应地去run,run的入参可能不同
|
||||
if isinstance(todo, Speak):
|
||||
# rsp = await todo.run(profile=self.profile, context=memories)
|
||||
rsp = await Impersonate().run(profile=self.profile, context=memories)
|
||||
# rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
rsp = await Impersonate().run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Speak, send_to="", restricted_to="",
|
||||
)
|
||||
|
||||
elif isinstance(todo, Hunt):
|
||||
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=Hunt, send_to="",
|
||||
|
|
|
|||
|
|
@ -38,11 +38,12 @@ class Witch(BasePlayer):
|
|||
|
||||
# 可以用这个函数获取该角色的全部记忆
|
||||
memories = self.get_all_memories()
|
||||
latest_instruction = self.get_latest_instruction()
|
||||
# print("*" * 10, f"{self._setting}'s current memories: {memories}", "*" * 10)
|
||||
|
||||
# 根据自己定义的角色Action,对应地去run,run的入参可能不同
|
||||
if isinstance(todo, Speak):
|
||||
rsp = await todo.run(profile=self.profile, context=memories)
|
||||
rsp = await todo.run(profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction)
|
||||
msg = Message(
|
||||
content=rsp, role=self.profile, sent_from=self.name,
|
||||
cause_by=Speak, send_to="", restricted_to="",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue