Eric Botti
commited on
Commit
·
2f9cbc8
1
Parent(s):
40fe597
messages can be assigned to multiple players
Browse files- src/agent_interfaces.py +6 -5
- src/game.py +31 -17
- src/game_chameleon.py +21 -3
- src/message.py +21 -10
- src/player.py +1 -1
src/agent_interfaces.py
CHANGED
@@ -19,6 +19,9 @@ class BaseAgentInterface(BaseModel):
|
|
19 |
|
20 |
agent_id: str
|
21 |
"""The id of the agent."""
|
|
|
|
|
|
|
22 |
log_messages: bool = True
|
23 |
"""Whether to log messages or not."""
|
24 |
messages: List[Message] = []
|
@@ -32,10 +35,7 @@ class BaseAgentInterface(BaseModel):
|
|
32 |
|
33 |
def add_message(self, message: Message):
|
34 |
"""Adds a message to the message history, without generating a response."""
|
35 |
-
|
36 |
-
if self.log_messages:
|
37 |
-
save(bound_message)
|
38 |
-
self.messages.append(bound_message)
|
39 |
|
40 |
# Respond To methods - These take a message as input and generate a response
|
41 |
|
@@ -64,6 +64,7 @@ class BaseAgentInterface(BaseModel):
|
|
64 |
if content:
|
65 |
response = Message(type="agent", content=content)
|
66 |
self.add_message(response)
|
|
|
67 |
return response
|
68 |
else:
|
69 |
return None
|
@@ -128,7 +129,7 @@ class OpenAIAgentInterface(BaseAgentInterface):
|
|
128 |
"""An interface that uses the OpenAI API (or compatible 3rd parties) to generate responses."""
|
129 |
model_config = ConfigDict(protected_namespaces=())
|
130 |
|
131 |
-
model_name: str ="gpt-3.5-turbo"
|
132 |
"""The name of the model to use for generating responses."""
|
133 |
client: Any = Field(default_factory=OpenAI, exclude=True)
|
134 |
"""The OpenAI client used to generate responses."""
|
|
|
19 |
|
20 |
agent_id: str
|
21 |
"""The id of the agent."""
|
22 |
+
game_id: str
|
23 |
+
"""The id of the game the agent is in."""
|
24 |
+
|
25 |
log_messages: bool = True
|
26 |
"""Whether to log messages or not."""
|
27 |
messages: List[Message] = []
|
|
|
35 |
|
36 |
def add_message(self, message: Message):
|
37 |
"""Adds a message to the message history, without generating a response."""
|
38 |
+
self.messages.append(message)
|
|
|
|
|
|
|
39 |
|
40 |
# Respond To methods - These take a message as input and generate a response
|
41 |
|
|
|
64 |
if content:
|
65 |
response = Message(type="agent", content=content)
|
66 |
self.add_message(response)
|
67 |
+
save(AgentMessage.from_message(response, [self.agent_id], self.game_id))
|
68 |
return response
|
69 |
else:
|
70 |
return None
|
|
|
129 |
"""An interface that uses the OpenAI API (or compatible 3rd parties) to generate responses."""
|
130 |
model_config = ConfigDict(protected_namespaces=())
|
131 |
|
132 |
+
model_name: str = "gpt-3.5-turbo"
|
133 |
"""The name of the model to use for generating responses."""
|
134 |
client: Any = Field(default_factory=OpenAI, exclude=True)
|
135 |
"""The OpenAI client used to generate responses."""
|
src/game.py
CHANGED
@@ -3,7 +3,7 @@ from typing import Optional, Type, List, ClassVar
|
|
3 |
from pydantic import BaseModel, Field
|
4 |
|
5 |
from game_utils import *
|
6 |
-
from message import Message, MessageType
|
7 |
from agent_interfaces import HumanAgentCLI, OpenAIAgentInterface, HumanAgentInterface
|
8 |
from player import Player
|
9 |
from data_collection import save
|
@@ -14,12 +14,12 @@ class Game(BaseModel):
|
|
14 |
|
15 |
# Required
|
16 |
|
|
|
|
|
17 |
players: List[Player] = Field(exclude=True)
|
18 |
"""The players in the game."""
|
19 |
-
observer: Optional[Player]
|
20 |
"""An observer who can see all public messages, but doesn't actually play."""
|
21 |
-
game_id: str
|
22 |
-
"""The unique id of the game."""
|
23 |
|
24 |
# Default
|
25 |
|
@@ -48,7 +48,7 @@ class Game(BaseModel):
|
|
48 |
def game_message(
|
49 |
self,
|
50 |
content: str,
|
51 |
-
recipient:
|
52 |
exclude: bool = False, # If True, the message is broadcast to all players except the chosen player
|
53 |
message_type: MessageType = "info"
|
54 |
):
|
@@ -58,14 +58,28 @@ class Game(BaseModel):
|
|
58 |
If exclude is True, the message is broadcast to all players except the recipient.
|
59 |
Some message types are only available to player with access (e.g. verbose, debug).
|
60 |
"""
|
61 |
-
message = Message(type=message_type, content=content)
|
62 |
-
|
63 |
if exclude or not recipient:
|
64 |
-
|
65 |
-
|
66 |
-
|
|
|
67 |
else:
|
68 |
-
recipient
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
def verbose_message(self, content: str, **kwargs):
|
71 |
"""
|
@@ -96,8 +110,6 @@ class Game(BaseModel):
|
|
96 |
|
97 |
save(self)
|
98 |
|
99 |
-
|
100 |
-
|
101 |
@classmethod
|
102 |
def from_human_name(
|
103 |
cls, human_name: str = None,
|
@@ -122,19 +134,21 @@ class Game(BaseModel):
|
|
122 |
players = []
|
123 |
|
124 |
for i in range(0, cls.number_of_players):
|
125 |
-
|
126 |
-
player_dict = {"game_id": game_id, "player_id": player_id}
|
127 |
|
128 |
if human_index == i:
|
129 |
player_dict["name"] = human_name
|
130 |
-
|
|
|
131 |
player_dict["message_level"] = human_message_level
|
132 |
else:
|
133 |
player_dict["name"] = ai_names.pop()
|
|
|
134 |
# all AI players use the OpenAI interface for now - this can be changed in the future
|
135 |
-
player_dict["interface"] = OpenAIAgentInterface(agent_id=player_id)
|
136 |
player_dict["message_level"] = "info"
|
137 |
|
|
|
138 |
players.append(cls.player_class(**player_dict))
|
139 |
|
140 |
# Add Observer - an Agent who can see all the messages, but doesn't actually play
|
|
|
3 |
from pydantic import BaseModel, Field
|
4 |
|
5 |
from game_utils import *
|
6 |
+
from message import Message, MessageType, AgentMessage
|
7 |
from agent_interfaces import HumanAgentCLI, OpenAIAgentInterface, HumanAgentInterface
|
8 |
from player import Player
|
9 |
from data_collection import save
|
|
|
14 |
|
15 |
# Required
|
16 |
|
17 |
+
game_id: str
|
18 |
+
"""The unique id of the game."""
|
19 |
players: List[Player] = Field(exclude=True)
|
20 |
"""The players in the game."""
|
21 |
+
observer: Optional[Player]
|
22 |
"""An observer who can see all public messages, but doesn't actually play."""
|
|
|
|
|
23 |
|
24 |
# Default
|
25 |
|
|
|
48 |
def game_message(
|
49 |
self,
|
50 |
content: str,
|
51 |
+
recipient: Player | List[Player] | None = None, # If None, message is broadcast to all players
|
52 |
exclude: bool = False, # If True, the message is broadcast to all players except the chosen player
|
53 |
message_type: MessageType = "info"
|
54 |
):
|
|
|
58 |
If exclude is True, the message is broadcast to all players except the recipient.
|
59 |
Some message types are only available to player with access (e.g. verbose, debug).
|
60 |
"""
|
|
|
|
|
61 |
if exclude or not recipient:
|
62 |
+
# These are public messages, exclude is used to exclude the sender from the recipient list.
|
63 |
+
recipients = [player for player in self.players if player != recipient]
|
64 |
+
if self.observer:
|
65 |
+
recipients.append(self.observer)
|
66 |
else:
|
67 |
+
if isinstance(recipient, Player):
|
68 |
+
recipients = [recipient]
|
69 |
+
else:
|
70 |
+
recipients = recipient
|
71 |
+
|
72 |
+
message = Message(type=message_type, content=content)
|
73 |
+
recipient_ids = []
|
74 |
+
|
75 |
+
for player in recipients:
|
76 |
+
if player.can_receive_message(message_type):
|
77 |
+
player.interface.add_message(message)
|
78 |
+
recipient_ids.append(player.player_id)
|
79 |
+
|
80 |
+
agent_message = AgentMessage.from_message(message, recipient_ids, self.game_id)
|
81 |
+
save(agent_message)
|
82 |
+
|
83 |
|
84 |
def verbose_message(self, content: str, **kwargs):
|
85 |
"""
|
|
|
110 |
|
111 |
save(self)
|
112 |
|
|
|
|
|
113 |
@classmethod
|
114 |
def from_human_name(
|
115 |
cls, human_name: str = None,
|
|
|
134 |
players = []
|
135 |
|
136 |
for i in range(0, cls.number_of_players):
|
137 |
+
player_dict = {"game_id": game_id}
|
|
|
138 |
|
139 |
if human_index == i:
|
140 |
player_dict["name"] = human_name
|
141 |
+
player_id = f"{game_id}-human"
|
142 |
+
player_dict["interface"] = human_interface(agent_id=player_id, game_id=game_id)
|
143 |
player_dict["message_level"] = human_message_level
|
144 |
else:
|
145 |
player_dict["name"] = ai_names.pop()
|
146 |
+
player_id = f"{game_id}-{player_dict['name']}"
|
147 |
# all AI players use the OpenAI interface for now - this can be changed in the future
|
148 |
+
player_dict["interface"] = OpenAIAgentInterface(agent_id=player_id, game_id=game_id)
|
149 |
player_dict["message_level"] = "info"
|
150 |
|
151 |
+
player_dict["player_id"] = player_id
|
152 |
players.append(cls.player_class(**player_dict))
|
153 |
|
154 |
# Add Observer - an Agent who can see all the messages, but doesn't actually play
|
src/game_chameleon.py
CHANGED
@@ -49,6 +49,11 @@ class ChameleonGame(Game):
|
|
49 |
"""Returns the current chameleon."""
|
50 |
return self.player_from_id(self.chameleon_ids[-1])
|
51 |
|
|
|
|
|
|
|
|
|
|
|
52 |
@property
|
53 |
def herd_animal(self) -> str:
|
54 |
"""Returns the current herd animal."""
|
@@ -146,16 +151,29 @@ class ChameleonGame(Game):
|
|
146 |
|
147 |
# Assign Roles
|
148 |
chameleon_index = random_index(len(self.players))
|
149 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
|
151 |
for i, player in enumerate(self.players):
|
152 |
if i == chameleon_index:
|
153 |
player.assign_role("chameleon")
|
154 |
-
self.game_message(fetch_prompt("assign_chameleon"), player)
|
155 |
self.debug_message(f"{player.name} is the Chameleon!")
|
156 |
else:
|
157 |
player.assign_role("herd")
|
158 |
-
self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), player)
|
159 |
|
160 |
# Empty Animal Descriptions
|
161 |
self.all_animal_descriptions.append([])
|
|
|
49 |
"""Returns the current chameleon."""
|
50 |
return self.player_from_id(self.chameleon_ids[-1])
|
51 |
|
52 |
+
@property
|
53 |
+
def chameleon_id(self) -> str:
|
54 |
+
"""Returns the current chameleon's id."""
|
55 |
+
return self.chameleon_ids[-1]
|
56 |
+
|
57 |
@property
|
58 |
def herd_animal(self) -> str:
|
59 |
"""Returns the current herd animal."""
|
|
|
151 |
|
152 |
# Assign Roles
|
153 |
chameleon_index = random_index(len(self.players))
|
154 |
+
chameleon = self.players[chameleon_index]
|
155 |
+
|
156 |
+
self.chameleon_ids.append(chameleon.player_id)
|
157 |
+
|
158 |
+
self.game_message(fetch_prompt("assign_chameleon"), chameleon)
|
159 |
+
|
160 |
+
herd = []
|
161 |
+
for i, player in enumerate(self.players):
|
162 |
+
if i == chameleon_index:
|
163 |
+
player.assign_role("chameleon")
|
164 |
+
self.debug_message(f"{player.name} is the Chameleon!")
|
165 |
+
else:
|
166 |
+
player.assign_role("herd")
|
167 |
+
herd.append(player)
|
168 |
+
|
169 |
+
self.game_message(format_prompt("assign_herd", herd_animal=herd_animal), herd)
|
170 |
|
171 |
for i, player in enumerate(self.players):
|
172 |
if i == chameleon_index:
|
173 |
player.assign_role("chameleon")
|
|
|
174 |
self.debug_message(f"{player.name} is the Chameleon!")
|
175 |
else:
|
176 |
player.assign_role("herd")
|
|
|
177 |
|
178 |
# Empty Animal Descriptions
|
179 |
self.all_animal_descriptions.append([])
|
src/message.py
CHANGED
@@ -1,8 +1,17 @@
|
|
1 |
-
from typing import Literal
|
2 |
-
from pydantic import BaseModel, computed_field
|
3 |
|
4 |
MessageType = Literal["prompt", "info", "agent", "retry", "error", "format", "verbose", "debug"]
|
5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
class Message(BaseModel):
|
8 |
"""A generic message, these are used to communicate between the game and the players."""
|
@@ -37,24 +46,26 @@ class Message(BaseModel):
|
|
37 |
|
38 |
|
39 |
class AgentMessage(Message):
|
40 |
-
"""A message
|
41 |
|
42 |
-
|
43 |
-
"""The id of the
|
44 |
-
|
|
|
|
|
45 |
"""The number of the message, indicating the order in which it was sent."""
|
46 |
|
47 |
@computed_field
|
48 |
def message_id(self) -> str:
|
49 |
"""Returns the message id in the format used by the LLM."""
|
50 |
-
return f"{self.
|
51 |
|
52 |
@classmethod
|
53 |
-
def from_message(cls, message: Message,
|
54 |
"""Creates an AgentMessage from a Message."""
|
55 |
return cls(
|
56 |
type=message.type,
|
57 |
content=message.content,
|
58 |
-
|
59 |
-
|
60 |
)
|
|
|
1 |
+
from typing import Literal, List
|
2 |
+
from pydantic import BaseModel, computed_field, Field
|
3 |
|
4 |
MessageType = Literal["prompt", "info", "agent", "retry", "error", "format", "verbose", "debug"]
|
5 |
|
6 |
+
message_number = 0
|
7 |
+
|
8 |
+
|
9 |
+
def next_message_number():
|
10 |
+
global message_number
|
11 |
+
current_message_number = message_number
|
12 |
+
message_number += 1
|
13 |
+
return current_message_number
|
14 |
+
|
15 |
|
16 |
class Message(BaseModel):
|
17 |
"""A generic message, these are used to communicate between the game and the players."""
|
|
|
46 |
|
47 |
|
48 |
class AgentMessage(Message):
|
49 |
+
"""A message that has been sent to 1 or more agents."""
|
50 |
|
51 |
+
agent_ids: List[str]
|
52 |
+
"""The id/ids of the agent that the message was sent by/to."""
|
53 |
+
game_id: str
|
54 |
+
"""The id of the game the message was sent during."""
|
55 |
+
message_number: int = Field(default_factory=next_message_number)
|
56 |
"""The number of the message, indicating the order in which it was sent."""
|
57 |
|
58 |
@computed_field
|
59 |
def message_id(self) -> str:
|
60 |
"""Returns the message id in the format used by the LLM."""
|
61 |
+
return f"{self.game_id}-{self.message_number}"
|
62 |
|
63 |
@classmethod
|
64 |
+
def from_message(cls, message: Message, agent_ids: List[str], game_id: str) -> "AgentMessage":
|
65 |
"""Creates an AgentMessage from a Message."""
|
66 |
return cls(
|
67 |
type=message.type,
|
68 |
content=message.content,
|
69 |
+
agent_ids=agent_ids,
|
70 |
+
game_id=game_id
|
71 |
)
|
src/player.py
CHANGED
@@ -43,7 +43,7 @@ class Player(BaseModel):
|
|
43 |
"""Creates an observer player."""
|
44 |
name = "Observer"
|
45 |
player_id = f"{game_id}-observer"
|
46 |
-
interface = interface_type(agent_id=player_id)
|
47 |
|
48 |
return cls(name=name, player_id=player_id, game_id=game_id, interface=interface, message_level=message_level)
|
49 |
|
|
|
43 |
"""Creates an observer player."""
|
44 |
name = "Observer"
|
45 |
player_id = f"{game_id}-observer"
|
46 |
+
interface = interface_type(agent_id=player_id, game_id=game_id)
|
47 |
|
48 |
return cls(name=name, player_id=player_id, game_id=game_id, interface=interface, message_level=message_level)
|
49 |
|