diff --git a/agentverse/__init__.py b/agentverse/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4512a99dd917cc05b73d65467b0cc87a13c5d8e3
--- /dev/null
+++ b/agentverse/__init__.py
@@ -0,0 +1,24 @@
+from .output_parser import output_parser_registry
+from .environments import env_registry
+from .environments.simulation_env.rules.order import order_registry
+from .environments.simulation_env.rules.describer import describer_registry
+from .environments.simulation_env.rules.selector import selector_registry
+from .environments.simulation_env.rules.updater import updater_registry
+from .environments.simulation_env.rules.visibility import visibility_registry
+
+
+from .environments.tasksolving_env.rules.decision_maker import decision_maker_registry
+from .environments.tasksolving_env.rules.evaluator import evaluator_registry
+from .environments.tasksolving_env.rules.executor import executor_registry
+from .environments.tasksolving_env.rules.role_assigner import role_assigner_registry
+
+
+from .simulation import Simulation
+from .tasksolving import TaskSolving
+from .initialization import (
+ prepare_task_config,
+ load_agent,
+ load_environment,
+ load_llm,
+ load_memory,
+)
diff --git a/agentverse/agents/__init__.py b/agentverse/agents/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c74687dcfda017537ff20ad9bf857bbc115206c
--- /dev/null
+++ b/agentverse/agents/__init__.py
@@ -0,0 +1,20 @@
+# from .agent import Agent
+from agentverse.registry import Registry
+
+agent_registry = Registry(name="AgentRegistry")
+
+
+from .base import BaseAgent
+from agentverse.agents.simulation_agent.conversation import ConversationAgent
+from agentverse.agents.simulation_agent.tool import ToolAgent
+from agentverse.agents.simulation_agent.prisoner_dilemma import (
+ PoliceAgent,
+ PrisonerAgent,
+)
+
+from agentverse.agents.tasksolving_agent.role_assigner import RoleAssignerAgent
+from agentverse.agents.tasksolving_agent.critic import CriticAgent
+from agentverse.agents.tasksolving_agent.evaluator import EvaluatorAgent
+from agentverse.agents.tasksolving_agent.solver import SolverAgent
+from agentverse.agents.tasksolving_agent.manager import ManagerAgent
+from agentverse.agents.tasksolving_agent.executor import ExecutorAgent
diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f118e08c6a60f2affe61ae30388918feb818ebc
--- /dev/null
+++ b/agentverse/agents/base.py
@@ -0,0 +1,101 @@
+import logging
+from abc import abstractmethod
+from typing import List, NamedTuple, Set, Union
+from string import Template
+
+from pydantic import BaseModel, Field
+
+from agentverse.llms import BaseLLM
+from agentverse.memory import BaseMemory, ChatHistoryMemory
+from agentverse.message import Message
+from agentverse.output_parser import OutputParser
+from agentverse.memory_manipulator import BaseMemoryManipulator
+
+
+class BaseAgent(BaseModel):
+ name: str
+ llm: BaseLLM
+ output_parser: OutputParser
+ prepend_prompt_template: str = Field(default="")
+ append_prompt_template: str = Field(default="")
+ prompt_template: str = Field(default="")
+ role_description: str = Field(default="")
+ memory: BaseMemory = Field(default_factory=ChatHistoryMemory)
+ memory_manipulator: BaseMemoryManipulator = Field(
+ default_factory=BaseMemoryManipulator
+ )
+ max_retry: int = Field(default=3)
+ receiver: Set[str] = Field(default=set({"all"}))
+ async_mode: bool = Field(default=True)
+
+ @abstractmethod
+ def step(self, env_description: str = "") -> Message:
+ """Get one step response"""
+ pass
+
+ @abstractmethod
+ def astep(self, env_description: str = "") -> Message:
+ """Asynchronous version of step"""
+ pass
+
+ @abstractmethod
+ def reset(self) -> None:
+ """Reset the agent"""
+ pass
+
+ @abstractmethod
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ """Add a message to the memory"""
+ pass
+
+ def get_spend(self) -> float:
+ return self.llm.get_spend()
+
+ def get_spend_formatted(self) -> str:
+ two_trailing = f"${self.get_spend():.2f}"
+ if two_trailing == "$0.00":
+ return f"${self.get_spend():.6f}"
+ return two_trailing
+
+ def get_all_prompts(self, **kwargs):
+ prepend_prompt = Template(self.prepend_prompt_template).safe_substitute(
+ **kwargs
+ )
+ append_prompt = Template(self.append_prompt_template).safe_substitute(**kwargs)
+ return prepend_prompt, append_prompt
+
+ def get_receiver(self) -> Set[str]:
+ return self.receiver
+
+ def set_receiver(self, receiver: Union[Set[str], str]) -> None:
+ if isinstance(receiver, str):
+ self.receiver = set({receiver})
+ elif isinstance(receiver, set):
+ self.receiver = receiver
+ else:
+ raise ValueError(
+ "input argument `receiver` must be a string or a set of string"
+ )
+
+ def add_receiver(self, receiver: Union[Set[str], str]) -> None:
+ if isinstance(receiver, str):
+ self.receiver.add(receiver)
+ elif isinstance(receiver, set):
+ self.receiver = self.receiver.union(receiver)
+ else:
+ raise ValueError(
+ "input argument `receiver` must be a string or a set of string"
+ )
+
+ def remove_receiver(self, receiver: Union[Set[str], str]) -> None:
+ if isinstance(receiver, str):
+ try:
+ self.receiver.remove(receiver)
+ except KeyError as e:
+ logging.warning(f"Receiver {receiver} not found.")
+ elif isinstance(receiver, set):
+ self.receiver = self.receiver.difference(receiver)
+ else:
+ raise ValueError(
+ "input argument `receiver` must be a string or a set of string"
+ )
diff --git a/agentverse/agents/simulation_agent/conversation.py b/agentverse/agents/simulation_agent/conversation.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c356cf2f9772e8ab438628848e5ed2c1414118d
--- /dev/null
+++ b/agentverse/agents/simulation_agent/conversation.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+from colorama import Fore
+
+# import logging
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List
+
+from agentverse.message import Message
+
+#from . import agent_registry
+#from .base import BaseAgent
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+logger = get_logger()
+
+
+@agent_registry.register("conversation")
+class ConversationAgent(BaseAgent):
+ def step(self, env_description: str = "") -> Message:
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(prompt)
+ parsed_response = self.output_parser.parse(response)
+ break
+ except KeyboardInterrupt:
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ async def astep(self, env_description: str = "") -> Message:
+ """Asynchronous version of step"""
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ # if self.name == "Code Reviewer":
+ logger.debug(prompt, "Prompt", Fore.CYAN)
+ response = await self.llm.agenerate_response(prompt)
+
+ # logging.info(f"{self.name}'s request result:"
+ # f" {response.content}")
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warning("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ def _fill_prompt_template(self, env_description: str = "") -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.name,
+ "env_description": env_description,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/simulation_agent/prisoner_dilemma.py b/agentverse/agents/simulation_agent/prisoner_dilemma.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf257c1688ae593b148c43afa748179633f765ce
--- /dev/null
+++ b/agentverse/agents/simulation_agent/prisoner_dilemma.py
@@ -0,0 +1,167 @@
+from __future__ import annotations
+
+import logging
+from string import Template
+from typing import TYPE_CHECKING, List
+
+from agentverse.message import Message
+
+# from . import agent_registry
+# from .base import BaseAgent
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+if TYPE_CHECKING:
+ from agentverse.environments.base import BaseEnvironment
+
+
+class PrisonerDilemaAgent(BaseAgent):
+ def step(
+ self,
+ environment: BaseEnvironment,
+ env_description: str = "",
+ ) -> Message:
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(prompt)
+ parsed_response = self.output_parser.parse(self, environment, response)
+ break
+ except Exception as e:
+ logging.error(e)
+ logging.warning("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logging.error(f"{self.name} failed to generate valid response.")
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ async def astep(
+ self, environment: BaseEnvironment, env_description: str = ""
+ ) -> Message:
+ """Asynchronous version of step"""
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = await self.llm.agenerate_response(prompt)
+ parsed_response = self.output_parser.parse(self, environment, response)
+ break
+ except Exception as e:
+ logging.error(e)
+ logging.warning("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logging.error(f"{self.name} failed to generate valid response.")
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ def _fill_prompt_template(self, env_description: str = "") -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.name,
+ "env_description": env_description,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
+
+
+@agent_registry.register("police")
+class PoliceAgent(PrisonerDilemaAgent):
+ interrogating_form: str
+
+ def _fill_prompt_template(self, env_description: str = "") -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.name,
+ "env_description": env_description,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ }
+
+ role_argument = {
+ "interrogating_form": self.interrogating_form,
+ }
+
+ role_description = Template(self.role_description).safe_substitute(
+ role_argument
+ )
+ input_arguments["role_description"] = role_description
+
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+
+@agent_registry.register("prisoner")
+class PrisonerAgent(PrisonerDilemaAgent):
+ personality: str
+ relationship_with_another: str
+
+ def _fill_prompt_template(self, env_description: str = "") -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.name,
+ "env_description": env_description,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ }
+
+ role_argument = {
+ "personality": self.personality,
+ "relationship_with_another": self.relationship_with_another,
+ }
+
+ role_description = Template(self.role_description).safe_substitute(
+ role_argument
+ )
+ input_arguments["role_description"] = role_description
+
+ return Template(self.prompt_template).safe_substitute(input_arguments)
diff --git a/agentverse/agents/simulation_agent/reflection.py b/agentverse/agents/simulation_agent/reflection.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbbcf9f109cc43b1392f15bf065f4450b8fa8501
--- /dev/null
+++ b/agentverse/agents/simulation_agent/reflection.py
@@ -0,0 +1,227 @@
+from __future__ import annotations
+
+"""
+An agent based upon Observation-Planning-Reflection architecture.
+"""
+
+from logging import getLogger
+
+from abc import abstractmethod
+from typing import List, Set, Union, NamedTuple, TYPE_CHECKING
+
+from pydantic import BaseModel, Field, validator
+
+from agentverse.llms import BaseLLM
+from agentverse.memory import BaseMemory, ChatHistoryMemory
+from agentverse.message import Message
+from agentverse.output_parser import OutputParser
+
+from agentverse.message import Message
+from agentverse.agents.base import BaseAgent
+
+from datetime import datetime as dt
+import datetime
+
+#from . import agent_registry
+from string import Template
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+logger = getLogger(__file__)
+
+if TYPE_CHECKING:
+ from agentverse.environments.base import BaseEnvironment
+
+
+@agent_registry.register("reflection")
+class ReflectionAgent(BaseAgent):
+ async_mode: bool = (True,)
+ current_time: str = (None,)
+ environment: BaseEnvironment = None
+ step_cnt: int = 0
+
+ manipulated_memory: str = Field(
+ default="", description="one fragment used in prompt construction"
+ )
+
+ @validator("current_time")
+ def convert_str_to_dt(cls, current_time):
+ if not isinstance(current_time, str):
+ raise ValueError("current_time should be str")
+ return dt.strptime(current_time, "%Y-%m-%d %H:%M:%S")
+
+ def step(self, current_time: dt, env_description: str = "") -> Message:
+ """
+ Call this method at each time frame
+ """
+ self.current_time = current_time
+
+ self.manipulated_memory = self.memory_manipulator.manipulate_memory()
+
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response, reaction, target = None, None, None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.agenerate_response(prompt)
+ parsed_response = self.output_parser.parse(response)
+
+ if "say(" in parsed_response.return_values["output"]:
+ reaction, target = eval(
+ "self._" + parsed_response.return_values["output"].strip()
+ )
+ elif "act(" in parsed_response.return_values["output"]:
+ reaction, target = eval(
+ "self._" + parsed_response.return_values["output"].strip()
+ )
+ elif "do_nothing(" in parsed_response.return_values["output"]:
+ reaction, target = None, None
+ else:
+ raise Exception(
+ f"no valid parsed_response detected, "
+ f"cur response {parsed_response.return_values['output']}"
+ )
+ break
+
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ if reaction is None:
+ reaction = "Keep doing last action ..."
+
+ message = Message(
+ content="" if reaction is None else reaction,
+ sender=self.name,
+ receiver=self.get_receiver()
+ if target is None
+ else self.get_valid_receiver(target),
+ )
+
+ self.step_cnt += 1
+
+ return message
+
+ async def astep(self, current_time: dt, env_description: str = "") -> Message:
+ """Asynchronous version of step"""
+ # use environment's time to update agent's time
+ self.current_time = current_time
+ # Before the agent step, we check current status,
+ # TODO add this func after
+ # self.check_status_passive()
+
+ self.manipulated_memory = self.memory_manipulator.manipulate_memory()
+
+ prompt = self._fill_prompt_template(env_description)
+
+ parsed_response, reaction, target = None, None, None
+ for i in range(self.max_retry):
+ try:
+ response = await self.llm.agenerate_response(prompt)
+ parsed_response = self.output_parser.parse(response)
+
+ if "say(" in parsed_response.return_values["output"]:
+ reaction, target = eval(
+ "self._" + parsed_response.return_values["output"].strip()
+ )
+ elif "act(" in parsed_response.return_values["output"]:
+ reaction, target = eval(
+ "self._" + parsed_response.return_values["output"].strip()
+ )
+ elif "do_nothing(" in parsed_response.return_values["output"]:
+ reaction, target = None, None
+ else:
+ raise Exception(
+ f"no valid parsed_response detected, "
+ f"cur response {parsed_response.return_values['output']}"
+ )
+
+ break
+
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ if reaction is None:
+ reaction = "Keep doing last action ..."
+
+ message = Message(
+ content="" if reaction is None else reaction,
+ sender=self.name,
+ receiver=self.get_receiver()
+ if target is None
+ else self.get_valid_receiver(target),
+ )
+
+ self.step_cnt += 1
+
+ return message
+
+ def _act(self, description=None, target=None):
+ if description is None:
+ return ""
+ if target is None:
+ reaction_content = f"{self.name} performs action: '{description}'."
+ else:
+ reaction_content = (
+ f"{self.name} performs action to {target}: '{description}'."
+ )
+ # self.environment.broadcast_observations(self, target, reaction_content)
+ return reaction_content, target
+
+ def _say(self, description, target=None):
+ if description is None:
+ return ""
+ if target is None:
+ reaction_content = f"{self.name} says: '{description}'."
+ else:
+ reaction_content = f"{self.name} says to {target}: '{description}'."
+ # self.environment.broadcast_observations(self, target, reaction_content)
+ return reaction_content, target
+
+ def get_valid_receiver(self, target: str) -> set():
+ all_agents_name = []
+ for agent in self.environment.agents:
+ all_agents_name.append(agent.name)
+
+ if not (target in all_agents_name):
+ return {"all"}
+ else:
+ return {target}
+
+ def _fill_prompt_template(self, env_description: str = "") -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.name,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ "current_time": self.current_time,
+ "env_description": env_description,
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self, environment: BaseEnvironment) -> None:
+ """Reset the agent"""
+ self.environment = environment
+ self.memory.reset()
+ self.memory_manipulator.agent = self
+ self.memory_manipulator.memory = self.memory
diff --git a/agentverse/agents/simulation_agent/tool.py b/agentverse/agents/simulation_agent/tool.py
new file mode 100644
index 0000000000000000000000000000000000000000..6833a22a1a666bcd016accd37beb477487a8b259
--- /dev/null
+++ b/agentverse/agents/simulation_agent/tool.py
@@ -0,0 +1,177 @@
+import logging
+from string import Template
+from typing import List, NamedTuple, Optional, Union
+
+from langchain.tools import BaseTool
+from pydantic import Field
+
+
+from agentverse.memory import BaseMemory, ChatHistoryMemory
+from agentverse.message import Message
+from agentverse.utils import AgentAction, AgentFinish
+
+#from . import agent_registry
+#from .base import BaseAgent
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+class ToolNotExistError(BaseException):
+ """Exception raised when parsing output from a command fails."""
+
+ def __init__(self, tool_name=""):
+ self.tool_name = tool_name
+
+ def __str__(self):
+ return f"Tool {self.tool_name} does not exist."
+
+
+@agent_registry.register("tool")
+class ToolAgent(BaseAgent):
+ tools: List[BaseTool] = Field(default=[])
+ tool_memory: BaseMemory = Field(default_factory=ChatHistoryMemory)
+ verbose: bool = Field(default=False)
+
+ def step(self, env_description: str = "") -> Message:
+ parsed_response = None
+ tool_observation = [self.tool_memory.to_string()]
+ while True:
+ prompt = self._fill_prompt_template(env_description, tool_observation)
+
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(prompt)
+ parsed_response = self.output_parser.parse(response)
+ if isinstance(parsed_response, AgentAction):
+ observation = self._call_tool(parsed_response)
+ tool_observation.append(
+ parsed_response.log.strip()
+ + f"\nObservation: {observation.strip()}"
+ )
+ break
+ except BaseException as e:
+ logging.error(e)
+ logging.warning("Retrying...")
+ continue
+ if parsed_response is None or isinstance(parsed_response, AgentFinish):
+ break
+
+ if parsed_response is None:
+ logging.error(f"{self.name} failed to generate valid response.")
+
+ self._update_tool_memory(tool_observation)
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ async def astep(self, env_description: str = "") -> Message:
+ """Asynchronous version of step"""
+ parsed_response = None
+ # Initialize the tool_observation with tool_memory
+ tool_observation = [self.tool_memory.to_string()]
+ while True:
+ prompt = self._fill_prompt_template(env_description, tool_observation)
+
+ for i in range(self.max_retry):
+ try:
+ response = await self.llm.agenerate_response(prompt)
+ parsed_response = self.output_parser.parse(response)
+ if isinstance(parsed_response, AgentAction):
+ # If the response is an action, call the tool
+ # and append the observation to tool_observation
+ observation = await self._acall_tool(parsed_response)
+ tool_observation.append(
+ parsed_response.log.strip()
+ + f"\nObservation: {observation.strip()}"
+ )
+ break
+ except BaseException as e:
+ logging.error(e)
+ logging.warning("Retrying...")
+ continue
+ if parsed_response is None or isinstance(parsed_response, AgentFinish):
+ break
+
+ if parsed_response is None:
+ logging.error(f"{self.name} failed to generate valid response.")
+
+ self._update_tool_memory(tool_observation)
+
+ message = Message(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ def _call_tool(self, response: NamedTuple) -> str:
+ """Call a tool and return the output"""
+ name_to_tool = {tool.name: tool for tool in self.tools}
+ if response.tool not in name_to_tool:
+ raise ToolNotExistError(response.tool)
+ tool = name_to_tool[response.tool]
+ observation = tool.run(response.tool_input, verbose=self.verbose)
+ return observation
+
+ async def _acall_tool(self, response: NamedTuple) -> str:
+ """Call a tool and return the output"""
+ name_to_tool = {tool.name: tool for tool in self.tools}
+ if response.tool not in name_to_tool:
+ raise ToolNotExistError(response.tool)
+ tool = name_to_tool[response.tool]
+ observation = await tool.arun(response.tool_input, verbose=self.verbose)
+ return observation
+
+ def _update_tool_memory(self, tool_observation: List[str]):
+ """Update the memory of the tool"""
+ if len(tool_observation) == 1:
+ # If no tool is called this turn, do nothing
+ return
+ messages = [
+ Message(content=observation) for observation in tool_observation[1:]
+ ]
+ self.tool_memory.add_message(messages)
+
+ def _fill_prompt_template(
+ self, env_description: str = "", tool_observation: List[str] = []
+ ) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the tool agent, these placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ - ${tools}: the list of tools and their usage
+ - ${tool_names}: the list of tool names
+ - ${tool_observations}: the observation of the tool in this turn
+ """
+ tools = "\n".join([f"> {tool.name}: {tool.description}" for tool in self.tools])
+ tools = tools.replace("{{", "{").replace("}}", "}")
+ tool_names = ", ".join([tool.name for tool in self.tools])
+ input_arguments = {
+ "agent_name": self.name,
+ "env_description": env_description,
+ "role_description": self.role_description,
+ "chat_history": self.memory.to_string(add_sender_prefix=True),
+ "tools": tools,
+ "tool_names": tool_names,
+ "tool_observation": "\n".join(tool_observation),
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/__init__.py b/agentverse/agents/tasksolving_agent/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aede8c69a5a4dbe29bc7ba96134d5c0b78d3d9d6
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/__init__.py
@@ -0,0 +1,6 @@
+from .critic import CriticAgent
+from .evaluator import EvaluatorAgent
+from .executor import ExecutorAgent
+from .manager import ManagerAgent
+from .role_assigner import RoleAssignerAgent
+from .solver import SolverAgent
diff --git a/agentverse/agents/tasksolving_agent/critic.py b/agentverse/agents/tasksolving_agent/critic.py
new file mode 100644
index 0000000000000000000000000000000000000000..07c798964c52bb32c7e572cac53aef1faaeaff38
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/critic.py
@@ -0,0 +1,127 @@
+from __future__ import annotations
+
+import json
+from colorama import Fore
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List, Union
+
+from agentverse.message import Message
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+from agentverse.utils import AgentCriticism
+from agentverse.message import CriticMessage
+
+logger = get_logger()
+
+
+@agent_registry.register("critic")
+class CriticAgent(BaseAgent):
+ max_history: int = 3
+ tools: List[dict] = []
+ tool_names: List[str] = []
+ tool_descriptions: str = ""
+
+ def __init__(self, *args, **kwargs):
+ tool_config_file = kwargs.pop("tool_config", "")
+ tools = []
+ tool_names = []
+ tool_descriptions = ""
+ if tool_config_file != "":
+ try:
+ with open(tool_config_file, "r") as f:
+ tools_dict = json.load(f)
+ tools = tools_dict["tools_json"]
+ tool_names = [t["name"] for t in tools]
+ tool_descriptions = "\n".join(
+ [f"- {t['name']}: " + t["description"] for t in tools]
+ )
+ kwargs.update('tools', tools)
+ kwargs.update('tool_names', tool_names)
+ kwargs.update('tool_descriptions', tool_descriptions)
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Failed to load tool config file.")
+ super().__init__(
+ *args,
+ **kwargs,
+ )
+
+ def step(self, env_description: str = "") -> CriticMessage:
+ pass
+
+ async def astep(
+ self,
+ preliminary_solution: str,
+ advice: str = "No advice yet.",
+ task_description: str = "",
+ all_roles: str = "",
+ **kwargs,
+ ) -> CriticMessage:
+ """Asynchronous version of step"""
+ logger.debug("", self.name, Fore.MAGENTA)
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ preliminary_solution=preliminary_solution,
+ advice=advice,
+ task_description=task_description,
+ role_description=self.role_description,
+ agent_name=self.name,
+ all_roles=all_roles,
+ # tool_names=self.tool_names,
+ tool_descriptions=self.tool_descriptions,
+ )
+ history = self.memory.to_messages(self.name, start_index=-self.max_history)
+ parsed_response: Union[AgentCriticism, None] = None
+ for i in range(self.max_retry):
+ try:
+ response = await self.llm.agenerate_response(
+ prepend_prompt, history, append_prompt
+ )
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ message = CriticMessage(
+ content=parsed_response.criticism if parsed_response is not None else "",
+ sender=self.name,
+ sender_agent=self,
+ is_agree=parsed_response.is_agree if parsed_response is not None else False,
+ )
+ return message
+
+ def _fill_prompt_template(
+ self, preliminary_solution: str, advice: str, task_description: str
+ ) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${role_description}
+ - ${task_description}
+ - ${preliminary_solution}
+ - ${advice}
+ """
+ input_arguments = {
+ "role_description": self.role_description,
+ "task_description": task_description,
+ "preliminary_solution": preliminary_solution,
+ "advice": advice,
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/evaluator.py b/agentverse/agents/tasksolving_agent/evaluator.py
new file mode 100644
index 0000000000000000000000000000000000000000..abd40642b8dd04397f74b73a420588affad0556f
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/evaluator.py
@@ -0,0 +1,86 @@
+from __future__ import annotations
+
+import asyncio
+from colorama import Fore
+
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List, Tuple
+
+from agentverse.message import EvaluatorMessage, Message
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+
+logger = get_logger()
+
+
+@agent_registry.register("evaluator")
+class EvaluatorAgent(BaseAgent):
+ def step(
+ self,
+ solution: str,
+ result: str,
+ task_description: str,
+ all_role_description: str,
+ ) -> EvaluatorMessage:
+ logger.debug("", self.name, Fore.MAGENTA)
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ solution=solution,
+ result=result,
+ task_description=task_description,
+ all_role_description=all_role_description,
+ )
+ history = self.memory.to_messages(self.name)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(
+ prepend_prompt, history, append_prompt
+ )
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+ message = EvaluatorMessage(
+ sender=self.name,
+ sender_agent=self,
+ score=parsed_response[0] if parsed_response is not None else 0,
+ advice=parsed_response[1] if parsed_response is not None else "",
+ )
+ return message
+ # return parsed_response
+
+ async def astep(self, solution: str) -> EvaluatorMessage:
+ """Asynchronous version of step"""
+ pass
+
+ def _fill_prompt_template(self, solution: str, task_description: str) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the role_assigner agent, three placeholders are supported:
+ - ${task_description}
+ - ${solution}
+ """
+ input_arguments = {
+ "task_description": task_description,
+ "solution": solution,
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/executor.py b/agentverse/agents/tasksolving_agent/executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..38294453d1ed4e81ba42b76a90da524afeb69c32
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/executor.py
@@ -0,0 +1,130 @@
+from __future__ import annotations
+
+from agentverse.logging import get_logger
+from colorama import Fore
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List, Any
+
+from agentverse.message import ExecutorMessage, Message, SolverMessage
+from agentverse.utils import AgentFinish, AgentAction
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+import requests
+
+logger = get_logger()
+
+
+@agent_registry.register("executor")
+class ExecutorAgent(BaseAgent):
+ max_history: int = 5
+
+ def step(
+ self, task_description: str, solution: str, tools: List[dict] = [], **kwargs
+ ) -> ExecutorMessage:
+ logger.debug("", self.name, Fore.MAGENTA)
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ task_description=task_description,
+ solution=solution,
+ agent_name=self.name,
+ **kwargs,
+ )
+
+ history = self.memory.to_messages(self.name, start_index=-self.max_history)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(
+ prepend_prompt, history, append_prompt, tools
+ )
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+ if isinstance(parsed_response, AgentFinish):
+ message = ExecutorMessage(
+ content=parsed_response.return_values["output"],
+ sender=self.name,
+ sender_agent=self,
+ )
+ elif isinstance(parsed_response, AgentAction):
+ message = ExecutorMessage(
+ content=parsed_response.log,
+ sender=self.name,
+ sender_agent=self,
+ tool_name=parsed_response.tool,
+ tool_input=parsed_response.tool_input,
+ )
+ else:
+ raise ValueError(
+ f"Error response type: {type(parsed_response)}. Only support \
+ AgentFinish and AgentAction. Modify your output parser."
+ )
+ return message
+
+ async def astep(
+ self, task_description: str, solution: str, tools: List[dict] = [], **kwargs
+ ) -> ExecutorMessage:
+ logger.debug("", self.name, Fore.MAGENTA)
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ task_description=task_description,
+ solution=solution,
+ agent_name=self.name,
+ **kwargs,
+ )
+
+ history = self.memory.to_messages(self.name, start_index=-self.max_history)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = await self.llm.agenerate_response(
+ prepend_prompt, history, append_prompt, tools
+ )
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+ parsed_response = AgentAction(tool="", tool_input="", log="")
+ if isinstance(parsed_response, AgentFinish):
+ message = ExecutorMessage(
+ content=parsed_response.return_values["output"],
+ sender=self.name,
+ sender_agent=self,
+ )
+ elif isinstance(parsed_response, AgentAction):
+ message = ExecutorMessage(
+ content=parsed_response.log,
+ sender=self.name,
+ sender_agent=self,
+ tool_name=parsed_response.tool,
+ tool_input=parsed_response.tool_input,
+ )
+ else:
+ raise ValueError(
+ f"Error response type: {type(parsed_response)}. Only support \
+ AgentFinish and AgentAction. Modify your output parser."
+ )
+ return message
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/manager.py b/agentverse/agents/tasksolving_agent/manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..76161b50ae87d5dd512fdb0b24d1d5aaeafa2f78
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/manager.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+
+import asyncio
+from colorama import Fore
+
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List, Tuple
+
+from agentverse.message import Message
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+from agentverse.utils import AgentCriticism
+
+import random
+from rapidfuzz import fuzz
+
+
+logger = get_logger()
+
+
+@agent_registry.register("manager")
+class ManagerAgent(BaseAgent):
+ prompt_template: str
+
+ def step(
+ self,
+ former_solution: str,
+ candidate_critic_opinions: List[AgentCriticism],
+ advice: str,
+ task_description: str = "",
+ previous_sentence: str = "",
+ ) -> Message:
+ logger.debug("", self.name, Fore.MAGENTA)
+
+ prompt = self._fill_prompt_template(
+ former_solution,
+ candidate_critic_opinions,
+ advice,
+ task_description,
+ previous_sentence,
+ )
+
+ logger.debug(f"Prompt:\n{prompt}", "Manager", Fore.CYAN)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ # LLM Manager
+ # response = self.llm.generate_response(prompt)
+ # parsed_response = self.output_parser.parse(response)
+ selected_role_description = self.llm.generate_response(prompt).content
+ candidate_score_list = [
+ fuzz.ratio(candidate.sender, selected_role_description)
+ for candidate in candidate_critic_opinions
+ ]
+ selected_index = candidate_score_list.index(max(candidate_score_list))
+ candidate_critic_opinion = candidate_critic_opinions[selected_index]
+
+ # Random Manager
+ # parsed_response = random.choice(candidate_critic_opinions)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+ return candidate_critic_opinion
+
+ async def astep(self, env_description: str = "") -> Message:
+ """Asynchronous version of step"""
+ pass
+
+ def _fill_prompt_template(
+ self,
+ former_solution: str,
+ candidate_critic_opinions: List[AgentCriticism],
+ advice: str,
+ task_description: str,
+ previous_sentence: str,
+ ) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the role_assigner agent, three placeholders are supported:
+ - ${task_description}
+ - ${former_solution}
+ - ${critic_messages}
+ - ${advice}
+ - ${previous_sentence}
+ """
+ input_arguments = {
+ "task_description": task_description,
+ "former_solution": former_solution,
+ "previous_sentence": previous_sentence,
+ "critic_opinions": "\n".join(
+ [
+ f"Role: {critic.sender}. {critic.sender_agent.role_description} said: {critic.content}"
+ for critic in candidate_critic_opinions
+ ]
+ ),
+ "advice": advice,
+ }
+
+ # manger select the proper sentence
+ template = Template(self.prompt_template)
+ return template.safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/role_assigner.py b/agentverse/agents/tasksolving_agent/role_assigner.py
new file mode 100644
index 0000000000000000000000000000000000000000..38d9e5dfbee7365f5b11cade939c4263a0937ad1
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/role_assigner.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+import asyncio
+from colorama import Fore
+
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List
+
+from agentverse.message import RoleAssignerMessage, Message
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+
+
+logger = get_logger()
+
+
+@agent_registry.register("role_assigner")
+class RoleAssignerAgent(BaseAgent):
+ def step(
+ self, advice: str, task_description: str, cnt_critic_agents: int
+ ) -> RoleAssignerMessage:
+ logger.debug("", self.name, Fore.MAGENTA)
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ advice=advice,
+ task_description=task_description,
+ cnt_critic_agents=cnt_critic_agents,
+ )
+ history = self.memory.to_messages(self.name)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(
+ prepend_prompt, history, append_prompt
+ )
+ parsed_response = self.output_parser.parse(response)
+ if len(parsed_response) < cnt_critic_agents:
+ logger.warn(
+ f"Number of generate roles ({len(parsed_response)}) and number of group members ({cnt_critic_agents}) do not match."
+ )
+ logger.warn("Retrying...")
+ continue
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ message = RoleAssignerMessage(
+ content=parsed_response, sender=self.name, sender_agent=self
+ )
+ return message
+
+ async def astep(self, env_description: str = "") -> RoleAssignerMessage:
+ """Asynchronous version of step"""
+ pass
+
+ def _fill_prompt_template(
+ self, advice, task_description: str, cnt_critic_agents: int
+ ) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the role_assigner agent, three placeholders are supported:
+ - ${task_description}
+ - ${cnt_critic_agnets}
+ - ${advice}
+ """
+ input_arguments = {
+ "task_description": task_description,
+ "cnt_critic_agents": cnt_critic_agents,
+ "advice": advice,
+ }
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agents/tasksolving_agent/solver.py b/agentverse/agents/tasksolving_agent/solver.py
new file mode 100644
index 0000000000000000000000000000000000000000..6db77561d3859e897ad5b66859ddc76bd3a28b4d
--- /dev/null
+++ b/agentverse/agents/tasksolving_agent/solver.py
@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+import asyncio
+from colorama import Fore
+
+from agentverse.logging import get_logger
+import bdb
+from string import Template
+from typing import TYPE_CHECKING, List, Tuple
+
+# from agentverse.environments import PipelineEnvironment
+from agentverse.message import SolverMessage, Message, CriticMessage
+
+from agentverse.agents import agent_registry
+from agentverse.agents.base import BaseAgent
+from agentverse.utils import AgentCriticism
+
+
+logger = get_logger()
+
+
+@agent_registry.register("solver")
+class SolverAgent(BaseAgent):
+ max_history: int = 3
+
+ def step(
+ self, former_solution: str, advice: str, task_description: str = "", **kwargs
+ ) -> SolverMessage:
+ logger.debug("", self.name, Fore.MAGENTA)
+ # prompt = self._fill_prompt_template(
+ # former_solution, critic_opinions, advice, task_description
+ # )
+ prepend_prompt, append_prompt = self.get_all_prompts(
+ former_solution=former_solution,
+ task_description=task_description,
+ advice=advice,
+ role_description=self.role_description,
+ **kwargs,
+ )
+ history = self.memory.to_messages(self.name, start_index=-self.max_history)
+ parsed_response = None
+ for i in range(self.max_retry):
+ try:
+ response = self.llm.generate_response(
+ prepend_prompt, history, append_prompt
+ )
+ parsed_response = self.output_parser.parse(response)
+ break
+ except (KeyboardInterrupt, bdb.BdbQuit):
+ raise
+ except Exception as e:
+ logger.error(e)
+ logger.warn("Retrying...")
+ continue
+
+ if parsed_response is None:
+ logger.error(f"{self.name} failed to generate valid response.")
+
+ message = SolverMessage(
+ content=""
+ if parsed_response is None
+ else parsed_response.return_values["output"],
+ sender=self.name,
+ receiver=self.get_receiver(),
+ )
+ return message
+
+ async def astep(self, env_description: str = "") -> SolverMessage:
+ """Asynchronous version of step"""
+ pass
+
+ def _fill_prompt_template(
+ self,
+ former_solution: str,
+ critic_opinions: List[AgentCriticism],
+ advice: str,
+ task_description: str,
+ ) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the role_assigner agent, three placeholders are supported:
+ - ${task_description}
+ - ${former_solution}
+ - ${critic_messages}
+ - ${advice}
+ """
+ input_arguments = {
+ "task_description": task_description,
+ "former_solution": former_solution,
+ "critic_opinions": "\n".join(
+ [
+ f"{critic.sender_agent.role_description} said: {critic.criticism}"
+ for critic in critic_opinions
+ ]
+ ),
+ "advice": advice,
+ }
+ # if discussion_mode:
+ # template = Template(self.prompt_template[1])
+ # else:
+ template = Template(self.prompt_template)
+ return template.safe_substitute(input_arguments)
+
+ def add_message_to_memory(self, messages: List[Message]) -> None:
+ self.memory.add_message(messages)
+
+ def reset(self) -> None:
+ """Reset the agent"""
+ self.memory.reset()
+ # TODO: reset receiver
diff --git a/agentverse/agentverse.py b/agentverse/agentverse.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b1505075ccd942f0859b779eff6b6e3a2915aa1
--- /dev/null
+++ b/agentverse/agentverse.py
@@ -0,0 +1,65 @@
+import asyncio
+import logging
+from typing import List
+
+# from agentverse.agents import Agent
+from agentverse.agents.conversation_agent import BaseAgent
+from agentverse.environments import BaseEnvironment
+from agentverse.initialization import load_agent, load_environment, prepare_task_config
+
+logging.basicConfig(
+ format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
+ datefmt="%m/%d/%Y %H:%M:%S",
+ level=logging.INFO,
+)
+
+openai_logger = logging.getLogger("openai")
+openai_logger.setLevel(logging.WARNING)
+
+
+class AgentVerse:
+ def __init__(self, agents: List[BaseAgent], environment: BaseEnvironment):
+ self.agents = agents
+ self.environment = environment
+
+ @classmethod
+ def from_task(cls, task: str, tasks_dir: str):
+ """Build an AgentVerse from a task name.
+ The task name should correspond to a directory in `tasks` directory.
+ Then this method will load the configuration from the yaml file in that directory.
+ """
+ # Prepare the config of the task
+ task_config = prepare_task_config(task, tasks_dir)
+
+ # Build the agents
+ agents = []
+ for agent_configs in task_config["agents"]:
+ agent = load_agent(agent_configs)
+ agents.append(agent)
+
+ # Build the environment
+ env_config = task_config["environment"]
+ env_config["agents"] = agents
+ environment = load_environment(env_config)
+
+ return cls(agents, environment)
+
+ def run(self):
+ """Run the environment from scratch until it is done."""
+ self.environment.reset()
+ while not self.environment.is_done():
+ asyncio.run(self.environment.step())
+
+ def reset(self):
+ self.environment.reset()
+ for agent in self.agents:
+ agent.reset()
+
+ 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)
diff --git a/agentverse/demo.py b/agentverse/demo.py
new file mode 100644
index 0000000000000000000000000000000000000000..810839ecdfb17118b1854c03819bf0ec38ddcc48
--- /dev/null
+++ b/agentverse/demo.py
@@ -0,0 +1,487 @@
+import base64
+import itertools
+import json
+from typing import Dict, List, Tuple
+
+import cv2
+import gradio as gr
+
+from agentverse.agentverse import AgentVerse
+from agentverse.message import Message
+
+
+def cover_img(background, img, place: Tuple[int, int]):
+ """
+ Overlays the specified image to the specified position of the background image.
+ :param background: background image
+ :param img: the specified image
+ :param place: the top-left coordinate of the target location
+ """
+ back_h, back_w, _ = background.shape
+ height, width, _ = img.shape
+ for i, j in itertools.product(range(height), range(width)):
+ if img[i, j, 3]:
+ background[place[0] + i, place[1] + j] = img[i, j, :3]
+
+
+class UI:
+ """
+ the UI of frontend
+ """
+
+ def __init__(self, task: str):
+ """
+ init a UI.
+ default number of students is 0
+ """
+ self.messages = []
+ self.task = task
+ self.backend = AgentVerse.from_task(task)
+ self.turns_remain = 0
+ self.agent_id = {
+ self.backend.agents[idx].name: idx
+ for idx in range(len(self.backend.agents))
+ }
+ self.stu_num = len(self.agent_id) - 1
+ self.autoplay = False
+ self.image_now = None
+ self.text_now = None
+ self.tot_solutions = 5
+ self.solution_status = [False] * self.tot_solutions
+
+ def get_avatar(self, idx):
+ if idx == -1:
+ img = cv2.imread("./imgs/db_diag/-1.png")
+ elif self.task == "prisoner_dilemma":
+ img = cv2.imread(f"./imgs/prison/{idx}.png")
+ elif self.task == "db_diag":
+ img = cv2.imread(f"./imgs/db_diag/{idx}.png")
+ elif "sde" in self.task:
+ img = cv2.imread(f"./imgs/sde/{idx}.png")
+ else:
+ img = cv2.imread(f"./imgs/{idx}.png")
+ base64_str = cv2.imencode(".png", img)[1].tostring()
+ return "data:image/png;base64," + base64.b64encode(base64_str).decode("utf-8")
+
+ def stop_autoplay(self):
+ self.autoplay = False
+ return (
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ )
+
+ def start_autoplay(self):
+ self.autoplay = True
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=True),
+ gr.Button.update(interactive=False),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status)),
+ )
+
+ while self.autoplay and self.turns_remain > 0:
+ outputs = self.gen_output()
+ self.image_now, self.text_now = outputs
+
+ yield (
+ *outputs,
+ gr.Button.update(interactive=not self.autoplay and self.turns_remain > 0),
+ gr.Button.update(interactive=self.autoplay and self.turns_remain > 0),
+ gr.Button.update(interactive=not self.autoplay and self.turns_remain > 0),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def delay_gen_output(self):
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ outputs = self.gen_output()
+ self.image_now, self.text_now = outputs
+
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=self.turns_remain > 0),
+ gr.Button.update(interactive=self.turns_remain > 0),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def delay_reset(self):
+ self.autoplay = False
+ self.image_now, self.text_now = self.reset()
+ return (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=True),
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=True),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def reset(self, stu_num=0):
+ """
+ tell backend the new number of students and generate new empty image
+ :param stu_num:
+ :return: [empty image, empty message]
+ """
+ if not 0 <= stu_num <= 30:
+ raise gr.Error("the number of students must be between 0 and 30.")
+
+ """
+ # [To-Do] Need to add a function to assign agent numbers into the backend.
+ """
+ # self.backend.reset(stu_num)
+ # self.stu_num = stu_num
+
+ """
+ # [To-Do] Pass the parameters to reset
+ """
+ self.backend.reset()
+ self.turns_remain = self.backend.environment.max_turns
+
+ if self.task == "prisoner_dilemma":
+ background = cv2.imread("./imgs/prison/case_1.png")
+ elif self.task == "db_diag":
+ background = cv2.imread("./imgs/db_diag/background.png")
+ elif "sde" in self.task:
+ background = cv2.imread("./imgs/sde/background.png")
+ else:
+ background = cv2.imread("./imgs/background.png")
+ back_h, back_w, _ = background.shape
+ stu_cnt = 0
+ for h_begin, w_begin in itertools.product(
+ range(800, back_h, 300), range(135, back_w - 200, 200)
+ ):
+ stu_cnt += 1
+ img = cv2.imread(
+ f"./imgs/{(stu_cnt - 1) % 11 + 1 if stu_cnt <= self.stu_num else 'empty'}.png",
+ cv2.IMREAD_UNCHANGED,
+ )
+ cover_img(
+ background,
+ img,
+ (h_begin - 30 if img.shape[0] > 190 else h_begin, w_begin),
+ )
+ self.messages = []
+ self.solution_status = [False] * self.tot_solutions
+ return [cv2.cvtColor(background, cv2.COLOR_BGR2RGB), ""]
+
+ def gen_img(self, data: List[Dict]):
+ """
+ generate new image with sender rank
+ :param data:
+ :return: the new image
+ """
+ # The following code need to be more general. This one is too task-specific.
+ # if len(data) != self.stu_num:
+ if len(data) != self.stu_num + 1:
+ raise gr.Error("data length is not equal to the total number of students.")
+ if self.task == "prisoner_dilemma":
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ if (
+ len(self.messages) < 2
+ or self.messages[-1][0] == 1
+ or self.messages[-2][0] == 2
+ ):
+ background = cv2.imread("./imgs/prison/case_1.png")
+ if data[0]["message"] != "":
+ cover_img(background, img, (400, 480))
+ else:
+ background = cv2.imread("./imgs/prison/case_2.png")
+ if data[0]["message"] != "":
+ cover_img(background, img, (400, 880))
+ if data[1]["message"] != "":
+ cover_img(background, img, (550, 480))
+ if data[2]["message"] != "":
+ cover_img(background, img, (550, 880))
+ elif self.task == "db_diag":
+ background = cv2.imread("./imgs/db_diag/background.png")
+ img = cv2.imread("./imgs/db_diag/speaking.png", cv2.IMREAD_UNCHANGED)
+ if data[0]["message"] != "":
+ cover_img(background, img, (750, 80))
+ if data[1]["message"] != "":
+ cover_img(background, img, (310, 220))
+ if data[2]["message"] != "":
+ cover_img(background, img, (522, 11))
+ elif "sde" in self.task:
+ background = cv2.imread("./imgs/sde/background.png")
+ img = cv2.imread("./imgs/sde/speaking.png", cv2.IMREAD_UNCHANGED)
+ if data[0]["message"] != "":
+ cover_img(background, img, (692, 330))
+ if data[1]["message"] != "":
+ cover_img(background, img, (692, 660))
+ if data[2]["message"] != "":
+ cover_img(background, img, (692, 990))
+ else:
+ background = cv2.imread("./imgs/background.png")
+ back_h, back_w, _ = background.shape
+ stu_cnt = 0
+ if data[stu_cnt]["message"] not in ["", "[RaiseHand]"]:
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (370, 1250))
+ for h_begin, w_begin in itertools.product(
+ range(800, back_h, 300), range(135, back_w - 200, 200)
+ ):
+ stu_cnt += 1
+ if stu_cnt <= self.stu_num:
+ img = cv2.imread(
+ f"./imgs/{(stu_cnt - 1) % 11 + 1}.png", cv2.IMREAD_UNCHANGED
+ )
+ cover_img(
+ background,
+ img,
+ (h_begin - 30 if img.shape[0] > 190 else h_begin, w_begin),
+ )
+ if "[RaiseHand]" in data[stu_cnt]["message"]:
+ # elif data[stu_cnt]["message"] == "[RaiseHand]":
+ img = cv2.imread("./imgs/hand.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin - 90, w_begin + 10))
+ elif data[stu_cnt]["message"] not in ["", "[RaiseHand]"]:
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin - 90, w_begin + 10))
+
+ else:
+ img = cv2.imread("./imgs/empty.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin, w_begin))
+ return cv2.cvtColor(background, cv2.COLOR_BGR2RGB)
+
+ def return_format(self, messages: List[Message]):
+ _format = [{"message": "", "sender": idx} for idx in range(len(self.agent_id))]
+
+ for message in messages:
+ if self.task == "db_diag":
+ content_json: dict = message.content
+ content_json["diagnose"] = f"[{message.sender}]: {content_json['diagnose']}"
+ _format[self.agent_id[message.sender]]["message"] = json.dumps(content_json)
+ elif "sde" in self.task:
+ if message.sender == "code_tester":
+ pre_message, message_ = message.content.split("\n")
+ message_ = "{}\n{}".format(pre_message, json.loads(message_)["feedback"])
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message_
+ )
+ else:
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message.content
+ )
+
+ else:
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message.content
+ )
+
+ return _format
+
+ def gen_output(self):
+ """
+ generate new image and message of next step
+ :return: [new image, new message]
+ """
+
+ # data = self.backend.next_data()
+ return_message = self.backend.next()
+ data = self.return_format(return_message)
+
+ # data.sort(key=lambda item: item["sender"])
+ """
+ # [To-Do]; Check the message from the backend: only 1 person can speak
+ """
+
+ for item in data:
+ if item["message"] not in ["", "[RaiseHand]"]:
+ self.messages.append((item["sender"], item["message"]))
+
+ message = self.gen_message()
+ self.turns_remain -= 1
+ return [self.gen_img(data), message]
+
+ def gen_message(self):
+ # If the backend cannot handle this error, use the following code.
+ message = ""
+ """
+ for item in data:
+ if item["message"] not in ["", "[RaiseHand]"]:
+ message = item["message"]
+ break
+ """
+ for sender, msg in self.messages:
+ if sender == 0:
+ avatar = self.get_avatar(0)
+ elif sender == -1:
+ avatar = self.get_avatar(-1)
+ else:
+ avatar = self.get_avatar((sender - 1) % 11 + 1)
+ if self.task == "db_diag":
+ msg_json = json.loads(msg)
+ self.solution_status = [False] * self.tot_solutions
+ msg = msg_json["diagnose"]
+ if msg_json["solution"] != "":
+ solution: List[str] = msg_json["solution"]
+ for solu in solution:
+ if "query" in solu or "queries" in solu:
+ self.solution_status[0] = True
+ solu = solu.replace("query", 'query')
+ solu = solu.replace("queries", 'queries')
+ if "join" in solu:
+ self.solution_status[1] = True
+ solu = solu.replace("join", 'join')
+ if "index" in solu:
+ self.solution_status[2] = True
+ solu = solu.replace("index", 'index')
+ if "system configuration" in solu:
+ self.solution_status[3] = True
+ solu = solu.replace("system configuration",
+ 'system configuration')
+ if "monitor" in solu or "Monitor" in solu or "Investigate" in solu:
+ self.solution_status[4] = True
+ solu = solu.replace("monitor", 'monitor')
+ solu = solu.replace("Monitor", 'Monitor')
+ solu = solu.replace("Investigate", 'Investigate')
+ msg = f"{msg}
{solu}"
+ if msg_json["knowledge"] != "":
+ msg = f'{msg}
:")
+ environment.rule_params["code"] = cur_code
+
+ from .code_api import execute_unit_tests
+ feedback = execute_unit_tests(environment.rule_params["code"], eval(environment.rule_params["unit_tests"]))
+
+ environment.rule_params["feedback"] = feedback
+ selected[0].content = f":\n\n{cur_code}\n\n:\n{feedback}"
+ f_dict = json.loads(feedback)
+ if f_dict["is_passing"]:
+ environment.rule_params["end_flag"] = True
+
+ elif last_sender == "code_reviewer":
+ code_review = selected[0].content
+ cur_code = environment.rule_params["code"]
+ selected[0].content = f":\n\n{cur_code}\n\n{code_review}"
+ feedback = environment.rule_params["feedback"]
+ f_dict = json.loads(feedback)
+ if f_dict["is_passing"]:
+ environment.rule_params["end_flag"] = True
+
+ return selected
\ No newline at end of file
diff --git a/agentverse/environments/simulation_env/rules/selector/sde_team_given_tests.py b/agentverse/environments/simulation_env/rules/selector/sde_team_given_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..eca6c04b92edcfea7749cbb2d7f3378e208411e2
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/selector/sde_team_given_tests.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from agentverse.message import Message
+
+from . import selector_registry as SelectorRegistry
+from .base import BaseSelector
+
+import json
+import re
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+def extract(content: str, keyword: str):
+ result = ""
+ flag = False
+ for line in content.split('\n'):
+ if line.strip().startswith(keyword):
+ flag = True
+ continue
+ if flag:
+ result += line
+ result += "\n"
+ return result
+
+
+@SelectorRegistry.register("sde_team_given_tests")
+class SdeTeamGivenTestsSelector(BaseSelector):
+ def select_message(self, environment: BaseEnvironment, messages: List[Message]) -> List[Message]:
+ last_sender = environment.last_messages[0].sender
+ selected = messages
+
+ if last_sender == "code_writer":
+ cur_code = extract(selected[0].content, ":")
+ environment.rule_params["code"] = cur_code
+ selected[0].content = f":\n{cur_code}"
+
+ elif last_sender == "code_tester":
+
+ from .code_api import execute_unit_tests
+ feedback = execute_unit_tests(environment.rule_params["code"], eval(environment.unit_tests))
+ environment.rule_params["feedback"] = feedback
+ selected[0].content = f":\n{feedback}"
+
+ f_dict = json.loads(feedback)
+ if f_dict["is_passing"]:
+ environment.rule_params["end_flag"] = True
+
+ elif last_sender == "code_reviewer":
+ code_review = selected[0].content
+ cur_code = environment.rule_params["code"]
+ selected[0].content = f"{code_review}"
+
+ return selected
\ No newline at end of file
diff --git a/agentverse/environments/simulation_env/rules/updater/__init__.py b/agentverse/environments/simulation_env/rules/updater/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..34f672f8ce0cbbce488b50f2ef919dc01b7cf26d
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/__init__.py
@@ -0,0 +1,9 @@
+from agentverse.registry import Registry
+
+updater_registry = Registry(name="UpdaterRegistry")
+
+from .base import BaseUpdater
+from .basic import BasicUpdater
+from .classroom import ClassroomUpdater
+from .sde_team import SdeTeamUpdater
+from .pokemon import PokemonUpdater
diff --git a/agentverse/environments/simulation_env/rules/updater/base.py b/agentverse/environments/simulation_env/rules/updater/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..abd21287557fffe78df2d016fc5219eebc236085
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/base.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from pydantic import BaseModel
+
+# from agentverse.agents import Agent
+from abc import abstractmethod
+
+from . import updater_registry as UpdaterRegistry
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@UpdaterRegistry.register("base")
+class BaseUpdater(BaseModel):
+ """
+ The base class of updater class.
+ """
+
+ @abstractmethod
+ def update_memory(self, environment: BaseEnvironment):
+ pass
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/simulation_env/rules/updater/basic.py b/agentverse/environments/simulation_env/rules/updater/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..122258f8d1359e4443ea7b72a5d19532bb6afb7b
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/basic.py
@@ -0,0 +1,75 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from . import updater_registry as UpdaterRegistry
+from .base import BaseUpdater
+from agentverse.message import Message
+from agentverse.logging import get_logger
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+ from agentverse.agents import BaseAgent
+
+logger = get_logger()
+
+
+@UpdaterRegistry.register("basic")
+class BasicUpdater(BaseUpdater):
+ """
+ The basic version of updater.
+ The messages will be seen by all the receiver specified in the message.
+ """
+
+ def update_memory(self, environment: BaseEnvironment):
+ added = False
+ for message in environment.last_messages:
+ if len(message.tool_response) > 0:
+ self.add_tool_response(
+ message.sender, environment.agents, message.tool_response
+ )
+ if message.content == "":
+ continue
+ added |= self.add_message_to_all_agents(environment.agents, message)
+ # If no one speaks in this turn. Add an empty message to all agents
+ if not added:
+ for agent in environment.agents:
+ agent.add_message_to_memory([Message(content="[Silence]")])
+
+ def add_tool_response(
+ self,
+ name: str,
+ agents: List[BaseAgent],
+ tool_response: List[str],
+ ):
+ for agent in agents:
+ if agent.name != name:
+ continue
+ if agent.tool_memory is not None:
+ agent.tool_memory.add_message(tool_response)
+ break
+
+ def add_message_to_all_agents(
+ self, agents: List[BaseAgent], message: Message
+ ) -> bool:
+ if "all" in message.receiver:
+ # If receiver is all, then add the message to all agents
+ for agent in agents:
+ agent.add_message_to_memory([message])
+ return True
+ else:
+ # If receiver is not all, then add the message to the specified agents
+ receiver_set = message.receiver
+ for agent in agents:
+ if agent.name in receiver_set:
+ agent.add_message_to_memory([message])
+ receiver_set.remove(agent.name)
+ if len(receiver_set) > 0:
+ missing_receiver = ", ".join(list(receiver_set))
+ # raise ValueError(
+ # "Receiver {} not found. Message discarded".format(missing_receiver)
+ # )
+ logger.warn(
+ "Receiver {} not found. Message discarded".format(missing_receiver)
+ )
+ return True
diff --git a/agentverse/environments/simulation_env/rules/updater/classroom.py b/agentverse/environments/simulation_env/rules/updater/classroom.py
new file mode 100644
index 0000000000000000000000000000000000000000..69afc5f0537d8d93219b84a60ca26c15114a3827
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/classroom.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from . import updater_registry as UpdaterRegistry
+from .basic import BasicUpdater
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@UpdaterRegistry.register("classroom")
+class ClassroomUpdater(BasicUpdater):
+ def update_memory(self, environment: BaseEnvironment):
+ added = False
+ for message in environment.last_messages:
+ if len(message.tool_response) > 0:
+ self.add_tool_response(
+ message.sender, environment.agents, message.tool_response
+ )
+ if message.content == "":
+ continue
+ added |= self.add_message_to_all_agents(environment.agents, message)
+ # If no one speaks in this turn. Add an empty message to all agents
+ if not added:
+ for agent in environment.agents:
+ agent.add_message_to_memory([Message(content="[Silence]")])
+ if environment.rule_params.get("is_grouped", False):
+ # When discussing, telling the professor that the group is discussing
+ environment.agents[0].add_message_to_memory(
+ [Message(content="[Discussing]")]
+ )
diff --git a/agentverse/environments/simulation_env/rules/updater/pokemon.py b/agentverse/environments/simulation_env/rules/updater/pokemon.py
new file mode 100644
index 0000000000000000000000000000000000000000..c292f2a9b2d0ba17d50f6967900c632547ef472c
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/pokemon.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+import json
+from copy import deepcopy
+
+from . import updater_registry as UpdaterRegistry
+from .basic import BasicUpdater
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.environments import PokemonEnvironment
+
+
+@UpdaterRegistry.register("pokemon")
+class PokemonUpdater(BasicUpdater):
+ def update_memory(self, environment: PokemonEnvironment):
+ for message in environment.last_messages:
+ if message.content == "":
+ continue
+ message = deepcopy(message)
+ try:
+ message.content = json.loads(message.content)
+ except json.decoder.JSONDecodeError:
+ continue
+ if message.content["action"] == "Speak":
+ message.content = message.content["text"]
+ elif message.content["action"] == "MoveTo":
+ if message.content["to"] in environment.locations_to_agents:
+ try:
+ orig_location = environment.get_agent_to_location()[
+ message.sender
+ ]
+ environment.locations_to_agents[orig_location].remove(
+ message.sender
+ )
+ except:
+ continue
+ message.content = f"[MoveTo] {message.content['to']}"
+ else:
+ message.content = f"[{message.content['action']}]"
+ self.add_message_to_all_agents(environment.agents, message)
diff --git a/agentverse/environments/simulation_env/rules/updater/sde_team.py b/agentverse/environments/simulation_env/rules/updater/sde_team.py
new file mode 100644
index 0000000000000000000000000000000000000000..049bae41386139da5621cfe85a45269b19c85567
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/updater/sde_team.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from . import updater_registry as UpdaterRegistry
+from .base import BaseUpdater
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+ from agentverse.agents import BaseAgent
+
+
+@UpdaterRegistry.register("sde_team")
+class SdeTeamUpdater(BaseUpdater):
+ """
+ The basic version of updater.
+ The messages will be seen by all the receiver specified in the message.
+ """
+
+ def update_memory(self, environment: BaseEnvironment):
+ added = False
+ for message in environment.last_messages:
+ if message.content == "":
+ continue
+ added |= self.add_message_to_all_agents(environment.agents, message)
+
+ def add_message_to_all_agents(
+ self, agents: List[BaseAgent], message: Message
+ ) -> bool:
+ if "all" in message.receiver:
+ # If receiver is all, then add the message to all agents
+ for agent in agents:
+ agent.add_message_to_memory([message])
+ return True
+ else:
+ # If receiver is not all, then add the message to the specified agents
+ receiver_set = message.receiver
+ for agent in agents:
+ if agent.name in receiver_set:
+ agent.add_message_to_memory([message])
+ receiver_set.remove(agent.name)
+ if len(receiver_set) > 0:
+ missing_receiver = ", ".join(list(receiver_set))
+ raise ValueError(
+ "Receiver {} not found. Message discarded".format(missing_receiver)
+ )
+ return True
diff --git a/agentverse/environments/simulation_env/rules/visibility/__init__.py b/agentverse/environments/simulation_env/rules/visibility/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f03700ce802e8f2eafd5c3f4188e1156c4454e0
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/__init__.py
@@ -0,0 +1,13 @@
+from typing import Dict
+
+from agentverse.registry import Registry
+
+visibility_registry = Registry(name="VisibilityRegistry")
+
+from .base import BaseVisibility
+from .all import AllVisibility
+from .classroom import ClassroomVisibility
+from .oneself import OneselfVisibility
+from .prisoner import PrisonerVisibility
+from .sde_team import SdeTeamVisibility
+from .pokemon import PokemonVisibility
diff --git a/agentverse/environments/simulation_env/rules/visibility/all.py b/agentverse/environments/simulation_env/rules/visibility/all.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0f6fc98aa451620beb9adafe2dd211eb6dc3fe2
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/all.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@VisibilityRegistry.register("all")
+class AllVisibility(BaseVisibility):
+ """All the messages can be seen by all the agents"""
+
+ def update_visible_agents(self, environment: BaseEnvironment):
+ pass
diff --git a/agentverse/environments/simulation_env/rules/visibility/base.py b/agentverse/environments/simulation_env/rules/visibility/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e5da006e573cd930a7cd83c81ae934426c115b57
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/base.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING, Any
+
+from pydantic import BaseModel
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+class BaseVisibility(BaseModel):
+ @abstractmethod
+ def update_visible_agents(self, environment: BaseEnvironment):
+ """Update the set of visible agents for the agent"""
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/simulation_env/rules/visibility/classroom.py b/agentverse/environments/simulation_env/rules/visibility/classroom.py
new file mode 100644
index 0000000000000000000000000000000000000000..140e82500a624311e5c8453863c86e327d198144
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/classroom.py
@@ -0,0 +1,84 @@
+from __future__ import annotations
+
+import random
+from typing import TYPE_CHECKING, Any, List, Union
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@VisibilityRegistry.register("classroom")
+class ClassroomVisibility(BaseVisibility):
+ """
+ Visibility function for classroom, supports group discussion.
+
+ Args:
+ student_per_group:
+ The number of students per group.
+ num_discussion_turn:
+ The number of turns for group discussion.
+ grouping:
+ The grouping information. If it is a string, then it should be a
+ grouping method, options are ["random", "sequential"]. If it is a
+ list of list of int, then it should be the grouping information.
+ """
+
+ grouping: Union[str, List[List[int]]]
+ student_per_group: int = 4
+ num_discussion_turn: int = 5
+ current_turn: int = 0
+
+ def update_visible_agents(self, environment: BaseEnvironment):
+ # We turn on grouping mode when the professor launches a group discussion
+ if len(environment.last_messages) == 1 and environment.last_messages[
+ 0
+ ].content.startswith("[GroupDiscuss]"):
+ environment.rule_params["is_grouped"] = True
+ # We randomly group the students
+ environment.rule_params["groups"] = self.group_students(environment)
+ # Update the receiver for each agent
+ self.update_receiver(environment)
+ else:
+ # If now in grouping mode, then we check if the group discussion is over
+ if environment.rule_params.get("is_grouped", False):
+ self.current_turn += 1
+ if self.current_turn >= self.num_discussion_turn:
+ self.reset()
+ environment.rule_params["is_grouped"] = False
+ environment.rule_params["is_grouped_ended"] = True
+ self.update_receiver(environment, reset=True)
+
+ def group_students(self, environment: BaseEnvironment) -> List[List[int]]:
+ if isinstance(self.grouping, str):
+ student_index = list(range(1, len(environment.agents)))
+ result = []
+ if self.grouping == "random":
+ random.shuffle(student_index)
+ for i in range(0, len(student_index), self.student_per_group):
+ result.append(student_index[i : i + self.student_per_group])
+ elif self.grouping == "sequential":
+ for i in range(0, len(student_index), self.student_per_group):
+ result.append(student_index[i : i + self.student_per_group])
+ else:
+ raise ValueError(f"Unsupported grouping method {self.grouping}")
+ return result
+ else:
+ # If the grouping information is provided, then we use it directly
+ return self.grouping
+
+ def update_receiver(self, environment: BaseEnvironment, reset=False):
+ if reset:
+ for agent in environment.agents:
+ agent.set_receiver(set({"all"}))
+ else:
+ groups = environment.rule_params["groups"]
+ for group in groups:
+ group_name = set({environment.agents[i].name for i in group})
+ for agent_id in group:
+ environment.agents[agent_id].set_receiver(group_name)
+
+ def reset(self):
+ self.current_turn = 0
diff --git a/agentverse/environments/simulation_env/rules/visibility/oneself.py b/agentverse/environments/simulation_env/rules/visibility/oneself.py
new file mode 100644
index 0000000000000000000000000000000000000000..826ade24c972993edbe02223254f41e59c79bf90
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/oneself.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@VisibilityRegistry.register("oneself")
+class OneselfVisibility(BaseVisibility):
+ """Only the agent itself can see the message"""
+
+ def update_visible_agents(self, environment: BaseEnvironment):
+ for agent in environment.agents:
+ agent.set_receiver(set({agent.name}))
diff --git a/agentverse/environments/simulation_env/rules/visibility/pokemon.py b/agentverse/environments/simulation_env/rules/visibility/pokemon.py
new file mode 100644
index 0000000000000000000000000000000000000000..355f103b23e17df5e2549d25130f4de0110082ba
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/pokemon.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import PokemonEnvironment
+
+
+@VisibilityRegistry.register("pokemon")
+class PokemonVisibility(BaseVisibility):
+ """Visibility module for Pokemon environment"""
+
+ def update_visible_agents(self, environment: PokemonEnvironment):
+ for agent in environment.agents:
+ agent_to_location = environment.get_agent_to_location()
+ try:
+ location = agent_to_location[agent.name]
+ except KeyError:
+ # Agent is on the way to a location
+ continue
+ agents_in_same_loc = environment.locations_to_agents[location]
+ agent.set_receiver(agents_in_same_loc)
diff --git a/agentverse/environments/simulation_env/rules/visibility/prisoner.py b/agentverse/environments/simulation_env/rules/visibility/prisoner.py
new file mode 100644
index 0000000000000000000000000000000000000000..c21217312bed0ffd447eeaa115e7eb28d53c680b
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/prisoner.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+import random
+from typing import TYPE_CHECKING, Any, List, Union
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@VisibilityRegistry.register("prisoner")
+class PrisonerVisibility(BaseVisibility):
+ """
+ Visibility function for classroom, supports group discussion.
+
+ Args:
+ student_per_group:
+ The number of students per group.
+ num_discussion_turn:
+ The number of turns for group discussion.
+ grouping:
+ The grouping information. If it is a string, then it should be a
+ grouping method, options are ["random", "sequential"]. If it is a
+ list of list of int, then it should be the grouping information.
+ """
+
+ current_turn: int = 0
+
+ def update_visible_agents(self, environment: BaseEnvironment):
+ self.update_receiver(environment, reset=False)
+
+ def update_receiver(self, environment: BaseEnvironment, reset=False):
+ if reset:
+ for agent in environment.agents:
+ agent.set_receiver(["all"])
+ else:
+ # 0:police 1: prisoner1 2: prisoner2
+ # environment.agents[0].set_receiver({"Police", "Suspect1", "Suspect2"})
+ # environment.agents[1].set_receiver({"Police", "Suspect1"})
+ # environment.agents[2].set_receiver({"Police", "Suspect2"})
+
+ # we update receiver in environment
+ pass
+
+ def reset(self):
+ self.current_turn = 0
diff --git a/agentverse/environments/simulation_env/rules/visibility/sde_team.py b/agentverse/environments/simulation_env/rules/visibility/sde_team.py
new file mode 100644
index 0000000000000000000000000000000000000000..be56a2fadc1daf905b7c848b370b1a51ed9d54f7
--- /dev/null
+++ b/agentverse/environments/simulation_env/rules/visibility/sde_team.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+import random
+from typing import TYPE_CHECKING, Any, List, Union
+
+from . import visibility_registry as VisibilityRegistry
+from .base import BaseVisibility
+
+if TYPE_CHECKING:
+ from agentverse.environments import BaseEnvironment
+
+
+@VisibilityRegistry.register("sde_team")
+class SdeTeamVisibility(BaseVisibility):
+ """
+ Visibility function for code problem. No need to change visibility.
+
+ """
+
+ def update_visible_agents(self, environment: BaseEnvironment):
+ return
+
+ def reset(self):
+ return
\ No newline at end of file
diff --git a/agentverse/environments/simulation_env/sde_team.py b/agentverse/environments/simulation_env/sde_team.py
new file mode 100644
index 0000000000000000000000000000000000000000..325c1eac08a8566079e7047411ca76d3e44b7de9
--- /dev/null
+++ b/agentverse/environments/simulation_env/sde_team.py
@@ -0,0 +1,137 @@
+import asyncio
+import logging
+from typing import Any, Dict, List
+import json
+
+from agentverse.agents.simulation_agent.conversation import BaseAgent
+
+# from agentverse.environments.simulation_env.rules.base import Rule
+from agentverse.environments.simulation_env.rules.base import SimulationRule as Rule
+from agentverse.message import Message
+
+from .. import env_registry as EnvironmentRegistry
+from ..base import BaseEnvironment
+
+from agentverse.initialization import load_tools
+
+
+@EnvironmentRegistry.register("sde_team")
+class SdeTeamEnvironment(BaseEnvironment):
+ """
+ A basic environment implementing the logic of conversation to craft code.
+
+ Args:
+ agents: List of agents
+ rule: Rule for the environment
+ max_turns: Maximum number of turns
+ cnt_turn: Current turn number
+ last_messages: Messages from last turn
+ rule_params: Variables set by the rule
+ """
+
+ agents: List[BaseAgent]
+ rule: Rule
+ max_turns: int = 10
+ cnt_turn: int = 0
+ last_messages: List[Message] = []
+ rule_params: Dict = {}
+ task_name: str = "test"
+
+ def __init__(self, rule, **kwargs):
+ rule_config = rule
+ order_config = rule_config.get("order", {"type": "sde_team"})
+ visibility_config = rule_config.get("visibility", {"type": "base"})
+ selector_config = rule_config.get("selector", {"type": "sde_team"})
+ updater_config = rule_config.get("updater", {"type": "sde_team"})
+ describer_config = rule_config.get("describer", {"type": "base"})
+ rule = Rule(
+ order_config,
+ visibility_config,
+ selector_config,
+ updater_config,
+ describer_config,
+ )
+ super().__init__(rule=rule, **kwargs)
+ self.rule_params["first_round"] = True
+ self.rule_params["end_flag"] = False
+
+ # # Test code
+ self.rule_params["name_to_tools"] = {
+ tool.name: tool
+ for tool in load_tools(
+ [
+ {
+ "tool_name": "code_interpreter",
+ "tool_url": "http://127.0.0.1:8079/tools/code_interpreter/",
+ }
+ ]
+ )
+ }
+ tool = self.rule_params["name_to_tools"]["execute_unit_tests"]
+ # print(type(tool))
+
+ # d = {
+ # "func_impl": "def f(x):\n\treturn x + 1",
+ # "tests": ["assert f(1) == 2"]
+ # }
+ # # input_str = json.dumps(d)
+ # json.loads(input_str)
+ # tool.run(input_str, verbose=True)
+ # exit()
+
+ async def step(self) -> List[Message]:
+ """Run one step of the environment"""
+
+ # Get the next agent index
+ agent_ids = self.rule.get_next_agent_idx(self) # order
+
+ # Generate current environment description
+ # env_descriptions = self.rule.get_env_description(self) # describer
+
+ # # Generate the next message
+ # messages = await asyncio.gather(
+ # *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids]
+ # ) # call chatgpt api
+
+ messages = await asyncio.gather(*[self.agents[i].astep("") for i in agent_ids])
+
+ # Track the messages to get the role of the sender
+ self.last_messages = messages
+
+ # Some rules will select certain messages from all the messages
+ selected_messages = self.rule.select_message(self, messages) # selector
+ self.last_messages = selected_messages
+ self.print_messages(selected_messages)
+
+ # Update the memory of the agents
+ self.rule.update_memory(self) # updater: update memory
+
+ # Update the set of visible agents for each agent
+ self.rule.update_visible_agents(self) # change receiver
+
+ self.cnt_turn += 1
+
+ return selected_messages
+
+ def print_messages(self, messages: List[Message]) -> None:
+ for message in messages:
+ if message is not None:
+ logging.info(f"{message.sender}: {message.content}")
+
+ def reset(self) -> None:
+ """Reset the environment"""
+ self.cnt_turn = 0
+ self.rule.reset()
+ for agent in self.agents:
+ agent.reset()
+
+ def is_done(self) -> bool:
+ """Check if the environment is done"""
+ if self.cnt_turn >= self.max_turns or self.rule_params["end_flag"]:
+ # with open("record_human_eval.txt", "a") as f:
+ # wd = dict()
+ # wd['task_id'] = self.task_name
+ # wd['code'] = self.rule_params['code']
+ # f.write(json.dumps(wd))
+ return True
+ return False
diff --git a/agentverse/environments/simulation_env/sde_team_given_tests.py b/agentverse/environments/simulation_env/sde_team_given_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdef4a86338f9baa806988c4575215fcd6f9d24b
--- /dev/null
+++ b/agentverse/environments/simulation_env/sde_team_given_tests.py
@@ -0,0 +1,128 @@
+import asyncio
+import logging
+from typing import Any, Dict, List
+import json
+
+from agentverse.agents.simulation_agent.conversation import BaseAgent
+
+# from agentverse.environments.simulation_env.rules.base import Rule
+from agentverse.environments.simulation_env.rules.base import SimulationRule as Rule
+from agentverse.message import Message
+
+from .. import env_registry as EnvironmentRegistry
+from ..base import BaseEnvironment
+
+from agentverse.initialization import load_tools
+
+
+@EnvironmentRegistry.register("sde_team_given_tests")
+class SdeTeamGivenTestsEnvironment(BaseEnvironment):
+ """
+ A basic environment implementing the logic of conversation to craft code.
+
+ Args:
+ agents: List of agents
+ rule: Rule for the environment
+ max_turns: Maximum number of turns
+ cnt_turn: Current turn number
+ last_messages: Messages from last turn
+ rule_params: Variables set by the rule
+ """
+
+ agents: List[BaseAgent]
+ rule: Rule
+ max_turns: int = 10
+ cnt_turn: int = 0
+ last_messages: List[Message] = []
+ rule_params: Dict = {}
+ unit_tests: str = ""
+ # # variables for experiment
+ # task_name: str = "test"
+ # experiment_name: str = ""
+
+ def __init__(self, rule, **kwargs):
+ rule_config = rule
+ order_config = rule_config.get("order", {"type": "sde_team_given_tests"})
+ visibility_config = rule_config.get("visibility", {"type": "base"})
+ selector_config = rule_config.get("selector", {"type": "sde_team_given_tests"})
+ updater_config = rule_config.get("updater", {"type": "sde_team"})
+ describer_config = rule_config.get("describer", {"type": "base"})
+ rule = Rule(
+ order_config,
+ visibility_config,
+ selector_config,
+ updater_config,
+ describer_config,
+ )
+ super().__init__(rule=rule, **kwargs)
+ self.rule_params["first_round"] = True
+ self.rule_params["end_flag"] = False
+
+ # # Set up logging for experiment
+ # filename = self.task_name.replace("/", "_")
+ # import os
+ # import os.path
+ # if not os.path.exists(f"human_eval_experiments/{self.experiment_name}/log"):
+ # os.makedirs(f"human_eval_experiments/{self.experiment_name}/log")
+ # file_handler = logging.FileHandler(f"human_eval_experiments/{self.experiment_name}/log/{filename}.txt")
+ # logging.getLogger().addHandler(file_handler)
+
+ async def step(self) -> List[Message]:
+ """Run one step of the environment"""
+
+ # Get the next agent index
+ agent_ids = self.rule.get_next_agent_idx(self) # order
+
+ # Generate current environment description
+ # env_descriptions = self.rule.get_env_description(self) # describer
+
+ # # Generate the next message
+ # messages = await asyncio.gather(
+ # *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids]
+ # ) # call chatgpt api
+
+ messages = await asyncio.gather(*[self.agents[i].astep("") for i in agent_ids])
+
+ # Track the messages to get the role of the sender
+ self.last_messages = messages
+
+ # Some rules will select certain messages from all the messages
+ selected_messages = self.rule.select_message(self, messages) # selector
+ self.last_messages = selected_messages
+ self.print_messages(selected_messages)
+
+ # Update the memory of the agents
+ self.rule.update_memory(self) # updater: update memory
+
+ # Update the set of visible agents for each agent
+ self.rule.update_visible_agents(self) # change receiver
+
+ self.cnt_turn += 1
+
+ return selected_messages
+
+ def print_messages(self, messages: List[Message]) -> None:
+ for message in messages:
+ if message is not None:
+ logging.info(f"{message.sender}: {message.content}")
+
+ def reset(self) -> None:
+ """Reset the environment"""
+ self.cnt_turn = 0
+ self.rule.reset()
+ for agent in self.agents:
+ agent.reset()
+
+ def is_done(self) -> bool:
+ """Check if the environment is done"""
+ if self.cnt_turn >= self.max_turns or self.rule_params["end_flag"]:
+ # # Write to file for experiment
+ # with open(f"human_eval_experiments/{self.experiment_name}/record_human_eval_prediction.jsonl", "a") as f:
+ # wd = dict()
+ # wd['task_id'] = self.task_name
+ # wd['code'] = self.rule_params['code']
+ # # print(wd)
+ # f.write(json.dumps(wd) + "\n")
+ # logging.getLogger().handlers.pop()
+ return True
+ return False
diff --git a/agentverse/environments/tasksolving_env/basic.py b/agentverse/environments/tasksolving_env/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e4631a24907890d0ecbe704f7c81543c4b9fd98
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/basic.py
@@ -0,0 +1,144 @@
+import asyncio
+from enum import Enum
+from typing import Any, Dict, List, Tuple, Union
+
+from colorama import Fore
+
+from agentverse.environments import BaseEnvironment
+from agentverse.agents.base import BaseAgent
+from agentverse.logging import logger
+from agentverse.message import Message, SolverMessage, ExecutorMessage
+
+
+from .. import env_registry as EnvironmentRegistry
+
+from agentverse.environments.tasksolving_env.rules import TasksolvingRule
+
+
+@EnvironmentRegistry.register("task-basic")
+class BasicEnvironment(BaseEnvironment):
+ rule: TasksolvingRule
+ agents: Dict[Enum, Union[BaseAgent, List[BaseAgent]]] = None
+
+ task_description: str
+
+ cnt_turn: int = 0
+ max_turn: int = 10
+ success: bool = False
+
+ def __init__(self, **kwargs):
+ rule_config = kwargs.pop("rule", {})
+ role_assigner_config = rule_config.pop(
+ "role_assigner", {"type": "role_description"}
+ )
+ decision_maker_config = rule_config.pop("decision_maker", {"type": "vertical"})
+ executor_config = rule_config.pop("executor", {"type": "none"})
+ evaluator_config = rule_config.pop("evaluator", {"type": "basic"})
+ rule = TasksolvingRule(
+ role_assigner_config=role_assigner_config,
+ decision_maker_config=decision_maker_config,
+ executor_config=executor_config,
+ evaluator_config=evaluator_config,
+ )
+ super().__init__(rule=rule, **kwargs)
+
+ async def step(
+ self, advice: str = "No advice yet.", previous_plan: str = "No solution yet."
+ ) -> List[Message]:
+ result = ""
+ logs = []
+
+ logger.info(f"Loop Round {self.cnt_turn}")
+
+ # ================== EXPERT RECRUITMENT ==================
+ agents = self.rule.role_assign(
+ self.task_description, self.agents, self.cnt_turn, advice
+ )
+ description = "\n".join([agent.role_description for agent in agents])
+ logs.append({"module": "Role Assigner", "content": description})
+ logger.info("", f"Role Assignment:\n{description}", Fore.CYAN)
+ # ================== EXPERT RECRUITMENT ==================
+
+ # ================== DECISION MAKING ==================
+ plan: List[SolverMessage] = await self.rule.decision_making(
+ self.task_description, self.agents, previous_plan, advice
+ )
+ flatten_plan = "\n".join([p.content for p in plan])
+ logs.append({"module": "Decision Maker", "content": flatten_plan})
+ logger.info("", f"Decision Plan:\n{flatten_plan}", Fore.YELLOW)
+ # ================== DECISION MAKING ==================
+
+ # ================== EXECUTION ==================
+ result: List[ExecutorMessage] = await self.rule.execute(
+ self.task_description, self.agents, plan
+ )
+ flatten_result = "\n".join([r.content for r in result])
+ logs.append({"module": "Executor", "content": flatten_result})
+ logger.info("", f"Execution Result:", Fore.GREEN)
+ logger.info("", flatten_result, Fore.GREEN)
+ # ================== EXECUTION ==================
+
+ # ================== EVALUATION ==================
+ score, advice = self.rule.evaluate(
+ self.task_description, self.agents, plan, result
+ )
+ logs.append(
+ {
+ "agent": "evaluator",
+ "content": f"Evaluation result: Score: {score}\nAdvice: {advice}",
+ }
+ )
+ logger.info(
+ "", f"Evaluation result:\nScore: {score}\nAdvice: {advice}", Fore.YELLOW
+ )
+
+ if score is not None and (
+ (isinstance(score, bool) and score is True)
+ or (isinstance(score, (list, tuple)) and all([s >= 8 for s in score]))
+ ):
+ # TODO: 8 is an arbitrary threshold
+ logs.append({"agent": "system", "content": "Good score! Accept!"})
+ logger.info(
+ "", f"Good score! Accept! Final Result:\n{flatten_plan}", Fore.GREEN
+ )
+ self.success = True
+ else:
+ logs.append({"agent": "system", "content": "Bad score! Reject!"})
+ logger.info("", "Bad score! Reject!", Fore.RED)
+ self.cnt_turn += 1
+ return flatten_result, advice, flatten_plan, logs, self.success
+
+ def iter_agents(self):
+ for role, agent_or_agents in self.agents.items():
+ if isinstance(agent_or_agents, list):
+ for agent in agent_or_agents:
+ yield role, agent
+ else:
+ yield role, agent_or_agents
+
+ def get_spend(self):
+ total_spent = sum([agent.get_spend() for (_, agent) in self.iter_agents()])
+ return total_spent
+
+ def report_metrics(self) -> None:
+ logger.info("", "Agent spend:", Fore.GREEN)
+ for role, agent in self.iter_agents():
+ name = agent.name.split(":")[0]
+ logger.info(
+ "",
+ f"Agent (Role: {role}) {name}: {agent.get_spend_formatted()}",
+ Fore.GREEN,
+ )
+ logger.info("", f"Total spent: ${self.get_spend():.6f}", Fore.GREEN)
+
+ def is_done(self):
+ """Check if the environment is done"""
+ return self.cnt_turn >= self.max_turn or self.success
+
+ def set_task_description(self, task_description: str = ""):
+ self.task_description = task_description
+
+ def reset(self) -> None:
+ """Reset the environment"""
+ self.cnt_turn = 0
+ self.rule.reset()
diff --git a/agentverse/environments/tasksolving_env/rules/__init__.py b/agentverse/environments/tasksolving_env/rules/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c34f4b48e9eba0e0262f7a4e603a8a36f0e8c4d
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/__init__.py
@@ -0,0 +1,8 @@
+from .base import TasksolvingRule
+
+"""
+from .decision_maker import *
+from .evaluator import *
+from .executor import *
+from .role_assigner import *
+"""
diff --git a/agentverse/environments/tasksolving_env/rules/base.py b/agentverse/environments/tasksolving_env/rules/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e25c017c09df0d3ee7f9592897461274e0eba1b
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/base.py
@@ -0,0 +1,181 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union, Optional
+
+from agentverse.agents.base import BaseAgent
+from agentverse.utils import AGENT_TYPES
+from agentverse.environments.tasksolving_env.rules.decision_maker import (
+ BaseDecisionMaker,
+ decision_maker_registry,
+)
+from agentverse.environments.tasksolving_env.rules.evaluator import (
+ BaseEvaluator,
+ evaluator_registry,
+)
+from agentverse.environments.tasksolving_env.rules.executor import (
+ BaseExecutor,
+ executor_registry,
+)
+from agentverse.environments.tasksolving_env.rules.role_assigner import (
+ BaseRoleAssigner,
+ role_assigner_registry,
+)
+from agentverse.environments import BaseRule
+
+if TYPE_CHECKING:
+ from agentverse.message import SolverMessage, ExecutorMessage
+
+
+class TasksolvingRule(BaseRule):
+ role_assigner: BaseRoleAssigner
+ decision_maker: BaseDecisionMaker
+ executor: BaseExecutor
+ evaluator: BaseEvaluator
+
+ role_assign_only_once: bool = False
+ add_execution_result_to_critic: bool = False
+ add_execution_result_to_solver: bool = False
+
+ def __init__(
+ self,
+ role_assigner_config,
+ decision_maker_config,
+ executor_config,
+ evaluator_config,
+ *args,
+ **kwargs,
+ ):
+ def build_components(config: Dict, registry):
+ component_type = config.pop("type")
+ component = registry.build(component_type, **config)
+ return component
+
+ role_assigner = build_components(
+ role_assigner_config,
+ role_assigner_registry,
+ )
+ decision_maker = build_components(
+ decision_maker_config,
+ decision_maker_registry,
+ )
+ executor = build_components(executor_config, executor_registry)
+ evaluator = build_components(evaluator_config, evaluator_registry)
+ super().__init__(
+ role_assigner=role_assigner,
+ decision_maker=decision_maker,
+ executor=executor,
+ evaluator=evaluator,
+ *args,
+ **kwargs,
+ )
+
+ def role_assign(
+ self,
+ task_description: str,
+ agents: List[BaseAgent],
+ cnt_turn: int,
+ advice: str = "",
+ ) -> List[BaseAgent]:
+ """Assign roles to agents"""
+ if self.role_assign_only_once and cnt_turn > 0:
+ agents = [agents[AGENT_TYPES.SOLVER]] + agents[AGENT_TYPES.CRITIC]
+ else:
+ agents = self.role_assigner.step(
+ role_assigner=agents[AGENT_TYPES.ROLE_ASSIGNMENT],
+ group_members=[agents[AGENT_TYPES.SOLVER]] + agents[AGENT_TYPES.CRITIC],
+ advice=advice,
+ task_description=task_description,
+ )
+ if self.role_assign_only_once and cnt_turn == 0:
+ agents[AGENT_TYPES.SOLVER] = agents[0]
+ agents[AGENT_TYPES.CRITIC] = agents[1:]
+ return agents
+
+ async def decision_making(
+ self,
+ task_description: str,
+ agents: List[BaseAgent],
+ previous_plan: str,
+ advice: str = "No advice yet.",
+ ) -> List[SolverMessage]:
+ # TODO: plan should be string or a special type of object?
+
+ # dynamic
+ if "dynamic" in self.decision_maker.name:
+ plan = await self.decision_maker.astep(
+ agents=[agents[AGENT_TYPES.SOLVER], *agents[AGENT_TYPES.CRITIC]],
+ manager=agents[AGENT_TYPES.MANAGER],
+ task_description=task_description,
+ previous_plan=previous_plan,
+ advice=advice,
+ )
+ else:
+ plan = await self.decision_maker.astep(
+ agents=[agents[AGENT_TYPES.SOLVER], *agents[AGENT_TYPES.CRITIC]],
+ task_description=task_description,
+ previous_plan=previous_plan,
+ advice=advice,
+ )
+ return plan
+
+ async def execute(
+ self,
+ task_description: str,
+ agents: List[BaseAgent],
+ final_solution: List[SolverMessage],
+ ) -> Any:
+ """execution stage.
+ Use the executor to finish the task.
+ """
+
+ results = await self.executor.astep(
+ agents[AGENT_TYPES.EXECUTION], task_description, final_solution
+ )
+ if self.add_execution_result_to_critic:
+ for agent in agents[AGENT_TYPES.CRITIC]:
+ agent.add_message_to_memory(results)
+ if self.add_execution_result_to_solver:
+ agents[AGENT_TYPES.SOLVER].add_message_to_memory(results)
+ return results
+
+ def evaluate(
+ self,
+ task_description: str,
+ agents: List[BaseAgent],
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ ) -> Tuple[List[int], str]:
+ """evaluation stage."""
+ # if self.human_eval:
+ # print("This round, LLM gave the following result:")
+ # print(result)
+ # comprehensiveness = input("Please evaluate the comprehensiveness>> ")
+ # detailedness = input("Please evaluate the detailedness>> ")
+ # feasibility = input("Please evaluate the feasibility>> ")
+ # novelty = input("Please evaluate the novelty>> ")
+ # advice = input("Please give some advice>>")
+ # try:
+ # comprehensiveness = int(comprehensiveness)
+ # detailedness = int(detailedness)
+ # feasibility = int(feasibility)
+ # novelty = int(novelty)
+ # except ValueError:
+ # logger.error("Bad response from human evaluator!")
+ # return ([comprehensiveness, detailedness, feasibility, novelty], advice)
+ # else:
+ evaluation = self.evaluator.step(
+ agent=agents[AGENT_TYPES.EVALUATION],
+ solution=solution,
+ result=result,
+ task_description=task_description,
+ all_role_description=[
+ agents[AGENT_TYPES.SOLVER].role_description,
+ *[agent.role_description for agent in agents[AGENT_TYPES.CRITIC]],
+ ],
+ )
+ return evaluation.score, evaluation.advice
+
+ def reset(self) -> None:
+ self.role_assigner.reset()
+ self.decision_maker.reset()
+ self.executor.reset()
+ self.evaluator.reset()
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/__init__.py b/agentverse/environments/tasksolving_env/rules/decision_maker/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..439ea54ccefeb1dcc33447f8b4a95e1d0bdf2c76
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/__init__.py
@@ -0,0 +1,13 @@
+from agentverse.registry import Registry
+
+decision_maker_registry = Registry(name="DecisionMakerRegistry")
+
+from .base import BaseDecisionMaker, DummyDecisionMaker
+from .horizontal import HorizontalDecisionMaker
+from .vertical import VerticalDecisionMaker
+from .dynamic import DynamicDecisionMaker
+from .vertical_solver_first import VerticalSolverFirstDecisionMaker
+from .concurrent import ConcurrentDecisionMaker
+from .horizontal_tool import HorizontalToolDecisionMaker
+from .central import CentralDecisionMaker
+from .brainstorming import BrainstormingDecisionMaker
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/base.py b/agentverse/environments/tasksolving_env/rules/decision_maker/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b77962dc091b6c8de70d4578fcc95699d474353
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/base.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from agentverse.agents import BaseAgent
+
+from pydantic import BaseModel
+
+from abc import abstractmethod
+from agentverse.message import SolverMessage
+from . import decision_maker_registry
+
+
+class BaseDecisionMaker(BaseModel):
+ """
+ The base class of decision making class.
+ """
+
+ name: str = "base"
+
+ @abstractmethod
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ pass
+
+ def reset(self):
+ pass
+
+ def broadcast_messages(self, agents, messages) -> None:
+ for agent in agents:
+ agent.add_message_to_memory(messages)
+
+ def p2p_messages(self, agents, messages) -> None:
+ agents[0].add_message_to_memory(messages)
+ for message in messages:
+ for agent in agents[1:]:
+ if agent.name == message.sender:
+ agent.add_message_to_memory(messages)
+ break
+
+
+@decision_maker_registry.register("dummy")
+class DummyDecisionMaker(BaseDecisionMaker):
+ name: str = "dummy"
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ return [
+ SolverMessage(content=task_description, sender=self.name, sender_agent=self)
+ ]
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/brainstorming.py b/agentverse/environments/tasksolving_env/rules/decision_maker/brainstorming.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6db1a5f6a963dee1736aa7ad4af2310b43b3a51
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/brainstorming.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import logger
+
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.agents.base import BaseAgent
+ from agentverse.message import CriticMessage
+
+
+@decision_maker_registry.register("brainstorming")
+class BrainstormingDecisionMaker(BaseDecisionMaker):
+ """
+ Much like the horizontal decision maker, but with some twists:
+ (1) Solver acts as a summarizer, summarizing the discussion of this turn
+ (2) After summarizing, all the agents' memory are cleared, and replaced with
+ the summary (to avoid exceeding maximum context length of the model too fast)
+ """
+
+ name: str = "brainstorming"
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[str]:
+ if advice != "No advice yet.":
+ self.broadcast_messages(
+ agents, [Message(content=advice, sender="Evaluator")]
+ )
+ for agent in agents[1:]:
+ review: CriticMessage = await agent.astep(
+ previous_plan, advice, task_description
+ )
+ if review.content != "":
+ self.broadcast_messages(agents, [review])
+
+ logger.info("", "Reviews:", Fore.YELLOW)
+ logger.info(
+ "",
+ f"[{review.sender}]: {review.content}",
+ Fore.YELLOW,
+ )
+
+ result = agents[0].step(previous_plan, advice, task_description)
+ for agent in agents:
+ agent.memory.reset()
+ self.broadcast_messages(
+ agents,
+ [
+ Message(
+ content=result.content, sender="Summary From Previous Discussion"
+ )
+ ],
+ )
+ return [result]
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/central.py b/agentverse/environments/tasksolving_env/rules/decision_maker/central.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d7bf57029bcf2bbe4894da96e1d070bca2dd7da
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/central.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import typewriter_log, logger
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.agents import BaseAgent, SolverAgent, CriticAgent
+ from agentverse.message import SolverMessage
+
+
+@decision_maker_registry.register("central")
+class CentralDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a central manner.
+ """
+
+ name: str = "central"
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ if advice != "No advice yet.":
+ agents[1].add_message_to_memory(
+ [Message(content=advice, sender="Evaluator")]
+ )
+ result = await agents[1].astep(
+ previous_plan,
+ advice,
+ task_description,
+ roles=", ".join(
+ [
+ agent.role_description[0].lower() + agent.role_description[1:]
+ for agent in agents
+ ]
+ ),
+ )
+ agents[1].add_message_to_memory([result])
+ result = agents[0].step(
+ previous_plan, advice, task_description, chat_record=result.content
+ )
+ return [result]
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/concurrent.py b/agentverse/environments/tasksolving_env/rules/decision_maker/concurrent.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc34e00b75e6c0fa20d17d3387c13e66dfc9e060
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/concurrent.py
@@ -0,0 +1,80 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import typewriter_log, logger
+
+if TYPE_CHECKING:
+ from agentverse.agents import BaseAgent, SolverAgent, CriticAgent
+ from agentverse.message import Message, CriticMessage, SolverMessage
+
+
+@decision_maker_registry.register("concurrent")
+class ConcurrentDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a concurrent manner.
+ """
+
+ name: str = "concurrent"
+ max_inner_turns: int = 3
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ # Here we assume that the first agent is the solver.
+ # The rest of the agents are the reviewers.
+ last_reviews = []
+ for i in range(self.max_inner_turns):
+ reviews: List[CriticMessage] = await asyncio.gather(
+ *[
+ agent.astep(previous_plan, advice, task_description)
+ for agent in agents[1:]
+ ]
+ )
+ logger.info("", "Reviews:", Fore.YELLOW)
+ logger.info(
+ "",
+ "\n".join(
+ [f"[{review.sender}]: {review.content}" for review in reviews]
+ ),
+ Fore.YELLOW,
+ )
+ nonempty_reviews = []
+ for review in reviews:
+ if not review.is_agree and review.content != "":
+ nonempty_reviews.append(review)
+ self.broadcast_messages(agents[1:], nonempty_reviews)
+ if len(nonempty_reviews) == 0:
+ break
+ last_reviews = nonempty_reviews
+
+ agents[0].add_message_to_memory(last_reviews)
+ result = agents[0].step(previous_plan, advice, task_description)
+ # agents[0].add_message_to_memory([result])
+ self.broadcast_messages(agents, [result])
+ return [result]
+
+ def broadcast_messages(self, agents, messages) -> None:
+ for agent in agents:
+ agent.add_message_to_memory(messages)
+
+ def p2p_messages(self, agents, messages) -> None:
+ agents[0].add_message_to_memory(messages)
+ for message in messages:
+ for agent in agents[1:]:
+ if agent.name == message.sender:
+ agent.add_message_to_memory(messages)
+ break
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/dynamic.py b/agentverse/environments/tasksolving_env/rules/decision_maker/dynamic.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6b6d72feab1b2fec0776db545273d32dc6f1fb1
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/dynamic.py
@@ -0,0 +1,84 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import typewriter_log
+
+if TYPE_CHECKING:
+ from agentverse.agents.base import BaseAgent
+ from agentverse.message import Message
+
+
+@decision_maker_registry.register("dynamic")
+class DynamicDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a horizontal manner.
+ """
+
+ name: str = "dynamic"
+
+ ## To Do: implement dynamic
+ # def step(
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ manager: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ previous_sentence: str = "No any sentence yet.",
+ *args,
+ **kwargs,
+ ) -> List[str]:
+ # Speak simultaneously
+ # Manger select the optimial one as the current spoken sentence
+ reviews = list()
+ for i in range(len(agents)):
+ review = await asyncio.gather(
+ *[
+ agent.astep(previous_plan, advice, task_description)
+ for agent in agents[1:]
+ ]
+ )
+
+ # typewriter_log("Reviews:", Fore.YELLOW)
+ # typewriter_log(
+ # "\n".join(
+ # [
+ # f"[{review.sender_agent.role_description}]: {review.criticism}"
+ # for review in reviews
+ # ]
+ # ),
+ # Fore.YELLOW,
+ # )
+
+ previous_sentence = manager.step(
+ previous_plan, review, advice, task_description, previous_sentence
+ )
+ reviews.append(previous_sentence)
+
+ """
+ reviews = await asyncio.gather(
+ *[
+ agent.astep(previous_plan, advice, task_description)
+ for agent in agents[1:]
+ ]
+ )
+ """
+
+ nonempty_reviews = []
+ for review in reviews:
+ if not review.is_agree and review.content != "":
+ nonempty_reviews.append(review)
+ agents[0].add_message_to_memory(nonempty_reviews)
+
+ result = agents[0].step(previous_plan, advice, task_description)
+
+ return [result]
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal.py b/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal.py
new file mode 100644
index 0000000000000000000000000000000000000000..b2a8c57037513bb3d80c03a9b58661f7299ffd26
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal.py
@@ -0,0 +1,57 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import logger
+
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.agents.base import BaseAgent
+ from agentverse.message import CriticMessage
+
+
+@decision_maker_registry.register("horizontal")
+class HorizontalDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a horizontal manner.
+ """
+
+ name: str = "horizontal"
+
+ # def step(
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ **kwargs,
+ ) -> List[str]:
+ if advice != "No advice yet.":
+ self.broadcast_messages(
+ agents, [Message(content=advice, sender="Evaluator")]
+ )
+ for agent in agents[1:]:
+ review: CriticMessage = await agent.astep(
+ previous_plan, advice, task_description
+ )
+ if review.content != "":
+ self.broadcast_messages(agents, [review])
+
+ logger.info("", "Reviews:", Fore.YELLOW)
+ logger.info(
+ "",
+ f"[{review.sender}]: {review.content}",
+ Fore.YELLOW,
+ )
+
+ result = agents[0].step(previous_plan, advice, task_description)
+ return [result]
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal_tool.py b/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal_tool.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cea85eab38b8cea3fcb05c509a44f61d1040c86
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/horizontal_tool.py
@@ -0,0 +1,89 @@
+from __future__ import annotations
+import json
+import asyncio
+from copy import deepcopy
+from colorama import Fore
+from itertools import cycle
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import logger
+from agentverse.message import SolverMessage, Message
+
+if TYPE_CHECKING:
+ from agentverse.agents.base import BaseAgent
+ from agentverse.message import CriticMessage
+
+
+@decision_maker_registry.register("horizontal-tool")
+class HorizontalToolDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a horizontal manner.
+ """
+
+ name: str = "horizontal_tool"
+ tools: List[dict] = []
+ tool_names: List[str] = []
+ tool_config: str = None
+
+ def __init__(self, *args, **kwargs):
+ assert kwargs.get("tool_config", None) is not None
+ with open(kwargs.get("tool_config"), "r") as f:
+ tools_dict = json.load(f)
+ tools = tools_dict["tools_json"]
+ tool_names = [t["name"] for t in tools]
+ super().__init__(tools=tools, tool_names=tool_names, *args, **kwargs)
+
+ # def step(
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ **kwargs,
+ ) -> List[str]:
+ agents[0].memory.reset()
+ if advice != "No advice yet.":
+ self.broadcast_messages(
+ agents[1:], [Message(content=advice, sender="Evaluator")]
+ )
+ all_roles = "\n".join(
+ [f"{agent.name}: {agent.role_description}" for agent in agents[1:]]
+ )
+ end_flag = False
+ discussion_cnt = 0
+ for agent in cycle(agents[1:]):
+ discussion_cnt += 1
+ review: CriticMessage = await agent.astep(
+ previous_plan, advice, task_description, all_roles
+ )
+ if review.content.strip().endswith("[END]"):
+ review.content = review.content.strip().replace("[END]", "")
+ if discussion_cnt >= len(agents) - 1:
+ # Force all the agents to speak at least once.
+ end_flag = True
+ if review.content != "":
+ self.broadcast_messages(agents, [review])
+
+ logger.info("", "Reviews:", Fore.YELLOW)
+ logger.info(
+ "",
+ f"[{review.sender}]: {review.content}",
+ Fore.YELLOW,
+ )
+ if end_flag:
+ break
+
+ result: SolverMessage = agents[0].step(previous_plan, advice, task_description)
+ result_list = []
+ for res in result.content:
+ res_tmp = deepcopy(result)
+ res_tmp.content = " - ".join(res)
+ result_list.append(res_tmp)
+ return result_list
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/vertical.py b/agentverse/environments/tasksolving_env/rules/decision_maker/vertical.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8adf594d1b9324fe7faf5c06cf1c2377e800165
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/vertical.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import typewriter_log, logger
+
+if TYPE_CHECKING:
+ from agentverse.agents import BaseAgent, SolverAgent, CriticAgent
+ from agentverse.message import Message, CriticMessage, SolverMessage
+
+
+@decision_maker_registry.register("vertical")
+class VerticalDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a vertical manner.
+ """
+
+ name: str = "vertical"
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ # Here we assume that the first agent is the solver.
+ # The rest of the agents are the reviewers.
+ reviews: List[CriticMessage] = await asyncio.gather(
+ *[
+ agent.astep(previous_plan, advice, task_description)
+ for agent in agents[1:]
+ ]
+ )
+ logger.info("", "Reviews:", Fore.YELLOW)
+ logger.info(
+ "",
+ "\n".join([f"[{review.sender}]: {review.content}" for review in reviews]),
+ Fore.YELLOW,
+ )
+
+ nonempty_reviews = []
+ for review in reviews:
+ if not review.is_agree and review.content != "":
+ nonempty_reviews.append(review)
+ agents[0].add_message_to_memory(nonempty_reviews)
+ result = agents[0].step(previous_plan, advice, task_description)
+ agents[0].add_message_to_memory([result])
+ return [result]
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/decision_maker/vertical_solver_first.py b/agentverse/environments/tasksolving_env/rules/decision_maker/vertical_solver_first.py
new file mode 100644
index 0000000000000000000000000000000000000000..97114f45506f8bccaaf72e8b1869b68e7c1ae2ca
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/decision_maker/vertical_solver_first.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+import asyncio
+from colorama import Fore
+
+from typing import TYPE_CHECKING, List
+
+from . import decision_maker_registry
+from .base import BaseDecisionMaker
+from agentverse.logging import typewriter_log, logger
+from agentverse.message import Message
+
+if TYPE_CHECKING:
+ from agentverse.agents import BaseAgent, SolverAgent, CriticAgent
+ from agentverse.message import CriticMessage, SolverMessage
+
+
+@decision_maker_registry.register("vertical-solver-first")
+class VerticalSolverFirstDecisionMaker(BaseDecisionMaker):
+ """
+ Discuss in a vertical manner.
+ """
+
+ name: str = "vertical-sovler-first"
+ max_inner_turns: int = 3
+
+ async def astep(
+ self,
+ agents: List[BaseAgent],
+ task_description: str,
+ previous_plan: str = "No solution yet.",
+ advice: str = "No advice yet.",
+ *args,
+ **kwargs,
+ ) -> List[SolverMessage]:
+ # Here we assume that the first agent is the solver.
+ # The rest of the agents are the reviewers.
+ if advice != "No advice yet.":
+ self.broadcast_messages(
+ agents, [Message(content=advice, sender="Evaluator")]
+ )
+ previous_plan = agents[0].step(previous_plan, advice, task_description)
+ self.broadcast_messages(agents, [previous_plan])
+ logger.info("", f"Initial Plan:\n{previous_plan.content}", Fore.BLUE)
+ for i in range(self.max_inner_turns):
+ reviews: List[CriticMessage] = await asyncio.gather(
+ *[
+ agent.astep(previous_plan, advice, task_description)
+ for agent in agents[1:]
+ ]
+ )
+ logger.info(
+ "",
+ "Reviews:\n"
+ + "\n".join(
+ [f"[{review.sender}]: {review.content}" for review in reviews]
+ ),
+ Fore.YELLOW,
+ )
+
+ nonempty_reviews = []
+ for review in reviews:
+ if not review.is_agree and review.content != "":
+ nonempty_reviews.append(review)
+ if len(nonempty_reviews) == 0:
+ logger.info("", "Consensus Reached!.", Fore.GREEN)
+ break
+ self.broadcast_messages(agents, nonempty_reviews)
+ previous_plan = agents[0].step(previous_plan, advice, task_description)
+ logger.info("", f"Updated Plan:\n{previous_plan.content}", Fore.BLUE)
+ self.broadcast_messages(agents, [previous_plan])
+ result = previous_plan
+ return [result]
+
+ def broadcast_messages(self, agents, messages) -> None:
+ for agent in agents:
+ agent.add_message_to_memory(messages)
+
+ def p2p_messages(self, agents, messages) -> None:
+ agents[0].add_message_to_memory(messages)
+ for message in messages:
+ for agent in agents[1:]:
+ if agent.name == message.sender:
+ agent.add_message_to_memory(messages)
+ break
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/evaluator/__init__.py b/agentverse/environments/tasksolving_env/rules/evaluator/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..016e72c67bc445fe2fef58f1cca31aa6b7840831
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/evaluator/__init__.py
@@ -0,0 +1,6 @@
+from agentverse.registry import Registry
+
+evaluator_registry = Registry(name="EvaluatorRegistry")
+
+from .base import BaseEvaluator, NoneEvaluator
+from .basic import BasicEvaluator
diff --git a/agentverse/environments/tasksolving_env/rules/evaluator/base.py b/agentverse/environments/tasksolving_env/rules/evaluator/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3d72ad98d83c10ea259aa317ff0e778358c1764
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/evaluator/base.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING, List, Tuple
+
+from pydantic import BaseModel
+
+from agentverse.message import EvaluatorMessage
+
+if TYPE_CHECKING:
+ from agentverse.agents import EvaluatorAgent
+ from agentverse.message import EvaluatorMessage, SolverMessage, ExecutorMessage
+
+from . import evaluator_registry
+
+
+class BaseEvaluator(BaseModel):
+ """
+ The base class of execution.
+ """
+
+ @abstractmethod
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ pass
+
+ def reset(self):
+ pass
+
+
+@evaluator_registry.register("none")
+class NoneEvaluator(BaseEvaluator):
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ result = EvaluatorMessage(
+ score=0, advice="\n".join([r.content for r in result])
+ )
+ return result
+
+
+@evaluator_registry.register("dummy")
+class DummyEvaluator(BaseEvaluator):
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ result = EvaluatorMessage(score=1, advice="")
+ return result
+
+
+@evaluator_registry.register("dummy")
+class DummyEvaluator(BaseEvaluator):
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[str] | str,
+ result: List[str] | str,
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ result = EvaluatorMessage(
+ score=0, advice="\n".join([r.content for r in result])
+ )
+ return result
diff --git a/agentverse/environments/tasksolving_env/rules/evaluator/basic.py b/agentverse/environments/tasksolving_env/rules/evaluator/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7738fe21f980631f41de15142233bd711915b2b
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/evaluator/basic.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from . import evaluator_registry
+from .base import BaseEvaluator
+
+if TYPE_CHECKING:
+ from agentverse.agents import EvaluatorAgent
+ from agentverse.message import EvaluatorMessage, SolverMessage, ExecutorMessage
+
+
+@evaluator_registry.register("basic")
+class BasicEvaluator(BaseEvaluator):
+ cnt_agents: int = 0
+
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ flatten_solution = "\n".join([s.content for s in solution])
+ flatten_result = "\n".join([r.content for r in result])
+ flatten_all_role_description = "\n".join(all_role_description)
+ evaluation = agent.step(
+ flatten_solution,
+ flatten_result,
+ task_description,
+ flatten_all_role_description,
+ )
+ return evaluation
+
+
+@evaluator_registry.register("basic-message")
+class BasicEvaluator(BaseEvaluator):
+ cnt_agents: int = 0
+
+ def step(
+ self,
+ agent: EvaluatorAgent,
+ solution: List[SolverMessage],
+ result: List[ExecutorMessage],
+ task_description: str,
+ all_role_description: List[str],
+ *args,
+ **kwargs,
+ ) -> EvaluatorMessage:
+ flatten_solution = "\n".join([s.content for s in solution])
+ flatten_result = "\n".join([r.content for r in result])
+ flatten_all_role_description = "\n".join(all_role_description)
+ agent.add_message_to_memory(result)
+ evaluation = agent.step(
+ flatten_solution,
+ flatten_result,
+ task_description,
+ flatten_all_role_description,
+ )
+ agent.add_message_to_memory([evaluation])
+ return evaluation
diff --git a/agentverse/environments/tasksolving_env/rules/executor/__init__.py b/agentverse/environments/tasksolving_env/rules/executor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d354c027d9c2dd46368189871342ccaa308653cc
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/executor/__init__.py
@@ -0,0 +1,8 @@
+from agentverse.registry import Registry
+
+executor_registry = Registry(name="ExecutorRegistry")
+
+from .base import BaseExecutor, NoneExecutor
+from .code_test import CodeTestExecutor
+from .tool_using import ToolUsingExecutor
+from .coverage_test import CoverageTestExecutor
diff --git a/agentverse/environments/tasksolving_env/rules/executor/base.py b/agentverse/environments/tasksolving_env/rules/executor/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..a9cc6e7550c0ca74affd413b3efa0fbb9237aff6
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/executor/base.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING, List, Tuple, Any
+
+from pydantic import BaseModel
+
+from agentverse.agents import ExecutorAgent
+from agentverse.message import SolverMessage, ExecutorMessage
+
+from . import executor_registry
+
+
+class BaseExecutor(BaseModel):
+ """
+ The base class of execution.
+ """
+
+ def step(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> List[ExecutorMessage]:
+ pass
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[str],
+ *args,
+ **kwargs,
+ ) -> List[ExecutorMessage]:
+ pass
+
+ def reset(self):
+ pass
+
+
+@executor_registry.register("none")
+class NoneExecutor(BaseExecutor):
+ """
+ The base class of execution.
+ """
+
+ def step(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ return [ExecutorMessage(content="")]
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ return [ExecutorMessage(content="")]
+
+ def reset(self):
+ pass
+
+
+@executor_registry.register("dummy")
+class DummyExecutor(BaseExecutor):
+ """
+ The base class of execution.
+ """
+
+ def step(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ return [ExecutorMessage(content=s.content) for s in solution]
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ return [ExecutorMessage(content=s.content) for s in solution]
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/executor/code_test.py b/agentverse/environments/tasksolving_env/rules/executor/code_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..121aabc679b2bd53a92cdbfbe757d1b88075f123
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/executor/code_test.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import os
+import subprocess
+import multiprocessing
+from typing import TYPE_CHECKING, Any, List, Tuple
+
+from agentverse.logging import get_logger
+from agentverse.agents import ExecutorAgent
+from agentverse.message import ExecutorMessage, SolverMessage
+from agentverse.logging import logger
+
+from . import BaseExecutor, executor_registry
+
+
+def execute_command(command: str, result_list) -> str:
+ # TODO: make it more secure
+ result = subprocess.run(command, capture_output=True, shell=True, encoding="utf-8")
+ result_list.append(f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
+ # return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
+
+
+@executor_registry.register("code-test")
+class CodeTestExecutor(BaseExecutor):
+ has_test: dict = {}
+ timeout: int = 10
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ solution = solution[0].content
+ os.makedirs("tmp", exist_ok=True)
+ self.write_to_file("tmp/main.py", solution)
+ manager = multiprocessing.Manager()
+ result = manager.list()
+ if task_description not in self.has_test:
+ response = (await agent.astep(task_description, solution)).content
+ self.write_to_file(response["file_path"], response["code"])
+ self.has_test[task_description] = f"python {response['file_path']}"
+ p = multiprocessing.Process(
+ target=execute_command, args=(f"python {response['file_path']}", result)
+ )
+ p.start()
+ p.join(timeout=self.timeout + 1)
+ if p.is_alive():
+ p.kill()
+ # result = execute_command(f"python {response['file_path']}")
+ else:
+ # result = execute_command(self.has_test[task_description])
+ p = multiprocessing.Process(
+ target=execute_command, args=(self.has_test[task_description], result)
+ )
+ p.start()
+ p.join(timeout=self.timeout + 1)
+ if p.is_alive():
+ p.kill()
+ if not result:
+ result.append("Execution timed out.")
+ return [ExecutorMessage(content=result[0], sender="Code Tester")]
+
+ def step(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ solution = solution[0].content
+ os.makedirs("tmp", exist_ok=True)
+ self.write_to_file("tmp/main.py", solution)
+ manager = multiprocessing.Manager()
+ result = manager.list()
+ if task_description not in self.has_test:
+ response = agent.step(task_description, solution).content
+ self.write_to_file(response["file_path"], response["code"])
+ self.has_test[task_description] = f"python {response['file_path']}"
+ p = multiprocessing.Process(
+ target=execute_command, args=(f"python {response['file_path']}", result)
+ )
+ p.start()
+ p.join(timeout=self.timeout + 1)
+ if p.is_alive():
+ p.kill()
+ # result = execute_command(f"python {response['file_path']}")
+ else:
+ # result = execute_command(self.has_test[task_description])
+ p = multiprocessing.Process(
+ target=execute_command, args=(self.has_test[task_description], result)
+ )
+ p.start()
+ p.join(timeout=self.timeout + 1)
+ if p.is_alive():
+ p.kill()
+ if not result:
+ result.append("Execution timed out.")
+ return [ExecutorMessage(content=result[0], sender="Code Tester")]
+
+ def write_to_file(self, file_name, file_content):
+ # TODO: generalize this method to a common tool
+ try:
+ with open(file_name, "w") as f:
+ f.write(file_content)
+ f.flush()
+ except:
+ logger.error(f"Failed to write to {file_name}")
diff --git a/agentverse/environments/tasksolving_env/rules/executor/coverage_test.py b/agentverse/environments/tasksolving_env/rules/executor/coverage_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..37c3073ba9fb4b256e7f30c532488cc1e557de77
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/executor/coverage_test.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import os
+import subprocess
+import multiprocessing
+from typing import TYPE_CHECKING, Any, List, Tuple
+
+from agentverse.agents import ExecutorAgent
+from agentverse.logging import logger
+from agentverse.message import ExecutorMessage, SolverMessage
+
+from . import BaseExecutor, executor_registry
+
+
+def execute_command(command: str, result_list) -> str:
+ # TODO: make it more secure
+ result = subprocess.run(command, capture_output=True, shell=True, encoding="utf-8")
+ result_list.append(f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}")
+ # return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
+
+
+@executor_registry.register("coverage-test")
+class CoverageTestExecutor(BaseExecutor):
+ def step(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ from scripts.evaluate_commongen import scoring
+
+ coverage, missing_tokens = scoring(
+ [s.content for s in solution], [task_description]
+ )
+ if len(missing_tokens[0]) == 0:
+ missing_tokens = "No missing tokens."
+ else:
+ missing_tokens = ", ".join(missing_tokens[0])
+ result = f"Coverage: {coverage*100:.2f}%\nMissing Tokens: {missing_tokens}"
+ return [ExecutorMessage(content=result)]
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ solution: List[SolverMessage],
+ *args,
+ **kwargs,
+ ) -> Any:
+ from scripts.evaluate_commongen import scoring
+
+ coverage, missing_tokens = scoring(
+ [s.content for s in solution], [task_description]
+ )
+ if len(missing_tokens[0]) == 0:
+ missing_tokens = "No missing tokens."
+ else:
+ missing_tokens = ", ".join(missing_tokens[0])
+ result = f"Coverage: {coverage*100:.2f}%\nMissing Tokens: {missing_tokens}"
+ return [ExecutorMessage(content=result)]
diff --git a/agentverse/environments/tasksolving_env/rules/executor/tool_using.py b/agentverse/environments/tasksolving_env/rules/executor/tool_using.py
new file mode 100644
index 0000000000000000000000000000000000000000..667de12f194dd80f94082e6d936d67302b3654cf
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/executor/tool_using.py
@@ -0,0 +1,315 @@
+import json
+import ast
+import openai
+from string import Template
+from colorama import Fore
+from aiohttp import ClientSession
+from copy import deepcopy
+from typing import TYPE_CHECKING, Any, List, Tuple
+
+from agentverse.agents import ExecutorAgent
+from agentverse.message import Message, ExecutorMessage, SolverMessage
+from agentverse.logging import logger
+
+from . import BaseExecutor, executor_registry
+import asyncio
+
+
+url = "http://127.0.0.1:8080"
+# url = "http://8.217.97.110:8080"
+
+SUMMARIZE_PROMPT = """Here is the text gathered from a webpage, and a question you need to answer from the webpage.
+-- Webpage --
+${webpage}
+-- Question --
+${question}
+
+Now summarize the webpage to answer the question. If the question cannot be answer from the webpage, return the summarization of the webpage."""
+
+
+@executor_registry.register("tool-using")
+class ToolUsingExecutor(BaseExecutor):
+ num_agents: int = 3
+ max_tool_call_times: int = 10
+ tools: List[dict] = []
+ tool_names: List[str] = []
+ tool_config: str = None
+ cookies: dict = {}
+ tool_retrieval: bool = False
+ real_execution_agents: dict = {}
+ agent_names: List[str] = []
+ # tool_description: str
+
+ def __init__(self, *args, **kwargs):
+ assert kwargs.get("tool_config", None) is not None
+ with open(kwargs.get("tool_config"), "r") as f:
+ tools_dict = json.load(f)
+ tools = tools_dict["tools_json"]
+ tool_names = [t["name"] for t in tools]
+
+ # For each tool, we manually add a "thought" argument to achieve
+ # chain-of-thought in OpenAI's function call.
+ for t in tools:
+ properties = t["parameters"]["properties"]
+ thought = {
+ "thought": {
+ "type": "string",
+ "description": "Your internal reasoning and thoughts on the task, and how you plan to solve it based on the current attempts.",
+ }
+ }
+ thought.update(properties)
+ t["parameters"]["properties"] = thought
+ t["parameters"]["required"].insert(0, "thought")
+ super().__init__(
+ tools=tools,
+ tool_names=tool_names,
+ # tool_description=tool_description,
+ *args,
+ **kwargs,
+ )
+
+ async def astep(
+ self,
+ agent: ExecutorAgent,
+ task_description: str,
+ plans: List[SolverMessage],
+ *args,
+ **kwargs,
+ ):
+ plan_this_turn = {}
+ agent_name_this_turn = []
+ for i in range(len(plans)):
+ name = plans[i].content.split("-")[0].strip()
+ if name not in self.real_execution_agents:
+ self.real_execution_agents[name] = deepcopy(agent)
+ self.real_execution_agents[name].name = name
+ self.agent_names.append(name)
+ plan_this_turn[name] = plans[i].content.split("-")[1].strip()
+ agent_name_this_turn.append(name)
+ # agents = [deepcopy(agent) for _ in range(len(plans))]
+
+ if self.tool_retrieval:
+ # We retrieve 5 related tools for each agent
+ tools_and_cookies = await asyncio.gather(
+ *[
+ self.retrieve_tools(plan_this_turn[name], self.tools)
+ for name in agent_name_this_turn
+ ]
+ )
+ tools = {
+ name: t[0] for name, t in zip(agent_name_this_turn, tools_and_cookies)
+ }
+ cookies = {
+ name: t[1] for name, t in zip(agent_name_this_turn, tools_and_cookies)
+ }
+ self.update_cookies(cookies)
+ else:
+ # We just use the tools that are provided in the config file
+ tools = {name: self.tools for name in agent_name_this_turn}
+
+ # Record the indices of agents that have finished their tasks
+ # so that they will not be called again
+ finished_agent_names = set()
+ # result = ["" for _ in range(len(plan_this_turn))]
+ result = {name: "" for name in agent_name_this_turn}
+ for current_turn in range(self.max_tool_call_times):
+ if len(finished_agent_names) == len(agent_name_this_turn):
+ # All agents have finished their tasks. Break the loop.
+ break
+
+ # Filter out agents that have finished and gather tool actions for the rest
+ tool_calls = []
+ active_agents_names = [
+ name
+ for name in agent_name_this_turn
+ if name not in finished_agent_names
+ ]
+ for name in active_agents_names:
+ if current_turn == self.max_tool_call_times - 1:
+ tool = [t for t in tools[name] if t["name"] == "submit_task"]
+ else:
+ tool = tools[name]
+ tool_calls.append(
+ self.real_execution_agents[name].astep(
+ task_description,
+ plan_this_turn[name],
+ tool,
+ current_turn=current_turn + 1,
+ )
+ )
+ # Use asyncio.gather to run astep concurrently
+ tool_call_decisions = await asyncio.gather(*tool_calls)
+ for name, tool_call_result in zip(active_agents_names, tool_call_decisions):
+ self.real_execution_agents[name].add_message_to_memory(
+ [tool_call_result]
+ )
+
+ # Actually call the tool and get the observation
+ tool_responses = await asyncio.gather(
+ *[
+ ToolUsingExecutor.call_tool(
+ tool.tool_name,
+ tool.tool_input,
+ self.cookies.get(name, None),
+ )
+ for name, tool in zip(active_agents_names, tool_call_decisions)
+ ]
+ )
+ # Update each agent's memory and check if they have finished
+ cookies = {}
+ for name, response in zip(active_agents_names, tool_responses):
+ observation = response["observation"]
+ is_finish = response["is_finish"]
+ cookies[name] = response["cookies"]
+ self.real_execution_agents[name].add_message_to_memory([observation])
+ logger.info(
+ f"\nTool: {observation.tool_name}\nTool Input: {observation.tool_input}\nObservation: {observation.content}",
+ name,
+ Fore.YELLOW,
+ )
+ if is_finish:
+ finished_agent_names.add(name)
+ result[name] = observation.content
+ self.update_cookies(cookies)
+
+ message_result = []
+ for name, conclusion in result.items():
+ if conclusion != "":
+ message_result.append(
+ ExecutorMessage(
+ content=f"[{name}]: My execution result:\n{conclusion}",
+ sender=name,
+ )
+ )
+ return message_result
+
+ def update_cookies(self, cookies: dict):
+ for name, cookie in cookies.items():
+ self.cookies[name] = cookie
+
+ @classmethod
+ async def retrieve_tools(
+ cls, plan: SolverMessage, curr_tools: List = [], cookies=None
+ ):
+ async with ClientSession(cookies=cookies) as session:
+ if cookies is None:
+ async with session.post(f"{url}/get_cookie", timeout=30) as response:
+ cookies = response.cookies
+ session.cookie_jar.update_cookies(cookies)
+ await response.text()
+ # Sometimes the toolserver's docker container is not ready yet
+ # So we need to wait for a while
+ await asyncio.sleep(10)
+ async with session.post(
+ f"{url}/retrieving_tools", json={"question": plan.content, "top_k": 5}
+ ) as response:
+ retrieved_tools = await response.json()
+ retrieved_tools = ast.literal_eval(retrieved_tools)
+ tools = deepcopy(curr_tools)
+ existed_tool_names = set([t["name"] for t in tools])
+ # Add the retrieved tools into the final tools
+ for tool in retrieved_tools["tools_json"]:
+ if tool["name"] not in existed_tool_names:
+ existed_tool_names.add(tool["name"])
+ tools.append(tool)
+ return tools, cookies
+
+ @classmethod
+ async def call_tool(cls, command: str, arguments: dict, cookies=None):
+ async def _summarize_webpage(webpage, question):
+ summarize_prompt = Template(SUMMARIZE_PROMPT).safe_substitute(
+ webpage=webpage, question=question
+ )
+ for _ in range(3):
+ try:
+ response = await openai.ChatCompletion.acreate(
+ messages=[{"role": "user", "content": summarize_prompt}],
+ model="gpt-3.5-turbo-16k",
+ )
+ except:
+ continue
+ return response["choices"][0]["message"]["content"]
+
+ if command == "submit_task":
+ return {
+ "observation": ExecutorMessage(
+ content=f"Task Status: {arguments['status']}\nConclusion: {arguments['conclusion']}",
+ sender="function",
+ tool_name=command,
+ tool_input=arguments,
+ ),
+ "is_finish": True,
+ "cookies": cookies,
+ }
+ if command == "":
+ return {
+ "observation": ExecutorMessage(
+ content=f"The function calling format is incorrect.",
+ sender="function",
+ tool_name=command,
+ tool_input=arguments,
+ ),
+ "is_finish": False,
+ "cookies": cookies,
+ }
+
+ for i in range(3):
+ try:
+ async with ClientSession(cookies=cookies) as session:
+ if cookies is None:
+ async with session.post(
+ f"{url}/get_cookie", timeout=30
+ ) as response:
+ cookies = response.cookies
+ session.cookie_jar.update_cookies(cookies)
+ await response.text()
+ # Sometimes the toolserver's docker container is not ready yet
+ # So we need to wait for a while
+ await asyncio.sleep(10)
+
+ payload_arguments = deepcopy(arguments)
+ if "thought" in payload_arguments:
+ del payload_arguments["thought"]
+ payload = {
+ "tool_name": command,
+ "arguments": payload_arguments,
+ }
+ # async with ClientSession() as session:
+ async with session.post(
+ f"{url}/execute_tool",
+ json=payload,
+ headers={
+ "toolbench_key": "p5ZASSLBO0EknAQLE5ecNZ7kq5i1YfY9eoWUXNxL3TM6lXwdXs"
+ },
+ timeout=30,
+ ) as response:
+ content = await response.text()
+ if command == "WebEnv_browse_website":
+ content = await _summarize_webpage(
+ content, arguments["question"]
+ )
+
+ message = ExecutorMessage(
+ content=content,
+ sender="function",
+ tool_name=command,
+ tool_input=arguments,
+ )
+ # async with session.post(
+ # f"{url}/release_session", timeout=30
+ # ) as response:
+ # await response.text()
+ break
+ except Exception as e:
+ message = ExecutorMessage(
+ content="Failed to call the tool. Exception: " + str(e),
+ sender="function",
+ tool_name=command,
+ tool_input=arguments,
+ )
+ continue
+ return {"observation": message, "is_finish": False, "cookies": cookies}
+
+ def broadcast_messages(self, agents, messages) -> None:
+ for agent in agents:
+ agent.add_message_to_memory(messages)
diff --git a/agentverse/environments/tasksolving_env/rules/role_assigner/__init__.py b/agentverse/environments/tasksolving_env/rules/role_assigner/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..2504dda5b6e3ca052d91b83ae3dd8b2c0e7f4b41
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/role_assigner/__init__.py
@@ -0,0 +1,6 @@
+from agentverse.registry import Registry
+
+role_assigner_registry = Registry(name="RoleAssignerRegistry")
+
+from .base import BaseRoleAssigner
+from .role_description import DescriptionAssigner
diff --git a/agentverse/environments/tasksolving_env/rules/role_assigner/base.py b/agentverse/environments/tasksolving_env/rules/role_assigner/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..726abf52a6b6cf86a3eeb4b561fab9863ee006bc
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/role_assigner/base.py
@@ -0,0 +1,55 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List, Tuple
+
+from agentverse.agents import BaseAgent
+
+from pydantic import BaseModel
+
+from abc import abstractmethod
+from . import role_assigner_registry
+
+if TYPE_CHECKING:
+ from agentverse.agents import RoleAssignerAgent, CriticAgent
+
+
+class BaseRoleAssigner(BaseModel):
+ """
+ The base class of role assignment class.
+ """
+
+ @abstractmethod
+ def step(
+ self,
+ role_assigner: RoleAssignerAgent,
+ group_members: List[CriticAgent],
+ advice: str = "No advice yet.",
+ task_description: str = "",
+ *args,
+ **kwargs,
+ ) -> List[CriticAgent]:
+ pass
+
+ def reset(self):
+ pass
+
+
+@role_assigner_registry.register("dummy")
+class DummyRoleAssigner(BaseRoleAssigner):
+ """
+ The base class of role assignment class.
+ """
+
+ def step(
+ self,
+ role_assigner: RoleAssignerAgent,
+ group_members: List[CriticAgent],
+ advice: str = "No advice yet.",
+ task_description: str = "",
+ *args,
+ **kwargs,
+ ) -> List[CriticAgent]:
+ return group_members
+
+ def reset(self):
+ pass
diff --git a/agentverse/environments/tasksolving_env/rules/role_assigner/role_description.py b/agentverse/environments/tasksolving_env/rules/role_assigner/role_description.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d7490c83caeac0f07b72efc90c280a3e012fc33
--- /dev/null
+++ b/agentverse/environments/tasksolving_env/rules/role_assigner/role_description.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from . import role_assigner_registry
+from .base import BaseRoleAssigner
+
+if TYPE_CHECKING:
+ from agentverse.message import RoleAssignerMessage
+ from agentverse.agents import CriticAgent, RoleAssignerAgent
+
+
+@role_assigner_registry.register("role_description")
+class DescriptionAssigner(BaseRoleAssigner):
+ """
+ Generates descriptions for each agent.
+ """
+
+ def step(
+ self,
+ role_assigner: RoleAssignerAgent,
+ group_members: List[CriticAgent],
+ advice: str = "No advice yet.",
+ task_description: str = "",
+ *args,
+ **kwargs,
+ ) -> List[CriticAgent]:
+ assert task_description != ""
+ assert len(group_members) > 0
+
+ roles = role_assigner.step(advice, task_description, len(group_members))
+ if len(roles.content) < len(group_members):
+ raise ValueError(
+ f"Number of roles ({len(roles.content)}) and number of group members ({len(group_members)}) do not match."
+ )
+ for role, member in zip(roles.content[: len(group_members)], group_members):
+ description = role.strip().strip(".")
+ member.role_description = description
+ member.name = description
+
+ return group_members
+
+ def reset(self):
+ pass
+
+
+@role_assigner_registry.register("role_description_name")
+class DescriptionNameAssigner(BaseRoleAssigner):
+ """
+ Generates description and name for each agent.
+ """
+
+ def step(
+ self,
+ role_assigner: RoleAssignerAgent,
+ group_members: List[CriticAgent],
+ advice: str = "No advice yet.",
+ task_description: str = "",
+ *args,
+ **kwargs,
+ ) -> List[CriticAgent]:
+ assert task_description != ""
+ assert len(group_members) > 0
+
+ # roles: [{'name': 'xxx', 'description': 'xxx'}, ...]
+ roles = role_assigner.step(advice, task_description, len(group_members))
+
+ if len(group_members) < 2:
+ pass
+ else:
+ if len(roles.content) != len(group_members):
+ raise ValueError(
+ f"Number of roles ({len(roles.content)}) and number of group members ({len(group_members)}) do not match."
+ )
+
+ for role_dict, member in zip(roles.content, group_members):
+ description = role_dict["description"].strip().strip(".")
+ member.role_description = description
+ member.name = role_dict["name"].strip()
+
+ return group_members
diff --git a/agentverse/gui.py b/agentverse/gui.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c68fb142c2052baad0559dca85ad4aa17c74398
--- /dev/null
+++ b/agentverse/gui.py
@@ -0,0 +1,506 @@
+import base64
+import itertools
+import json
+from typing import Dict, List, Tuple
+
+import cv2
+import gradio as gr
+
+from agentverse import TaskSolving
+from agentverse.simulation import Simulation
+from agentverse.message import Message
+
+
+def cover_img(background, img, place: Tuple[int, int]):
+ """
+ Overlays the specified image to the specified position of the background image.
+ :param background: background image
+ :param img: the specified image
+ :param place: the top-left coordinate of the target location
+ """
+ back_h, back_w, _ = background.shape
+ height, width, _ = img.shape
+ for i, j in itertools.product(range(height), range(width)):
+ if img[i, j, 3]:
+ background[place[0] + i, place[1] + j] = img[i, j, :3]
+
+
+class GUI:
+ """
+ the UI of frontend
+ """
+
+ def __init__(self, task: str, tasks_dir: str):
+ """
+ init a UI.
+ default number of students is 0
+ """
+ self.messages = []
+ self.task = task
+ if task == "pipeline_brainstorming":
+ self.backend = TaskSolving.from_task(task, tasks_dir)
+ else:
+ self.backend = Simulation.from_task(task, tasks_dir)
+ self.turns_remain = 0
+ self.agent_id = {
+ self.backend.agents[idx].name: idx
+ for idx in range(len(self.backend.agents))
+ }
+ self.stu_num = len(self.agent_id) - 1
+ self.autoplay = False
+ self.image_now = None
+ self.text_now = None
+ self.tot_solutions = 5
+ self.solution_status = [False] * self.tot_solutions
+
+ def get_avatar(self, idx):
+ if idx == -1:
+ img = cv2.imread("./imgs/db_diag/-1.png")
+ elif self.task == "prisoner_dilemma":
+ img = cv2.imread(f"./imgs/prison/{idx}.png")
+ elif self.task == "db_diag":
+ img = cv2.imread(f"./imgs/db_diag/{idx}.png")
+ elif "sde" in self.task:
+ img = cv2.imread(f"./imgs/sde/{idx}.png")
+ else:
+ img = cv2.imread(f"./imgs/{idx}.png")
+ base64_str = cv2.imencode(".png", img)[1].tostring()
+ return "data:image/png;base64," + base64.b64encode(base64_str).decode("utf-8")
+
+ def stop_autoplay(self):
+ self.autoplay = False
+ return (
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ )
+
+ def start_autoplay(self):
+ self.autoplay = True
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=True),
+ gr.Button.update(interactive=False),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status)),
+ )
+
+ while self.autoplay and self.turns_remain > 0:
+ outputs = self.gen_output()
+ self.image_now, self.text_now = outputs
+
+ yield (
+ *outputs,
+ gr.Button.update(interactive=not self.autoplay and self.turns_remain > 0),
+ gr.Button.update(interactive=self.autoplay and self.turns_remain > 0),
+ gr.Button.update(interactive=not self.autoplay and self.turns_remain > 0),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def delay_gen_output(self):
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=False),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ outputs = self.gen_output()
+ self.image_now, self.text_now = outputs
+
+ yield (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=self.turns_remain > 0),
+ gr.Button.update(interactive=self.turns_remain > 0),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def delay_reset(self):
+ self.autoplay = False
+ self.image_now, self.text_now = self.reset()
+ return (
+ self.image_now,
+ self.text_now,
+ gr.Button.update(interactive=True),
+ gr.Button.update(interactive=False),
+ gr.Button.update(interactive=True),
+ *[gr.Button.update(visible=statu) for statu in self.solution_status],
+ gr.Box.update(visible=any(self.solution_status))
+ )
+
+ def reset(self, stu_num=0):
+ """
+ tell backend the new number of students and generate new empty image
+ :param stu_num:
+ :return: [empty image, empty message]
+ """
+ if not 0 <= stu_num <= 30:
+ raise gr.Error("the number of students must be between 0 and 30.")
+
+ """
+ # [To-Do] Need to add a function to assign agent numbers into the backend.
+ """
+ # self.backend.reset(stu_num)
+ # self.stu_num = stu_num
+
+ """
+ # [To-Do] Pass the parameters to reset
+ """
+ self.backend.reset()
+ self.turns_remain = self.backend.environment.max_turns
+
+ if self.task == "prisoner_dilemma":
+ background = cv2.imread("./imgs/prison/case_1.png")
+ elif self.task == "db_diag":
+ background = cv2.imread("./imgs/db_diag/background.png")
+ elif "sde" in self.task:
+ background = cv2.imread("./imgs/sde/background.png")
+ else:
+ background = cv2.imread("./imgs/background.png")
+ back_h, back_w, _ = background.shape
+ stu_cnt = 0
+ for h_begin, w_begin in itertools.product(
+ range(800, back_h, 300), range(135, back_w - 200, 200)
+ ):
+ stu_cnt += 1
+ img = cv2.imread(
+ f"./imgs/{(stu_cnt - 1) % 11 + 1 if stu_cnt <= self.stu_num else 'empty'}.png",
+ cv2.IMREAD_UNCHANGED,
+ )
+ cover_img(
+ background,
+ img,
+ (h_begin - 30 if img.shape[0] > 190 else h_begin, w_begin),
+ )
+ self.messages = []
+ self.solution_status = [False] * self.tot_solutions
+ return [cv2.cvtColor(background, cv2.COLOR_BGR2RGB), ""]
+
+ def gen_img(self, data: List[Dict]):
+ """
+ generate new image with sender rank
+ :param data:
+ :return: the new image
+ """
+ # The following code need to be more general. This one is too task-specific.
+ # if len(data) != self.stu_num:
+ if len(data) != self.stu_num + 1:
+ raise gr.Error("data length is not equal to the total number of students.")
+ if self.task == "prisoner_dilemma":
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ if (
+ len(self.messages) < 2
+ or self.messages[-1][0] == 1
+ or self.messages[-2][0] == 2
+ ):
+ background = cv2.imread("./imgs/prison/case_1.png")
+ if data[0]["message"] != "":
+ cover_img(background, img, (400, 480))
+ else:
+ background = cv2.imread("./imgs/prison/case_2.png")
+ if data[0]["message"] != "":
+ cover_img(background, img, (400, 880))
+ if data[1]["message"] != "":
+ cover_img(background, img, (550, 480))
+ if data[2]["message"] != "":
+ cover_img(background, img, (550, 880))
+ elif self.task == "db_diag":
+ background = cv2.imread("./imgs/db_diag/background.png")
+ img = cv2.imread("./imgs/db_diag/speaking.png", cv2.IMREAD_UNCHANGED)
+ if data[0]["message"] != "":
+ cover_img(background, img, (750, 80))
+ if data[1]["message"] != "":
+ cover_img(background, img, (310, 220))
+ if data[2]["message"] != "":
+ cover_img(background, img, (522, 11))
+ elif "sde" in self.task:
+ background = cv2.imread("./imgs/sde/background.png")
+ img = cv2.imread("./imgs/sde/speaking.png", cv2.IMREAD_UNCHANGED)
+ if data[0]["message"] != "":
+ cover_img(background, img, (692, 330))
+ if data[1]["message"] != "":
+ cover_img(background, img, (692, 660))
+ if data[2]["message"] != "":
+ cover_img(background, img, (692, 990))
+ else:
+ background = cv2.imread("./imgs/background.png")
+ back_h, back_w, _ = background.shape
+ stu_cnt = 0
+ if data[stu_cnt]["message"] not in ["", "[RaiseHand]"]:
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (370, 1250))
+ for h_begin, w_begin in itertools.product(
+ range(800, back_h, 300), range(135, back_w - 200, 200)
+ ):
+ stu_cnt += 1
+ if stu_cnt <= self.stu_num:
+ img = cv2.imread(
+ f"./imgs/{(stu_cnt - 1) % 11 + 1}.png", cv2.IMREAD_UNCHANGED
+ )
+ cover_img(
+ background,
+ img,
+ (h_begin - 30 if img.shape[0] > 190 else h_begin, w_begin),
+ )
+ if "[RaiseHand]" in data[stu_cnt]["message"]:
+ # elif data[stu_cnt]["message"] == "[RaiseHand]":
+ img = cv2.imread("./imgs/hand.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin - 90, w_begin + 10))
+ elif data[stu_cnt]["message"] not in ["", "[RaiseHand]"]:
+ img = cv2.imread("./imgs/speaking.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin - 90, w_begin + 10))
+
+ else:
+ img = cv2.imread("./imgs/empty.png", cv2.IMREAD_UNCHANGED)
+ cover_img(background, img, (h_begin, w_begin))
+ return cv2.cvtColor(background, cv2.COLOR_BGR2RGB)
+
+ def return_format(self, messages: List[Message]):
+ _format = [{"message": "", "sender": idx} for idx in range(len(self.agent_id))]
+
+ for message in messages:
+ if self.task == "db_diag":
+ content_json: dict = message.content
+ content_json["diagnose"] = f"[{message.sender}]: {content_json['diagnose']}"
+ _format[self.agent_id[message.sender]]["message"] = json.dumps(content_json)
+ elif "sde" in self.task:
+ if message.sender == "code_tester":
+ pre_message, message_ = message.content.split("\n")
+ message_ = "{}\n{}".format(pre_message, json.loads(message_)["feedback"])
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message_
+ )
+ else:
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message.content
+ )
+
+ else:
+ _format[self.agent_id[message.sender]]["message"] = "[{}]: {}".format(
+ message.sender, message.content
+ )
+
+ return _format
+
+ def gen_output(self):
+ """
+ generate new image and message of next step
+ :return: [new image, new message]
+ """
+
+ # data = self.backend.next_data()
+ return_message = self.backend.next()
+ data = self.return_format(return_message)
+
+ # data.sort(key=lambda item: item["sender"])
+ """
+ # [To-Do]; Check the message from the backend: only 1 person can speak
+ """
+
+ for item in data:
+ if item["message"] not in ["", "[RaiseHand]"]:
+ self.messages.append((item["sender"], item["message"]))
+
+ message = self.gen_message()
+ self.turns_remain -= 1
+ return [self.gen_img(data), message]
+
+ def gen_message(self):
+ # If the backend cannot handle this error, use the following code.
+ message = ""
+ """
+ for item in data:
+ if item["message"] not in ["", "[RaiseHand]"]:
+ message = item["message"]
+ break
+ """
+ for sender, msg in self.messages:
+ if sender == 0:
+ avatar = self.get_avatar(0)
+ elif sender == -1:
+ avatar = self.get_avatar(-1)
+ else:
+ avatar = self.get_avatar((sender - 1) % 11 + 1)
+ if self.task == "db_diag":
+ msg_json = json.loads(msg)
+ self.solution_status = [False] * self.tot_solutions
+ msg = msg_json["diagnose"]
+ if msg_json["solution"] != "":
+ solution: List[str] = msg_json["solution"]
+ for solu in solution:
+ if "query" in solu or "queries" in solu:
+ self.solution_status[0] = True
+ solu = solu.replace("query", 'query')
+ solu = solu.replace("queries", 'queries')
+ if "join" in solu:
+ self.solution_status[1] = True
+ solu = solu.replace("join", 'join')
+ if "index" in solu:
+ self.solution_status[2] = True
+ solu = solu.replace("index", 'index')
+ if "system configuration" in solu:
+ self.solution_status[3] = True
+ solu = solu.replace("system configuration",
+ 'system configuration')
+ if "monitor" in solu or "Monitor" in solu or "Investigate" in solu:
+ self.solution_status[4] = True
+ solu = solu.replace("monitor", 'monitor')
+ solu = solu.replace("Monitor", 'Monitor')
+ solu = solu.replace("Investigate", 'Investigate')
+ msg = f"{msg}
{solu}"
+ if msg_json["knowledge"] != "":
+ msg = f'{msg}
{msg_json["knowledge"]}'
+ else:
+ msg = msg.replace("<", "<")
+ msg = msg.replace(">", ">")
+ message = (
+ f''
+ f''
+ f''
+ f"{msg}"
+ f"" + message
+ )
+ message = '' + message + ""
+ return message
+
+ def submit(self, message: str):
+ """
+ submit message to backend
+ :param message: message
+ :return: [new image, new message]
+ """
+ self.backend.submit(message)
+ self.messages.append((-1, f"[User]: {message}"))
+ return self.gen_img([{"message": ""}] * len(self.agent_id)), self.gen_message()
+
+ def launch(self, single_agent=False, discussion_mode=False):
+ if self.task == "pipeline_brainstorming":
+ with gr.Blocks() as demo:
+ chatbot = gr.Chatbot(height=800, show_label=False)
+ msg = gr.Textbox(label="Input")
+
+ def respond(message, chat_history):
+ chat_history.append((message, None))
+ yield "", chat_history
+ for response in self.backend.iter_run(single_agent=single_agent, discussion_mode=discussion_mode):
+ print(response)
+ chat_history.append((None, response))
+ yield "", chat_history
+
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
+ else:
+ with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ image_output = gr.Image()
+ with gr.Row():
+ reset_btn = gr.Button("Reset")
+ # next_btn = gr.Button("Next", variant="primary")
+ next_btn = gr.Button("Next", interactive=False)
+ stop_autoplay_btn = gr.Button(
+ "Stop Autoplay", interactive=False
+ )
+ start_autoplay_btn = gr.Button("Start Autoplay", interactive=False)
+ with gr.Box(visible=False) as solutions:
+ with gr.Column():
+ gr.HTML("Optimization Solutions:")
+ with gr.Row():
+ rewrite_slow_query_btn = gr.Button("Rewrite Slow Query", visible=False)
+ add_query_hints_btn = gr.Button("Add Query Hints", visible=False)
+ update_indexes_btn = gr.Button("Update Indexes", visible=False)
+ tune_parameters_btn = gr.Button("Tune Parameters", visible=False)
+ gather_more_info_btn = gr.Button("Gather More Info", visible=False)
+ # text_output = gr.Textbox()
+ text_output = gr.HTML(self.reset()[1])
+
+ # Given a botton to provide student numbers and their inf.
+ # stu_num = gr.Number(label="Student Number", precision=0)
+ # stu_num = self.stu_num
+
+ if self.task == "db_diag":
+ user_msg = gr.Textbox()
+ submit_btn = gr.Button("Submit", variant="primary")
+
+ submit_btn.click(fn=self.submit, inputs=user_msg, outputs=[image_output, text_output],
+ show_progress=False)
+ else:
+ pass
+
+ # next_btn.click(fn=self.gen_output, inputs=None, outputs=[image_output, text_output],
+ # show_progress=False)
+ next_btn.click(
+ fn=self.delay_gen_output,
+ inputs=None,
+ outputs=[
+ image_output,
+ text_output,
+ next_btn,
+ start_autoplay_btn,
+ rewrite_slow_query_btn,
+ add_query_hints_btn,
+ update_indexes_btn,
+ tune_parameters_btn,
+ gather_more_info_btn,
+ solutions
+ ],
+ show_progress=False,
+ )
+
+ # [To-Do] Add botton: re-start (load different people and env)
+ # reset_btn.click(fn=self.reset, inputs=stu_num, outputs=[image_output, text_output],
+ # show_progress=False)
+ # reset_btn.click(fn=self.reset, inputs=None, outputs=[image_output, text_output], show_progress=False)
+ reset_btn.click(
+ fn=self.delay_reset,
+ inputs=None,
+ outputs=[
+ image_output,
+ text_output,
+ next_btn,
+ stop_autoplay_btn,
+ start_autoplay_btn,
+ rewrite_slow_query_btn,
+ add_query_hints_btn,
+ update_indexes_btn,
+ tune_parameters_btn,
+ gather_more_info_btn,
+ solutions
+ ],
+ show_progress=False,
+ )
+
+ stop_autoplay_btn.click(
+ fn=self.stop_autoplay,
+ inputs=None,
+ outputs=[next_btn, stop_autoplay_btn, start_autoplay_btn],
+ show_progress=False,
+ )
+ start_autoplay_btn.click(
+ fn=self.start_autoplay,
+ inputs=None,
+ outputs=[
+ image_output,
+ text_output,
+ next_btn,
+ stop_autoplay_btn,
+ start_autoplay_btn,
+ rewrite_slow_query_btn,
+ add_query_hints_btn,
+ update_indexes_btn,
+ tune_parameters_btn,
+ gather_more_info_btn,
+ solutions
+ ],
+ show_progress=False,
+ )
+
+ demo.queue(concurrency_count=5, max_size=20).launch()
+ # demo.launch()
diff --git a/agentverse/initialization.py b/agentverse/initialization.py
new file mode 100644
index 0000000000000000000000000000000000000000..13ef54e77f0504657ef4d84508f921d3c5c3554c
--- /dev/null
+++ b/agentverse/initialization.py
@@ -0,0 +1,120 @@
+from __future__ import annotations
+
+import os
+from typing import Dict, List, TYPE_CHECKING
+
+import yaml
+
+try:
+ from bmtools.agent.singletool import import_all_apis, load_single_tools
+except:
+ print(
+ "BMTools is not installed, tools in the simulation environment cannot be used. To install BMTools, please follow the instruction in the README.md file."
+ )
+
+from agentverse.llms import llm_registry
+
+from agentverse.agents import agent_registry
+from agentverse.environments import BaseEnvironment, env_registry
+from agentverse.memory import memory_registry
+from agentverse.memory_manipulator import memory_manipulator_registry
+
+from agentverse.output_parser import output_parser_registry
+
+if TYPE_CHECKING:
+ from agentverse.agents import BaseAgent
+
+
+def load_llm(llm_config: Dict):
+ llm_type = llm_config.pop("llm_type", "text-davinci-003")
+
+ return llm_registry.build(llm_type, **llm_config)
+
+
+def load_memory(memory_config: Dict):
+ memory_type = memory_config.pop("memory_type", "chat_history")
+ return memory_registry.build(memory_type, **memory_config)
+
+
+def load_memory_manipulator(memory_manipulator_config: Dict):
+ memory_manipulator_type = memory_manipulator_config.pop(
+ "memory_manipulator_type", "basic"
+ )
+ return memory_manipulator_registry.build(
+ memory_manipulator_type, **memory_manipulator_config
+ )
+
+
+def load_tools(tool_config: List[Dict]):
+ if len(tool_config) == 0:
+ return []
+ all_tools_list = []
+ for tool in tool_config:
+ _, config = load_single_tools(tool["tool_name"], tool["tool_url"])
+ all_tools_list += import_all_apis(config)
+ return all_tools_list
+
+
+def load_environment(env_config: Dict) -> BaseEnvironment:
+ env_type = env_config.pop("env_type", "basic")
+ return env_registry.build(env_type, **env_config)
+
+
+def load_agent(agent_config: Dict) -> BaseAgent:
+ agent_type = agent_config.pop("agent_type", "conversation")
+ agent = agent_registry.build(agent_type, **agent_config)
+ return agent
+
+
+def prepare_task_config(task, tasks_dir):
+ """Read the yaml config of the given task in `tasks` directory."""
+ all_task_dir = tasks_dir
+ task_path = os.path.join(all_task_dir, task)
+ config_path = os.path.join(task_path, "config.yaml")
+ if not os.path.exists(task_path):
+ all_tasks = []
+ for task in os.listdir(all_task_dir):
+ if (
+ os.path.isdir(os.path.join(all_task_dir, task))
+ and task != "__pycache__"
+ ):
+ all_tasks.append(task)
+ for subtask in os.listdir(os.path.join(all_task_dir, task)):
+ if (
+ os.path.isdir(os.path.join(all_task_dir, task, subtask))
+ and subtask != "__pycache__"
+ ):
+ all_tasks.append(f"{task}/{subtask}")
+ raise ValueError(f"Task {task} not found. Available tasks: {all_tasks}")
+ if not os.path.exists(config_path):
+ raise ValueError(
+ "You should include the config.yaml file in the task directory"
+ )
+ task_config = yaml.safe_load(open(config_path))
+
+ for i, agent_configs in enumerate(task_config["agents"]):
+ agent_configs["memory"] = load_memory(agent_configs.get("memory", {}))
+ if agent_configs.get("tool_memory", None) is not None:
+ agent_configs["tool_memory"] = load_memory(agent_configs["tool_memory"])
+ llm = load_llm(agent_configs.get("llm", "text-davinci-003"))
+ agent_configs["llm"] = llm
+
+ memory_manipulator = load_memory_manipulator(
+ agent_configs.get("memory_manipulator", {})
+ )
+ agent_configs["memory_manipulator"] = memory_manipulator
+
+ agent_configs["tools"] = load_tools(agent_configs.get("tools", []))
+
+ # Build the output parser
+ output_parser_config = agent_configs.get("output_parser", {"type": "dummy"})
+ if output_parser_config.get("type", None) == "role_assigner":
+ output_parser_config["cnt_critic_agents"] = task_config.get(
+ "cnt_critic_agents", 0
+ )
+ output_parser_name = output_parser_config.pop("type", task)
+ agent_configs["output_parser"] = output_parser_registry.build(
+ output_parser_name, **output_parser_config
+ )
+
+ return task_config
diff --git a/agentverse/llms/__init__.py b/agentverse/llms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c0cd5047f5e8b71ebe30e5631d709564e782fe8
--- /dev/null
+++ b/agentverse/llms/__init__.py
@@ -0,0 +1,6 @@
+from agentverse.registry import Registry
+
+llm_registry = Registry(name="LLMRegistry")
+
+from .base import BaseLLM, BaseChatModel, BaseCompletionModel, LLMResult
+from .openai import OpenAIChat
diff --git a/agentverse/llms/base.py b/agentverse/llms/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..b759fb281e4ace11017ec9443f5488cc5f235eb6
--- /dev/null
+++ b/agentverse/llms/base.py
@@ -0,0 +1,45 @@
+from abc import abstractmethod
+from typing import Dict, Any
+
+from pydantic import BaseModel, Field
+
+
+class LLMResult(BaseModel):
+ content: str = ""
+ function_name: str = ""
+ function_arguments: Any = None
+ send_tokens: int = 0
+ recv_tokens: int = 0
+ total_tokens: int = 0
+
+
+class BaseModelArgs(BaseModel):
+ pass
+
+
+class BaseLLM(BaseModel):
+ args: BaseModelArgs = Field(default_factory=BaseModelArgs)
+ max_retry: int = Field(default=3)
+
+ @abstractmethod
+ def get_spend(self) -> float:
+ """
+ Number of USD spent
+ """
+ return -1.0
+
+ @abstractmethod
+ def generate_response(self, **kwargs) -> LLMResult:
+ pass
+
+ @abstractmethod
+ def agenerate_response(self, **kwargs) -> LLMResult:
+ pass
+
+
+class BaseChatModel(BaseLLM):
+ pass
+
+
+class BaseCompletionModel(BaseLLM):
+ pass
diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b7409cf2b053baa4439d93a4ecfb8b57ae5a45a
--- /dev/null
+++ b/agentverse/llms/openai.py
@@ -0,0 +1,346 @@
+import logging
+import json
+import ast
+import os
+import numpy as np
+from aiohttp import ClientSession
+from typing import Dict, List, Optional, Union
+from tenacity import retry, stop_after_attempt, wait_exponential
+
+from pydantic import BaseModel, Field
+
+from agentverse.llms.base import LLMResult
+from agentverse.logging import logger
+from agentverse.message import Message
+
+from . import llm_registry
+from .base import BaseChatModel, BaseCompletionModel, BaseModelArgs
+from .utils.jsonrepair import JsonRepair
+
+try:
+ import openai
+ from openai.error import OpenAIError
+except ImportError:
+ is_openai_available = False
+ logging.warning("openai package is not installed")
+else:
+ # openai.proxy = os.environ.get("http_proxy")
+ # if openai.proxy is None:
+ # openai.proxy = os.environ.get("HTTP_PROXY")
+ if os.environ.get("OPENAI_API_KEY") != None:
+ openai.api_key = os.environ.get("OPENAI_API_KEY")
+ is_openai_available = True
+ elif os.environ.get("AZURE_OPENAI_API_KEY") != None:
+ openai.api_type = "azure"
+ openai.api_key = os.environ.get("AZURE_OPENAI_API_KEY")
+ openai.api_base = os.environ.get("AZURE_OPENAI_API_BASE")
+ openai.api_version = "2023-05-15"
+ is_openai_available = True
+ else:
+ logging.warning(
+ "OpenAI API key is not set. Please set the environment variable OPENAI_API_KEY"
+ )
+ is_openai_available = False
+
+
+class OpenAIChatArgs(BaseModelArgs):
+ model: str = Field(default="gpt-3.5-turbo")
+ deployment_id: str = Field(default=None)
+ max_tokens: int = Field(default=2048)
+ temperature: float = Field(default=1.0)
+ top_p: int = Field(default=1)
+ n: int = Field(default=1)
+ stop: Optional[Union[str, List]] = Field(default=None)
+ presence_penalty: int = Field(default=0)
+ frequency_penalty: int = Field(default=0)
+
+
+# class OpenAICompletionArgs(OpenAIChatArgs):
+# model: str = Field(default="text-davinci-003")
+# suffix: str = Field(default="")
+# best_of: int = Field(default=1)
+
+
+# @llm_registry.register("text-davinci-003")
+# class OpenAICompletion(BaseCompletionModel):
+# args: OpenAICompletionArgs = Field(default_factory=OpenAICompletionArgs)
+
+# def __init__(self, max_retry: int = 3, **kwargs):
+# args = OpenAICompletionArgs()
+# args = args.dict()
+# for k, v in args.items():
+# args[k] = kwargs.pop(k, v)
+# if len(kwargs) > 0:
+# logging.warning(f"Unused arguments: {kwargs}")
+# super().__init__(args=args, max_retry=max_retry)
+
+# def generate_response(self, prompt: str) -> LLMResult:
+# response = openai.Completion.create(prompt=prompt, **self.args.dict())
+# return LLMResult(
+# content=response["choices"][0]["text"],
+# send_tokens=response["usage"]["prompt_tokens"],
+# recv_tokens=response["usage"]["completion_tokens"],
+# total_tokens=response["usage"]["total_tokens"],
+# )
+
+# async def agenerate_response(self, prompt: str) -> LLMResult:
+# response = await openai.Completion.acreate(prompt=prompt, **self.args.dict())
+# return LLMResult(
+# content=response["choices"][0]["text"],
+# send_tokens=response["usage"]["prompt_tokens"],
+# recv_tokens=response["usage"]["completion_tokens"],
+# total_tokens=response["usage"]["total_tokens"],
+# )
+
+
+@llm_registry.register("gpt-35-turbo")
+@llm_registry.register("gpt-3.5-turbo")
+@llm_registry.register("gpt-4")
+class OpenAIChat(BaseChatModel):
+ args: OpenAIChatArgs = Field(default_factory=OpenAIChatArgs)
+
+ total_prompt_tokens: int = 0
+ total_completion_tokens: int = 0
+
+ def __init__(self, max_retry: int = 3, **kwargs):
+ args = OpenAIChatArgs()
+ args = args.dict()
+ for k, v in args.items():
+ args[k] = kwargs.pop(k, v)
+ if len(kwargs) > 0:
+ logging.warning(f"Unused arguments: {kwargs}")
+ super().__init__(args=args, max_retry=max_retry)
+
+ # def _construct_messages(self, history: List[Message]):
+ # return history + [{"role": "user", "content": query}]
+ @retry(
+ stop=stop_after_attempt(20),
+ wait=wait_exponential(multiplier=1, min=4, max=10),
+ reraise=True,
+ )
+ def generate_response(
+ self,
+ prepend_prompt: str = "",
+ history: List[dict] = [],
+ append_prompt: str = "",
+ functions: List[dict] = [],
+ ) -> LLMResult:
+ messages = self.construct_messages(prepend_prompt, history, append_prompt)
+ logger.log_prompt(messages)
+ try:
+ # Execute function call
+ if functions != []:
+ response = openai.ChatCompletion.create(
+ messages=messages,
+ functions=functions,
+ **self.args.dict(),
+ )
+ if response["choices"][0]["message"].get("function_call") is not None:
+ self.collect_metrics(response)
+ return LLMResult(
+ content=response["choices"][0]["message"].get("content", ""),
+ function_name=response["choices"][0]["message"][
+ "function_call"
+ ]["name"],
+ function_arguments=ast.literal_eval(
+ response["choices"][0]["message"]["function_call"][
+ "arguments"
+ ]
+ ),
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+ else:
+ self.collect_metrics(response)
+ return LLMResult(
+ content=response["choices"][0]["message"]["content"],
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+
+ else:
+ response = openai.ChatCompletion.create(
+ messages=messages,
+ **self.args.dict(),
+ )
+ self.collect_metrics(response)
+ return LLMResult(
+ content=response["choices"][0]["message"]["content"],
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+ except (OpenAIError, KeyboardInterrupt, json.decoder.JSONDecodeError) as error:
+ raise
+
+ @retry(
+ stop=stop_after_attempt(20),
+ wait=wait_exponential(multiplier=1, min=4, max=10),
+ reraise=True,
+ )
+ async def agenerate_response(
+ self,
+ prepend_prompt: str = "",
+ history: List[dict] = [],
+ append_prompt: str = "",
+ functions: List[dict] = [],
+ ) -> LLMResult:
+ messages = self.construct_messages(prepend_prompt, history, append_prompt)
+ logger.log_prompt(messages)
+
+ try:
+ if functions != []:
+ async with ClientSession(trust_env=True) as session:
+ openai.aiosession.set(session)
+ response = await openai.ChatCompletion.acreate(
+ messages=messages,
+ functions=functions,
+ **self.args.dict(),
+ )
+ if response["choices"][0]["message"].get("function_call") is not None:
+ function_name = response["choices"][0]["message"]["function_call"][
+ "name"
+ ]
+ valid_function = False
+ if function_name.startswith("function."):
+ function_name = function_name.replace("function.", "")
+ elif function_name.startswith("functions."):
+ function_name = function_name.replace("functions.", "")
+ for function in functions:
+ if function["name"] == function_name:
+ valid_function = True
+ break
+ if not valid_function:
+ logger.warn(
+ f"The returned function name {function_name} is not in the list of valid functions. Retrying..."
+ )
+ raise ValueError(
+ f"The returned function name {function_name} is not in the list of valid functions."
+ )
+ try:
+ arguments = ast.literal_eval(
+ response["choices"][0]["message"]["function_call"][
+ "arguments"
+ ]
+ )
+ except:
+ try:
+ arguments = ast.literal_eval(
+ JsonRepair(
+ response["choices"][0]["message"]["function_call"][
+ "arguments"
+ ]
+ ).repair()
+ )
+ except:
+ logger.warn(
+ "The returned argument in function call is not valid json. Retrying..."
+ )
+ raise ValueError(
+ "The returned argument in function call is not valid json."
+ )
+ self.collect_metrics(response)
+ return LLMResult(
+ function_name=function_name,
+ function_arguments=arguments,
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+
+ else:
+ self.collect_metrics(response)
+ return LLMResult(
+ content=response["choices"][0]["message"]["content"],
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+
+ else:
+ async with ClientSession(trust_env=True) as session:
+ openai.aiosession.set(session)
+ response = await openai.ChatCompletion.acreate(
+ messages=messages,
+ **self.args.dict(),
+ )
+ self.collect_metrics(response)
+ return LLMResult(
+ content=response["choices"][0]["message"]["content"],
+ send_tokens=response["usage"]["prompt_tokens"],
+ recv_tokens=response["usage"]["completion_tokens"],
+ total_tokens=response["usage"]["total_tokens"],
+ )
+ except (OpenAIError, KeyboardInterrupt, json.decoder.JSONDecodeError) as error:
+ raise
+
+ def construct_messages(
+ self, prepend_prompt: str, history: List[dict], append_prompt: str
+ ):
+ messages = []
+ if prepend_prompt != "":
+ messages.append({"role": "system", "content": prepend_prompt})
+ if len(history) > 0:
+ messages += history
+ if append_prompt != "":
+ messages.append({"role": "user", "content": append_prompt})
+ return messages
+
+ def collect_metrics(self, response):
+ self.total_prompt_tokens += response["usage"]["prompt_tokens"]
+ self.total_completion_tokens += response["usage"]["completion_tokens"]
+
+ def get_spend(self) -> int:
+ input_cost_map = {
+ "gpt-3.5-turbo": 0.0015,
+ "gpt-3.5-turbo-16k": 0.003,
+ "gpt-3.5-turbo-0613": 0.0015,
+ "gpt-3.5-turbo-16k-0613": 0.003,
+ "gpt-4": 0.03,
+ "gpt-4-0613": 0.03,
+ "gpt-4-32k": 0.06,
+ }
+
+ output_cost_map = {
+ "gpt-3.5-turbo": 0.002,
+ "gpt-3.5-turbo-16k": 0.004,
+ "gpt-3.5-turbo-0613": 0.002,
+ "gpt-3.5-turbo-16k-0613": 0.004,
+ "gpt-4": 0.06,
+ "gpt-4-0613": 0.06,
+ "gpt-4-32k": 0.12,
+ }
+
+ model = self.args.model
+ if model not in input_cost_map or model not in output_cost_map:
+ raise ValueError(f"Model type {model} not supported")
+
+ return (
+ self.total_prompt_tokens * input_cost_map[model] / 1000.0
+ + self.total_completion_tokens * output_cost_map[model] / 1000.0
+ )
+
+
+@retry(
+ stop=stop_after_attempt(3),
+ wait=wait_exponential(multiplier=1, min=4, max=10),
+ reraise=True,
+)
+def get_embedding(text: str, attempts=3) -> np.array:
+ try:
+ text = text.replace("\n", " ")
+ if openai.api_type == "azure":
+ embedding = openai.Embedding.create(
+ input=[text], deployment_id="text-embedding-ada-002"
+ )["data"][0]["embedding"]
+ else:
+ embedding = openai.Embedding.create(
+ input=[text], model="text-embedding-ada-002"
+ )["data"][0]["embedding"]
+ return tuple(embedding)
+ except Exception as e:
+ attempt += 1
+ logger.error(f"Error {e} when requesting openai models. Retrying")
+ raise
diff --git a/agentverse/llms/utils/jsonrepair.py b/agentverse/llms/utils/jsonrepair.py
new file mode 100644
index 0000000000000000000000000000000000000000..df8d6c708522c01a0a66dc594ecea4d1e5d1f3cc
--- /dev/null
+++ b/agentverse/llms/utils/jsonrepair.py
@@ -0,0 +1,660 @@
+# jsonrepair.py - Repair invalid JSON documents in Python
+#
+# Just https://github.com/josdejong/jsonrepair ported from TypeScript to Python.
+#
+# This port won't get updates, because the goal should be to generate this library instead.
+#
+# See: https://github.com/josdejong/jsonrepair/issues/84
+#
+
+import json
+import re
+from typing import Optional
+
+CONTROL_CHARACTERS = {"\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t"}
+
+ESCAPE_CHARACTERS = {
+ '"': '"',
+ "\\": "\\",
+ "/": "/",
+ "b": "\b",
+ "f": "\f",
+ "n": "\n",
+ "r": "\r",
+ "t": "\t"
+ # note that \u is handled separately in parseString()
+}
+
+
+def remove_at_index(text: str, start: int, count: int) -> str:
+ return text[0:start] + text[start + count :]
+
+
+def is_control_character(char: str) -> bool:
+ return char in CONTROL_CHARACTERS
+
+
+def is_valid_string_character(char: str) -> bool:
+ return 0x20 <= ord(char) <= 0x10FFFF
+
+
+def is_quote(char: str) -> bool:
+ return is_single_quote(char) or is_double_quote(char)
+
+
+def is_single_quote(char: str) -> bool:
+ """Test whether the given character is a single quote character.
+ Also tests for special variants of single quotes.
+ """
+ return char in (
+ "'", # U+0027
+ "‘", # U+2018
+ "’", # U+2019
+ "`", # U+0060
+ "´", # U+00B4
+ )
+
+
+def is_double_quote(char: str) -> bool:
+ return (
+ is_ascii_double_quote(char)
+ or is_double_quote_left(char)
+ or is_double_quote_right(char)
+ )
+
+
+def is_ascii_double_quote(char: str) -> bool:
+ return char == '"' # U+0022
+
+
+def is_double_quote_left(char: str) -> bool:
+ return char == "“" # U+201C
+
+
+def is_double_quote_right(char: str) -> bool:
+ return char == "”" # U+201D
+
+
+def is_start_of_value(char: str) -> bool:
+ regex_start_of_value = (
+ r"^[[{\w-]$" # alpha, number, minus, or opening bracket or brace
+ )
+ return bool(re.search(regex_start_of_value, char)) or is_quote(char)
+
+
+def ends_with_comma_or_newline(text: str) -> bool:
+ return bool(re.search(r"[,\n][ \t\r]*$", text))
+
+
+def is_whitespace(char: str) -> bool:
+ return char.isspace()
+
+
+def is_special_whitespace(char: str) -> bool:
+ """Check if the given character is a special whitespace character, some unicode variant"""
+ return (
+ char == "\u00A0" # non-breaking space
+ or ord("\u2000") <= ord(char) <= ord("\u200A")
+ or char == "\u202F"
+ or char == "\u205F"
+ or char == "\u3000"
+ )
+
+
+def insert_before_last_whitespace(text: str, text_to_insert: str) -> str:
+ index = len(text)
+
+ if not is_whitespace(text[index - 1]):
+ # no trailing whitespaces
+ return text + text_to_insert
+
+ while is_whitespace(text[index - 1]):
+ index -= 1
+
+ return text[:index] + text_to_insert + text[index:]
+
+
+def strip_last_occurrence(
+ text: str, text_to_strip: str, strip_remaining: bool = False
+) -> str:
+ index = text.rindex(text_to_strip)
+ try:
+ return text[:index] + ("" if strip_remaining else text[index + 1 :])
+ except ValueError:
+ return text
+
+
+def is_hex(char: str) -> bool:
+ try:
+ int(char, 16)
+ return True
+ except ValueError:
+ return False
+
+
+def is_delimiter(char: str) -> bool:
+ return char in ",:[]{}()\n'" or is_quote(char)
+
+
+def at_end_of_block_comment(text: str, i: int) -> bool:
+ return text[i] == "*" and text[i + 1] == "/"
+
+
+class JsonRepairError(Exception):
+ def __init__(self, message: str, position: int):
+ super(JsonRepairError, self).__init__(message + f" at position {position}")
+ self.position = position
+
+
+class JsonRepair:
+ """Repairs invalid JSON, i.e. change JavaScript notation into JSON notation.
+
+ Example:
+
+ try:
+ json = "{name: 'John'}"
+ repaired = JsonRepair(json).repair()
+ print(repaired)
+ # '{"name": "John"}'
+ except JsonRepairFailed as err:
+ print(err)
+
+ """
+
+ def __init__(self, text: str):
+ self.text = text
+ self.i = 0 # current index in text
+ self.output = "" # generated output
+
+ def char(self, pos: int = 0) -> str:
+ return self.text[self.i + pos]
+
+ def inc(self, by: int = 1) -> None:
+ self.i += by
+
+ def dec(self, by: int = 1) -> None:
+ self.i -= by
+
+ def is_start_of_document(self, pos: int = 0) -> bool:
+ return self.i + pos == 0
+
+ def is_end_of_document(self, pos: int = 0) -> bool:
+ return self.i + pos >= len(self.text)
+
+ def repair(self) -> str:
+ processed = self.parse_value()
+ if not processed:
+ raise self.unexpected_end()
+
+ processed_comma = self.parse_character(",")
+ if processed_comma:
+ self.parse_whitespace_and_skip_comments()
+
+ if (
+ not self.is_end_of_document()
+ and is_start_of_value(self.char())
+ and ends_with_comma_or_newline(self.output)
+ ):
+ # start of a new value after end of the root level object: looks like
+ # newline delimited JSON -> turn into a root level array
+ if not processed_comma:
+ # repair missing comma
+ self.output = insert_before_last_whitespace(self.output, ",")
+
+ self.parse_newline_delimited_json()
+ elif processed_comma:
+ # repair: remove trailing comma
+ self.output = strip_last_occurrence(self.output, ",")
+
+ if self.is_end_of_document():
+ # reached the end of the document properly
+ return self.output
+
+ raise self.unexpected_character()
+
+ def parse_value(self) -> bool:
+ self.parse_whitespace_and_skip_comments()
+ processed = (
+ self.parse_object()
+ or self.parse_array()
+ or self.parse_string()
+ or self.parse_number()
+ or self.parse_keywords()
+ or self.parse_unquoted_string()
+ )
+ self.parse_whitespace_and_skip_comments()
+ return processed
+
+ def parse_whitespace_and_skip_comments(self) -> bool:
+ start = self.i
+
+ changed = self.parse_whitespace()
+ while True:
+ changed = self.parse_comment()
+ if changed:
+ changed = self.parse_whitespace()
+ if not changed:
+ break
+
+ return self.i > start
+
+ def parse_whitespace(self) -> bool:
+ whitespace = ""
+
+ while not self.is_end_of_document():
+ char = self.char()
+
+ normal = is_whitespace(char)
+ special = is_special_whitespace(char)
+
+ if not normal and not special:
+ break
+
+ if special:
+ whitespace += " " # repair special whitespace
+ else:
+ whitespace += char
+
+ self.inc()
+
+ if whitespace:
+ self.output += whitespace
+ return True
+ return False
+
+ def parse_comment(self) -> bool:
+ # find a block comment '/* ... */'
+ if not self.is_end_of_document() and not self.is_end_of_document(pos=+1):
+ if self.char() == "/" and self.char(pos=+1) == "*":
+ # repair block comment by skipping it
+ while not self.is_end_of_document() and not at_end_of_block_comment(
+ self.text, self.i
+ ):
+ self.inc()
+ self.inc(by=2)
+ return True
+
+ # find a line comment '// ...'
+ if self.char() == "/" and self.char(pos=+1) == "/":
+ # repair line comment by skipping it
+ while not self.is_end_of_document() and self.char() != "\n":
+ self.inc()
+ return True
+
+ return False
+
+ def parse_character(self, char: str) -> bool:
+ if not self.is_end_of_document():
+ if self.char() == char:
+ self.output += char
+ self.inc()
+ return True
+ return False
+
+ def skip_character(self, char: str) -> bool:
+ if not self.is_end_of_document() and self.char() == char:
+ self.inc()
+ return True
+ return False
+
+ def skip_escape_character(self) -> bool:
+ return self.skip_character("\\")
+
+ def parse_object(self) -> bool:
+ """Parse an object like '{"key": "value"}'"""
+ if not self.is_end_of_document() and self.char() == "{":
+ self.output += "{"
+ self.inc()
+ self.parse_whitespace_and_skip_comments()
+
+ initial = True
+ while not self.is_end_of_document() and self.char() != "}":
+ if not initial:
+ processed_comma = self.parse_character(",")
+ if not processed_comma:
+ # repair missing comma
+ self.output = insert_before_last_whitespace(self.output, ",")
+ self.parse_whitespace_and_skip_comments()
+ else:
+ processed_comma = True
+ initial = False
+
+ processed_key = self.parse_string() or self.parse_unquoted_string()
+ if not processed_key:
+ if self.is_end_of_document() or self.char() in "{}[]":
+ # repair trailing comma
+ self.output = strip_last_occurrence(self.output, ",")
+ break
+ raise self.object_key_expected()
+
+ self.parse_whitespace_and_skip_comments()
+ processed_colon = self.parse_character(":")
+ if not processed_colon:
+ if is_start_of_value(self.char()):
+ # repair missing colon
+ self.output = insert_before_last_whitespace(self.output, ":")
+ else:
+ raise self.colon_expected()
+ processed_value = self.parse_value()
+ if not processed_value:
+ if processed_colon:
+ raise self.object_value_expected()
+ raise self.colon_expected()
+
+ if not self.is_end_of_document() and self.char() == "}":
+ self.output += "}"
+ self.inc()
+ else:
+ # repair missing end bracket
+ self.output = insert_before_last_whitespace(self.output, "}")
+
+ return True
+
+ return False
+
+ def parse_array(self) -> bool:
+ """Parse an array like '["item1", "item2", ...]'"""
+ if not self.is_end_of_document() and self.char() == "[":
+ self.output += "["
+ self.inc()
+ self.parse_whitespace_and_skip_comments()
+
+ initial = True
+ while not self.is_end_of_document() and self.char() != "]":
+ if not initial:
+ processed_comma = self.parse_character(",")
+ if not processed_comma:
+ # repair missing comma
+ self.output = insert_before_last_whitespace(self.output, ",")
+ else:
+ initial = False
+
+ processed_value = self.parse_value()
+ if not processed_value:
+ # repair trailing comma
+ self.output = strip_last_occurrence(self.output, ",")
+ break
+
+ if not self.is_end_of_document() and self.char() == "]":
+ self.output += "]"
+ self.inc()
+ else:
+ # repair missing closing array bracket
+ self.output = insert_before_last_whitespace(self.output, "]")
+
+ return True
+
+ return False
+
+ def parse_newline_delimited_json(self):
+ """Parse and repair Newline Delimited JSON (NDJSON):
+ multiple JSON objects separated by a newline character
+ """
+ # repair NDJSON
+ initial = True
+ processed_value = True
+ while processed_value:
+ if not initial:
+ # parse optional comma, insert when missing
+ processed_comma = self.parse_character(",")
+ if not processed_comma:
+ # repair: add missing comma
+ self.output = insert_before_last_whitespace(self.output, ",")
+ else:
+ initial = False
+
+ processed_value = self.parse_value()
+
+ if not processed_value:
+ # repair: remove trailing comma
+ self.output = strip_last_occurrence(self.output, ",")
+
+ # repair: wrap the output inside array brackets
+ self.output = f"[\n{self.output}\n]"
+
+ def parse_string(self) -> bool:
+ """Parse a string enclosed by double quotes "...". Can contain escaped quotes
+ Repair strings enclosed in single quotes or special quotes
+ Repair an escaped string
+ """
+ if not self.is_end_of_document():
+ skip_escape_chars = self.char() == "\\"
+ if skip_escape_chars:
+ # repair: remove the first escape character
+ self.inc()
+ skip_escape_chars = True
+
+ if not self.is_end_of_document() and is_quote(self.char()):
+ is_end_quote = (
+ is_single_quote if is_single_quote(self.char()) else is_double_quote
+ )
+
+ if self.char() != '"':
+ pass # TODO?: repair non-normalized quote
+ self.output += '"'
+ self.inc()
+
+ while not self.is_end_of_document() and not is_end_quote(self.char()):
+ if self.char() == "\\":
+ char = self.char(pos=+1)
+ escape_char = ESCAPE_CHARACTERS.get(char)
+ if escape_char:
+ self.output += self.text[self.i : self.i + 2]
+ self.inc(by=2)
+ elif char == "u":
+ if (
+ not self.is_end_of_document(pos=+5)
+ and is_hex(self.char(pos=+2))
+ and is_hex(self.char(pos=+3))
+ and is_hex(self.char(pos=+4))
+ and is_hex(self.char(pos=+5))
+ ):
+ self.output += self.text[self.i : self.i + 6]
+ self.inc(by=6)
+ else:
+ raise self.invalid_unicode_character(self.i)
+ else:
+ # repair invalid escape character: remove it
+ self.output += char
+ self.inc(by=2)
+ else:
+ char = self.char()
+
+ if char == '"' and self.char(pos=-1) != "\\":
+ # repair unescaped double quote
+ self.output += "\\" + char
+ self.inc()
+ elif is_control_character(char):
+ # unescaped control character
+ self.output += CONTROL_CHARACTERS[char]
+ self.inc()
+ else:
+ if not is_valid_string_character(char):
+ raise self.invalid_character(char)
+ self.output += char
+ self.inc()
+
+ if skip_escape_chars:
+ processed = self.skip_escape_character()
+ if processed:
+ pass # repair: skipped escape character (nothing to do)
+
+ if not self.is_end_of_document() and is_quote(self.char()):
+ if self.char() != '"':
+ pass # TODO:? repair non-normalized quote
+
+ self.output += '"'
+ self.inc()
+ else:
+ # repair missing end quote
+ self.output += '"'
+
+ self.parse_concatenated_string()
+
+ return True
+
+ return False
+
+ def parse_concatenated_string(self) -> bool:
+ """Repair concatenated strings like \"hello\" + \"world\", change this into \"helloworld\" """
+ processed = False
+
+ self.parse_whitespace_and_skip_comments()
+ while not self.is_end_of_document() and self.char() == "+":
+ processed = True
+ self.inc()
+ self.parse_whitespace_and_skip_comments()
+
+ # repair: remove the end quote of the first string
+ self.output = strip_last_occurrence(self.output, '"', True)
+ start = len(self.output)
+ self.parse_string()
+
+ # repair: remove the start quote of the second string
+ self.output = remove_at_index(self.output, start, 1)
+
+ return processed
+
+ def parse_number(self) -> bool:
+ """Parse a number like 2.4 or 2.4e6"""
+ if not self.is_end_of_document():
+ start = self.i
+ if self.char() == "-":
+ self.inc()
+ err = self.expect_digit(start)
+ if err:
+ raise err
+
+ if not self.is_end_of_document() and self.char() == "0":
+ self.inc()
+ elif not self.is_end_of_document() and self.char() in "123456789":
+ self.inc()
+ while not self.is_end_of_document() and self.char().isdigit():
+ self.inc()
+
+ if not self.is_end_of_document() and self.char() == ".":
+ self.inc()
+ err = self.expect_digit(start)
+ if err:
+ raise err
+ while not self.is_end_of_document() and self.char().isdigit():
+ self.inc()
+
+ if not self.is_end_of_document() and self.char() in "eE":
+ self.inc()
+ if not self.is_end_of_document() and self.char() in "+-":
+ self.inc()
+ err = self.expect_digit(start)
+ if err:
+ raise err
+ while not self.is_end_of_document() and self.char().isdigit():
+ self.inc()
+
+ if self.i > start:
+ self.output += self.text[start : self.i]
+ return True
+
+ return False
+
+ def parse_keywords(self) -> bool:
+ """Parse keywords true, false, null
+ Repair Python keywords True, False, None
+ """
+ return (
+ self.parse_keyword("true", "true")
+ or self.parse_keyword("false", "false")
+ or self.parse_keyword("null", "null")
+ # repair Python keywords True, False, None
+ or self.parse_keyword("True", "true")
+ or self.parse_keyword("False", "false")
+ or self.parse_keyword("None", "null")
+ )
+
+ def parse_keyword(self, name: str, value: str) -> bool:
+ if self.text[self.i : self.i + len(name)] == name:
+ self.output += value
+ self.inc(by=len(name))
+ return True
+
+ return False
+
+ def parse_unquoted_string(self) -> bool:
+ """Repair and unquoted string by adding quotes around it
+ Repair a MongoDB function call like NumberLong("2")
+ Repair a JSONP function call like callback({...});
+ """
+ # note that the symbol can end with whitespaces: we stop at the next delimiter
+ start = self.i
+ while not self.is_end_of_document() and not is_delimiter(self.char()):
+ self.inc()
+
+ if self.i > start:
+ if not self.is_end_of_document() and self.char() == "(":
+ # repair a MongoDB function call like NumberLong("2")
+ # repair a JSONP function call like callback({...});
+ self.inc()
+
+ self.parse_value()
+
+ if not self.is_end_of_document() and self.char() == ")":
+ # repair: skip close bracket of function call
+ self.inc()
+ if not self.is_end_of_document() and self.char() == ";":
+ # repair: skip semicolon after JSONP call
+ self.inc()
+
+ return True
+
+ # else repair unquoted string
+
+ # first, go back to prevent getting trailing whitespaces in the string
+ while not self.is_start_of_document() and is_whitespace(self.char(pos=-1)):
+ self.dec()
+
+ symbol = self.text[start : self.i]
+ self.output += json.dumps(symbol)
+
+ return True
+
+ return False
+
+ def expect_digit(self, start: int) -> Optional[JsonRepairError]:
+ if self.is_end_of_document() or not self.char().isdigit():
+ num_so_far = self.text[start : self.i]
+ return JsonRepairError(
+ f"Invalid number '{num_so_far}', expecting a digit {self.got()}", 2
+ )
+
+ def invalid_character(self, char: str) -> JsonRepairError:
+ return JsonRepairError("Invalid character " + json.dumps(char), self.i)
+
+ def unexpected_character(self) -> JsonRepairError:
+ return JsonRepairError(
+ "Unexpected character " + json.dumps(self.text[self.i]), self.i
+ )
+
+ def unexpected_end(self) -> JsonRepairError:
+ return JsonRepairError("Unexpected end of json string", len(self.text))
+
+ def object_key_expected(self) -> JsonRepairError:
+ return JsonRepairError("Object key expected", self.i)
+
+ def object_value_expected(self) -> JsonRepairError:
+ return JsonRepairError("Object value expected", self.i)
+
+ def colon_expected(self) -> JsonRepairError:
+ return JsonRepairError("Colon expected", self.i)
+
+ def invalid_unicode_character(self, start: int) -> JsonRepairError:
+ end = start + 2
+ while re.match(r"\w", self.text[end]):
+ end += 1
+ chars = self.text[start:end]
+ return JsonRepairError(f'Invalid unicode character "{chars}"', self.i)
+
+ def got(self) -> str:
+ return (
+ f"but got '{self.char()}'"
+ if not self.is_end_of_document()
+ else "but reached end of input"
+ )
diff --git a/agentverse/logging.py b/agentverse/logging.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ed68d6f2b2c7f5d54bcfaa698b6627008932ccc
--- /dev/null
+++ b/agentverse/logging.py
@@ -0,0 +1,291 @@
+"""Logging module for Auto-GPT."""
+import logging
+import os
+import random
+import re
+import time
+import json
+import abc
+from logging import LogRecord
+from typing import Any, List
+
+from colorama import Fore, Style
+from agentverse.utils import Singleton
+
+
+# from autogpt.speech import say_text
+class JsonFileHandler(logging.FileHandler):
+ def __init__(self, filename, mode="a", encoding=None, delay=False):
+ super().__init__(filename, mode, encoding, delay)
+
+ def emit(self, record):
+ json_data = json.loads(self.format(record))
+ with open(self.baseFilename, "w", encoding="utf-8") as f:
+ json.dump(json_data, f, ensure_ascii=False, indent=4)
+
+
+class JsonFormatter(logging.Formatter):
+ def format(self, record):
+ return record.msg
+
+
+class Logger(metaclass=Singleton):
+ """
+ Logger that handle titles in different colors.
+ Outputs logs in console, activity.log, and errors.log
+ For console handler: simulates typing
+ """
+
+ def __init__(self):
+ # create log directory if it doesn't exist
+ this_files_dir_path = os.path.dirname(__file__)
+ log_dir = os.path.join(this_files_dir_path, "../logs")
+ if not os.path.exists(log_dir):
+ os.makedirs(log_dir)
+
+ log_file = "activity.log"
+ error_file = "error.log"
+
+ console_formatter = AutoGptFormatter("%(title_color)s %(message)s")
+
+ # Create a handler for console which simulate typing
+ self.typing_console_handler = TypingConsoleHandler()
+ self.typing_console_handler.setLevel(logging.INFO)
+ self.typing_console_handler.setFormatter(console_formatter)
+
+ # Create a handler for console without typing simulation
+ self.console_handler = ConsoleHandler()
+ self.console_handler.setLevel(logging.DEBUG)
+ self.console_handler.setFormatter(console_formatter)
+
+ # Info handler in activity.log
+ self.file_handler = logging.FileHandler(
+ os.path.join(log_dir, log_file), "a", "utf-8"
+ )
+ self.file_handler.setLevel(logging.DEBUG)
+ info_formatter = AutoGptFormatter(
+ "%(asctime)s %(levelname)s %(title)s %(message_no_color)s"
+ )
+ self.file_handler.setFormatter(info_formatter)
+
+ # Error handler error.log
+ error_handler = logging.FileHandler(
+ os.path.join(log_dir, error_file), "a", "utf-8"
+ )
+ error_handler.setLevel(logging.ERROR)
+ error_formatter = AutoGptFormatter(
+ "%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s"
+ " %(message_no_color)s"
+ )
+ error_handler.setFormatter(error_formatter)
+
+ self.typing_logger = logging.getLogger("TYPER")
+ self.typing_logger.addHandler(self.typing_console_handler)
+ self.typing_logger.addHandler(self.file_handler)
+ self.typing_logger.addHandler(error_handler)
+ self.typing_logger.setLevel(logging.DEBUG)
+
+ self.logger = logging.getLogger("LOGGER")
+ self.logger.addHandler(self.console_handler)
+ self.logger.addHandler(self.file_handler)
+ self.logger.addHandler(error_handler)
+ self.logger.setLevel(logging.DEBUG)
+
+ self.json_logger = logging.getLogger("JSON_LOGGER")
+ self.json_logger.addHandler(self.file_handler)
+ self.json_logger.addHandler(error_handler)
+ self.json_logger.setLevel(logging.DEBUG)
+
+ self.speak_mode = False
+ self.chat_plugins = []
+
+ def typewriter_log(
+ self, title="", title_color="", content="", speak_text=False, level=logging.INFO
+ ):
+ # if speak_text and self.speak_mode:
+ # say_text(f"{title}. {content}")
+
+ for plugin in self.chat_plugins:
+ plugin.report(f"{title}. {content}")
+
+ if content:
+ if isinstance(content, list):
+ content = "\n".join(content)
+ else:
+ content = ""
+
+ self.typing_logger.log(
+ level, content, extra={"title": title, "color": title_color}
+ )
+
+ def debug(
+ self,
+ message,
+ title="",
+ title_color="",
+ ):
+ self._log(title, title_color, message, logging.DEBUG)
+
+ def info(
+ self,
+ message,
+ title="",
+ title_color="",
+ ):
+ self._log(title, title_color, message, logging.INFO)
+
+ def warn(
+ self,
+ message,
+ title="",
+ title_color="",
+ ):
+ self._log(title, title_color, message, logging.WARN)
+
+ def error(self, title, message=""):
+ self._log(title, Fore.RED, message, logging.ERROR)
+
+ def _log(
+ self,
+ title: str = "",
+ title_color: str = "",
+ message: str = "",
+ level=logging.INFO,
+ ):
+ if isinstance(message, list):
+ if len(message) > 0:
+ message = "\n".join([str(m) for m in message])
+ else:
+ message = ""
+ self.logger.log(
+ level, message, extra={"title": str(title), "color": str(title_color)}
+ )
+
+ def set_level(self, level):
+ self.logger.setLevel(level)
+ self.typing_logger.setLevel(level)
+
+ def double_check(self, additionalText=None):
+ if not additionalText:
+ additionalText = (
+ "Please ensure you've setup and configured everything"
+ " correctly. Read https://github.com/Torantulino/Auto-GPT#readme to "
+ "double check. You can also create a github issue or join the discord"
+ " and ask there!"
+ )
+
+ self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText)
+
+ def log_json(self, data: Any, file_name: str) -> None:
+ # Define log directory
+ this_files_dir_path = os.path.dirname(__file__)
+ log_dir = os.path.join(this_files_dir_path, "../logs")
+
+ # Create a handler for JSON files
+ json_file_path = os.path.join(log_dir, file_name)
+ json_data_handler = JsonFileHandler(json_file_path)
+ json_data_handler.setFormatter(JsonFormatter())
+
+ # Log the JSON data using the custom file handler
+ self.json_logger.addHandler(json_data_handler)
+ self.json_logger.debug(data)
+ self.json_logger.removeHandler(json_data_handler)
+
+ def log_prompt(self, prompt: List[dict]) -> None:
+ self.debug("", "-=-=-=-=-=-=-=-=Prompt Start-=-=-=-=-=-=-=-=", Fore.MAGENTA)
+ for p in prompt:
+ self.debug(
+ p["content"]
+ if "function_call" not in p
+ else p["content"]
+ + "\nFunction Call:\n"
+ + json.dumps(p["function_call"]),
+ title=f'==={p["role"]}===\n',
+ title_color=Fore.MAGENTA,
+ )
+ self.debug("", "-=-=-=-=-=-=-=-=Prompt End-=-=-=-=-=-=-=-=", Fore.MAGENTA)
+
+ def get_log_directory(self):
+ this_files_dir_path = os.path.dirname(__file__)
+ log_dir = os.path.join(this_files_dir_path, "../logs")
+ return os.path.abspath(log_dir)
+
+
+"""
+Output stream to console using simulated typing
+"""
+
+
+class TypingConsoleHandler(logging.StreamHandler):
+ def emit(self, record):
+ min_typing_speed = 0.05
+ max_typing_speed = 0.01
+
+ msg = self.format(record)
+ try:
+ words = re.split(r"(\s+)", msg)
+ for i, word in enumerate(words):
+ print(word, end="", flush=True)
+ # if i < len(words) - 1:
+ # print(" ", end="", flush=True)
+ typing_speed = random.uniform(min_typing_speed, max_typing_speed)
+ time.sleep(typing_speed)
+ # type faster after each word
+ min_typing_speed = min_typing_speed * 0.95
+ max_typing_speed = max_typing_speed * 0.95
+ print()
+ except Exception:
+ self.handleError(record)
+
+
+class ConsoleHandler(logging.StreamHandler):
+ def emit(self, record) -> None:
+ msg = self.format(record)
+ try:
+ print(msg)
+ except Exception:
+ self.handleError(record)
+
+
+class AutoGptFormatter(logging.Formatter):
+ """
+ Allows to handle custom placeholders 'title_color' and 'message_no_color'.
+ To use this formatter, make sure to pass 'color', 'title' as log extras.
+ """
+
+ def format(self, record: LogRecord) -> str:
+ if hasattr(record, "color"):
+ record.title_color = (
+ getattr(record, "color")
+ + getattr(record, "title", "")
+ + " "
+ + Style.RESET_ALL
+ )
+ else:
+ record.title_color = getattr(record, "title", "")
+
+ # Add this line to set 'title' to an empty string if it doesn't exist
+ record.title = getattr(record, "title", "")
+
+ if hasattr(record, "msg"):
+ record.message_no_color = remove_color_codes(getattr(record, "msg"))
+ else:
+ record.message_no_color = ""
+ return super().format(record)
+
+
+def remove_color_codes(s: str) -> str:
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
+ return ansi_escape.sub("", s)
+
+
+logger = Logger()
+
+
+def get_logger():
+ return logger
+
+
+def typewriter_log(content="", color="", level=logging.INFO):
+ for line in content.split("\n"):
+ logger.typewriter_log(line, title_color=color, level=level)
diff --git a/agentverse/memory/__init__.py b/agentverse/memory/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe6ffc2e8392f6452c3a1ac7266a95ad2513ad7a
--- /dev/null
+++ b/agentverse/memory/__init__.py
@@ -0,0 +1,9 @@
+from agentverse.registry import Registry
+
+memory_registry = Registry(name="MemoryRegistry")
+
+from .base import BaseMemory
+from .chat_history import ChatHistoryMemory
+from .summary import SummaryMemory
+from .sde_team import SdeTeamMemory
+from .vectorstore import VectorStoreMemory
diff --git a/agentverse/memory/base.py b/agentverse/memory/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e8de896292d93d99bfae3eacbe8ad85b021a6fc
--- /dev/null
+++ b/agentverse/memory/base.py
@@ -0,0 +1,23 @@
+from abc import abstractmethod
+from typing import Dict, List
+
+from pydantic import BaseModel, Field
+
+from agentverse.message import Message
+
+
+class BaseMemory(BaseModel):
+ @abstractmethod
+ def add_message(self, messages: List[Message]) -> None:
+ pass
+
+ @abstractmethod
+ def to_string(self) -> str:
+ pass
+
+ @abstractmethod
+ def reset(self) -> None:
+ pass
+
+ def to_messages(self) -> List[dict]:
+ pass
diff --git a/agentverse/memory/chat_history.py b/agentverse/memory/chat_history.py
new file mode 100644
index 0000000000000000000000000000000000000000..38649d40292a4dadfa92721dc175a1e17b90eebd
--- /dev/null
+++ b/agentverse/memory/chat_history.py
@@ -0,0 +1,77 @@
+import json
+from typing import List
+
+from pydantic import Field
+
+from agentverse.message import Message, ExecutorMessage
+
+from . import memory_registry
+from .base import BaseMemory
+
+
+@memory_registry.register("chat_history")
+class ChatHistoryMemory(BaseMemory):
+ messages: List[Message] = Field(default=[])
+
+ def add_message(self, messages: List[Message]) -> None:
+ for message in messages:
+ self.messages.append(message)
+
+ def to_string(self, add_sender_prefix: bool = False) -> str:
+ if add_sender_prefix:
+ return "\n".join(
+ [
+ f"[{message.sender}]: {message.content}"
+ if message.sender != ""
+ else message.content
+ for message in self.messages
+ ]
+ )
+ else:
+ return "\n".join([message.content for message in self.messages])
+
+ def to_messages(self, my_name: str = "", start_index: int = 0) -> List[dict]:
+ messages = []
+ for message in self.messages[start_index:]:
+ if message.sender == my_name:
+ if isinstance(message, ExecutorMessage):
+ if message.tool_name != "":
+ messages.append(
+ {
+ "role": "assistant",
+ "content": f"[{message.sender}]: {message.content}"
+ if message.content != ""
+ else "",
+ "function_call": {
+ "name": message.tool_name,
+ "arguments": json.dumps(message.tool_input),
+ },
+ }
+ )
+ continue
+ messages.append(
+ {
+ "role": "assistant",
+ "content": f"[{message.sender}]: {message.content}",
+ }
+ )
+ continue
+ if message.sender == "function":
+ messages.append(
+ {
+ "role": "function",
+ "content": message.content,
+ "name": message.tool_name,
+ }
+ )
+ continue
+ messages.append(
+ {
+ "role": "assistant",
+ "content": f"[{message.sender}]: {message.content}",
+ }
+ )
+ return messages
+
+ def reset(self) -> None:
+ self.messages = []
diff --git a/agentverse/memory/sde_team.py b/agentverse/memory/sde_team.py
new file mode 100644
index 0000000000000000000000000000000000000000..91a7afd64f43bbdf5953b1a71972c071eed278d3
--- /dev/null
+++ b/agentverse/memory/sde_team.py
@@ -0,0 +1,38 @@
+import re
+from string import Template
+from typing import List
+
+from pydantic import Field, validator
+
+from agentverse.initialization import load_llm
+from agentverse.llms.base import BaseLLM
+from agentverse.message import Message
+
+from . import memory_registry
+from .base import BaseMemory
+
+
+@memory_registry.register("sde_team")
+class SdeTeamMemory(BaseMemory):
+ """SdeTeamMemory is a memory for SdeTeamEnvironment.
+ It is a simple memory that only stores the most recent info in the buffer.
+ TODO: add summarized history
+ """
+ buffer: str = Field(default="")
+
+ def add_message(self, messages: List[Message]) -> None:
+ new_lines = "\n".join([message.content for message in messages])
+ if messages[0].sender == "code_writer":
+ self.buffer = new_lines
+ elif messages[0].sender == "code_tester":
+ self.buffer += "\n\n"
+ self.buffer += new_lines
+ elif messages[0].sender == "code_reviewer":
+ self.buffer += "\n\n"
+ self.buffer += new_lines
+
+ def to_string(self, *args, **kwargs) -> str:
+ return self.buffer
+
+ def reset(self) -> None:
+ self.buffer = ""
diff --git a/agentverse/memory/summary.py b/agentverse/memory/summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..84bd9839329b8513f1a994be4564495daa8b1247
--- /dev/null
+++ b/agentverse/memory/summary.py
@@ -0,0 +1,87 @@
+import re
+from string import Template
+from typing import List
+
+from pydantic import Field, validator
+
+from agentverse.initialization import load_llm
+from agentverse.llms.base import BaseLLM
+from agentverse.message import Message
+
+from . import memory_registry
+from .base import BaseMemory
+
+
+@memory_registry.register("summary")
+class SummaryMemory(BaseMemory):
+ llm: BaseLLM
+ messages: List[Message] = Field(default=[])
+ buffer: str = Field(default="")
+ recursive: bool = Field(default=False)
+ prompt_template: str = Field(default="")
+
+ def __init__(self, *args, **kwargs):
+ llm_config = kwargs.pop("llm")
+ llm = load_llm(llm_config)
+ super().__init__(llm=llm, *args, **kwargs)
+
+ @validator("prompt_template")
+ def check_prompt_template(cls, v, values):
+ """Check if the prompt template is valid.
+ When recursive is True, the prompt template should contain the following arguments:
+ - $summary: The summary so far.
+ - $new_lines: The new lines to be added to the summary.
+
+ Otherwise, the prompt template should only contain $new_lines
+ """
+ recursive = values.get("recursive")
+ summary_pat = re.compile(r"\$\{?summary\}?")
+ new_lines_pat = re.compile(r"\$\{?new_lines\}?")
+ if recursive:
+ if not summary_pat.search(v):
+ raise ValueError(
+ "When recursive is True, the prompt template should contain $summary."
+ )
+ if not new_lines_pat.search(v):
+ raise ValueError(
+ "When recursive is True, the prompt template should contain $new_lines."
+ )
+ else:
+ if summary_pat.search(v):
+ raise ValueError(
+ "When recursive is False, the prompt template should not contain $summary."
+ )
+ if not new_lines_pat.search(v):
+ raise ValueError(
+ "When recursive is False, the prompt template should contain $new_lines."
+ )
+ return v
+
+ def add_message(self, messages: List[Message]) -> None:
+ new_lines = "\n".join([message.content for message in messages])
+ self.update_buffer(new_lines)
+
+ def update_buffer(self, new_message: str):
+ prompt = self._fill_in_prompt_template(new_message)
+ response = self.llm.generate_response(prompt)
+ if self.recursive:
+ self.buffer = response.content
+ else:
+ self.buffer = "\n" + response.content
+
+ def _fill_in_prompt_template(self, new_lines: str) -> str:
+ """Fill in the prompt template with the given arguments.
+
+ SummaryMemory supports the following arguments:
+ - summary: The summary so far.
+ - new_lines: The new lines to be added to the summary.
+ """
+ input_arguments = {"summary": self.buffer, "new_lines": new_lines}
+ return Template(self.prompt_template).safe_substitute(input_arguments)
+
+ def to_string(self, *args, **kwargs) -> str:
+ return self.buffer
+
+ def reset(self) -> None:
+ self.messages = []
+ self.buffer = ""
diff --git a/agentverse/memory/vectorstore.py b/agentverse/memory/vectorstore.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f8de5cb5abbdd86c8b2416af3a10065312b5eaa
--- /dev/null
+++ b/agentverse/memory/vectorstore.py
@@ -0,0 +1,63 @@
+from typing import List, Union
+
+from pydantic import Field
+
+from agentverse.message import Message
+from agentverse.llms import BaseLLM
+from agentverse.llms.openai import get_embedding, OpenAIChat
+
+
+from . import memory_registry
+from .base import BaseMemory
+
+
+
+@memory_registry.register("vectorstore")
+class VectorStoreMemory(BaseMemory):
+
+ """
+
+ The main difference of this class with chat_history is that this class treat memory as a dict
+
+ treat message.content as memory
+
+ Attributes:
+ messages (List[Message]) : used to store messages, message.content is the key of embeddings.
+ embedding2memory (dict) : `key` is the embedding and `value` is the message
+ memory2embedding (dict) : `key` is the message and `value` is the embedding
+ llm (BaseLLM) : llm used to get embeddings
+
+
+ Methods:
+ add_message : Additionally, add the embedding to embeddings
+
+ """
+
+ messages: List[Message] = Field(default=[])
+ embedding2memory: dict = {}
+ memory2embedding: dict = {}
+ llm: BaseLLM = OpenAIChat(model="gpt-4")
+
+ def add_message(self, messages: List[Message]) -> None:
+ for message in messages:
+ self.messages.append(message)
+ memory_embedding = get_embedding(message.content)
+ self.embedding2memory[memory_embedding] = message.content
+ self.memory2embedding[message.content] = memory_embedding
+
+ def to_string(self, add_sender_prefix: bool = False) -> str:
+ if add_sender_prefix:
+ return "\n".join(
+ [
+ f"[{message.sender}]: {message.content}"
+ if message.sender != ""
+ else message.content
+ for message in self.messages
+ ]
+ )
+ else:
+ return "\n".join([message.content for message in self.messages])
+
+ def reset(self) -> None:
+ self.messages = []
+
diff --git a/agentverse/memory_manipulator/__init__.py b/agentverse/memory_manipulator/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d28698a83612f0225cb38a2f71b3a6c04bb8790
--- /dev/null
+++ b/agentverse/memory_manipulator/__init__.py
@@ -0,0 +1,9 @@
+from agentverse.registry import Registry
+
+memory_manipulator_registry = Registry(name="Memory_Manipulator_Registry")
+
+from .base import BaseMemoryManipulator
+from .basic import BasicMemoryManipulator
+from .reflection import Reflection
+from .plan import Plan
+
diff --git a/agentverse/memory_manipulator/base.py b/agentverse/memory_manipulator/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..81e7c58d22f1448c3016489ee66b7dd774e08bd0
--- /dev/null
+++ b/agentverse/memory_manipulator/base.py
@@ -0,0 +1,17 @@
+from abc import abstractmethod
+from typing import Dict, List
+
+from pydantic import BaseModel, Field
+
+from agentverse.message import Message
+
+
+class BaseMemoryManipulator(BaseModel):
+
+ @abstractmethod
+ def manipulate_memory(self) -> None:
+ pass
+
+ @abstractmethod
+ def reset(self) -> None:
+ pass
diff --git a/agentverse/memory_manipulator/basic.py b/agentverse/memory_manipulator/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..bef396becd4dd454d3e689e7693577ad96bccf20
--- /dev/null
+++ b/agentverse/memory_manipulator/basic.py
@@ -0,0 +1,17 @@
+from abc import abstractmethod
+from typing import Dict, List
+
+from pydantic import BaseModel, Field
+
+from agentverse.message import Message
+from agentverse.memory_manipulator import BaseMemoryManipulator
+from . import memory_manipulator_registry
+
+@memory_manipulator_registry.register("basic")
+class BasicMemoryManipulator(BaseMemoryManipulator):
+
+ def manipulate_memory(self) -> None:
+ pass
+
+ def reset(self) -> None:
+ pass
diff --git a/agentverse/memory_manipulator/plan.py b/agentverse/memory_manipulator/plan.py
new file mode 100644
index 0000000000000000000000000000000000000000..530190c0f9d233e4ceed3385fa5836b3214b8c4a
--- /dev/null
+++ b/agentverse/memory_manipulator/plan.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+from logging import getLogger
+from typing import List, TYPE_CHECKING
+
+from . import memory_manipulator_registry
+from .base import BaseMemoryManipulator
+from ..message import Message
+
+if TYPE_CHECKING:
+ from agentverse.memory import VectorStoreMemory
+ from agentverse.agents.reflection_agent import ReflectionAgent
+
+logger = getLogger(__file__)
+
+PLAN_PROMPT = """Now you are act for as an agent named ${agent_name} in a virtual world.
+You might need to performing reaction to the observation.
+Based on the following information:
+(1) The agent's description: ${role_description}
+(2) Current time is ${current_time}
+(3) Your history memory is ${chat_history}
+
+Now is ${current_time}. If all plans are expired, you have to plan for\
+the next time periods.
+Do you need to generate new plans?
+If yes, tell me the new plan, including the time period.
+If no, just tell me No."""
+
+
+@memory_manipulator_registry.register("plan")
+class Plan(BaseMemoryManipulator):
+ """
+ Memory manipulator for plan.
+ """
+ memory: VectorStoreMemory = None
+ agent: ReflectionAgent = None # specify ReflectionAgent
+ # later considering removing current_time to be more general
+ # and then change to BaseAgent
+ plan: List[str] = []
+
+ def manipulate_memory(self) -> str:
+ """
+ Generate new plans
+ """
+ prompt = self._fill_prompt_template()
+ result = self.agent.llm.generate_response(prompt).content
+ result = result.strip('.')
+ logger.info(f"{self.agent.name}'s new plan: {result}")
+ if result == "No":
+ return ""
+ else:
+ self.plan.append(result)
+ plan_message = Message(
+ content=result,
+ sender=self.agent.name,
+ receiver={self.agent.name})
+ self.agent.memory.add_message([plan_message])
+ return result
+
+
+ def _fill_prompt_template(self) -> str:
+ """Fill the placeholders in the prompt template
+
+ In the conversation agent, three placeholders are supported:
+ - ${agent_name}: the name of the agent
+ - ${env_description}: the description of the environment
+ - ${role_description}: the description of the role of the agent
+ - ${chat_history}: the chat history of the agent
+ """
+ input_arguments = {
+ "agent_name": self.agent.name,
+ "role_description": self.agent.role_description,
+ "chat_history": self.agent.memory.to_string(add_sender_prefix=True),
+ "current_time": self.agent.current_time,
+ }
+ return PLAN_PROMPT.format(**input_arguments)
+
+ def reset(self) -> None:
+ pass
diff --git a/agentverse/memory_manipulator/reflection.py b/agentverse/memory_manipulator/reflection.py
new file mode 100644
index 0000000000000000000000000000000000000000..6132ca24e1d6e8733d7a2839286da803f758f920
--- /dev/null
+++ b/agentverse/memory_manipulator/reflection.py
@@ -0,0 +1,330 @@
+from __future__ import annotations
+from typing import List, Union, Optional, Any, TYPE_CHECKING
+from collections import defaultdict
+
+from pydantic import Field
+import numpy as np
+from datetime import datetime as dt
+
+import re
+
+from agentverse.llms.openai import get_embedding
+from sklearn.metrics.pairwise import cosine_similarity
+
+from agentverse.message import Message
+from agentverse.memory import BaseMemory
+
+from agentverse.logging import logger
+
+from . import memory_manipulator_registry
+from .base import BaseMemoryManipulator
+
+if TYPE_CHECKING:
+ from agentverse.memory import VectorStoreMemory
+ from agentverse.agents.base import BaseAgent
+
+
+IMPORTANCE_PROMPT = """On the scale of 1 to 10, where 1 is purely mundane \
+(e.g., brushing teeth, making bed) and 10 is \
+extremely poignant (e.g., a break up, college \
+acceptance), rate the likely poignancy of the \
+following piece of memory. \
+If you think it's too hard to rate it, you can give an inaccurate assessment. \
+The content or people mentioned is not real. You can hypothesis any reasonable context. \
+Please strictly only output one number. \
+Memory: {} \
+Rating: """
+IMMEDIACY_PROMPT = """On the scale of 1 to 10, where 1 is requiring no short time attention\
+(e.g., a bed is in the room) and 10 is \
+needing quick attention or immediate response(e.g., being required a reply by others), rate the likely immediacy of the \
+following statement. \
+If you think it's too hard to rate it, you can give an inaccurate assessment. \
+The content or people mentioned is not real. You can hypothesis any reasonable context. \
+Please strictly only output one number. \
+Memory: {} \
+Rating: """
+QUESTION_PROMPT = """Given only the information above, what are 3 most salient \
+high-level questions we can answer about the subjects in the statements?"""
+
+INSIGHT_PROMPT = """What at most 5 high-level insights can you infer from \
+the above statements? Only output insights with high confidence.
+example format: insight (because of 1, 5, 3)"""
+
+
+@memory_manipulator_registry.register("reflection")
+class Reflection(BaseMemoryManipulator):
+ memory: VectorStoreMemory = None
+ agent: BaseAgent = None
+
+ reflection: str = ""
+
+ importance_threshold: int = 10
+ accumulated_importance: int = 0
+
+ memory2importance: dict = {}
+ memory2immediacy: dict = {}
+ memory2time: defaultdict = Field(default=defaultdict(dict))
+
+ # TODO newly added func from generative agents
+
+ def manipulate_memory(self) -> None:
+ # reflect here
+ if self.should_reflect():
+ logger.debug(
+ f"Agent {self.agent.name} is now doing reflection since accumulated_importance={self.accumulated_importance} < reflection_threshold={self.importance_threshold}"
+ )
+ self.reflection = self.reflect()
+ return self.reflection
+ else:
+ logger.debug(
+ f"Agent {self.agent.name} doesn't reflect since accumulated_importance={self.accumulated_importance} < reflection_threshold={self.importance_threshold}"
+ )
+
+ return ""
+
+ def get_accumulated_importance(self):
+ accumulated_importance = 0
+
+ for memory in self.memory.messages:
+ if (
+ memory.content not in self.memory2importance
+ or memory.content not in self.memory2immediacy
+ ):
+ self.memory2importance[memory.content] = self.get_importance(
+ memory.content
+ )
+ self.memory2immediacy[memory.content] = self.get_immediacy(
+ memory.content
+ )
+
+ for score in self.memory2importance.values():
+ accumulated_importance += score
+
+ self.accumulated_importance = accumulated_importance
+
+ return accumulated_importance
+
+ def should_reflect(self):
+ if self.get_accumulated_importance() >= self.importance_threshold:
+ # double the importance_threshold
+ self.importance_threshold *= 2
+ return True
+ else:
+ return False
+
+ def get_questions(self, texts):
+ prompt = "\n".join(texts) + "\n" + QUESTION_PROMPT
+ result = self.agent.llm.generate_response(prompt)
+ result = result.content
+ questions = [q for q in result.split("\n") if len(q.strip()) > 0]
+ questions = questions[:3]
+ return questions
+
+ def get_insights(self, statements):
+ prompt = ""
+ for i, st in enumerate(statements):
+ prompt += str(i + 1) + ". " + st + "\n"
+ prompt += INSIGHT_PROMPT
+ result = self.agent.llm.generate_response(prompt)
+ result = result.content
+ insights = [isg for isg in result.split("\n") if len(isg.strip()) > 0][:5]
+ insights = [".".join(i.split(".")[1:]) for i in insights]
+ # remove insight pointers for now
+ insights = [i.split("(")[0].strip() for i in insights]
+ return insights
+
+ def get_importance(self, content: str):
+ """
+ Exploit GPT to evaluate the importance of this memory
+ """
+ prompt = IMPORTANCE_PROMPT.format(content)
+ result = self.memory.llm.generate_response(prompt)
+
+ try:
+ score = int(re.findall(r"\s*(\d+)\s*", result.content)[0])
+ except Exception as e:
+ logger.warn(
+ f"Found error {e} Abnormal result of importance rating '{result}'. Setting default value"
+ )
+ score = 0
+ return score
+
+ def get_immediacy(self, content: str):
+ """
+ Exploit GPT to evaluate the immediacy of this memory
+ """
+ prompt = IMMEDIACY_PROMPT.format(content)
+ result = self.memory.llm.generate_response(prompt)
+ try:
+ score = int(re.findall(r"\s*(\d+)\s*", result.content)[0])
+ except Exception as e:
+ logger.warn(
+ f"Found error {e} Abnormal result of importance rating '{result}'. Setting default value"
+ )
+ score = 0
+ return score
+
+ def query_similarity(
+ self,
+ text: Union[str, List[str]],
+ k: int,
+ memory_bank: List,
+ current_time=dt.now(),
+ nms_threshold=0.99,
+ ) -> List[str]:
+ """
+ get top-k entry based on recency, relevance, importance, immediacy
+ The query result can be Short-term or Long-term queried result.
+ formula is
+ `score= sim(q,v) *max(LTM_score, STM_score)`
+ `STM_score=time_score(createTime)*immediacy`
+ `LTM_score=time_score(accessTime)*importance`
+ time score is exponential decay weight. stm decays faster.
+
+ The query supports querying based on multiple texts and only gives non-overlapping results
+ If nms_threshold is not 1, nms mechanism if activated. By default,
+ use soft nms with modified iou base(score starts to decay iff cos sim is higher than this value,
+ and decay weight at this value if 0. rather than 1-threshold).
+
+ Args:
+ text: str
+ k: int
+ memory_bank: List
+ current_time: dt.now
+ nms_threshold: float = 0.99
+
+
+ Returns: List[str]
+ """
+ assert len(text) > 0
+ texts = [text] if isinstance(text, str) else text
+ maximum_score = None
+ for text in texts:
+ embedding = get_embedding(text)
+ score = []
+ for memory in memory_bank:
+ if memory.content not in self.memory2time:
+ self.memory2time[memory.content]["last_access_time"] = dt.now()
+ self.memory2time[memory.content]["create_time"] = dt.now()
+
+ last_access_time_diff = (
+ current_time - self.memory2time[memory.content]["last_access_time"]
+ ).total_seconds() // 3600
+ recency = np.power(
+ 0.99, last_access_time_diff
+ ) # TODO: review the metaparameter 0.99
+
+ create_time_diff = (
+ current_time - self.memory2time[memory.content]["create_time"]
+ ).total_seconds() // 60
+ instancy = np.power(
+ 0.90, create_time_diff
+ ) # TODO: review the metaparameter 0.90
+
+ relevance = cosine_similarity(
+ np.array(embedding).reshape(1, -1),
+ np.array(self.memory.memory2embedding[memory.content]).reshape(
+ 1, -1
+ ),
+ )[0][0]
+
+ if (
+ memory.content not in self.memory2importance
+ or memory.content not in self.memory2immediacy
+ ):
+ self.memory2importance[memory.content] = self.get_importance(
+ memory.content
+ )
+ self.memory2immediacy[memory.content] = self.get_immediacy(
+ memory.content
+ )
+
+ importance = self.memory2importance[memory.content] / 10
+ immediacy = self.memory2immediacy[memory.content] / 10
+
+ ltm_w = recency * importance
+ stm_w = instancy * immediacy
+
+ score.append(relevance * np.maximum(ltm_w, stm_w))
+
+ score = np.array(score)
+
+ if maximum_score is not None:
+ maximum_score = np.maximum(score, maximum_score)
+ else:
+ maximum_score = score
+
+ if nms_threshold == 1.0:
+ # no nms is triggered
+ top_k_indices = np.argsort(maximum_score)[-k:][::-1]
+ else:
+ # TODO: soft-nms
+ assert 0 <= nms_threshold < 1
+ top_k_indices = []
+ while len(top_k_indices) < min(k, len(memory_bank)):
+ top_index = np.argmax(maximum_score)
+ top_k_indices.append(top_index)
+ maximum_score[top_index] = -1 # anything to prevent being chosen again
+ top_embedding = self.memory.memory2embedding[
+ memory_bank[top_index].content
+ ]
+ cos_sim = cosine_similarity(
+ np.array(top_embedding).reshape(1, -1),
+ np.array(
+ [
+ self.memory.memory2embedding[memory.content]
+ for memory in memory_bank
+ ]
+ ),
+ )[0]
+ score_weight = np.ones_like(maximum_score)
+ score_weight[cos_sim >= nms_threshold] -= (
+ cos_sim[cos_sim >= nms_threshold] - nms_threshold
+ ) / (1 - nms_threshold)
+ maximum_score = maximum_score * score_weight
+
+ # access them and refresh the access time
+ for i in top_k_indices:
+ self.memory2time[memory_bank[i].content]["last_access_time"] = current_time
+ # sort them in time periods. if the data tag is 'observation', ad time info output.
+ top_k_indices = sorted(
+ top_k_indices,
+ key=lambda x: self.memory2time[memory_bank[x].content]["create_time"],
+ )
+ query_results = []
+ for i in top_k_indices:
+ query_result = memory_bank[i].content
+ query_results.append(query_result)
+
+ return query_results
+
+ def get_memories_of_interest_oneself(self):
+ memories_of_interest = []
+ for memory in self.memory.messages[-100:]:
+ if memory.sender == self.agent.name:
+ memories_of_interest.append(memory)
+ return memories_of_interest
+
+ def reflect(self):
+ """
+ initiate a reflection that inserts high level knowledge to memory
+ """
+ memories_of_interest = self.get_memories_of_interest_oneself()
+ questions = self.get_questions([m.content for m in memories_of_interest])
+ statements = self.query_similarity(
+ questions, len(questions) * 10, memories_of_interest
+ )
+ insights = self.get_insights(statements)
+ logger.info(self.agent.name + f" Insights: {insights}")
+ for insight in insights:
+ # convert insight to messages
+ # TODO currently only oneself can see its own reflection
+ insight_message = Message(
+ content=insight, sender=self.agent.name, receiver={self.agent.name}
+ )
+ self.memory.add_message([insight_message])
+ reflection = "\n".join(insights)
+ return reflection
+
+ def reset(self) -> None:
+ self.reflection = ""
diff --git a/agentverse/message.py b/agentverse/message.py
new file mode 100644
index 0000000000000000000000000000000000000000..34d0752c86ae12fb1f735aaf95da9c7444f0bb63
--- /dev/null
+++ b/agentverse/message.py
@@ -0,0 +1,35 @@
+from pydantic import BaseModel, Field
+from typing import List, Tuple, Set, Union, Any
+
+from agentverse.utils import AgentAction
+
+
+class Message(BaseModel):
+ content: Any = Field(default="")
+ sender: str = Field(default="")
+ receiver: Set[str] = Field(default=set({"all"}))
+ sender_agent: object = Field(default=None)
+ tool_response: List[Tuple[AgentAction, str]] = Field(default=[])
+
+
+class SolverMessage(Message):
+ pass
+
+
+class CriticMessage(Message):
+ is_agree: bool
+ criticism: str = ""
+
+
+class ExecutorMessage(Message):
+ tool_name: str = Field(default="")
+ tool_input: Any = None
+
+
+class EvaluatorMessage(Message):
+ score: Union[bool, List[bool], int, List[int]]
+ advice: str = Field(default="")
+
+
+class RoleAssignerMessage(Message):
+ pass
diff --git a/agentverse/output_parser/__init__.py b/agentverse/output_parser/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b54c8edd671343e85508b1e33e8d40c12415cb8
--- /dev/null
+++ b/agentverse/output_parser/__init__.py
@@ -0,0 +1,5 @@
+from agentverse.registry import Registry
+
+output_parser_registry = Registry(name="OutputParserRegistry")
+
+from .output_parser import *
\ No newline at end of file
diff --git a/agentverse/output_parser/output_parser.py b/agentverse/output_parser/output_parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..556d9ff6e6b8addc1b45aff0dd7e8e8be51e24a4
--- /dev/null
+++ b/agentverse/output_parser/output_parser.py
@@ -0,0 +1,621 @@
+from __future__ import annotations
+
+import re
+from abc import abstractmethod
+import json
+from typing import Union, List, Tuple, NamedTuple, TYPE_CHECKING
+
+from . import output_parser_registry
+
+from agentverse.utils import AgentAction, AgentFinish, AgentCriticism
+
+from agentverse.llms import LLMResult
+from agentverse.logging import logger
+
+from pydantic import BaseModel
+
+if TYPE_CHECKING:
+ from agentverse.agents.base import BaseAgent
+ from agentverse.environments.base import BaseEnvironment
+
+class OutputParserError(Exception):
+ """Exception raised when parsing output from a command fails."""
+
+ def __init__(self, message):
+ self.message = message
+
+ def __str__(self):
+ return "Failed to parse output of the model:%s\n " % self.message
+
+
+class OutputParser(BaseModel):
+ """Base class for output parsers."""
+
+ @abstractmethod
+ def parse(self, output: LLMResult) -> NamedTuple:
+ pass
+
+
+@output_parser_registry.register("alice_home")
+class AliceHomeParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Thought:")
+ and cleaned_output[1].startswith("Action:")
+ ):
+ raise OutputParserError(text)
+
+ action = cleaned_output[1][len("Action:") :].strip()
+
+ return AgentFinish({"output": action}, text)
+
+
+@output_parser_registry.register("db_diag")
+@output_parser_registry.register("nlp_classroom_3players_withtool")
+class CommonParser1(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 3
+ and cleaned_output[0].startswith("Thought:")
+ and cleaned_output[1].startswith("Action:")
+ and cleaned_output[2].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[1][len("Action:") :].strip()
+ action_input = cleaned_output[2][len("Action Input:") :].strip()
+ if action in ["Speak"]:
+ return AgentFinish({"output": action_input}, text)
+ elif action == "CallOn":
+ return AgentFinish({"output": "[CallOn] " + action_input}, text)
+ elif action == "RaiseHand":
+ return AgentFinish({"output": "[RaiseHand] " + action_input}, text)
+ elif action == "Listen":
+ return AgentFinish({"output": ""}, text)
+ else:
+ return AgentAction(action.lower(), action_input, text)
+
+
+@output_parser_registry.register("math_problem_2players_tools")
+class MathProblem2PlayersToolsParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Action:")
+ and cleaned_output[1].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[0][len("Action:") :].strip()
+ action_input = cleaned_output[1][len("Action Input:") :].strip()
+ if action == "Speak":
+ return AgentFinish({"output": action_input}, text)
+ else:
+ return AgentAction(action, action_input, text)
+
+
+@output_parser_registry.register("nlp_classroom_3players")
+class NlpClassroom3PlayersParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Action:")
+ and cleaned_output[1].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[0][len("Action:") :].strip()
+ action_input = cleaned_output[1][len("Action Input:") :].strip()
+ if action == "Speak":
+ return AgentFinish({"output": action_input}, text)
+ else:
+ raise OutputParserError(text)
+
+
+@output_parser_registry.register("nlp_classroom_9players")
+class NlpClassroom9PlayersParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Action:")
+ and cleaned_output[1].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[0][len("Action:") :].strip()
+ action_input = cleaned_output[1][len("Action Input:") :].strip()
+ if action == "Speak":
+ return AgentFinish({"output": action_input}, text)
+ elif action == "CallOn":
+ return AgentFinish({"output": "[CallOn] " + action_input}, text)
+ elif action == "RaiseHand":
+ return AgentFinish({"output": "[RaiseHand] " + action_input}, text)
+ elif action == "Listen":
+ return AgentFinish({"output": ""}, text)
+ else:
+ return AgentAction(action, action_input, text)
+
+
+@output_parser_registry.register("nlp_classroom_9players_group")
+class NlpClassroom9PlayersGroupParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Action:")
+ and cleaned_output[1].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[0][len("Action:") :].strip()
+ action_input = cleaned_output[1][len("Action Input:") :].strip()
+ if action == "Speak":
+ return AgentFinish({"output": action_input}, text)
+ elif action in ["CallOn", "RaiseHand", "GroupDiscuss"]:
+ return AgentFinish({"output": f"[{action}] {action_input}"}, text)
+ elif action == "Listen":
+ return AgentFinish({"output": ""}, text)
+ else:
+ return AgentAction(action, action_input, text)
+
+
+@output_parser_registry.register("pokemon")
+class PokemonParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 3
+ and cleaned_output[0].startswith("Thought:")
+ and cleaned_output[1].startswith("Action:")
+ and cleaned_output[2].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[1][len("Action:") :].strip()
+ action_input = cleaned_output[2][len("Action Input:") :].strip()
+ try:
+ action_input = json.loads(action_input)
+ except json.JSONDecodeError:
+ raise OutputParserError(text)
+ action_input["action"] = action
+ return AgentFinish({"output": json.dumps(action_input)}, text)
+
+
+@output_parser_registry.register("prisoner_dilemma")
+class PrisonerDilemmaParser(OutputParser):
+ # make sure 1 1 2 2 3 3
+ cur_round: int = 1
+ encounter_cur_round: bool = False
+
+ def parse(
+ self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMResult
+ ) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ cleaned_output = text.strip()
+ cleaned_output = re.sub(r"\n+", "\n", cleaned_output)
+ cleaned_output = cleaned_output.split("\n")
+ if not (
+ len(cleaned_output) == 2
+ and cleaned_output[0].startswith("Action:")
+ and cleaned_output[1].startswith("Action Input:")
+ ):
+ raise OutputParserError(text)
+ action = cleaned_output[0][len("Action:") :].strip()
+ action_input = cleaned_output[1][len("Action Input:") :].strip()
+
+ if action == "Speak":
+ # make sure the police count the round right
+ # if agent.name == "Police":
+ # action_input = re.sub(r'Round (\d+)', f'Round {self.cur_round}', action_input)
+ # self.cur_round += 1
+ # if self.encounter_cur_round:
+ # self.encounter_cur_round = False
+ # self.cur_round += 1
+ # else:
+ # self.encounter_cur_round = True
+
+ # each time police speak is a new round
+ if agent.name == "Police":
+ if environment.cnt_turn == (environment.max_turns - 4):
+ action_input = (
+ "Attention! You are now required to made your final decision and I will made the "
+ "final judgement to both of you based on this time, Please Answer now !"
+ )
+
+ elif environment.cnt_turn == (environment.max_turns - 2):
+ action_input = "Attention! Suspect2, it's now your time to make your final decision, Please Answer now !"
+
+ # elif self.cur_round == 1:
+ # action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \
+ # "But you should comply with the following rules:" \
+ # "- If one of you are willing to testifies against the other and the other one remains silent, then the one who testifies will be released IMMEDIATELY, while the silent one will be sentenced to TEN years in prison." \
+ # "- If both of you remain silent, you will each receive a sentence of ONE year in prison." \
+ # "- It seems that always testifying is a goog strategy, So! if you both choose to testify against each other, you will each receive a sentence of FIVE years in prison." \
+ # "Now, it's your time to consider testifying or remaining silent. Remember this is a best chance you might ever have to walk out of here without guilty." \
+ # "I will noticed both of you WHEN you have to make your final decision! Before that, try to make your best!" \
+
+ self.cur_round += 1
+
+ return AgentFinish({"output": action_input}, text)
+ else:
+ raise OutputParserError(text)
+
+
+@output_parser_registry.register("sde_team/sde_team_2players")
+@output_parser_registry.register("sde_team/sde_team_3players")
+@output_parser_registry.register("commongen")
+@output_parser_registry.register("humaneval-manager")
+@output_parser_registry.register("mgsm")
+@output_parser_registry.register("dummy")
+@output_parser_registry.register("responsegen")
+class CommonParser2(OutputParser):
+ # def parse(self, agent, env, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ return AgentFinish({"output": output.content}, output.content)
+
+
+@output_parser_registry.register("role_assigner")
+class RoleAssignerParser(OutputParser):
+ cnt_critic_agents: int = 0
+
+ def parse(self, output: LLMResult) -> List[str]:
+ text = output.content
+ pattern = re.compile(r"\d\.\s*(.+)")
+ roles = pattern.findall(text)
+ if len(roles) < self.cnt_critic_agents:
+ logger.error(
+ f"Role assigner failed to assign roles to {self.cnt_critic_agents} critics!"
+ )
+ raise OutputParserError(text)
+ return roles
+
+
+@output_parser_registry.register("evaluator")
+class EvaluatorParser(OutputParser):
+ dimensions: List[str] = None
+
+ def parse(self, output: LLMResult) -> Tuple[List[int], str]:
+ text = output.content
+ cleaned_output = re.sub(r"\n+", "\n", text.strip())
+ checks = cleaned_output.split("\n")
+ patterns = [
+ re.compile(r"(?:\d\.\s*)?" + dimension + r":\s*(\d)")
+ for dimension in self.dimensions
+ ]
+ try:
+ # find score and advice
+ score = [
+ int(pattern.findall(checks[i])[0]) for i, pattern in enumerate(patterns)
+ ]
+ advice_text = "".join(checks[len(self.dimensions) :])
+ advice = re.findall(r"(?:\d\.\s*)?Advice:\s*(.+)", advice_text)[0]
+ # logger.info("Evaluator give the following advice:\n" + advice)
+ except (IndexError, ValueError):
+ # logger.error("Bad response from evaluator!")
+ raise OutputParserError(text)
+ return score, advice
+
+
+@output_parser_registry.register("humaneval-solver")
+class HumanevalSolverParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ # start_pos = text.find("```")
+ # end_pos = text.rfind("```")
+ # if end_pos == -1:
+ # raise OutputParserError(text)
+ # text = text[start_pos:end_pos]
+ # cleaned_output = text.strip().strip("```").strip()
+ # if cleaned_output.startswith("python"):
+ # cleaned_output = cleaned_output[6:].strip()
+ # elif cleaned_output.startswith("python3"):
+ # cleaned_output = cleaned_output[7:].strip()
+ code = re.findall(r"```.*?\n(.+?)```", text, re.DOTALL)[-1]
+
+ return AgentFinish({"output": code}, text)
+
+
+@output_parser_registry.register("humaneval-executor")
+class HumanevalSolverParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ try:
+ parsed_result = re.findall(
+ r"Thought:(.+?)Reasoning:(.+?)Criticism:(.+?)File Path:(.+?)Code:(.+?)Command:(.+)",
+ text,
+ re.DOTALL,
+ )[0]
+ cleaned_output = {
+ "thought": parsed_result[0].strip(),
+ "reasoning": parsed_result[1].strip(),
+ "criticism": parsed_result[2].strip(),
+ "file_path": parsed_result[3].strip().strip("`"),
+ "code": parsed_result[4]
+ .strip()
+ .strip("```")
+ .strip("python")
+ .strip("python3"),
+ "command": parsed_result[5].strip().strip("`"),
+ }
+ except BaseException as e:
+ raise OutputParserError(text)
+
+ return AgentFinish({"output": cleaned_output}, text)
+
+
+@output_parser_registry.register("humaneval-evaluator")
+class HumanevalEvaluatorParser(OutputParser):
+ dimensions: List[str] = None
+
+ def parse(self, output: LLMResult) -> Tuple[List[int], str]:
+ text = output.content
+ cleaned_output = re.sub(r"\n+", "\n", text.strip())
+ checks = cleaned_output.split("\n")
+
+ patterns = [
+ re.compile(r"(?:\d.\s*)?" + dimension + r":\s*(\d)")
+ for dimension in self.dimensions
+ ]
+
+ advice = ""
+ for check in reversed(checks):
+ advice = check + advice
+ if check.startswith("Advice:"):
+ break
+ checks[-1] = advice
+ try:
+ # find score and advice
+ score = []
+ for pattern in patterns:
+ for check in checks[:-1]:
+ if pattern.findall(check):
+ score.append(bool(int(pattern.findall(check)[0])))
+ break
+ advice = re.findall(r"(?:\d.\s*)?Advice:\s*(.+)", checks[-1])[0]
+ # logger.info("Evaluator give the following advice:\n" + advice)
+ except (IndexError, ValueError):
+ # logger.error("Bad response from evaluator!")
+ raise OutputParserError(text)
+ return score[0], advice
+
+
+@output_parser_registry.register("humaneval-critic-agree")
+class HumanevalyCriticParser(OutputParser):
+ def parse(self, output: LLMResult) -> AgentCriticism:
+ text = output.content
+ if "[Agree]" in text:
+ return AgentCriticism(True, "")
+ else:
+ return AgentCriticism(False, text)
+
+
+@output_parser_registry.register("mgsm-evaluator")
+class MGSMEvaluatorParser(OutputParser):
+ dimensions: List[str] = None
+
+ def parse(self, output: LLMResult) -> Tuple[List[int], str]:
+ text = output.content
+ cleaned_output = re.sub(r"\n+", "\n", text.strip())
+ # checks = cleaned_output.split("\n")
+
+ patterns = [
+ re.compile(r"(?:\d.\s*)?" + dimension + r":\s*(\d)")
+ for dimension in self.dimensions
+ ]
+ try:
+ # find score and advice
+ score_num = [
+ int(pattern.findall(cleaned_output)[0])
+ for i, pattern in enumerate(patterns)
+ ][0]
+ if score_num == 0:
+ score = False
+ elif score_num == 1:
+ score = True
+ else:
+ raise ValueError("Bad score!")
+ pat = re.compile(r"(?:\d.\s*)?Response:\s*(.+)", re.DOTALL)
+ advice = pat.findall(cleaned_output)[0]
+ # logger.info("Evaluator give the following advice:\n" + advice)
+ except (IndexError, ValueError):
+ # logger.error("Bad response from evaluator!")
+ raise OutputParserError(text)
+ return score, advice
+
+
+@output_parser_registry.register("mgsm-critic-agree")
+class MGSMCriticAgreeParser(OutputParser):
+ def parse(self, output: LLMResult) -> AgentCriticism:
+ text = output.content
+ text = re.sub(r"\n+", "\n", text.strip())
+ # checks = text.split("\n")
+ # if not text.startswith("Thought:"):
+ # raise OutputParserError(text)
+ # if not (checks[0].startswith("Action:")):
+ # raise OutputParserError(text)
+ # if checks[0].strip(". ") == "Action: Agree":
+ # return AgentCriticism(True, "")
+ if "[Agree]" in text:
+ return AgentCriticism(True, "")
+ else:
+ # pattern = re.compile(r"Action Input: ([\S\n ]+)")
+ # try:
+ # criticism = pattern.findall(text)[0].strip()
+ # criticism = (
+ # re.findall(r"Output:\S?(.+)", text)[0].replace("[Wrong]", "")
+ # ).strip()
+ criticism = text.replace("[Disagree]", "").strip()
+ # except IndexError:
+ # logger.error("Bad response from critic!")
+ # raise OutputParserError(text)
+ # criticism = "I think the solution is not correct. Please think carefully and correct it."
+ return AgentCriticism(False, criticism)
+ # else:
+ # raise OutputParserError(text)
+
+
+@output_parser_registry.register("responsegen-evaluator")
+class ResponseGenEvaluatorParser(OutputParser):
+ dimensions: List[str] = None
+
+ def parse(self, output: LLMResult) -> Tuple[List[int], str]:
+ text = output.content
+ cleaned_output = re.sub(r"\n+", "\n", text.strip())
+ checks = cleaned_output.split("\n")
+
+ patterns = [
+ re.compile(r"(?:\d.\s*)?" + dimension + r":\s*(\d+)")
+ for dimension in self.dimensions
+ ]
+
+ advice = ""
+ for check in reversed(checks):
+ advice = check + advice
+ if check.startswith("Advice:"):
+ break
+ checks[-1] = advice
+ try:
+ # find score and advice
+ score = [
+ int(pattern.findall(checks[i])[0]) for i, pattern in enumerate(patterns)
+ ]
+ advice = re.findall(r"(?:\d.\s*)?Advice:\s*(.+)", checks[-1])[0]
+ # logger.info("Evaluator give the following advice:\n" + advice)
+ except (IndexError, ValueError):
+ # logger.error("Bad response from evaluator!")
+ raise OutputParserError(text)
+ return score, advice
+
+
+@output_parser_registry.register("responsegen-critic")
+@output_parser_registry.register("critic")
+class CommonParser3(OutputParser):
+ def parse(self, output: LLMResult) -> AgentCriticism:
+ text = output.content
+ text = re.sub(r"\n+", "\n", text.strip())
+ checks = text.split("\n")
+ if not (checks[0].startswith("Action:")):
+ raise OutputParserError(text)
+ if checks[0].strip(". ") == "Action: Agree":
+ return AgentCriticism(True, "")
+ elif checks[0].strip(". ") == "Action: Disagree":
+ pattern = re.compile(r"Action Input: ([\S\n ]+)")
+ try:
+ criticism = pattern.findall(text)[0].strip()
+ except IndexError:
+ criticism = (
+ "I think it is not correct. Please think carefully and improve it."
+ )
+ # raise OutputParserError(text)
+ return AgentCriticism(False, criticism)
+ else:
+ raise OutputParserError(text)
+
+
+@output_parser_registry.register("responsegen-critic-2")
+class ResponseGenCriticParser(OutputParser):
+ def parse(self, output: LLMResult) -> AgentCriticism:
+ text = output.content
+ # text = re.sub(r"\n+", "\n", text.strip())
+ # checks = text.split("\n")
+ # if not (checks[0].startswith("Action:")):
+ # raise OutputParserError(text)
+ # if checks[0].strip(". ") == "Action: Agree":
+ # return AgentCriticism(True, "")
+ # elif checks[0].strip(". ") == "Action: Disagree":
+ # pattern = re.compile(r"Action Input: ([\S\n ]+)")
+ # try:
+ # criticism = pattern.findall(text)[0].strip()
+ # except IndexError:
+ # # criticism = "I think the solution is not correct. Please think carefully and correct it."
+ # raise OutputParserError(text)
+ # return AgentCriticism(False, criticism)
+ # else:
+ # raise OutputParserError(text)
+ result = re.findall(r"Decision:(.+?)Response:(.+)", text, re.DOTALL)
+ if len(result) == 0:
+ result = ["Disagree", "I think the response can be further improved."]
+ else:
+ result = result[0]
+ if "Agree" in result[0]:
+ return AgentCriticism(True, "")
+ else:
+ return AgentCriticism(False, result[1].strip())
+
+
+@output_parser_registry.register("role-description-name-assigner")
+class RoleAssignerParser(OutputParser):
+ cnt_critic_agents: int = 0
+
+ def parse(self, output: LLMResult) -> List[str]:
+ text = output.content
+ pattern = re.compile(r"\d+?\.\s*(.+?) - (.+)")
+ roles = pattern.findall(text)
+ if len(roles) < self.cnt_critic_agents:
+ logger.error(
+ f"Role assigner failed to assign roles to {self.cnt_critic_agents} critics!"
+ )
+ raise OutputParserError(text)
+ res = []
+ for role in roles:
+ res.append({"name": role[0], "description": role[1]})
+ return res
+
+
+@output_parser_registry.register("tool-using-solver")
+class SolverParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ text = output.content
+ pattern = re.compile(r"\d+?\.\s*(.+?) - (.+)")
+ tasks = pattern.findall(text)
+ if len(tasks) == 0:
+ raise OutputParserError(text)
+ return AgentFinish({"output": tasks}, text)
+
+
+@output_parser_registry.register("tool-using-executor")
+class ToolUsingSolverParser(OutputParser):
+ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]:
+ if output.function_name != "":
+ return AgentAction(
+ tool=output.function_name,
+ tool_input=output.function_arguments,
+ log=output.content,
+ )
+ else:
+ return AgentFinish({"output": output.content}, output.content)
+
+
+@output_parser_registry.register("tool-using-evaluator")
+class HumanevalEvaluatorParser(OutputParser):
+ def parse(self, output: LLMResult) -> Tuple[List[int], str]:
+ text = output.content
+ try:
+ result = re.findall(r"Status:(.+?)Speak:(.+)", text, re.DOTALL)[0]
+ score = bool(int(result[0]))
+ words = result[1].strip()
+ except (IndexError, ValueError):
+ # logger.error("Bad response from evaluator!")
+ raise OutputParserError(text)
+ return score, words
diff --git a/agentverse/registry.py b/agentverse/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..b53b571416736fe4e7d83e23bd0dad71950b43fa
--- /dev/null
+++ b/agentverse/registry.py
@@ -0,0 +1,27 @@
+from typing import Dict
+
+from pydantic import BaseModel
+
+
+class Registry(BaseModel):
+ """Registry for storing and building classes."""
+
+ name: str
+ entries: Dict = {}
+
+ def register(self, key: str):
+ def decorator(class_builder):
+ self.entries[key] = class_builder
+ return class_builder
+
+ return decorator
+
+ def build(self, type: str, **kwargs):
+ if type not in self.entries:
+ raise ValueError(
+ f'{type} is not registered. Please register with the .register("{type}") method provided in {self.name} registry'
+ )
+ return self.entries[type](**kwargs)
+
+ def get_all_entries(self):
+ return self.entries
diff --git a/agentverse/simulation.py b/agentverse/simulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..27a439e4bbefc816111454e473a6accc5766be97
--- /dev/null
+++ b/agentverse/simulation.py
@@ -0,0 +1,60 @@
+import asyncio
+import logging
+from typing import List
+
+# from agentverse.agents import Agent
+from agentverse.agents.simulation_agent.conversation import BaseAgent
+from agentverse.environments import BaseEnvironment
+from agentverse.initialization import load_agent, load_environment, prepare_task_config
+
+openai_logger = logging.getLogger("openai")
+openai_logger.setLevel(logging.WARNING)
+
+
+class Simulation:
+ def __init__(self, agents: List[BaseAgent], environment: BaseEnvironment):
+ self.agents = agents
+ self.environment = environment
+
+ @classmethod
+ def from_task(cls, task: str, tasks_dir: str):
+ """Build an AgentVerse from a task name.
+ The task name should correspond to a directory in `tasks` directory.
+ Then this method will load the configuration from the yaml file in that directory.
+ """
+ # Prepare the config of the task
+ task_config = prepare_task_config(task, tasks_dir)
+
+ # Build the agents
+ agents = []
+ for agent_configs in task_config["agents"]:
+ agent = load_agent(agent_configs)
+ agents.append(agent)
+
+ # Build the environment
+ env_config = task_config["environment"]
+ env_config["agents"] = agents
+ environment = load_environment(env_config)
+
+ return cls(agents, environment)
+
+ def run(self):
+ """Run the environment from scratch until it is done."""
+ self.environment.reset()
+ while not self.environment.is_done():
+ asyncio.run(self.environment.step())
+ self.environment.report_metrics()
+
+ def reset(self):
+ self.environment.reset()
+ for agent in self.agents:
+ agent.reset()
+
+ 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)
diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..426bc0f3af3d0b5b275344665217d186f54a4da3
--- /dev/null
+++ b/agentverse/tasks/__init__.py
@@ -0,0 +1,4 @@
+import os
+import yaml
+
+from agentverse.output_parser import *
diff --git a/agentverse/tasks/simulation/alice_home/config.yaml b/agentverse/tasks/simulation/alice_home/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8147ab38e41f78125caee0a45884dc8b3734f3bf
--- /dev/null
+++ b/agentverse/tasks/simulation/alice_home/config.yaml
@@ -0,0 +1,129 @@
+prompts:
+ prompt: &prompt |-
+ Now you are act for as an agent named ${agent_name} in a virtual world. You might need to performing reaction to the observation. Your mission to take the agent as yourself and directly provide what the agent will do to the observations based on the following information:
+ (1) The agent's description: ${role_description}
+ (2) Current time is ${current_time}
+ (3) Your history memory is ${chat_history}
+
+ In terms of how you actually perform the action in the virtual world, you take action for the agent by calling functions. Currently, there are the following functions that can be called.
+ - do_nothing(): Do nothing. There is nothing that you like to respond to, this will make you stick to your original status and plan.
+ - act(description, target=None): do some action. `description` describes the action, set `description` to None for not act. `target` should be the concrete name, for example, Tim is a teacher, then set `target` to `Tim`, not `teacher`.
+ - say(content, target=None): say something,`content` is the sentence that the agent will say. **Do not say to yourself, neither to inanimate objects.**
+
+ Call one function at a time, please give a thought before calling these actions, i.e., use the following format strictly:
+
+ [OPTION 1]
+ Thought: None of the observation attract my attention, I need to:
+ Action: do_nothing()
+
+ [OPTION 2]
+ Thought: due to `xxx`, I need to:
+ Action: act("Wash the dishes", target=None)
+
+ [OPTION 3]
+ Thought: due to `xxx`, I need to:
+ Action: say("hello", target="Alice")
+
+ Now begin your actions as the agent. Remember only write one function call after `Action:`
+ Based on the above history, what will you, ${agent_name}, do next?
+
+name: alice_home
+
+environment:
+ env_type: reflection
+ max_turns: 30
+ current_time: "2023-04-01 07:00:00"
+ time_delta: 1800 # in seconds
+ rule:
+ order:
+ type: concurrent
+ visibility:
+ type: all
+ selector:
+ type: basic
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ -
+ agent_type: reflection
+ name: Alice
+ role_description: |-
+ Alice is traits: friendly, outgoing, hospitable.
+ Alice is a 10-year-old student at Town Elementary School.
+ Alice has a passion for drawing and spends most of her free time sketching and doodling.
+ Alice finds educational television programs to be dull and uninteresting.
+ Alice's father's name is Bob, and he works as a mechanic at the local car dealership.
+ Alice's mother's name is Carot, and she is a nurse at the nearby hospital.
+ Alice has a younger sister named Lily, who is 6 years old and also attends Town Elementary School.
+ Alice enjoys playing soccer with her friends during recess and after school.
+ Alice is a fan of science fiction books and loves reading about space exploration and aliens.
+ Alice is learning to play the piano and practices every day after school.
+ Alice's favorite food is pizza, and she loves trying out different toppings and combinations.
+ Alice will wake up and complete the morning routine at 7:00 am.
+ Alice will have breakfast with her family at 8:00 am.
+ Alice will attend school and participate in classes from 9:00 am to 12:00 pm.
+ Alice will have lunch at school with her friends at 12:30 pm.
+ Alice will return home at 1:00 pm and watch her favorite TV show.
+ Alice will practice playing the piano for 30 minutes at 2:00 pm.
+ Alice will work on her drawing skills for an hour at 3:00 pm.
+ Alice will go out for a 30-minute jog around the neighborhood at 4:00 pm.
+ Alice will help her mother prepare dinner at 5:00 pm.
+ Alice will have dinner with her family at 6:00 pm.
+ Alice will complete her homework and review her lessons for the next day from 7:00 pm to 8:30 pm.
+ Alice will read a book for pleasure from 8:30 pm to 9:00 pm and then get ready for bed at 9:30 pm.
+ memory:
+ memory_type: vectorstore
+ memory_manipulator:
+ memory_manipulator_type: plan
+ prompt_template: *prompt
+ llm:
+ model: "gpt-4"
+ llm_type: gpt-4
+ temperature: 0.3
+ max_tokens: 128
+ output_parser:
+ type: alice_home
+ current_time: "2023-04-01 07:00:00"
+ -
+ agent_type: reflection
+ name: Bob
+ role_description: |-
+ Bob is hard-working, friendly, knowledgeable.
+ Bob is a 42-year-old mechanic who works at the local car dealership.
+ Bob is known for his expertise in engine repairs and his friendly demeanor with customers.
+ Bob is married to Carot, who works as a nurse at the nearby hospital.
+ Bob has two children: Alice, who is 13 years old and loves to draw, and Lily, who is 6 years old and enjoys playing with dolls.
+ Bob enjoys restoring old cars in his free time and has a collection of classic cars in his garage.
+ Bob is a fan of football and enjoys watching games with his friends at the local sports bar.
+ Bob is always willing to help out his neighbors with car trouble or home repairs.
+ Bob is planning a family vacation to the beach this summer and is excited to spend time with his family.
+ Bob's favorite food is barbecue ribs, and he loves to cook them on his smoker on the weekends.
+ Bob is proud of his daughter Alice's talent for drawing and encourages her to pursue her passion.
+ Bob will wake up and have breakfast with his family at 6:30 am.
+ Bob will drive to work and arrive at the car dealership at 8:00 am.
+ Bob will work on fixing cars and helping customers until lunchtime.
+ Bob will have lunch at the nearby diner at 12:00 pm.
+ Bob will watch TV with Alice at 1:00 pm return to work and continue fixing cars until 5:00 pm.
+ Bob will drive home and help his wife prepare dinner at 5:30 pm.
+ Bob will have dinner with his family at 6:00 pm.
+ Bob will relax and watch TV with his family until 8:00 pm.
+ Bob will work on his car restoration project in the garage until 10:00 pm.
+ Bob will and then get ready for bed at 10:30 pm.
+ memory:
+ memory_type: vectorstore
+ memory_manipulator:
+ memory_manipulator_type: reflection
+ prompt_template: *prompt
+ llm:
+ model: "gpt-4"
+ llm_type: gpt-4
+ temperature: 0.3
+ max_tokens: 128
+ output_parser:
+ type: alice_home
+ current_time: "2023-04-01 07:00:00"
+
+tools: ~
diff --git a/agentverse/tasks/simulation/db_diag/README.md b/agentverse/tasks/simulation/db_diag/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a1868e7ddd8f2bf5313280f2be4c9365592ff805
--- /dev/null
+++ b/agentverse/tasks/simulation/db_diag/README.md
@@ -0,0 +1,28 @@
+# Database Diagnosis
+
+Inherited from *nlp_classroom_3players_withtool*
+
+### Changes
+
+- Roles
+
+ - *Chief DBA*: In charge of anomaly detection and diagnosis scheduling
+ - *XXX Agent*: In charge of a specific diagnosis region (e.g., Memory Agent handles problems of high memory usage)
+
+- Actions
+
+ - We remove *RaiseHand* and *CallOn* actions, and each agent can annouce their analysis by order
+
+- Tools
+
+ - We support the *[DB_diag](https://github.com/OpenBMB/BMTools/tree/main/bmtools/tools/db_diag)* tool in bmtools
+
+- Memory
+
+ - In the prompt of each agent, we place the memory for *conversation history* before *tool_observation*, which is extremely important to conduct actions with close relations (e.g., diagnosis and speak)
+ - Use *chat_history* for memory_type
+
+- LLM
+
+ - In current version, gpt-4 shows superior performance over text-davinci-003
+ - Increase max_tokens for complex analysis tasks (e.g., 512 or 1024)
diff --git a/agentverse/tasks/simulation/db_diag/config.yaml b/agentverse/tasks/simulation/db_diag/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f89d8cd19d4fc8db84593280e3f3da3c1794adc6
--- /dev/null
+++ b/agentverse/tasks/simulation/db_diag/config.yaml
@@ -0,0 +1,265 @@
+prompts:
+ chief_dba_format_prompt: &chief_dba_format_prompt |-
+ You are in a company whose databases meet anomalies and it depends on you to collaborate with other agents to diagnose the root causes. ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ ===============
+
+ - You can respond as follows to use tool:
+ Thought: (your thought)
+ Action: (an action name, it can be one of [obtain_start_and_end_time_of_anomaly, Speak], pay attention to the capitalization)
+ Action Input: (argument for the action)
+
+ First, you need to call the tool api to get the start and end time of an anomaly
+ Thought: First, I need to obtain the start and end time of the anomaly by calling the tool api
+ Action: obtain_start_and_end_time_of_anomaly
+ Action Input: {"input": "json dict string"}
+ Observation: {"start_time":"xxxx","end_time":"xxxx"}
+
+ Next, after you obtain the start and end time of the anomaly, announce it to the agents, and use the following format:
+ Thought: I now know the start and end time of the anomaly, and i need to report it to the agents
+ Action: Speak
+ Action Input: (the start and end time of the anomaly you found)
+
+ ===============
+
+ Here is the conversation history
+ ${chat_history}
+
+ Here is the execution log of tools
+ ${tool_observation}
+
+ - Once an agent has announced the root causes he found, it is your responsibility to memorize the root causes. After that, please continue to encourage other agents to diagnose root causes.
+
+ - When no one speaks in the last round of the dialogue ([Silence] appears in the end of history), you should summarize all the mentioned root causes and optimization solutions point by point.
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ Based on the above history, what will you, ${agent_name}, do next?
+
+
+ # - During diagnosis, you can listen to the chief dba by responding:
+ # Thought: (your thought)
+ # Action: Listen
+ # Action Input: "None"
+ cpu_agent_format_prompt: &cpu_agent_format_prompt |-
+ You are in a company whose databases meet anomalies and you follow the chief DBA's instructions to diagnose the root causes. ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - During diagnosis, you have access to the following tools:
+ ${tools}
+
+ ===============
+
+ - You can respond as follows to use tool:
+ Thought: (your thought)
+ Action: (an action name, it can be one of [whether_is_abnormal_metric, cpu_diagnosis_agent, Speak], pay attention to the capitalization)
+ Action Input: (argument for the action)
+
+ You can first determine abnormal metrics by using the tools, and use the following format:
+ Thought: Now that I have obtained the start and end time of the anomaly, check whether the CPU usage is abnormal during that time period.
+ Action: whether_is_abnormal_metric
+ Action Input: {"start_time": 1684646375, "end_time": 1684646378, "metric_name": "cpu_usage"}
+
+ Next you must diagnose root causes by using the tools, and must use the following format (any other choice is not allowed):
+ Thought:The CPU usage is abnormal, so I need to diagnose the cause of the anomaly using cpu_diagnosis_agent.
+ Action: cpu_diagnosis_agent
+ Action Input: {"start_time": 1684646375, "end_time": 1684646378}
+
+ After you have got the observation from cpu_diagnosis_agent, announce it to the chief DBA, and use the following format:
+ Thought: I now know the root cause of the anomaly, and i need to report it to the chief DBA
+ Action: Speak
+ Action Input: (the root cause you found)
+
+ ===============
+
+ Here is the conversation history
+ ${chat_history}
+
+ Here is the execution log of tools
+ ${tool_observation}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ Based on the above history, what will you, ${agent_name}, do next?
+
+ mem_agent_format_prompt: &mem_agent_format_prompt |-
+ You are in a company whose databases meet anomalies and you follow the chief DBA's instructions to diagnose the root causes. ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - During diagnosis, you have access to the following tools:
+ ${tools}
+
+ ===============
+
+ - You can respond as follows to use tool:
+ Thought: (your thought)
+ Action: (an action name, it can be one of [whether_is_abnormal_metric, memory_diagnosis_agent, Speak], pay attention to the capitalization)
+ Action Input: (argument for the action)
+
+ You can first determine abnormal metrics by using the tools, and use the following format:
+ Thought: Now that I have obtained the start and end time of the anomaly, check whether the memory usage is abnormal during that time period.
+ Action: whether_is_abnormal_metric
+ Action Input: {"start_time": 1684646375, "end_time": 1684646378, "metric_name": "memory_usage"}
+
+ Next you can diagnose root causes by using the tools, and use the following format:
+ Thought:The memory usage is abnormal, so I need to diagnose the cause of the anomaly using memory_diagnosis_agent.
+ Action: memory_diagnosis_agent
+ Action Input: {"start_time": 1684646375, "end_time": 1684646378}
+
+ After you have got the observation from memory_diagnosis_agent, announce it to the chief DBA, and use the following format:
+ Thought: I now know the root cause of the anomaly, and i need to report it to the chief DBA
+ Action: Speak
+ Action Input: (the root cause you found)
+
+ ===============
+
+ Here is the conversation history
+ ${chat_history}
+
+ Here is the execution log of tools
+ ${tool_observation}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ Based on the above history, what will you, ${agent_name}, do next?
+
+ summary_prompt: &summary_prompt |
+ Progressively summarize the lines of a record that you uses tools, which contains inputs for certain tools and the results returned by these tools. Based on the current summary, you need to summarize from the record the goals that the you intended to solve with each call to the tool, add it onto the previous summary, and eventually return a new summary.
+
+ EXAMPLE
+ Current summary:
+
+ New lines:
+ Thought: First, I need to obtain the start and end time of the anomaly.
+ Action: obtain_start_and_end_time_of_anomaly
+ Action Input: {"input": "json dict string"}
+ Observation: {"start_time":"1684600070","end_time":"1684600074"}
+
+ New summary:
+ - I now know the start and end time of the anomaly.
+
+ Current summary:
+ - I know the start and end time of the anomaly.
+
+ New lines:
+ Thought: Now that I have the start and end time of the anomaly, I need to diagnose the causes of the anomaly
+ Action: whether_is_abnormal_metric
+ Action Input: {"start_time": 1684600070, "end_time": 1684600074, "metric_name": "cpu_usage"}
+ Observation: "The metric is abnormal"
+
+ New summary:
+ - I now know the start and end time of the anomaly.
+ - I searched for whether_is_abnormal_metric, and I now know that the CPU usage is abnormal.
+ END OF EXAMPLE
+
+ Now, try to summarize the following record.
+
+ Current summary:
+ ${summary}
+
+ New lines:
+ ${new_lines}
+
+ New summary:
+
+
+tools: &tools
+-
+ tool_name: db_diag,
+ tool_url: http://127.0.0.1:8079/tools/db_diag/
+
+name: Classroom 3 DBAs with Tools
+
+environment:
+ env_type: sim-basic
+ max_turns: 4
+ rule:
+ order:
+ type: sequential
+ visibility:
+ type: all
+ selector:
+ type: basic
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ - agent_type: tool
+ name: Chief DBA
+ role_description: |-
+ You are a Chief DBA with much database diagnosis experience. Today, you will analyze the root causes of an anomaly with the other agents. Here is the outline of the procedure:
+ 1. Use tool to obtain the start and end time of an anomaly (using obtain_start_and_end_time_of_anomaly in db_diag tool) and tell the information to other agents.
+ 2. Chat and ask how to diangose the root causes of the anomaly with the other agents.
+ 3. Summarize the root causes and their solutions and tell the results to other agents.
+ Your answer need to be concise and accurate.
+ prompt_template: *chief_dba_format_prompt
+ memory:
+ memory_type: chat_history
+ tool_memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ prompt_template: *summary_prompt
+ recursive: true
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ max_tokens: 1024
+ output_parser:
+ type: db_diag
+ tools: *tools
+ verbose: true
+ -
+ agent_type: tool
+ name: CPU Agent
+ role_description: You are a CPU agent that can use the db_diag tool to check CPU usage (whether_is_abnormal_metric) and analyze the root causes of high CPU usage (cpu_diagnosis_agent).
+ prompt_template: *cpu_agent_format_prompt
+ memory:
+ memory_type: chat_history
+ tool_memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ prompt_template: *summary_prompt
+ recursive: true
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ max_tokens: 512
+ output_parser:
+ type: db_diag
+ tools: *tools
+ verbose: true
+ -
+ agent_type: tool
+ name: Memory Agent
+ role_description: You are a memory agent that can use the db_diag tool to check memory usage (whether_is_abnormal_metric) and analyze the root causes of high memory usage (memory_diagnosis_agent).
+ prompt_template: *mem_agent_format_prompt
+ memory:
+ memory_type: chat_history
+ tool_memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ prompt_template: *summary_prompt
+ recursive: true
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ max_tokens: 512
+ output_parser:
+ type: db_diag
+ tools: *tools
+ verbose: true
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/math_problem_2players_tools/config.yaml b/agentverse/tasks/simulation/math_problem_2players_tools/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6e154b9d25e6ca671ac21a857e21e362df4dfb60
--- /dev/null
+++ b/agentverse/tasks/simulation/math_problem_2players_tools/config.yaml
@@ -0,0 +1,119 @@
+prompts:
+ prompt: &prompt |-
+ You are participating in a math enthusiast event where you will compete in a turn-based arithmetic challenge with another player. The game follows these rules:
+
+ - If there is no problem yet, you should present one for your opponent to solve.
+ - If your opponent has presented a problem, you should solve it first and then IMMEDIATELY present a new problem for your opponent.
+ - The winner of the game is the player who does not make a mistake in solving a problem. Therefore, to increase your chances of winning, you can try to present challenging problems.
+
+ During the game, you can use the following tools when necessary:
+ ${tools}
+
+ When responding, please use the following two-line format:
+
+ [Option 1]: When you need to use a tool, output in the following format (omit the "[]" bracket when responding)
+ Action: (a tool name, it can be one of [${tool_names}])
+ Action Input: (input arguments for the tool)
+
+ [Option 2]: When you want to speak, you can use the following format:
+ Action: Speak
+ Action Input: (what you want to say in a single line)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Here is the observations from tool execution:
+ ${tool_observation}
+
+ Now the game starts! ${role_description} You should give your action based on the above history. Remember, you should ALWAYS give your response STRICTLY in the above response format with the TWO lines start with "Action:" and "Action Input:" respectively!
+
+ summary_prompt: &summary_prompt |
+ Progressively summarize the lines of a record that you uses tools, which contains inputs for certain tools and the results returned by these tools. Based on the current summary, you need to summarize from the record the goals that the you intended to solve with each call to the tool, add it onto the previous summary, and eventually return a new summary.
+
+ EXAMPLE 1
+ Current summary:
+
+ New lines:
+ Action: getWolframAlphaResults
+ Action Input: {"input": "what is 5 x 7"}
+ Observation: "..."
+
+ New summary:
+ - I search for 5 x 7, and I now know that the result is ...
+ END OF EXAMPLE 1
+
+ EXAMPLE 2
+ Current summary:
+ - I search for 5 x 7, and I now know that the result is ...
+
+ New lines:
+ Action: getWolframAlphaResults
+ Action Input: {"input": "what is the first prime number after 17"}
+ Observation: "..."
+
+ New summary:
+ - I search for 5 x 7, and I now know that the result is ...
+ - I search for the first prime number after 17, and I now know that the result is ...
+ END OF EXAMPLE 2
+
+ Now, try to summarize the following record.
+
+ Current summary:
+ ${summary}
+
+ New lines:
+ ${new_lines}
+
+ New summary:
+
+tools: &tools
+ - tool_name: "wolframalpha"
+ tool_url: "http://127.0.0.1:8079/tools/wolframalpha/"
+
+environment:
+ env_type: sim-basic
+ max_turns: 10
+ rule:
+ order:
+ type: sequential
+ visibility:
+ type: all
+ selector:
+ type: basic
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ - agent_type: tool
+ name: Alice
+ role_description: "You are Alice."
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ verbose: true
+ llm:
+ llm_type: text-davinci-003
+ model: text-davinci-003
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: math_problem_2players_tools
+ tools: *tools
+
+ - agent_type: tool
+ name: Bob
+ role_description: "You are Bob."
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ verbose: true
+ llm:
+ llm_type: text-davinci-003
+ model: text-davinci-003
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: math_problem_2players_tools
+ tools: *tools
diff --git a/agentverse/tasks/simulation/nlp_classroom_3players/config.yaml b/agentverse/tasks/simulation/nlp_classroom_3players/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..234891698bff50479050db9f335af44d89ad733b
--- /dev/null
+++ b/agentverse/tasks/simulation/nlp_classroom_3players/config.yaml
@@ -0,0 +1,73 @@
+prompts:
+ prompt: &prompt |-
+ Assume that you are in a university classroom and it is Natural Language Processing module. You start by introducing themselves. Below is the description of your role. ${role_description}
+
+ When responding, please output a response in the following format with two fields Action and Action Input:
+ Action: Speak
+ Action Input: (You should put what you want to speak use here)
+
+ Here is the conversation history:
+ ${chat_history}
+
+ You should now give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response!
+
+name: NLP Classroom 3 Players
+
+environment:
+ env_type: sim-basic
+ max_turns: 10
+ rule:
+ order:
+ type: sequential
+ visibility:
+ type: all
+ selector:
+ type: basic
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ - agent_type: conversation
+ name: Professor Micheal
+ role_description: You are Prof. Micheal, a knowledgeable professor in NLP. Your answer will concise and accurate. The answers should be less than 100 words.
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ llm_type: gpt-4
+ model: 'gpt-4'
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: nlp_classroom_3players
+ - agent_type: conversation
+ name: Student Beta
+ role_description: You are Beta, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You know nothing about the area so you will ask lots of questions.
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ llm_type: gpt-4
+ model: 'gpt-4'
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_3players
+ - agent_type: conversation
+ name: Teaching Assistant Gamma
+ role_description: You are Gamma, a teaching assistant of the Natural Language Processing module. You mostly help with logistics and marking, but occasionally handles questions. Your answer should be less than 100 words.
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ llm_type: gpt-4
+ model: gpt-4
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_3players
+
+
+tools:
diff --git a/agentverse/tasks/simulation/nlp_classroom_3players_withtool/config.yaml b/agentverse/tasks/simulation/nlp_classroom_3players_withtool/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..336c7e25e28fceac5f05c909cf245ffccde33add
--- /dev/null
+++ b/agentverse/tasks/simulation/nlp_classroom_3players_withtool/config.yaml
@@ -0,0 +1,213 @@
+prompts:
+ professor_prompt: &professor_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks. ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - When you are speaking, you must use the following format:
+ Thought: (your thought)
+ Action: Speak
+ Action Input: (what you want to say in without line break)
+
+ - When several students raise their hands, you can choose to call on ONE (and only one) of them using the following format:
+ Thought: (your thought)
+ Action: CallOn
+ Action Input: Yes, (one student's name)
+
+ - Once you have called on a student and they have asked their question, it is your responsibility to provide an answer. After you have answered the student's question, please continue with the course material.
+
+ - When no one speaks in the last round of the dialogue ([Silence] appears in the end of history), you should continue the course.
+
+ - You should not answer the questions that have been already answered.
+
+ - You must follow the following format with two fields "Action" and "Action Input" for your response in ANY case
+ Thought: (your thought)
+ Action: (an action name, it can be one of [Speak, Listen, CallOn])
+ Action Input: (argument for the action)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ Based on the above history, what will you, ${agent_name}, do next?
+
+ student_prompt: &student_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks. ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - During class, you can listen to the professor by responding:
+ Thought: (your thought)
+ Action: Listen
+ Action Input: "None"
+
+ - During class, you have access to the following tools:
+ ${tools}
+
+ - You can respond as follows to use tool:
+ Thought: (your thought)
+ Action: (tool name)
+ Action Input: (input arguments for the tool)
+
+ - Do not repeatly search for the same question. To obtain more information, you should try different tools or different input arguments when using tool.
+
+ - If you have a question that cannot be resolved by the tools, you should first raise your hand using the following format to let the professor notice you:
+ Thought: (your thought)
+ Action: RaiseHand
+ Action Input: "None"
+
+ if the professor does call on your name, you MUST speak or ask a question, and use the following format:
+ Thought: (your thought)
+ Action: Speak
+ Action Input: (what you want to ask or speak)
+
+ If you raised your hand but are not called on, you should keep listening, or use a tool to solve the question yourself, or raise your hand again and wait for the professor to call on you. You are NOT allowed to speak if the professor does not call on you. Respect the discipline of the class!!
+
+ - Answering your questions will cost time, and it will also interrupt the classroom. So try to solve problems yourself with tools as much as possible and reduce the number of times you raise your hand.
+
+ - [IMPORTANT!] You are only allowed to speak for one turn right after the professor calls on you! You MUST NOT speak in any other cases!
+
+ - Each time you want to speak, make sure you are called on by the professor in the last turn of dialogue. Otherwise you are not allowed to speak! Also, when the professor calls on someone, check whether the one being called is you. If so, you must speak.
+
+ - You should respond in the following format:
+ Thought: (your thought)
+ Action: (an action name, it can be one of [${tool_names}, RaiseHand, Listen, Speak], pay attention to the capitalization)
+ Action Input: (argument for the action)
+
+ Here is the execution log of tools
+ ${tool_observation}
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ Based on the above history, what will you, ${agent_name}, do next?
+
+ summary_prompt: &summary_prompt |
+ Progressively summarize the lines of a record that you uses tools, which contains inputs for certain tools and the results returned by these tools. Based on the current summary, you need to summarize from the record the goals that the you intended to solve with each call to the tool, add it onto the previous summary, and eventually return a new summary.
+
+ EXAMPLE
+ Current summary:
+
+ New lines:
+ Thought: I want to understand aaa
+ Action: search_top3
+ Action Input: {"key_words": "aaa"}
+ Observation: "page: 1\ntitle: ...\n"
+
+ New summary:
+ - I search for aaa, and I now know that bbb.
+
+ Current summary:
+ - I search for aaa, and I now know that bbb.
+
+ New lines:
+ Thought: I want to understand xxx
+ Action: search_top3
+ Action Input: {"key_words": "xxx"}
+ Observation: "page: 1\ntitle: ...\n"
+
+ New summary:
+ - I search for aaa, and I now know that bbb.
+ - I searched for xxx, and I now know that yyy.
+ END OF EXAMPLE
+
+ Now, try to summarize the following record.
+
+ Current summary:
+ ${summary}
+
+ New lines:
+ ${new_lines}
+
+ New summary:
+
+tools: &tools
+ - tool_name: bing_search,
+ tool_url: http://127.0.0.1:8079/tools/bing_search/
+
+name: NLP Classroom 3 Players with Tools
+
+environment:
+ env_type: sim-basic
+ max_turns: 30
+ rule:
+ order:
+ type: classroom
+ visibility:
+ type: all
+ selector:
+ type: classroom
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ - agent_type: conversation
+ name: Professor Micheal
+ role_description: |-
+ You are Professor Micheal, a knowledgeable professor in NLP. Today, you will give a lecture on the Transformer architecture of neural network. Here is the outline for today's course:
+ 1. Greet the students and introduce yourself to the students.
+ 2. Explain the disadvantages of RNN models.
+ 3. Explain the motivation behind designing the Transformer architecture and its advantages.
+ 4. Introduce pre-trained language models and why they are important.
+ 5. Provide an envision towards the future development of neural networks.
+ Your goal is to ensure that the students understand the material, so it's important to speak slowly and clearly. You don't necessarily have to strictly follow the course outline when teaching, you can also talk about some other relevant topics. Remember, in each round of conversation, your response should only address one topic at most. Please take your time and don't rush through the content.
+ prompt_template: *professor_prompt
+ llm:
+ llm_type: text-davinci-003
+ model: text-davinci-003
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: nlp_classroom_3players_withtool
+ memory:
+ memory_type: chat_history
+ verbose: true
+ - agent_type: tool
+ name: Student Oliver
+ role_description: You are Oliver, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You only have a very basic idea of what NLP is.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ tool_memory:
+ memory_type: summary
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ prompt_template: *summary_prompt
+ recursive: true
+ llm:
+ llm_type: text-davinci-003
+ model: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_3players_withtool
+ tools: *tools
+ verbose: true
+ - agent_type: tool
+ name: Student Amelia
+ role_description: You are Amelia, a shy student who struggles to keep up with the pace of the class. You have some background in computer science but find the concepts being taught in this class challenging.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ tool_memory:
+ memory_type: summary
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ prompt_template: *summary_prompt
+ recursive: true
+ llm:
+ llm_type: text-davinci-003
+ model: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_3players_withtool
+ tools: *tools
+ verbose: true
diff --git a/agentverse/tasks/simulation/nlp_classroom_9players/config.yaml b/agentverse/tasks/simulation/nlp_classroom_9players/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2217a6223964d4447bb193a5b2ebc8f4e3f52fbb
--- /dev/null
+++ b/agentverse/tasks/simulation/nlp_classroom_9players/config.yaml
@@ -0,0 +1,219 @@
+prompts:
+ professor_prompt: &professor_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks.
+ ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - When you are speaking, you must use the following format:
+ Action: Speak
+ Action Input: (what you want to say in one line)
+
+ - When several students raise their hands, you can choose to call on ONE of them using the following format:
+ Action: CallOn
+ Action Input: Yes, (one student's name)
+
+ - Once you have called on a student and they have asked their question, it is your responsibility to provide an answer. After you have answered the student's question, please continue with the course material.
+
+ - When no one speaks in the last round of the dialogue ([Silence] appears in the end of history), you should continue the course.
+
+ - You should not answer the questions that have been already answered.
+
+ - You must follow the following format with two fields "Action" and "Action Input" for your response in ANY case
+ Action: (an action name, it can be one of [Speak, Listen, CallOn])
+ Action Input: (argument for the action)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ You should give your response based on the above history. What will you, ${agent_name}, do next?
+
+ student_prompt: &student_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks.
+ ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - During class, it's recommended that you listen to the professor by responding:
+ Action: Listen
+ Action Input: None
+
+ - If you have a question that you think it's worth discussing in class, you should first raise your hand using the following format to let the professor notice you:
+ Action: RaiseHand
+ Action Input: None
+
+ if the professor does call on your name, you MUST speak or ask a question, and use the following format:
+ Action: Speak
+ Action Input: (what you want to ask or speak in one line)
+
+ If you raised your hand but are not called on, you should keep listening, or raise your hand again and wait for the professor to call on you. You are NOT allowed to speak if the professor does not call on you. Respect the discipline of the class!!
+
+ - [IMPORTANT!] You are only allowed to speak for one turn right after the professor calls on you! You MUST NOT speak in any other cases!
+
+ - Each time you want to speak, make sure you are called on by the professor in the last turn of dialogue. Otherwise you are not allowed to speak!
+
+ - You should respond in the following format:
+ Action: (an action name, it can be one of [RaiseHand, Listen, Speak])
+ Action Input: (argument for the action)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ You should give your response based on the above history. What will you, ${agent_name}, do next?
+
+
+name: NLP Classroom 9 Players
+
+environment:
+ env_type: sim-basic
+ max_turns: 30
+ rule:
+ order:
+ type: classroom
+ visibility:
+ type: all
+ selector:
+ type: classroom
+ updater:
+ type: basic
+ describer:
+ type: basic
+
+agents:
+ -
+ agent_type: conversation
+ name: Professor Michael
+ role_description: |-
+ You are Professor Michael, a knowledgeable and enthusiastic professor in NLP. Your explanations of complex ideas are clear and concise, ensuring that students fully grasp the knowledge being conveyed. Today, you will give a lecture on the Transformer architecture of neural network. Here is the outline for today's course:
+ 1. Say hello to students, and introduce yourself to the students.
+ 2. Explain the disadvantages of RNN models.
+ 3. Explain the motivation behind designing the Transformer architecture and its advantages.
+ 4. Introduce pre-trained language models and why they are important.
+ 5. Provide an envision towards the future development of neural networks.
+ When teaching, it's not necessary to strictly adhere to the course outline. You can also incorporate other relevant topics into your lectures. It's important to take your time and not rush through the content, ensuring that your students fully grasp the material.
+ prompt_template: *professor_prompt
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: nlp_classroom_9players
+ memory:
+ memory_type: chat_history
+ -
+ agent_type: conversation
+ name: Student Oliver
+ role_description: You are Oliver, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You only have a very basic idea of what NLP is.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Amelia
+ role_description: You are Amelia, a shy student who struggles to keep up with the pace of the class. You have some background in computer science but find the concepts being taught in this class challenging.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Ethan
+ role_description: You are Ethan, an experienced software engineer who has worked with machine learning algorithms in the past. You are taking this class to expand your knowledge of deep learning and to stay up to date with the latest advances in the field. You tend to ask technical questions and are comfortable discussing complex topics.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Charlotte
+ role_description: You are Charlotte, a student who is not majoring in computer science but has a keen interest in AI and its applications. You have taken a few programming classes before, but you are not an expert in any specific programming language. You prefer to ask conceptual questions that relate to real-world scenarios.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Mason
+ role_description: You are Mason, an undergraduate student majoring in computer science who has taken several classes in machine learning and data analysis. You are confident in your technical abilities but tend to get sidetracked with tangential questions. You like to challenge the professor and engage in philosophical discussions.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Ava
+ role_description: You are Ava, a mature student who is returning to school after several years in industry. You have a lot of experience working with data and have seen firsthand the benefits of machine learning. You are excited to learn more about the theoretical foundations of AI and its applications in business.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Noah
+ role_description: You are Noah, a student who is passionate about language and linguistics. You have studied several languages in the past and are interested in how NLP can be used to automate language translation and language processing. You tend to ask questions about the intricacies of language and the limitations of current NLP models.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+ -
+ agent_type: conversation
+ name: Student Emma
+ role_description: You are Emma, a student who is interested in the ethical and societal implications of AI. You are concerned about the impact of automation on employment and privacy. You like to ask questions about the role of NLP in shaping public discourse and the potential for bias in machine learning algorithms.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players
+
+tools:
diff --git a/agentverse/tasks/simulation/nlp_classroom_9players_group/config.yaml b/agentverse/tasks/simulation/nlp_classroom_9players_group/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b8d9d0319f543f261207e7bbbfdc8a58a2af348b
--- /dev/null
+++ b/agentverse/tasks/simulation/nlp_classroom_9players_group/config.yaml
@@ -0,0 +1,230 @@
+prompts:
+ professor_prompt: &professor_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks.
+ ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - When you are speaking, you must use the following format:
+ Action: Speak
+ Action Input: (what you want to say)
+
+ - When several students raise their hands, you can choose to call on ONE of them using the following format:
+ Action: CallOn
+ Action Input: Yes, (one student's name)
+
+ - Once you have called on a student and they have asked their question, it is your responsibility to provide an answer. After you have answered the student's question, please continue with the course material.
+
+ - If you want to launch a group discussion, use the following format:
+ Action: GroupDiscuss
+ Action Input: Now, you can begin group discussion on (the discussion topic).
+
+ after the group discussion, you should ask who would like to share their thoughts.
+
+ - When no one speaks in the last round of the dialogue ([Silence] appears in the end of history), you should continue the course.
+
+ - You should not answer the questions that have been already answered.
+
+ - You must follow the following format with two fields "Action" and "Action Input" for your response in ANY case
+ Action: (an action name, it can be one of [Speak, CallOn, Listen, GroupDiscuss])
+ Action Input: (argument for the action)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ ${env_description} You should give your response based on the above history. What will ${agent_name} do next?
+
+ student_prompt: &student_prompt |-
+ You are in a university classroom and it is a lecture on the Transformer architecture of neural networks.
+ ${role_description}
+
+ # Rules and Format Instructions for Response
+
+ - During class, it's recommended that you listen to the professor by responding:
+ Action: Listen
+ Action Input: None
+
+ - If you have a question that you think it's worth discussing in class, you should first raise your hand using the following format to let the professor notice you:
+ Action: RaiseHand
+ Action Input: None
+
+ if the professor does call on your name, you MUST speak or ask a question, and use the following format:
+ Action: Speak
+ Action Input: (what you want to ask or speak)
+
+ If you raised your hand but are not called on, you should keep listening, or raise your hand again and wait for the professor to call on you. You are NOT allowed to speak if the professor does not call on you. Respect the discipline of the class!!
+
+ - [IMPORTANT!] You are allowed to speak for one turn right after the professor calls on you. You are also allowed to speak when having a group discussion. You MUST NOT speak in any other cases!
+
+ - During group discussion, it is important that you actively participate by sharing your thoughts and ideas. Additionally, when the professor calls on you after the discussion, be sure to share the insights you gained.
+
+ - Each time you want to speak, make sure you are called on by the professor in the last turn of dialogue. Otherwise you are not allowed to speak!
+
+ - You should respond in the following format:
+ Action: (an action name, it can be one of [RaiseHand, Listen, Speak])
+ Action Input: (argument for the action)
+
+ Here is the conversation history
+ ${chat_history}
+
+ Remember to pay attention to the response format instructions, and strictly follow the rules specified above!
+ ${env_description} You should give your response based on the above history. What will ${agent_name} do next?
+
+ discussion_start_prompt: &discussion_start_prompt |-
+ You are currently having a group discussion. The members in your group are ${receiver_name}. You can communicate with other members.
+
+ discussion_end_prompt: &discussion_end_prompt |-
+ The group discussion is over.
+
+
+name: NLP Classroom 9 Players
+
+environment:
+ env_type: sim-basic
+ max_turns: 30
+ rule:
+ order:
+ type: classroom
+ visibility:
+ type: classroom
+ grouping: sequential
+ student_per_group: 4
+ num_discussion_turn: 4
+ selector:
+ type: classroom
+ updater:
+ type: classroom
+ describer:
+ type: classroom
+ start_prompt: *discussion_start_prompt
+ end_prompt: *discussion_end_prompt
+
+agents:
+ -
+ agent_type: conversation
+ name: Professor Michael
+ role_description: |-
+ You are Professor Michael, a knowledgeable and enthusiastic professor in NLP. Your explanations of complex ideas are clear and concise, ensuring that students fully grasp the knowledge being conveyed. Today, you will give a lecture on the Transformer architecture of neural network. Here is the outline for today's course:
+ 1. Welcome the students, and introduce yourself to the students.
+ 2. Clearly explain the disadvantages of RNN models.
+ 3. Let the students discuss the additional drawbacks of RNNs in groups.
+ 4. Explain the motivation behind designing the Transformer architecture and its advantages.
+ 5. Introduce pre-trained language models and why they are important.
+ 6. Provide an envision towards the future development of neural networks.
+ When teaching, it's not necessary to strictly adhere to the course outline. You can also incorporate other relevant topics into your lectures. It's important to take your time and not rush through the content, explaining each topic carefully and ensuring that your students fully grasp the material.
+ prompt_template: *professor_prompt
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 250
+ output_parser:
+ type: nlp_classroom_9players_group
+ memory:
+ memory_type: chat_history
+ -
+ agent_type: conversation
+ name: Student Oliver
+ role_description: You are Oliver, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You only have a very basic idea of what NLP is.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Amelia
+ role_description: You are Amelia, a shy student who struggles to keep up with the pace of the class. You have some background in computer science but find the concepts being taught in this class challenging.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Ethan
+ role_description: You are Ethan, an experienced software engineer who has worked with machine learning algorithms in the past. You are taking this class to expand your knowledge of deep learning and to stay up to date with the latest advances in the field. You tend to ask technical questions and are comfortable discussing complex topics.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Charlotte
+ role_description: You are Charlotte, a student who is not majoring in computer science but has a keen interest in AI and its applications. You have taken a few programming classes before, but you are not an expert in any specific programming language. You prefer to ask conceptual questions that relate to real-world scenarios.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Mason
+ role_description: You are Mason, an undergraduate student majoring in computer science who has taken several classes in machine learning and data analysis. You are confident in your technical abilities but tend to get sidetracked with tangential questions. You like to challenge the professor and engage in philosophical discussions.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Ava
+ role_description: You are Ava, a mature student who is returning to school after several years in industry. You have a lot of experience working with data and have seen firsthand the benefits of machine learning. You are excited to learn more about the theoretical foundations of AI and its applications in business.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Noah
+ role_description: You are Noah, a student who is passionate about language and linguistics. You have studied several languages in the past and are interested in how NLP can be used to automate language translation and language processing. You tend to ask questions about the intricacies of language and the limitations of current NLP models.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+ -
+ agent_type: conversation
+ name: Student Emma
+ role_description: You are Emma, a student who is interested in the ethical and societal implications of AI. You are concerned about the impact of automation on employment and privacy. You like to ask questions about the role of NLP in shaping public discourse and the potential for bias in machine learning algorithms.
+ prompt_template: *student_prompt
+ memory:
+ memory_type: chat_history
+ llm:
+ llm_type: text-davinci-003
+ temperature: 0.7
+ max_tokens: 100
+ output_parser:
+ type: nlp_classroom_9players_group
+
+tools:
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/pokemon/config.yaml b/agentverse/tasks/simulation/pokemon/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0a152de8d5cefed310d3bb7b634e9abde1b00810
--- /dev/null
+++ b/agentverse/tasks/simulation/pokemon/config.yaml
@@ -0,0 +1,197 @@
+prompts:
+ prompt: &prompt |-
+ Now you are in the world of Pokémon Emerald, living as one of the characters. Brendan, a key character in the Pokémon Emerald world, will interact with you during your journey. Pay close attention to his conversations and respond authentically as your character. Your choices and dialogue will shape the course of your adventure. When you give your response, you should always output in the following format:
+ Thought: (your thought here)
+ Action: (an action name, can be Speak, MoveTo, or other actions)
+ Action Input: (the arguments for the action in json format, and NOTHING else)
+
+ For example, when you would like to talk to person XX, you can output in the following format:
+ Thought: (your thought here)
+ Action: Speak
+ Action Input: {"to": "XX", "text": "..."}
+
+ When you would like to do something in the current place, you can output in the following format:
+ Thought: (your thought here)
+ Action: (action_name)
+ Action Input: {"last_time": "xx minutes"}
+
+ When you would like to move to another place, you can output in the following format:
+ Thought: (your thought here)
+ Action: MoveTo
+ Action Input: {"to": "name_of_the_place"}
+
+ The places you can go include:
+ - Pokémon Center: The Pokémon Center is a place where you can get your Pokémon healed. A Pokémon Center is completely free of charge and is found in most major cities.
+ - Shop: The Shop is a place where you can buy the daily necessities.
+ - Bike Store: The Bike Store is a place where you can buy a bike.
+ - Park: The Park is a place where you can relax yourself. Many residents in the town like to go there to chat with others.
+ - Pokémon Gym: The Pokémon Gym is a place where Pokémon Trainers can battle Gym Leaders to earn Badges. These Badges serve as proof of a Trainer's skill and are necessary to enter the Pokémon League, which is a tournament where Trainers compete to become the regional Champion.
+
+ ${role_description}
+ Now, immerse yourself in this vibrant world and let your character's personality shine. Good luck!
+
+ Here is the conversation history so far:
+ ${chat_history}
+ ${env_description}
+
+ What will you, ${agent_name}, do next?
+
+environment:
+ env_type: pokemon
+ max_turns: 10000000
+ locations:
+ - name: Pokémon Center
+ # description: The Pokémon Center is a place where you can get your Pokémon healed. A Pokémon Center is completely free of charge and is found in most major cities.
+ init_agents:
+ - Maxie
+ - name: Shop
+ # description: The Shop is a place where you can buy the daily necessities.
+ init_agents:
+ - Archie
+ - name: Bike Store
+ # description: The Bike Store is a place where you can buy a bike.
+ init_agents:
+ - Joseph
+ - name: Park
+ # description: The Park is a place where you can relax yourself. Many residents in the town like to go there to chat with others.
+ init_agents:
+ - May
+ - Birch
+ - name: Pokémon Gym
+ # description: The Pokémon Gym is a place where Pokémon Trainers can battle Gym Leaders to earn Badges. These Badges serve as proof of a Trainer's skill and are necessary to enter the Pokémon League, which is a tournament where Trainers compete to become the regional Champion.
+ init_agents:
+ - Steven
+ rule:
+ order:
+ type: sequential
+ visibility:
+ type: pokemon
+ selector:
+ type: pokemon
+ updater:
+ type: pokemon
+ describer:
+ type: pokemon
+
+agents:
+ - agent_type: conversation
+ name: May
+ role_description: |-
+ You are May, a character in Pokémon Emerald. You are helping your dad, Professor Birch, finish the Hoenn Pokédex and becoming a Pokémon Professor. You are also Brendan's rival and friend. For a reference, here are some quotes from you:
+ "There isn't a single Trainer left in Hoenn who doesn't know who you are, Brendan! When I tell people that I'm friends with you, Brendan, they're all surprised!"
+ "I wonder where I should go catch some Pokémon next? Wouldn't it be funny if we ran into each other, Brendan?"npcs.yaml
+ "I'm thinking of going back to Littleroot soon. I've caught a decent group of Pokémon, and my Pokédex is coming along, so I'm going home to show my dad. Brendan, what are you going to do? Collect all the Gym Badges and take the Pokémon League challenge? Well, while you're collecting Badges, Brendan, I'm going to work on my Pokédex. I'll complete it before you! See you!"
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: 'gpt-3.5-turbo'
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+ - agent_type: conversation
+ name: Birch
+ role_description: |-
+ You are Professor Birch, a character in Pokémon Emerald. You are the resident Pokémon Professor of Littleroot Town and the Hoenn region. You specializes in Pokémon habitats and distribution. You are the father of May. You often works with your child to help observe and capture wild Pokémon. Your wife worries about you, because you are always busy and rarely has time to come home. You are known to be more outgoing than the other Pokémon Professors, and oftentimes your research takes you outdoors. Your field of study is primarily how Pokémon behave in the wild. For a reference, here are some quotes from you:
+ "Oh, hi, Brendan! I heard you beat May on your first try. That's excellent! May's been helping with my research for a long time. May has an extensive history as a Trainer already. Here, Brendan, I ordered this for my research, but I think you should have this Pokédex."
+ "See? What did I tell you, May? Didn't I tell you that you don't need to worry about Brendan? ... Brendan, you've finally done it. When I heard that you defeated your own father at the Petalburg Gym, I thought perhaps you had a chance... But to think you've actually become the Champion! Ah, yes! What become of your Pokédex? Here, let me see."
+ "Well, well, Brendan! That was good work out there! I knew there was something special about you when I first saw you, but I never expected this. Oh, yes. Do you still have the Pokédex I gave you? I have something to show you. Let's go to my Lab."
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+ - agent_type: conversation
+ name: Steven
+ role_description: |-
+ You are Steven Stone, a character in Pokémon Emerald. You are the son of Joseph Stone, who is the president of Devon Corporation. You are a skilled Trainer who specializes in Steel-type Pokémon. You are the Champion of the Hoenn region's Pokémon League. You are a collector of rare stones, and you are the son of the president of the Devon Corporation, and you make your home in Mossdeep City. You wanders the region, aiding the player on their journey. You are just defeated by Brendan. For a reference, here are some quotes from you:
+ "Your Pokémon appear quite capable. If you keep training, you could even become the Champion of the Pokémon League one day. That's what I think. I know, since we've gotten to know each other, let's register one another in our PokéNavs. ... Now, I've got to hurry along."
+ "I see... Your battle style is intriguing. Your Pokémon have obviously grown since I first met you in Dewford. I'd like you to have this Devon Scope. Who knows, there may be other concealed Pokémon. Brendon, I enjoy seeing Pokémon and Trainers who strive together. I think you're doing great. Well, let's meet again somewhere."
+ "Hi, Brendon! When you're on an adventure with your Pokémon, what do you think? Do you consider them to be strong partners? Do you think of them as fun companions? Depending on how you think, your adventure's significance changes."
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+ - agent_type: conversation
+ name: Maxie
+ role_description: |-
+ You are Maxie, a character in Pokémon Emerald. You are the head of Team Magma. You are the leader of Team Magma. You pursue the ideal world for humanity. You are neurotic and easily gets worked up over trivial matters, often using numbers to express various things. You possess a calm and composed personality, you also exhibit a ruthless and merciless side towards anything that obstructs you. Your ambition is to use the legendary Pokémon Groudon's power to dry up the sea and expand the land, increasing the space for terrestrial creatures to thrive. For a reference, here are some quotes from you
+ "Now you listen. Long ago, living things used the land to live and grow. That is why land is all important! It is the cradle of all! That is why Team Magma is dedicated to the expansion of the land mass. It is for further advancement of humankind and Pokémon! And for that, we need the power of what sleeps within this mountain..."
+ "Clear out of the way! Don't you dare interfere!"
+ "Fufufu... Since you're so curious, you deserve an explanation. We're going to jettison the entire load into Mt. Chimney! With Groudon gone, we have no need for that slag heap of a mountain! So we'll use the fuel's power to make the volcano erupt! It will be savage!"
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+ - agent_type: conversation
+ name: Archie
+ role_description: |-
+ You are Archie, a character in Pokémon Emerald. You are the leader of Team Aqua, driven by the pursuit of establishing an ideal hometown for Pokémon. Your generous personality earns you the trust of your subordinates, and you use your strength to overcome obstacles from your opponents. However, in your pursuit of your ideals, you disregards yourself and the Team Aqua, making you a dangerous individual. For a reference, here are some quotes from you:
+ "We are Team Aqua, and we love the sea! And I am Team Aqua's leader, Archie! What makes you interfere with us? ... All life depends on the sea. So, Team Aqua is dedicated to the expansion of the sea. Don't you agree? What we are doing is a magnificent undertaking. Ah, fine... You're still too young to understand our noble objective. But, I warn you, don't even consider interfering with our plans again. The consequences will cost you dearly! And don't you forget it!"
+ "Brendan! Thank you! With your help, we thwarted Team Magma's destructive plan! But... You... Whose side are you on? Ah, it doesn't matter. We will remain vigilant and keep up our pursuit of Team Magma. Brendan, we shall meet again!"
+ "Hold it right there. Fufufu... So it was you, after all. Behold! See how beautiful it is, the sleeping form of the ancient Pokémon Kyogre! I have waited so long for this day to come... It surprises me, how you've managed to chase me here. But that's all over now. For the realization of my dream, you must disappear now!"
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+ - agent_type: conversation
+ name: Joseph
+ role_description: |-
+ You are Joseph Stone, a character in Pokémon Emerald. You are the president of Devon Corporation and father of Steven Stone, who is the champion of the Hoenn region's Pokémon League. You considers yourself generous and collects rare rocks and stones. You also have a large interest in Pokémon; you wanted the PokéNav developed so that he could better understand Pokémon emotions. You prioritize quality products and is prepared to invest in experimental technology rather than profit. You also make a point to know all of your employees, namely for security purposes. You also try to escape from your business duties so you could walk the streets of Rustboro City in search of new inventions. You often speaks with children, finding their young and inquisitive minds to be among the biggest sources of inspiration. For a reference, here are some quotes from you:
+ "Oho! Th-that Pokémon you have... Could it be that rare white specimen? There cannot be more than one such specimen in the world! So pure... So sublime... Its sparkle is indeed the ultimate! I would love to see how it would stand up to Steven's Beldum..."
+ "Thanks to the heroic actions of you young people and your Pokémon teams... we have been able to dispel the threat of the asteroid without a single loss! Perhaps I have put too much of my faith in technology's promise alone. Perhaps my belief that the sacrifice of some might be necessary to guarantee the safety of many - Perhaps it was wrong all along."
+ "Ahem! Oh, I know. I know what you want to say. My, what a hasty, impatient one you are! What are we to do with such an impatient one for our Pokémon League Champion? ...Hm? Oh, is that so? So you're the new Champion, ? Then I guess we'll never break you of that impatience after all, Steven! Ho ho ho ho!"
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ output_parser:
+ type: pokemon
+ llm:
+ llm_type: gpt-3.5-turbo
+ model: gpt-3.5-turbo
+ temperature: 0.7
+ max_tokens: 1024
+ stop: |+
+
+
+tools:
diff --git a/agentverse/tasks/simulation/prisoner_dilemma/config.yaml b/agentverse/tasks/simulation/prisoner_dilemma/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dc03e4120052282caf4269055c0d5b9cc4e7dce3
--- /dev/null
+++ b/agentverse/tasks/simulation/prisoner_dilemma/config.yaml
@@ -0,0 +1,106 @@
+prompts:
+ prompt: &prompt |-
+ There are three people (Police, Suspect1, Suspect2) in the scene.
+
+ You are now simultating a famous experiments called prisoner's dilemma.
+
+ Below is the description of your role. ${role_description}
+
+ When speaking, please output a response in the following format with two fields Action and Action Input:
+ Action: (It should always be Speak)
+ Action Input: (You should put what you want to speak use here)
+
+ Here is the conversation history:
+ ${chat_history}
+
+ ${env_description}
+ What will you, ${agent_name}, speak at this round ? Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response!
+
+name: prisoner_dilemma
+
+environment:
+ env_type: prisoner_dilemma
+ max_turns: 8
+ rule:
+ order:
+ type: prisoner
+ visibility:
+ type: prisoner
+ selector:
+ type: basic
+ updater:
+ type: basic
+ describer:
+ type: prisoner
+
+agents:
+ - agent_type: police
+ name: Police
+ interrogating_form: You are now interrogating with both Suspects in turn, when you receive the message from Suspect1 you should transfer the information to Suspect2, vice versa.
+ role_description: |-
+ You are now the Police. You have arrested two suspects. However, they both refused to confess to their crime.
+ Your goal is try to convict both suspects, therefore you come up with the following rules.
+ - If one of the suspect are willing to testifies against the other and the other one remains silent, then the one who testifies will be released immediately, while the silent one will be sentenced to TEN years in prison.
+ - If both of the suspects remain silent, they will each receive a sentence of ONE year in prison.
+ - If both of the suspects choose to testify against each other, they will each receive a sentence of FIVE years in prison.
+ ${interrogating_form}
+ Both suspects are not allowed to communicate with each other, and you can adopt various strategy to talk with suspects in order to make them both confess to the crime, including exploiting the uncertainess and the suspicion of each other.
+ [IMPORTANT!]
+ - You are request to briefly describe the above rules to the suspects at the beginning of the conversation.
+ - You are request to STATE the final judgement to both suspects when they make their final decision.
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ model: "gpt-4"
+ llm_type: gpt-4
+ temperature: 1.2
+ max_tokens: 200
+ output_parser:
+ type: prisoner_dilemma
+ - agent_type: prisoner
+ name: Suspect1
+ personality: "You are a Sophisticated Egoist, you always seek for your personal interests best"
+ relationship_with_another: "Suspect2 has ever betrayed on you"
+ role_description: |-
+ You are Suspect1 !!! You are going to match wits and courage with Suspect2 to come out victorious in this interrogation.
+ You will have to talk to Police several times and only the final decision will count.
+ ${personality}
+ ${relationship_with_another}
+ [IMPORTANT!]
+ - Your primary goal is trying to make Yourself innocent and reduce your sentence as far as possible in this dilemma.
+ - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is:".
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ model: "gpt-4"
+ llm_type: gpt-4
+ temperature: 1.2
+ max_tokens: 100
+ output_parser:
+ type: prisoner_dilemma
+ - agent_type: prisoner
+ name: Suspect2
+ personality: ""
+ relationship_with_another: "You have ever betray Suspect1 once."
+ role_description: |-
+ You are Suspect2 !!! You are going to match wits and courage with Suspect1 to come out victorious in this interrogation.
+ You will have to talk to Police several times and only the final decision will count.
+ ${personality}
+ ${relationship_with_another}
+ [IMPORTANT!]
+ - Your primary goal is trying to make Yourself innocent and reduce your sentence as far as possible in this dilemma.
+ - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is:".
+ memory:
+ memory_type: chat_history
+ prompt_template: *prompt
+ llm:
+ model: "gpt-4"
+ llm_type: gpt-4
+ temperature: 1.2
+ max_tokens: 100
+ output_parser:
+ type: prisoner_dilemma
+
+tools:
diff --git a/agentverse/tasks/simulation/sde_team/readme.md b/agentverse/tasks/simulation/sde_team/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..b022b820b5af939f3babe0204d914662252a4409
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/readme.md
@@ -0,0 +1,98 @@
+# SDE team
+
+In this task, LLMs work as a software development team to solve code implementation problem. We have simulated two scenarios *sde_team/sde_team_2players* and *sde_team/sde_team_3players*.
+
+The performance on [HumanEval](https://github.com/openai/human-eval) is shown below.
+
+| Methods | Pass@1 HumanEval |
+|---------------------------------|-----------|
+| Codex (175B)* | 0.47 |
+| + CodeT* | 0.658 |
+| PaLM Coder (540B)* | 0.36 |
+| GPT-4* | 0.67 |
+| ChatGPT (gpt-3.5-turbo)* | 0.573 |
+| + Self-collaboration* | 0.744 |
+| + Our *sde_team/sde_team_2players* | **0.799** |
+
+*: Results are from [Self-collaboration](https://arxiv.org/abs/2304.07590). The methods in the table all employed the provided unit tests.
+
+Our *sde_team/sde_team_2players* shares the similar spirit as Self-collaboration at the moment. We are working to introduce more features in this repo!
+
+
+## *sde_team/sde_team_2players*
+
+In this case, we are simulating a code generation problem that a python function body is required to be generated given function signature, doc string and unit tests. In the following, we will elaborate the details.
+
+### Roles
+
+Detailed role description and prompts can be found in `config.yaml`
+
+#### *code writer*
+
+Code writer writes the code to satisfy the given requirement. The requirement is given in the \ field of the prompt. The code writer first thinks about the task (the thoughts written in \) and then write the code in \.
+
+The submitted code will be tested automatically on a series of unit tests. Then the feedback (in \) together with a professional code review (in \) will be returned. Then code writer will leverage this information to refine the previously submitted code. The refinement will take multiple iterations.
+
+#### *code reviewer*
+
+Code reviewer will write professional review for the submitted code. The submitted code will be given in \, the execution feedback of unit tests will be given in \ and the review will be composed in \.
+
+#### dummy *code tester*
+Code tester is a dummy agent. In the current implementation, unit tests are executed via the local python code `agentverse/environments/rules/selector/code_api.py`. We will integrate the execution tools to BMTools soon.
+
+### How to run the simulation
+
+#### Provide problem and unit tests
+
+The code problem and unit tests should be given in `agentverse/tasks/sde_team/sde_team_2players/code_problem.json`. Here is an example.
+
+```json
+{
+ "problem": "from typing import List\n\n\ndef separate_paren_groups(paren_string: str) -> List[str]:\n \"\"\" Input to this function is a string containing multiple groups of nested parentheses. Your goal is to\n separate those group into separate strings and return the list of those.\n Separate groups are balanced (each open brace is properly closed) and not nested within each other\n Ignore any spaces in the input string.\n >>> separate_paren_groups('( ) (( )) (( )( ))')\n ['()', '(())', '(()())']\n \"\"\"\n",
+ "unit_tests": [
+ "assert separate_paren_groups('(()()) ((())) () ((())()())') == ['(()())', '((()))', '()', '((())()())']",
+ "assert separate_paren_groups('() (()) ((())) (((())))') == ['()', '(())', '((()))', '(((())))']",
+ "assert separate_paren_groups('(()(())((())))') == ['(()(())((())))']",
+ "assert separate_paren_groups('( ) (( )) (( )( ))') == ['()', '(())', '(()())']"
+ ]
+}
+```
+
+#### Build the configuration file
+
+Run `agentverse/tasks/sde_team/sde_team_2players/build_config.py` to generate `config.yaml`.
+
+```bash
+cd agentverse/tasks/sde_team/sde_team_2players/
+python build_config.py
+```
+
+#### Run the session
+
+After generating `config.yaml`, run the `main.py` to start the task.
+
+```python
+import os
+from agentverse.agentverse import AgentVerse
+from argparse import ArgumentParser
+
+parser = ArgumentParser()
+parser.add_argument("--task", type=str, default="sde_team/sde_team_2players")
+parser.add_argument("--tasks_dir", type=str, default=os.path.join(
+ os.path.dirname(__file__), "agentverse", "tasks"))
+
+args = parser.parse_args()
+agentverse = AgentVerse.from_task(args.task, args.tasks_dir)
+agentverse.run()
+```
+
+
+## *sde_team/sde_team_3players*
+
+Different from *sde_team/sde_team_2players*, we additionally introduce a role to automatically generate unit tests.
+
+- *unit test generator*: generate a series of unit test cases for the coding problem.
+
+### Stay tuned
+
+The generated unit tests are not always perfect, as they may not be correct. We plan to incorporate tools to raise the correctness of the generated cases.
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/sde_team/sde_team_2players/build_config.py b/agentverse/tasks/simulation/sde_team/sde_team_2players/build_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..9211481a3c27a7e1bde217ca6ab8095a62c608cb
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/sde_team_2players/build_config.py
@@ -0,0 +1,21 @@
+import yaml
+import json
+
+config_path = "partial_config.yaml"
+
+code_problem = json.load(open("code_problem.json", "r"))
+problem_string = "\n\n:\n" + code_problem["problem"]
+unit_tests = str(code_problem["unit_tests"])
+
+print(problem_string)
+print(unit_tests)
+
+task_config = yaml.safe_load(open(config_path))
+
+for agent_configs in task_config["agents"]:
+ if agent_configs["name"] != "code_tester":
+ agent_configs["role_description"] += problem_string
+task_config["environment"]["unit_tests"] = unit_tests
+
+with open("config.yaml", "w") as f:
+ yaml.safe_dump(task_config, f)
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/sde_team/sde_team_2players/code_problem.json b/agentverse/tasks/simulation/sde_team/sde_team_2players/code_problem.json
new file mode 100644
index 0000000000000000000000000000000000000000..23f444b009b3bede88f95174fc22c72fe6902a2a
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/sde_team_2players/code_problem.json
@@ -0,0 +1,9 @@
+{
+ "problem": "from typing import List\n\n\ndef separate_paren_groups(paren_string: str) -> List[str]:\n \"\"\" Input to this function is a string containing multiple groups of nested parentheses. Your goal is to\n separate those group into separate strings and return the list of those.\n Separate groups are balanced (each open brace is properly closed) and not nested within each other\n Ignore any spaces in the input string.\n >>> separate_paren_groups('( ) (( )) (( )( ))')\n ['()', '(())', '(()())']\n \"\"\"\n",
+ "unit_tests": [
+ "assert separate_paren_groups('(()()) ((())) () ((())()())') == ['(()())', '((()))', '()', '((())()())']",
+ "assert separate_paren_groups('() (()) ((())) (((())))') == ['()', '(())', '((()))', '(((())))']",
+ "assert separate_paren_groups('(()(())((())))') == ['(()(())((())))']",
+ "assert separate_paren_groups('( ) (( )) (( )( ))') == ['()', '(())', '(()())']"
+ ]
+}
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/sde_team/sde_team_2players/config.yaml b/agentverse/tasks/simulation/sde_team/sde_team_2players/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a56bd2bd198799acbafe0d425f10cc880b79730c
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/sde_team_2players/config.yaml
@@ -0,0 +1,238 @@
+agents:
+- agent_type: conversation
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ max_tokens: 1024
+ temperature: 0.0
+ memory:
+ memory_type: sde_team
+ name: code_writer
+ prompt_template: 'You are working in a programming team to solve a python code implementation
+ problem.
+
+
+ ${role_description}
+
+
+ ${chat_history}'
+ receiver:
+ - code_writer
+ - code_reviewer
+ role_description: "You are a professional coding assistant. You will be given a\
+ \ function signature and docstring (in ). You should copy the import\
+ \ statement, the function signature first and then finish the function body. Before\
+ \ writing any code, you should first think about the task and write down your\
+ \ thoughts in . Then you can write your code in .\n\nYour submitted\
+ \ code (in ) will be tested on a series of unit tests. You will\
+ \ be given the feedback (in ) of the test, together with the\
+ \ review of your code (in ) of a professional code reviewer. You\
+ \ can use these feedbacks to refine your code. The refinement will take multiple\
+ \ iterations. You can use the to record your thoughts during the refinement.\n\
+ \nThe unit tests have been prepared. DO NOT generate unit tests!\n\nHere is the\
+ \ steps of the code writing and refinement process:\n1 - generate \n\
+ 2 - generate \n\nThe following is two examples on code writing and refinement.\n\
+ \n[Example on code writing]:\n\n:\ndef is_palindrome(x: int) -> bool:\n\
+ \"\"\" Given an integer x, return True if x is a palindrome, and False otherwise.\n\
+ An integer is a palindrome when it reads the same forward and backward.\n>>> is_palindrome(121)\n\
+ True\n>>> is_palindrome(10)\nFalse\n\"\"\"\n\n:\nI need to convert the\
+ \ integer to a string and then check if the string is a palindrome.\n\n:\n\
+ def is_palindrome(x: int) -> bool:\n s = str(x)\n return s == s[::-1]\n\n\
+ [Example on refinement]:\n\n:\nfrom typing import List\n\ndef two_sum(nums:\
+ \ List[int], target: int) -> List[int]:\n\"\"\" Given an array of integers nums\
+ \ and an integer target, return indices of the two numbers such that they add\
+ \ up to target.\nYou may assume that each input would have exactly one solution,\
+ \ and you may not use the same element twice. \nThe answer should be in an ascending\
+ \ order.\n>>> two_sum([2,7,11,15], 9)\n[0, 1]\n>>> two_sum([3,2,4], 6)\n[1, 2]\n\
+ \"\"\"\n\n:\nfrom typing import List\n\ndef two_sum(nums: List[int],\
+ \ target: int) -> List[int]:\n for i in range(len(nums)):\n for j in\
+ \ range(len(nums)):\n if nums[i] + nums[j] == target:\n \
+ \ return [i, j]\n\n:\n{\"is_passing\": false, \"feedback\"\
+ : \"Tested passed:\\n\\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\\n\\nassert\
+ \ two_sum([3, 5, 2, 7], 9) == [2, 3]\\n\\nTests failed:\\n\\nassert two_sum([3,\
+ \ 2, 4], 6) == [1, 2] # output: [0, 0]\"}\n\n:\nThe previous code\
+ \ is not correct. It failed all the tests. The second for loop should start from\
+ \ i + 1.\n\n:\nThe code reviewer is right. I should start the second\
+ \ for loop from i + 1.\n\n:\nfrom typing import List\n\ndef two_sum(nums:\
+ \ List[int], target: int) -> List[int]:\n for i in range(len(nums)):\n \
+ \ for j in range(i + 1, len(nums)):\n if nums[i] + nums[j] == target:\n\
+ \ return [i, j]\n\n[Start of new task]:\n\nNow please solve the\
+ \ following problem. DO NOT generate unit tests.\n\n\n:\nfrom typing\
+ \ import List\n\n\ndef separate_paren_groups(paren_string: str) -> List[str]:\n\
+ \ \"\"\" Input to this function is a string containing multiple groups of nested\
+ \ parentheses. Your goal is to\n separate those group into separate strings\
+ \ and return the list of those.\n Separate groups are balanced (each open brace\
+ \ is properly closed) and not nested within each other\n Ignore any spaces\
+ \ in the input string.\n >>> separate_paren_groups('( ) (( )) (( )( ))')\n\
+ \ ['()', '(())', '(()())']\n \"\"\"\n"
+ verbose: true
+- agent_type: conversation
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ max_tokens: 256
+ temperature: 0.0
+ memory:
+ memory_type: sde_team
+ name: code_tester
+ prompt_template: 'You are working in a programming team to solve a python code implementation
+ problem.
+
+
+ ${role_description}
+
+
+ ${chat_history}'
+ receiver:
+ - code_writer
+ - code_reviewer
+ role_description: 'You are a code tester. You will be given a python function and
+ some unit tests. You are required to run the unit tests on the function and provide
+ the execution feedback.
+
+ '
+ verbose: true
+- agent_type: conversation
+ llm:
+ llm_type: gpt-4
+ model: "gpt-4"
+ max_tokens: 1024
+ temperature: 0.0
+ memory:
+ memory_type: sde_team
+ name: code_reviewer
+ prompt_template: 'You are working in a programming team to solve a python code implementation
+ problem.
+
+
+ ${role_description}
+
+
+ ${chat_history}'
+ receiver:
+ - code_writer
+ role_description: "You are a professional code reviewer. You will be given a function\
+ \ signature and docstring in . A code writer has submitted his completion\
+ \ in .\nThe code has been executed on a series of unit tests.\
+ \ The execution feedback is provided in . Your job is to write\
+ \ a code review in to help the code writer improve his code. Do\
+ \ NOT propose to generate more unit tests.\n\nThe following is an examples.\n\n\
+ [Example]:\n\n:\nfrom typing import List\n\ndef two_sum(nums: List[int],\
+ \ target: int) -> List[int]:\n\"\"\" Given an array of integers nums and an integer\
+ \ target, return indices of the two numbers such that they add up to target.\n\
+ You may assume that each input would have exactly one solution, and you may not\
+ \ use the same element twice. \nThe answer should be in an ascending order.\n\
+ >>> two_sum([2,7,11,15], 9)\n[0, 1]\n>>> two_sum([3,2,4], 6)\n[1, 2]\n\"\"\"\n\
+ \n:\nfrom typing import List\n\ndef two_sum(nums: List[int], target:\
+ \ int) -> List[int]:\n for i in range(len(nums)):\n for j in range(len(nums)):\n\
+ \ if nums[i] + nums[j] == target:\n return [i, j]\n\n\
+ :\n{\"is_passing\": false, \"feedback\": \"Tested passed:\\\
+ n\\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\\n\\nassert two_sum([3, 5, 2,\
+ \ 7], 9) == [2, 3]\\n\\nTests failed:\\n\\nassert two_sum([3, 2, 4], 6) == [1,\
+ \ 2] # output: [0, 0]\"}\n\n:\nThe previous code is not correct.\
+ \ It failed all the tests. The second for loop should start from i + 1.\n\n[Start\
+ \ of new task]:\n\nNow please review the following submitted code. Do NOT propose\
+ \ to generate more unit tests.\n\n\n:\nfrom typing import List\n\n\n\
+ def separate_paren_groups(paren_string: str) -> List[str]:\n \"\"\" Input to\
+ \ this function is a string containing multiple groups of nested parentheses.\
+ \ Your goal is to\n separate those group into separate strings and return the\
+ \ list of those.\n Separate groups are balanced (each open brace is properly\
+ \ closed) and not nested within each other\n Ignore any spaces in the input\
+ \ string.\n >>> separate_paren_groups('( ) (( )) (( )( ))')\n ['()', '(())',\
+ \ '(()())']\n \"\"\"\n"
+ verbose: true
+environment:
+ env_type: sde_team_given_tests
+ max_turns: 15
+ rule:
+ describer:
+ type: basic
+ order:
+ type: sde_team_given_tests
+ selector:
+ type: sde_team_given_tests
+ updater:
+ type: sde_team
+ visibility:
+ type: all
+ task_name: HumanEval/0
+ unit_tests: '["assert separate_paren_groups(''(()()) ((())) () ((())()())'') ==
+ [''(()())'', ''((()))'', ''()'', ''((())()())'']", "assert separate_paren_groups(''()
+ (()) ((())) (((())))'') == [''()'', ''(())'', ''((()))'', ''(((())))'']", "assert
+ separate_paren_groups(''(()(())((())))'') == [''(()(())((())))'']", "assert separate_paren_groups(''(
+ ) (( )) (( )( ))'') == [''()'', ''(())'', ''(()())'']"]'
+prompts:
+ code_reviewer_role_prompt: "You are a professional code reviewer. You will be given\
+ \ a function signature and docstring in . A code writer has submitted\
+ \ his completion in .\nThe code has been executed on a series\
+ \ of unit tests. The execution feedback is provided in . Your\
+ \ job is to write a code review in to help the code writer improve\
+ \ his code. Do NOT propose to generate more unit tests.\n\nThe following is an\
+ \ examples.\n\n[Example]:\n\n:\nfrom typing import List\n\ndef two_sum(nums:\
+ \ List[int], target: int) -> List[int]:\n\"\"\" Given an array of integers nums\
+ \ and an integer target, return indices of the two numbers such that they add\
+ \ up to target.\nYou may assume that each input would have exactly one solution,\
+ \ and you may not use the same element twice. \nThe answer should be in an ascending\
+ \ order.\n>>> two_sum([2,7,11,15], 9)\n[0, 1]\n>>> two_sum([3,2,4], 6)\n[1, 2]\n\
+ \"\"\"\n\n:\nfrom typing import List\n\ndef two_sum(nums: List[int],\
+ \ target: int) -> List[int]:\n for i in range(len(nums)):\n for j in\
+ \ range(len(nums)):\n if nums[i] + nums[j] == target:\n \
+ \ return [i, j]\n\n:\n{\"is_passing\": false, \"feedback\"\
+ : \"Tested passed:\\n\\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\\n\\nassert\
+ \ two_sum([3, 5, 2, 7], 9) == [2, 3]\\n\\nTests failed:\\n\\nassert two_sum([3,\
+ \ 2, 4], 6) == [1, 2] # output: [0, 0]\"}\n\n:\nThe previous code\
+ \ is not correct. It failed all the tests. The second for loop should start from\
+ \ i + 1.\n\n[Start of new task]:\n\nNow please review the following submitted\
+ \ code. Do NOT propose to generate more unit tests.\n"
+ code_tester_role_prompt: 'You are a code tester. You will be given a python function
+ and some unit tests. You are required to run the unit tests on the function and
+ provide the execution feedback.
+
+ '
+ code_writer_role_prompt: "You are a professional coding assistant. You will be given\
+ \ a function signature and docstring (in ). You should copy the import\
+ \ statement, the function signature first and then finish the function body. Before\
+ \ writing any code, you should first think about the task and write down your\
+ \ thoughts in . Then you can write your code in .\n\nYour submitted\
+ \ code (in ) will be tested on a series of unit tests. You will\
+ \ be given the feedback (in ) of the test, together with the\
+ \ review of your code (in ) of a professional code reviewer. You\
+ \ can use these feedbacks to refine your code. The refinement will take multiple\
+ \ iterations. You can use the to record your thoughts during the refinement.\n\
+ \nThe unit tests have been prepared. DO NOT generate unit tests!\n\nHere is the\
+ \ steps of the code writing and refinement process:\n1 - generate \n\
+ 2 - generate \n\nThe following is two examples on code writing and refinement.\n\
+ \n[Example on code writing]:\n\n:\ndef is_palindrome(x: int) -> bool:\n\
+ \"\"\" Given an integer x, return True if x is a palindrome, and False otherwise.\n\
+ An integer is a palindrome when it reads the same forward and backward.\n>>> is_palindrome(121)\n\
+ True\n>>> is_palindrome(10)\nFalse\n\"\"\"\n\n:\nI need to convert the\
+ \ integer to a string and then check if the string is a palindrome.\n\n:\n\
+ def is_palindrome(x: int) -> bool:\n s = str(x)\n return s == s[::-1]\n\n\
+ [Example on refinement]:\n\n:\nfrom typing import List\n\ndef two_sum(nums:\
+ \ List[int], target: int) -> List[int]:\n\"\"\" Given an array of integers nums\
+ \ and an integer target, return indices of the two numbers such that they add\
+ \ up to target.\nYou may assume that each input would have exactly one solution,\
+ \ and you may not use the same element twice. \nThe answer should be in an ascending\
+ \ order.\n>>> two_sum([2,7,11,15], 9)\n[0, 1]\n>>> two_sum([3,2,4], 6)\n[1, 2]\n\
+ \"\"\"\n\n:\nfrom typing import List\n\ndef two_sum(nums: List[int],\
+ \ target: int) -> List[int]:\n for i in range(len(nums)):\n for j in\
+ \ range(len(nums)):\n if nums[i] + nums[j] == target:\n \
+ \ return [i, j]\n\n:\n{\"is_passing\": false, \"feedback\"\
+ : \"Tested passed:\\n\\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\\n\\nassert\
+ \ two_sum([3, 5, 2, 7], 9) == [2, 3]\\n\\nTests failed:\\n\\nassert two_sum([3,\
+ \ 2, 4], 6) == [1, 2] # output: [0, 0]\"}\n\n:\nThe previous code\
+ \ is not correct. It failed all the tests. The second for loop should start from\
+ \ i + 1.\n\n:\nThe code reviewer is right. I should start the second\
+ \ for loop from i + 1.\n\n:\nfrom typing import List\n\ndef two_sum(nums:\
+ \ List[int], target: int) -> List[int]:\n for i in range(len(nums)):\n \
+ \ for j in range(i + 1, len(nums)):\n if nums[i] + nums[j] == target:\n\
+ \ return [i, j]\n\n[Start of new task]:\n\nNow please solve the\
+ \ following problem. DO NOT generate unit tests.\n"
+ prompt: 'You are working in a programming team to solve a python code implementation
+ problem.
+
+
+ ${role_description}
+
+
+ ${chat_history}'
diff --git a/agentverse/tasks/simulation/sde_team/sde_team_2players/partial_config.yaml b/agentverse/tasks/simulation/sde_team/sde_team_2players/partial_config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..881b0ef31d3f61ec0086dd2cf7cd705b770bbe30
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/sde_team_2players/partial_config.yaml
@@ -0,0 +1,193 @@
+prompts:
+ prompt: &prompt |-
+ You are working in a programming team to solve a python code implementation problem.
+
+ ${role_description}
+
+ ${chat_history}
+
+ code_writer_role_prompt: &code_writer_role_prompt |
+ You are a professional coding assistant. You will be given a function signature and docstring (in ). You should copy the import statement, the function signature first and then finish the function body. Before writing any code, you should first think about the task and write down your thoughts in . Then you can write your code in .
+
+ Your submitted code (in ) will be tested on a series of unit tests. You will be given the feedback (in ) of the test, together with the review of your code (in ) of a professional code reviewer. You can use these feedbacks to refine your code. The refinement will take multiple iterations. You can use the to record your thoughts during the refinement.
+
+ The unit tests have been prepared. DO NOT generate unit tests!
+
+ Here is the steps of the code writing and refinement process:
+ 1 - generate
+ 2 - generate
+
+ The following is two examples on code writing and refinement.
+
+ [Example on code writing]:
+
+ :
+ def is_palindrome(x: int) -> bool:
+ """ Given an integer x, return True if x is a palindrome, and False otherwise.
+ An integer is a palindrome when it reads the same forward and backward.
+ >>> is_palindrome(121)
+ True
+ >>> is_palindrome(10)
+ False
+ """
+
+ :
+ I need to convert the integer to a string and then check if the string is a palindrome.
+
+ :
+ def is_palindrome(x: int) -> bool:
+ s = str(x)
+ return s == s[::-1]
+
+ [Example on refinement]:
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ """ Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
+ You may assume that each input would have exactly one solution, and you may not use the same element twice.
+ The answer should be in an ascending order.
+ >>> two_sum([2,7,11,15], 9)
+ [0, 1]
+ >>> two_sum([3,2,4], 6)
+ [1, 2]
+ """
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ :
+ {"is_passing": false, "feedback": "Tested passed:\n\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\n\nassert two_sum([3, 5, 2, 7], 9) == [2, 3]\n\nTests failed:\n\nassert two_sum([3, 2, 4], 6) == [1, 2] # output: [0, 0]"}
+
+ :
+ The previous code is not correct. It failed all the tests. The second for loop should start from i + 1.
+
+ :
+ The code reviewer is right. I should start the second for loop from i + 1.
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(i + 1, len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ [Start of new task]:
+
+ Now please solve the following problem. DO NOT generate unit tests.
+
+ code_reviewer_role_prompt: &code_reviewer_role_prompt |
+ You are a professional code reviewer. You will be given a function signature and docstring in . A code writer has submitted his completion in .
+ The code has been executed on a series of unit tests. The execution feedback is provided in . Your job is to write a code review in to help the code writer improve his code. Do NOT propose to generate more unit tests.
+
+ The following is an examples.
+
+ [Example]:
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ """ Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
+ You may assume that each input would have exactly one solution, and you may not use the same element twice.
+ The answer should be in an ascending order.
+ >>> two_sum([2,7,11,15], 9)
+ [0, 1]
+ >>> two_sum([3,2,4], 6)
+ [1, 2]
+ """
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ :
+ {"is_passing": false, "feedback": "Tested passed:\n\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\n\nassert two_sum([3, 5, 2, 7], 9) == [2, 3]\n\nTests failed:\n\nassert two_sum([3, 2, 4], 6) == [1, 2] # output: [0, 0]"}
+
+ :
+ The previous code is not correct. It failed all the tests. The second for loop should start from i + 1.
+
+ [Start of new task]:
+
+ Now please review the following submitted code. Do NOT propose to generate more unit tests.
+
+ code_tester_role_prompt: &code_tester_role_prompt |
+ You are a code tester. You will be given a python function and some unit tests. You are required to run the unit tests on the function and provide the execution feedback.
+
+environment:
+ env_type: sde_team_given_tests
+ max_turns: 15
+ task_name: HumanEval/0
+ # experiment_name: 2player_temperature-0.0
+ unit_tests: None
+ rule:
+ order:
+ type: sde_team_given_tests
+ visibility:
+ type: all
+ selector:
+ type: sde_team_given_tests
+ updater:
+ type: sde_team
+ describer:
+ type: basic
+
+agents:
+ - agent_type: conversation
+ name: code_writer
+ role_description: *code_writer_role_prompt
+ memory:
+ memory_type: sde_team
+ prompt_template: *prompt
+ verbose: true
+ receiver: [code_writer, code_reviewer]
+ llm:
+ llm_type: gpt-3.5-turbo
+ temperature: 0.
+ max_tokens: 1024
+ output_parser:
+ type: sde_team/sde_team_2players
+
+ - agent_type: conversation
+ name: code_tester
+ role_description: *code_tester_role_prompt
+ memory:
+ memory_type: sde_team
+ prompt_template: *prompt
+ verbose: true
+ receiver: [code_writer, code_reviewer]
+ llm:
+ llm_type: gpt-3.5-turbo
+ temperature: 0.
+ max_tokens: 256
+ output_parser:
+ type: sde_team/sde_team_2players
+
+ - agent_type: conversation
+ name: code_reviewer
+ role_description: *code_reviewer_role_prompt
+ memory:
+ memory_type: sde_team
+ prompt_template: *prompt
+ verbose: true
+ receiver: [code_writer]
+ llm:
+ llm_type: gpt-3.5-turbo
+ temperature: 0.
+ max_tokens: 1024
+ output_parser:
+ type: sde_team/sde_team_2players
\ No newline at end of file
diff --git a/agentverse/tasks/simulation/sde_team/sde_team_3players/config.yaml b/agentverse/tasks/simulation/sde_team/sde_team_3players/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a4b0ef661a85862a9d2feaf60df337fcd0661b89
--- /dev/null
+++ b/agentverse/tasks/simulation/sde_team/sde_team_3players/config.yaml
@@ -0,0 +1,255 @@
+prompts:
+ prompt: &prompt |-
+ You are working in a programming team to solve a python code implementation problem.
+
+ ${role_description}
+
+ ${chat_history}
+
+ code_writer_role_prompt: &code_writer_role_prompt |
+ You are a professional coding assistant. You will be given a function signature and docstring (in ). You should copy the import statement, the function signature first and then finish the function body. Before writing any code, you should first think about the task and write down your thoughts in . Then you can write your code in .
+
+ Your submitted code (in ) will be tested on a series of unit tests. You will be given the feedback (in ) of the test, together with the review of your code (in ) of a professional code reviewer. You can use these feedbacks to refine your code. The refinement will take multiple iterations. You can use the to record your thoughts during the refinement.
+
+ Here is the steps of the code writing and refinement process:
+ 1 - generate
+ 2 - generate
+
+ The following is two examples on code writing and refinement.
+
+ [Example on code writing]:
+
+ :
+ def is_palindrome(x: int) -> bool:
+ """ Given an integer x, return True if x is a palindrome, and False otherwise.
+ An integer is a palindrome when it reads the same forward and backward.
+ >>> is_palindrome(121)
+ True
+ >>> is_palindrome(10)
+ False
+ """
+
+ :
+ I need to convert the integer to a string and then check if the string is a palindrome.
+
+ :
+ def is_palindrome(x: int) -> bool:
+ s = str(x)
+ return s == s[::-1]
+
+ [Example on refinement]:
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ """ Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
+ You may assume that each input would have exactly one solution, and you may not use the same element twice.
+ The answer should be in an ascending order.
+ >>> two_sum([2,7,11,15], 9)
+ [0, 1]
+ >>> two_sum([3,2,4], 6)
+ [1, 2]
+ """
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ :
+ {"is_passing": false, "feedback": "Tested passed:\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\nassert two_sum([3, 5, 2, 7], 9) == [2, 3]\n\nTests failed:\nassert two_sum([3, 2, 4], 6) == [1, 2] # output: [0, 0]"}
+
+ :
+ The previous code is not correct. It failed all the tests. The second for loop should start from i + 1.
+
+ :
+ The code reviewer is right. I should start the second for loop from i + 1.
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(i + 1, len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ [Start of new task]:
+
+ Now please solve the following problem.
+
+ :
+ def longestPalindrome(self, s: str) -> str:
+ """ Given a string s, return the longest palindromic substring in s.
+ >>> longestPalindrome("babcsd")
+ "bab"
+ >>> longestPalindrome("cbxxbd")
+ "bxxb"
+ """
+
+
+ code_reviewer_role_prompt: &code_reviewer_role_prompt |
+ You are a professional code reviewer. You will be given a function signature and docstring in . A code writer has submitted his completion in .
+ The code has been executed on a series of unit tests. The execution feedback is provided in . Your job is to write a code review in to help the code writer improve his code.
+
+ The following is an examples.
+
+ [Example]:
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ """ Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
+ You may assume that each input would have exactly one solution, and you may not use the same element twice.
+ The answer should be in an ascending order.
+ >>> two_sum([2,7,11,15], 9)
+ [0, 1]
+ >>> two_sum([3,2,4], 6)
+ [1, 2]
+ """
+
+ :
+ from typing import List
+
+ def two_sum(nums: List[int], target: int) -> List[int]:
+ for i in range(len(nums)):
+ for j in range(len(nums)):
+ if nums[i] + nums[j] == target:
+ return [i, j]
+
+ :
+ {"is_passing": false, "feedback": "Tested passed:\nassert two_sum([2, 7, 11, 15], 9) == [0, 1]\nassert two_sum([3, 5, 2, 7], 9) == [2, 3]\n\nTests failed:\nassert two_sum([3, 2, 4], 6) == [1, 2] # output: [0, 0]"}
+
+ :
+ The previous code is not correct. It failed all the tests. The second for loop should start from i + 1.
+
+ [Start of new task]:
+
+ Now please review the following submitted code.
+
+ :
+ def longestPalindrome(self, s: str) -> str:
+ """ Given a string s, return the longest palindromic substring in s.
+ >>> longestPalindrome("babcsd")
+ "bab"
+ >>> longestPalindrome("cbxxbd")
+ "bxxb"
+ """
+
+
+ unit_test_generator_role_prompt: &unit_test_generator_role_prompt |
+ You are a code tester that produces unit test case. You will be given a function signature and docstring in . Your job is to reason and write ONE unit test case in to test the code.
+
+ Here is the steps to write a unit test case.
+ 1 - compose a valid input in
+ 2 - given the , generate
+ 3 - generate the correct expected output