Eric Botti commited on
Commit
2f9cbc8
·
1 Parent(s): 40fe597

messages can be assigned to multiple players

Browse files
Files changed (5) hide show
  1. src/agent_interfaces.py +6 -5
  2. src/game.py +31 -17
  3. src/game_chameleon.py +21 -3
  4. src/message.py +21 -10
  5. 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
- bound_message = AgentMessage.from_message(message, self.agent_id, len(self.messages))
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] = Field(exclude=True)
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: Optional[Player] = 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,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
- for player in self.players + [self.observer] if self.observer else self.players:
65
- if player != recipient and player.can_receive_message(message_type):
66
- player.interface.add_message(message)
 
67
  else:
68
- recipient.interface.add_message(message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- player_id = f"{game_id}-{i + 1}"
126
- player_dict = {"game_id": game_id, "player_id": player_id}
127
 
128
  if human_index == i:
129
  player_dict["name"] = human_name
130
- player_dict["interface"] = human_interface(agent_id=player_id)
 
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.chameleon_ids.append(self.players[chameleon_index].player_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 bound to a specific agent, this happens when an agent receives a message from the game."""
41
 
42
- agent_id: str
43
- """The id of the controller that the message was sent by/to."""
44
- message_number: int
 
 
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.agent_id}-{self.message_number}"
51
 
52
  @classmethod
53
- def from_message(cls, message: Message, agent_id: str, message_number: int) -> "AgentMessage":
54
  """Creates an AgentMessage from a Message."""
55
  return cls(
56
  type=message.type,
57
  content=message.content,
58
- agent_id=agent_id,
59
- message_number=message_number
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