Eric Botti commited on
Commit
5401171
·
1 Parent(s): abc228d

updated prompts, moved player input to game

Browse files
Files changed (4) hide show
  1. src/controllers.py +3 -10
  2. src/game.py +34 -22
  3. src/player.py +10 -8
  4. src/prompts.py +15 -13
src/controllers.py CHANGED
@@ -1,18 +1,13 @@
1
  import os
2
 
3
- from langchain_core.runnables import RunnableLambda
4
  from langchain_openai import ChatOpenAI
5
  from langchain_core.messages import AIMessage
6
 
7
-
8
- def player_input(prompt):
9
- # even though they are human, we still need to return an AIMessage, since the HumanMessages are from the GameMaster
10
- response = AIMessage(content=input())
11
- return response
12
-
13
  MAX_TOKENS = 50
14
 
15
- def controller_from_name(name: str):
 
16
  if name == "tgi":
17
  return ChatOpenAI(
18
  api_base=os.environ['HF_ENDPOINT_URL'] + "/v1/",
@@ -22,7 +17,5 @@ def controller_from_name(name: str):
22
  return ChatOpenAI(model="gpt-3.5-turbo", max_tokens=MAX_TOKENS)
23
  elif name == "ollama":
24
  return ChatOpenAI(model="mistral", openai_api_key="ollama", openai_api_base="http://localhost:11434/v1", max_tokens=MAX_TOKENS)
25
- elif name == "human":
26
- return RunnableLambda(player_input)
27
  else:
28
  raise ValueError(f"Unknown controller name: {name}")
 
1
  import os
2
 
3
+ from langchain_core.runnables import Runnable
4
  from langchain_openai import ChatOpenAI
5
  from langchain_core.messages import AIMessage
6
 
 
 
 
 
 
 
7
  MAX_TOKENS = 50
8
 
9
+
10
+ def controller_from_name(name: str) -> Runnable:
11
  if name == "tgi":
12
  return ChatOpenAI(
13
  api_base=os.environ['HF_ENDPOINT_URL'] + "/v1/",
 
17
  return ChatOpenAI(model="gpt-3.5-turbo", max_tokens=MAX_TOKENS)
18
  elif name == "ollama":
19
  return ChatOpenAI(model="mistral", openai_api_key="ollama", openai_api_base="http://localhost:11434/v1", max_tokens=MAX_TOKENS)
 
 
20
  else:
21
  raise ValueError(f"Unknown controller name: {name}")
src/game.py CHANGED
@@ -9,26 +9,33 @@ from models import *
9
  from player import Player
10
  from prompts import fetch_prompt, format_prompt
11
 
 
 
 
 
12
  # Default Values
13
  NUMBER_OF_PLAYERS = 6
14
  WINNING_SCORE = 11
15
 
16
  class Game:
 
17
  log_dir = os.path.join(os.pardir, "experiments")
18
- player_log_file = "{player_id}.jsonl"
19
- game_log_file = "{game_id}-game.jsonl"
 
 
 
20
  number_of_players = NUMBER_OF_PLAYERS
21
  """The number of players in the game."""
22
  winning_score = WINNING_SCORE
23
  """The Number of points required to win the game."""
24
- debug = True
25
- """If True, the game will print debug messages to the console."""
26
 
27
  def __init__(
28
  self,
29
  number_of_players: int = NUMBER_OF_PLAYERS,
30
  human_name: str = None,
31
- verbose = False
 
32
  ):
33
  # Game ID
34
  self.game_id = game_id()
@@ -48,16 +55,21 @@ class Game:
48
  self.human_index = None
49
 
50
  self.verbose = verbose
 
 
 
51
 
52
  # Add Players
53
  self.players = []
54
  for i in range(0, number_of_players):
55
  if self.human_index == i:
56
  name = human_name
57
- controller = "human"
 
58
  else:
59
  name = ai_names.pop()
60
- controller = "openai"
 
61
 
62
  if self.chameleon_index == i:
63
  role = "chameleon"
@@ -66,12 +78,12 @@ class Game:
66
 
67
  player_id = f"{self.game_id}-{i + 1}"
68
 
69
- log_path = os.path.join(
70
  self.log_dir,
71
- self.player_log_file.format(player_id=player_id)
72
  )
73
 
74
- self.players.append(Player(name, controller, player_id, log_filepath=log_path))
75
 
76
  # Game State
77
  self.player_responses = []
@@ -102,7 +114,7 @@ class Game:
102
  player.prompt_queue.append(message)
103
  if player.controller_type == "human":
104
  self.human_message(message)
105
- if self.verbose:
106
  self.human_message(message)
107
  else:
108
  recipient.prompt_queue.append(message)
@@ -119,7 +131,13 @@ class Game:
119
  # The following methods are used to broadcast messages to a human.
120
  # They are design so that they can be overridden by a subclass for a different player interface.
121
  @staticmethod
122
- def human_message(self, message: str):
 
 
 
 
 
 
123
  """Sends a message for the human player to read. No response is expected."""
124
  print(message)
125
 
@@ -146,7 +164,7 @@ class Game:
146
  "number_of_players": len(self.players),
147
  "human_player": self.players[self.human_index].id if self.human_index else "None",
148
  }
149
- game_log_path = os.path.join(self.log_dir, self.game_log_file.format(game_id=self.game_id))
150
 
151
  log(game_log, game_log_path)
152
 
@@ -166,11 +184,11 @@ class Game:
166
  for i, player in enumerate(self.players):
167
  if i == chameleon_index:
168
  player.assign_role("chameleon")
169
- self.game_message("You are the **Chameleon**, remain undetected and guess what animal the others are pretending to be", player)
170
  self.debug_message(f"{player.name} is the Chameleon!")
171
  else:
172
  player.assign_role("herd")
173
- self.game_message(f"You are a **{herd_animal}**, keep this secret at all costs and figure which player is not really a {herd_animal}", player)
174
 
175
  # Phase II: Collect Player Animal Descriptions
176
 
@@ -179,11 +197,7 @@ class Game:
179
  if current_player.controller_type != "human":
180
  self.verbose_message(f"{current_player.name} is thinking...")
181
 
182
- if i == 0:
183
- prompt = "Your Response:"
184
- else:
185
- prompt = "It's your turn to describe yourself. Do not repeat responses from other players.\nYour Response:"
186
-
187
 
188
  # Get Player Animal Description
189
  response = await self.instructional_message(prompt, current_player, AnimalDescriptionModel)
@@ -207,8 +221,6 @@ class Game:
207
  # Phase IV: The Herd Votes for who they think the Chameleon is
208
  self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
209
 
210
- self.game_message("The Chameleon has decided not to guess the animal. Now all players will vote on who they think the chameleon is.")
211
-
212
  player_votes = []
213
  for player in self.players:
214
  if player.role == "herd":
 
9
  from player import Player
10
  from prompts import fetch_prompt, format_prompt
11
 
12
+ from langchain_core.runnables import RunnableLambda
13
+ from langchain_core.messages import AIMessage
14
+ from controllers import controller_from_name
15
+
16
  # Default Values
17
  NUMBER_OF_PLAYERS = 6
18
  WINNING_SCORE = 11
19
 
20
  class Game:
21
+
22
  log_dir = os.path.join(os.pardir, "experiments")
23
+ """The directory where the logs will be saved."""
24
+ player_log_file_template = "{player_id}.jsonl"
25
+ """Template for the name of the log file for each player."""
26
+ game_log_file_template = "{game_id}-game.jsonl"
27
+ """Template for the name of the log file for the game."""
28
  number_of_players = NUMBER_OF_PLAYERS
29
  """The number of players in the game."""
30
  winning_score = WINNING_SCORE
31
  """The Number of points required to win the game."""
 
 
32
 
33
  def __init__(
34
  self,
35
  number_of_players: int = NUMBER_OF_PLAYERS,
36
  human_name: str = None,
37
+ verbose: bool = False,
38
+ debug: bool = False
39
  ):
40
  # Game ID
41
  self.game_id = game_id()
 
55
  self.human_index = None
56
 
57
  self.verbose = verbose
58
+ """If True, the game will display verbose messages to the player."""
59
+ self.debug = debug
60
+ """If True, the game will display debug messages to the player."""
61
 
62
  # Add Players
63
  self.players = []
64
  for i in range(0, number_of_players):
65
  if self.human_index == i:
66
  name = human_name
67
+ controller_name = "human"
68
+ controller = RunnableLambda(self.human_input)
69
  else:
70
  name = ai_names.pop()
71
+ controller_name = "openai"
72
+ controller = controller_from_name(controller_name)
73
 
74
  if self.chameleon_index == i:
75
  role = "chameleon"
 
78
 
79
  player_id = f"{self.game_id}-{i + 1}"
80
 
81
+ player_log_path = os.path.join(
82
  self.log_dir,
83
+ self.player_log_file_template.format(player_id=player_id)
84
  )
85
 
86
+ self.players.append(Player(name, controller, controller_name, player_id, log_filepath=player_log_path))
87
 
88
  # Game State
89
  self.player_responses = []
 
114
  player.prompt_queue.append(message)
115
  if player.controller_type == "human":
116
  self.human_message(message)
117
+ if self.verbose and not self.human_index:
118
  self.human_message(message)
119
  else:
120
  recipient.prompt_queue.append(message)
 
131
  # The following methods are used to broadcast messages to a human.
132
  # They are design so that they can be overridden by a subclass for a different player interface.
133
  @staticmethod
134
+ def human_input(prompt: str) -> str:
135
+ """Gets input from the human player."""
136
+ response = AIMessage(content=input())
137
+ return response
138
+
139
+ @staticmethod
140
+ def human_message(message: str):
141
  """Sends a message for the human player to read. No response is expected."""
142
  print(message)
143
 
 
164
  "number_of_players": len(self.players),
165
  "human_player": self.players[self.human_index].id if self.human_index else "None",
166
  }
