Eric Botti
commited on
Commit
·
5401171
1
Parent(s):
abc228d
updated prompts, moved player input to game
Browse files- src/controllers.py +3 -10
- src/game.py +34 -22
- src/player.py +10 -8
- src/prompts.py +15 -13
src/controllers.py
CHANGED
@@ -1,18 +1,13 @@
|
|
1 |
import os
|
2 |
|
3 |
-
from langchain_core.runnables import
|
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 |
-
|
|
|
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 |
-
|
19 |
-
|
|
|
|
|
|
|
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 |
-
|
|
|
58 |
else:
|
59 |
name = ai_names.pop()
|
60 |
-
|
|
|
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 |
-
|
70 |
self.log_dir,
|
71 |
-
self.
|
72 |
)
|
73 |
|
74 |
-
self.players.append(Player(name, controller, player_id, log_filepath=
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
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("
|
170 |
self.debug_message(f"{player.name} is the Chameleon!")
|
171 |
else:
|
172 |
player.assign_role("herd")
|
173 |
-
self.game_message(
|
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 |
-
|
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,
|
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
|
35 |
return "human"
|
36 |
else:
|
37 |
return "ai"
|
@@ -51,19 +51,20 @@ class Player:
|
|
51 |
def __init__(
|
52 |
self,
|
53 |
name: str,
|
54 |
-
controller:
|
|
|
55 |
player_id: str = None,
|
56 |
log_filepath: str = None
|
57 |
):
|
58 |
self.name = name
|
59 |
self.id = player_id
|
60 |
|
61 |
-
if
|
62 |
self.controller_type = "human"
|
63 |
else:
|
64 |
self.controller_type = "ai"
|
65 |
|
66 |
-
self.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":
|
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(
|
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 |
-
|
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 |
-
|
74 |
-
You are a {
|
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 |
-
|
80 |
-
You are the Chameleon
|
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 |
-
"
|
111 |
-
"
|
|
|
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:",
|