Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agentverse/agentverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ def next(self, *args, **kwargs):
"""Run the environment for one step and return the return message."""
return_message = asyncio.run(self.environment.step(*args, **kwargs))
return return_message

def update_state(self, *args, **kwargs):
"""Run the environment for one step and return the return message."""
self.environment.update_state(*args, **kwargs)
130 changes: 121 additions & 9 deletions agentverse/environments/pokemon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
import time
import datetime
import logging
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Set

# from agentverse.agents.agent import Agent
from agentverse.agents.conversation_agent import BaseAgent
Expand All @@ -19,6 +19,7 @@ class PokemonEnvironment(BaseEnvironment):

Args:
agents: List of agents
locations: A dict of locations to agents within them
rule: Rule for the environment
max_turns: Maximum number of turns
cnt_turn: Current turn number
Expand All @@ -27,13 +28,16 @@ class PokemonEnvironment(BaseEnvironment):
"""

agents: List[BaseAgent]
locations_to_agents: Dict[str, Set[str]]
# locations_descriptions: Dict[str, str]
time: datetime.datetime = datetime.datetime(2021, 1, 1, 8, 0, 0)
rule: Rule
max_turns: int = 10
cnt_turn: int = 0
last_messages: List[Message] = []
rule_params: Dict = {}

def __init__(self, rule, **kwargs):
def __init__(self, rule, locations, **kwargs):
rule_config = rule
order_config = rule_config.get("order", {"type": "sequential"})
visibility_config = rule_config.get("visibility", {"type": "all"})
Expand All @@ -47,16 +51,70 @@ def __init__(self, rule, **kwargs):
updater_config,
describer_config,
)
super().__init__(rule=rule, **kwargs)
locations_to_agents = {}
# locations_descriptions = {}
locations_config = locations
for loc in locations_config:
locations_to_agents[loc["name"]] = set(loc["init_agents"])
# locations_descriptions[loc["name"]] = loc["description"]
super().__init__(
rule=rule,
locations_to_agents=locations_to_agents,
# locations_descriptions=locations_descriptions,
**kwargs,
)

async def step(
self, player_content: str, receiver: str, receiver_id: Optional[int] = None
self,
is_player: bool = False,
player_content: str = None,
receiver: str = None,
receiver_id: Optional[int] = None,
agent_ids: Optional[List[int]] = None,
) -> List[Message]:
"""Run one step of the environment"""

# Get the next agent index
# time.sleep(8)
# return [Message(content="Test", sender="May", receiver=["May"])]
if is_player:
return await self._respond_to_player(player_content, receiver, receiver_id)
else:
return await self._routine_step(agent_ids)

async def _routine_step(self, agent_ids) -> List[Message]:
self.rule.update_visible_agents(self)

# agent_ids = self.rule.get_next_agent_idx(self)

# Generate current environment description
env_descriptions = self.rule.get_env_description(self)

# Generate the next message
messages = await asyncio.gather(
*[self.agents[i].astep(env_descriptions[i]) for i in agent_ids]
)
# messages = self.get_test_messages()

# Some rules will select certain messages from all the messages
selected_messages = self.rule.select_message(self, messages)

# Update the memory of the agents
self.last_messages = selected_messages
self.rule.update_memory(self)
self.print_messages(selected_messages)

self.cnt_turn += 1
self.time += datetime.timedelta(minutes=5)

return selected_messages

async def _respond_to_player(
self,
player_content: str = None,
receiver: str = None,
receiver_id: Optional[int] = None,
) -> List[Message]:
if receiver_id is None:
for agent in self.agents:
if agent.name == receiver:
Expand All @@ -80,16 +138,29 @@ async def step(
)

# Some rules will select certain messages from all the messages
selected_messages = self.rule.select_message(self, messages)
# selected_messages = self.rule.select_message(self, messages)

# Update the memory of the agents
self.last_messages = [player_message, *selected_messages]
self.last_messages = [player_message, *messages]
self.rule.update_memory(self)
self.print_messages(selected_messages)
self.print_messages(messages)

self.cnt_turn += 1

return selected_messages
return messages

def update_state(self, agent_location: Dict[str, str]):
for agent_name, location in agent_location.items():
# original_location = self.get_agent_to_location()[agent_name]
# self.locations_to_agents[original_location].remove(agent_name)
self.locations_to_agents[location].add(agent_name)

def get_agent_to_location(self) -> Dict[str, str]:
ret = {}
for location, agent_names in self.locations_to_agents.items():
for agent in agent_names:
ret[agent] = location
return ret

def print_messages(self, messages: List[Message]) -> None:
for message in messages:
Expand All @@ -106,3 +177,44 @@ def reset(self) -> None:
def is_done(self) -> bool:
"""Check if the environment is done"""
return self.cnt_turn >= self.max_turns

def get_test_messages(self) -> List[Message]:
messages = [
Message(
content='{"to": "Birch", "action": "Speak", "text": "Hi!!!"}',
sender="May",
receiver={"May", "Birch"},
tool_response=[],
),
Message(
content='{"to": "May", "text": "Good morning, May! How is your research going?", "action": "Speak"}',
sender="Birch",
receiver={"May", "Birch"},
tool_response=[],
),
Message(
content='{"to": "Pokémon Center", "action": "MoveTo"}',
sender="Steven",
receiver={"Steven"},
tool_response=[],
),
Message(
content='{"to": "Shop", "last_time": "10 minutes", "action": "MoveTo"}',
sender="Maxie",
receiver={"Maxie"},
tool_response=[],
),
Message(
content='{"to": "Pok\\u00e9mon Center", "action": "MoveTo"}',
sender="Archie",
receiver={"Archie"},
tool_response=[],
),
Message(
content='{"to": "Shop", "action": "MoveTo"}',
sender="Joseph",
receiver={"Joseph"},
tool_response=[],
),
]
return messages
41 changes: 34 additions & 7 deletions agentverse/environments/rules/describer/pokemon.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, List, Optional
from typing import TYPE_CHECKING, Any, List, Optional, Dict
from copy import deepcopy

from . import describer_registry as DescriberRegistry
from .base import BaseDescriber
Expand All @@ -14,11 +15,37 @@ class PokemonDescriber(BaseDescriber):
def get_env_description(
self,
environment: PokemonEnvironment,
player_content: str,
time: Optional[str] = None,
player_content: str = "",
) -> List[str]:
description = ""
if time is not None:
time = environment.time
if player_content == "":
agent_to_location = environment.get_agent_to_location()
descriptions = []
for agent in environment.agents:
description = ""
if agent.name not in agent_to_location:
# Agent is on the way to a location
descriptions.append("")
continue
location = agent_to_location[agent.name]
agents_in_same_loc = deepcopy(environment.locations_to_agents[location])
agents_in_same_loc.remove(agent.name)
agents_in_same_loc = list(agents_in_same_loc)
description += f"It is now {time}. You are at {location}."
if len(agents_in_same_loc) == 0:
description += " There is no one else here."
elif len(agents_in_same_loc) == 1:
description += f" {agents_in_same_loc[0]} is also here."
else:
other_agents = ", ".join(agents_in_same_loc)
description += f" {other_agents} are also here."
# description += " The locations you can go to include: \n"
# for loc, dsec in environment.locations_descriptions.items():
# description += f"{loc}: {dsec}\n"
descriptions.append(description)
return descriptions
else:
description = ""
description += f"It is now {time}. Brendan is talking to you.\n"
description += f"[Brendan]: {player_content}\n"
return [description for _ in range(len(environment.agents))]
description += f"[Brendan]: {player_content}\n"
return [description for _ in range(len(environment.agents))]
1 change: 1 addition & 0 deletions agentverse/environments/rules/selector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .classroom import ClassroomSelector
from .sde_team import SdeTeamSelector
from .sde_team_given_tests import SdeTeamGivenTestsSelector
from .pokemon import PokemonSelector
98 changes: 98 additions & 0 deletions agentverse/environments/rules/selector/pokemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

from typing import TYPE_CHECKING, List
import numpy as np
import json

from agentverse.message import Message

from . import selector_registry as SelectorRegistry
from .base import BaseSelector

if TYPE_CHECKING:
from agentverse.environments import PokemonEnvironment


@SelectorRegistry.register("pokemon")
class PokemonSelector(BaseSelector):
"""
Selector for Pokemon environment
"""

def select_message(
self, environment: PokemonEnvironment, messages: List[Message]
) -> List[Message]:
valid = []
talk_matrix = np.zeros((len(environment.agents), len(environment.agents)))
agent_to_idx = {agent.name: i for i, agent in enumerate(environment.agents)}
for i, message in enumerate(messages):
try:
content = json.loads(message.content)
except json.decoder.JSONDecodeError:
valid.append(0)
continue
if content["action"] == "Speak":
try:
if "to" not in content:
# If the model does not generate receiver, then we discard the message
valid.append(0)
elif content["to"] in agent_to_idx:
# TODO: allow talk to a list of agents
valid.append(1)
# talk_matrix[i][j] = 1 ==> i talk to j
talk_matrix[agent_to_idx[message.sender]][
agent_to_idx[content["to"]]
] = 1
else:
# If the receiver is not in the environment, then we discard the message
valid.append(0)
except:
valid.append(0)
continue
elif content["action"] == "MoveTo":
# If the agent move to a location that does not exist, then we discard the message
valid.append(
"to" in content and content["to"] in environment.locations_to_agents
)
else:
valid.append(1)
selected_messages = []
for i, message in enumerate(messages):
content = json.loads(message.content)
sender_idx = agent_to_idx[message.sender]
if valid[i] == 0:
selected_messages.append(Message())
continue
if content["action"] == "MoveTo":
if np.sum(talk_matrix[:, sender_idx]) > 0:
# If someone talk to this agent, then we discard the move action
selected_messages.append(Message())
else:
selected_messages.append(message)
elif content["action"] == "Speak":
receiver_idx = agent_to_idx[content["to"]]
if talk_matrix[sender_idx][receiver_idx] == 0:
# If this agent talk to someone who also talk to this agent, and we
# select the message from this agent, then we discard the message
selected_messages.append(Message())
continue
if np.sum(talk_matrix[receiver_idx, :]) > 0:
if talk_matrix[receiver_idx][sender_idx] == 1:
# If the receiver talk to this agent, then we randomly select one message
if sender_idx < receiver_idx:
if np.random.random() < 0.5:
selected_messages.append(message)
talk_matrix[receiver_idx][sender_idx] = 0
else:
selected_messages.append(Message())
talk_matrix[sender_idx][receiver_idx] = 0
else:
print("Shouldn't happen")
else:
# If the receiver talk to other agent, we still talk to the receiver (?)
selected_messages.append(message)
else:
selected_messages.append(message)
else:
selected_messages.append(message)
return selected_messages
1 change: 1 addition & 0 deletions agentverse/environments/rules/updater/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
from .basic import BasicUpdater
from .classroom import ClassroomUpdater
from .sde_team import SdeTeamUpdater
from .pokemon import PokemonUpdater
Loading