167
+ game_log_path = os.path.join(self.log_dir, self.game_log_file_template.format(game_id=self.game_id))
168
 
169
  log(game_log, game_log_path)
170
 
 
184
  for i, player in enumerate(self.players):
185
  if i == chameleon_index:
186
  player.assign_role("chameleon")
187
+ self.game_message(fetch_prompt("assign_chameleon"), player)
188
  self.debug_message(f"{player.name} is the Chameleon!")
189
  else:
190
  player.assign_role("herd")
191
+ self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), player)
192
 
193
  # Phase II: Collect Player Animal Descriptions
194
 
 
197
  if current_player.controller_type != "human":
198
  self.verbose_message(f"{current_player.name} is thinking...")
199
 
200
+ prompt = fetch_prompt("player_describe_animal")
 
 
 
 
201
 
202
  # Get Player Animal Description
203
  response = await self.instructional_message(prompt, current_player, AnimalDescriptionModel)
 
221
  # Phase IV: The Herd Votes for who they think the Chameleon is
222
  self.game_message("The Chameleon has guessed the animal. Now the Herd will vote on who they think the chameleon is.")
223
 
 
 
224
  player_votes = []
225
  for player in self.players:
226
  if player.role == "herd":
src/player.py CHANGED
@@ -2,7 +2,7 @@ import os
2
  from typing import Type, Literal, List
3
  import logging
4
 
5
- from langchain_core.runnables import Runnable, RunnableParallel, RunnableLambda, chain
6
 
7
  from langchain.output_parsers import PydanticOutputParser
8
  from langchain_core.prompts import PromptTemplate
@@ -24,14 +24,14 @@ logger = logging.getLogger("chameleon")
24
  # This doesn't make sense for our as Humans and AIs are both players in the game, meaning they have the same role.
25
  # The Langchain type field is used to convert to that syntax.
26
  class Message(BaseModel):
27
- type: Literal["prompt", "player"]
28
  """The type of the message. Can be "prompt" or "player"."""
29
  content: str
30
  """The content of the message."""
31
  @property
32
  def langchain_type(self):
33
  """Returns the langchain message type for the message."""
34
- if self.type == "prompt":
35
  return "human"
36
  else:
37
  return "ai"
@@ -51,19 +51,20 @@ class Player:
51
  def __init__(
52
  self,
53
  name: str,
54
- controller: str,
 
55
  player_id: str = None,
56
  log_filepath: str = None
57
  ):
58
  self.name = name
59
  self.id = player_id
60
 
61
- if controller == "human":
62
  self.controller_type = "human"
63
  else:
64
  self.controller_type = "ai"
65
 
66
- self.controller = controller_from_name(controller)
67
  """The controller for the player."""
68
  self.log_filepath = log_filepath
69
  """The filepath to the log file. If None, no logs will be written."""
@@ -78,7 +79,7 @@ class Player:
78
  "name": self.name,
79
  "role": self.role,
80
  "controller": {
81
- "name": controller,
82
  "type": self.controller_type
83
  }
84
  }
@@ -113,10 +114,11 @@ class Player:
113
  if retries < max_retries:
114
  retries += 1
115
  logger.warning(f"Player {self.id} failed to format response: {output} due to an exception: {e} \n\n Retrying {retries}/{max_retries}")
116
- self.add_to_history(HumanMessage(content=f"Error formatting response: {e} \n\n Please try again."))
117
  output = await self.format_output.ainvoke({"output_format": output_format})
118
 
119
  else:
 
120
  logging.error(f"Max retries reached due to Error: {e}")
121
  raise e
122
  else:
 
2
  from typing import Type, Literal, List
3
  import logging
4
 
5
+ from langchain_core.runnables import Runnable, RunnableLambda
6
 
7
  from langchain.output_parsers import PydanticOutputParser
8
  from langchain_core.prompts import PromptTemplate
 
24
  # This doesn't make sense for our as Humans and AIs are both players in the game, meaning they have the same role.
25
  # The Langchain type field is used to convert to that syntax.
26
  class Message(BaseModel):
27
+ type: Literal["prompt", "player", "retry", "error"]
28
  """The type of the message. Can be "prompt" or "player"."""
29
  content: str
30
  """The content of the message."""
31
  @property
32
  def langchain_type(self):
33
  """Returns the langchain message type for the message."""
34
+ if self.type in ["prompt", "retry", "error"]:
35
  return "human"
36
  else:
37
  return "ai"
 
51
  def __init__(
52
  self,
53
  name: str,
54
+ controller: Type[Runnable | RunnableLambda],
55
+ controller_name: str,
56
  player_id: str = None,
57
  log_filepath: str = None
58
  ):
59
  self.name = name
60
  self.id = player_id
61
 
62
+ if controller_name == "human":
63
  self.controller_type = "human"
64
  else:
65
  self.controller_type = "ai"
66
 
67
+ self.controller = controller
68
  """The controller for the player."""
69
  self.log_filepath = log_filepath
70
  """The filepath to the log file. If None, no logs will be written."""
 
79
  "name": self.name,
80
  "role": self.role,
81
  "controller": {
82
+ "name": controller_name,
83
  "type": self.controller_type
84
  }
85
  }
 
114
  if retries < max_retries:
115
  retries += 1
116
  logger.warning(f"Player {self.id} failed to format response: {output} due to an exception: {e} \n\n Retrying {retries}/{max_retries}")
117
+ self.add_to_history(Message(type="retry", content=f"Error formatting response: {e} \n\n Please try again."))
118
  output = await self.format_output.ainvoke({"output_format": output_format})
119
 
120
  else:
121
+ self.add_to_history(Message(type="error", content=f"Error formatting response: {e} \n\n Max retries reached."))
122
  logging.error(f"Max retries reached due to Error: {e}")
123
  raise e
124
  else:
src/prompts.py CHANGED
@@ -62,7 +62,7 @@ class Task:
62
 
63
 
64
  _game_rules = '''\
65
- GAME RULES: You are playing a social deduction game where every player pretends the be the same animal.
66
  During the round players go around the room and make an "I"-statement as if they were the animal.
67
  All players know what animal they are pretending to be, except one who is known as the Chameleon.
68
  The Chameleon and must blend in by providing details about the animal using context from other players.
@@ -70,20 +70,21 @@ The other players must be careful not to give away too much information with the
70
  After all players have spoken, they vote on who they think the Chameleon is. \
71
  '''
72
 
73
- _herd_animal = """\
74
- You are a {animal}, keep this a secret at all costs.
75
- In as few words as possible describe of yourself starting with "I". Your description should be vague but true, \
76
- since if the Chameleon can guess animal you are, you will LOSE. Do not repeat responses from other players.\
77
  """
78
 
79
- _chameleon_animal = """\
80
- You are the Chameleon, keep this a secret at all costs.
81
- You don't know what animal the other players are, your goal is to deduce it using the context they provide.
82
- Starting with "I" describe yourself in 10 words or less as if you are the same animal as the other players.
83
- If no one else has said anything try to say something generic that could be true of any animals.
84
- If the other players realize you are the Chameleon you will LOSE.\
85
  """
86
 
 
 
 
 
 
 
 
87
  _all_responses = """\
88
  Below are the responses from all the other players.
89
  {player_responses}
@@ -107,8 +108,9 @@ Now it is time to vote. Choose from the players above who you think the Chameleo
107
 
108
  prompts = {
109
  "game_rules": _game_rules,
110
- "herd_animal": _herd_animal,
111
- "chameleon_animal": _chameleon_animal,
 
112
  "chameleon_guess_decision": _all_responses + _chameleon_guess_decision,
113
  "chameleon_guess_animal": _chameleon_guess_animal,
114
  "response": "Your response:",
 
62
 
63
 
64
  _game_rules = '''\
65
+ You are playing a social deduction game where every player pretends the be the same animal.
66
  During the round players go around the room and make an "I"-statement as if they were the animal.
67
  All players know what animal they are pretending to be, except one who is known as the Chameleon.
68
  The Chameleon and must blend in by providing details about the animal using context from other players.
 
70
  After all players have spoken, they vote on who they think the Chameleon is. \
71
  '''
72
 
73
+ _assign_herd = """\
74
+ You are a **{herd_animal}**, keep this secret at all costs and figure which player is not really a {herd_animal}
 
 
75
  """
76
 
77
+ _assign_chameleon = """\
78
+ "You are the **Chameleon**, remain undetected and guess what animal the others are pretending to be"
 
 
 
 
79
  """
80
 
81
+ _player_describe_animal = """It's your turn to describe yourself. Remember:
82
+ - Start your response with "I"
83
+ - Keep your response as short as possible
84
+ - Do not repeat responses from other players.
85
+
86
+ Your Response:"""
87
+
88
  _all_responses = """\
89
  Below are the responses from all the other players.
90
  {player_responses}
 
108
 
109
  prompts = {
110
  "game_rules": _game_rules,
111
+ "assign_herd": _assign_herd,
112
+ "assign_chameleon": _assign_chameleon,
113
+ "player_describe_animal": _player_describe_animal,
114
  "chameleon_guess_decision": _all_responses + _chameleon_guess_decision,
115
  "chameleon_guess_animal": _chameleon_guess_animal,
116
  "response": "Your response:",