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}
{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): + """ + start a frontend + """ + 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/environments/__init__.py b/agentverse/environments/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..099ceb1bd0de04d8d64e0da1b117be482805a4e3 --- /dev/null +++ b/agentverse/environments/__init__.py @@ -0,0 +1,17 @@ +from typing import Dict +from agentverse.registry import Registry + + +env_registry = Registry(name="EnvironmentRegistry") + + +from .base import BaseEnvironment, BaseRule + +# from .basic import PipelineEnvironment +from .simulation_env.basic import BasicEnvironment +from .simulation_env.pokemon import PokemonEnvironment +from .simulation_env.prisoner_dilemma import PrisonerDilemmaEnvironment +from .simulation_env.sde_team import SdeTeamEnvironment +from .simulation_env.sde_team_given_tests import SdeTeamGivenTestsEnvironment + +from .tasksolving_env.basic import BasicEnvironment diff --git a/agentverse/environments/base.py b/agentverse/environments/base.py new file mode 100644 index 0000000000000000000000000000000000000000..7767dc248d825aff6a2c7d76532136dcbc23dfeb --- /dev/null +++ b/agentverse/environments/base.py @@ -0,0 +1,58 @@ +from __future__ import annotations +from agentverse.logging import logger + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, Dict, List + +from pydantic import BaseModel + +# from agentverse.agents.agent import Agent + +if TYPE_CHECKING: + from agentverse.agents.base import BaseAgent + from agentverse.message import Message + + +class BaseRule(BaseModel): + pass + + +class BaseEnvironment(BaseModel): + """ + Base class for environment. + + 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: BaseRule + max_turns: int = 10 + cnt_turn: int = 0 + last_messages: List[Message] = [] + rule_params: Dict = {} + + @abstractmethod + async def step(self) -> List[Message]: + """Run one step of the environment""" + pass + + @abstractmethod + def reset(self) -> None: + """Reset the environment""" + pass + + def report_metrics(self) -> None: + """Report useful metrics""" + total_spent = sum([agent.get_spend() for agent in self.agents]) + logger.info(f"Total spent: ${total_spent}") + pass + + def is_done(self) -> bool: + """Check if the environment is done""" + return self.cnt_turn >= self.max_turns diff --git a/agentverse/environments/simulation_env/basic.py b/agentverse/environments/simulation_env/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..8531d48a071e74bb7544da7b4dd6055156bb911b --- /dev/null +++ b/agentverse/environments/simulation_env/basic.py @@ -0,0 +1,101 @@ +import asyncio + +# import logging +from agentverse.logging import get_logger +from typing import Any, Dict, List + +# from agentverse.agents.agent import Agent +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 + +logger = get_logger() + +from .. import env_registry as EnvironmentRegistry +from ..base import BaseEnvironment + + +@EnvironmentRegistry.register("sim-basic") +class BasicEnvironment(BaseEnvironment): + """ + A basic environment implementing the logic of conversation. + + 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 = {} + + def __init__(self, rule, **kwargs): + rule_config = rule + order_config = rule_config.get("order", {"type": "sequential"}) + visibility_config = rule_config.get("visibility", {"type": "all"}) + selector_config = rule_config.get("selector", {"type": "basic"}) + updater_config = rule_config.get("updater", {"type": "basic"}) + describer_config = rule_config.get("describer", {"type": "basic"}) + rule = Rule( + order_config, + visibility_config, + selector_config, + updater_config, + describer_config, + ) + super().__init__(rule=rule, **kwargs) + + 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) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self) + + # Generate the next message + messages = await asyncio.gather( + *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids] + ) + + # Some rules will select certain messages from all the messages + selected_messages = self.rule.select_message(self, messages) + self.last_messages = selected_messages + self.print_messages(selected_messages) + + # Update the memory of the agents + self.rule.update_memory(self) + + # Update the set of visible agents for each agent + self.rule.update_visible_agents(self) + + 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}") + logger.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""" + return self.cnt_turn >= self.max_turns diff --git a/agentverse/environments/simulation_env/pokemon.py b/agentverse/environments/simulation_env/pokemon.py new file mode 100644 index 0000000000000000000000000000000000000000..d62b32cf6395e077c0e20d9fb60adf230be30e32 --- /dev/null +++ b/agentverse/environments/simulation_env/pokemon.py @@ -0,0 +1,222 @@ +import asyncio +import datetime +import logging +from typing import Any, Dict, List, Optional, Set + +# from agentverse.agents.agent import Agent +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 + + +@EnvironmentRegistry.register("pokemon") +class PokemonEnvironment(BaseEnvironment): + """ + An environment for Pokémon demo. + + Args: + agents: List of agents + locations: A dict of locations to agents within them + rule: Rule for the environment + max_turns: Maximum number of turns + cnt_turn: Current turn number + last_messages: Messages from last turn + rule_params: Variables set by the rule + """ + + agents: List[BaseAgent] + locations_to_agents: Dict[str, Set[str]] + # locations_descriptions: Dict[str, str] + time: datetime.datetime = datetime.datetime(2021, 1, 1, 8, 0, 0) + rule: Rule + max_turns: int = 10 + cnt_turn: int = 0 + last_messages: List[Message] = [] + rule_params: Dict = {} + + def __init__(self, rule, locations, **kwargs): + rule_config = rule + order_config = rule_config.get("order", {"type": "sequential"}) + visibility_config = rule_config.get("visibility", {"type": "all"}) + selector_config = rule_config.get("selector", {"type": "basic"}) + updater_config = rule_config.get("updater", {"type": "basic"}) + describer_config = rule_config.get("describer", {"type": "basic"}) + rule = Rule( + order_config, + visibility_config, + selector_config, + updater_config, + describer_config, + ) + locations_to_agents = {} + # locations_descriptions = {} + locations_config = locations + for loc in locations_config: + locations_to_agents[loc["name"]] = set(loc["init_agents"]) + # locations_descriptions[loc["name"]] = loc["description"] + super().__init__( + rule=rule, + locations_to_agents=locations_to_agents, + # locations_descriptions=locations_descriptions, + **kwargs, + ) + + async def step( + self, + is_player: bool = False, + player_content: str = None, + receiver: str = None, + receiver_id: Optional[int] = None, + agent_ids: Optional[List[int]] = None, + ) -> List[Message]: + """Run one step of the environment""" + + # Get the next agent index + # time.sleep(8) + # return [Message(content="Test", sender="May", receiver=["May"])] + if is_player: + return await self._respond_to_player(player_content, receiver, receiver_id) + else: + return await self._routine_step(agent_ids) + + async def _routine_step(self, agent_ids) -> List[Message]: + self.rule.update_visible_agents(self) + + # agent_ids = self.rule.get_next_agent_idx(self) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self) + + # Generate the next message + messages = await asyncio.gather( + *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids] + ) + # messages = self.get_test_messages() + + # Some rules will select certain messages from all the messages + selected_messages = self.rule.select_message(self, messages) + + # Update the memory of the agents + self.last_messages = selected_messages + self.rule.update_memory(self) + self.print_messages(selected_messages) + + self.cnt_turn += 1 + self.time += datetime.timedelta(minutes=5) + + return selected_messages + + async def _respond_to_player( + self, + player_content: str = None, + receiver: str = None, + receiver_id: Optional[int] = None, + ) -> List[Message]: + if receiver_id is None: + for agent in self.agents: + if agent.name == receiver: + receiver_id = agent.agent_id + break + agent_ids = [receiver_id] + agent_name = receiver + player_message = Message( + sender="Brenden", content=player_content, receiver=[agent_name] + ) + + # Update the set of visible agents for each agent + self.rule.update_visible_agents(self) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self, player_content) + + # Generate the next message + messages = await asyncio.gather( + *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids] + ) + + # Some rules will select certain messages from all the messages + # selected_messages = self.rule.select_message(self, messages) + + # Update the memory of the agents + self.last_messages = [player_message, *messages] + self.rule.update_memory(self) + self.print_messages(messages) + + self.cnt_turn += 1 + + return messages + + def update_state(self, agent_location: Dict[str, str]): + for agent_name, location in agent_location.items(): + # original_location = self.get_agent_to_location()[agent_name] + # self.locations_to_agents[original_location].remove(agent_name) + self.locations_to_agents[location].add(agent_name) + + def get_agent_to_location(self) -> Dict[str, str]: + ret = {} + for location, agent_names in self.locations_to_agents.items(): + for agent in agent_names: + ret[agent] = location + return ret + + def print_messages(self, messages: List[Message]) -> None: + for message in messages: + 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""" + return self.cnt_turn >= self.max_turns + + def get_test_messages(self) -> List[Message]: + messages = [ + Message( + content='{"to": "Birch", "action": "Speak", "text": "Hi!!!"}', + sender="May", + receiver={"May", "Birch"}, + tool_response=[], + ), + Message( + content='{"to": "May", "text": "Good morning, May! How is your research going?", "action": "Speak"}', + sender="Birch", + receiver={"May", "Birch"}, + tool_response=[], + ), + Message( + content='{"to": "Pokémon Center", "action": "MoveTo"}', + sender="Steven", + receiver={"Steven"}, + tool_response=[], + ), + Message( + content='{"to": "Shop", "last_time": "10 minutes", "action": "MoveTo"}', + sender="Maxie", + receiver={"Maxie"}, + tool_response=[], + ), + Message( + content='{"to": "Pok\\u00e9mon Center", "action": "MoveTo"}', + sender="Archie", + receiver={"Archie"}, + tool_response=[], + ), + Message( + content='{"to": "Shop", "action": "MoveTo"}', + sender="Joseph", + receiver={"Joseph"}, + tool_response=[], + ), + ] + return messages diff --git a/agentverse/environments/simulation_env/prisoner_dilemma.py b/agentverse/environments/simulation_env/prisoner_dilemma.py new file mode 100644 index 0000000000000000000000000000000000000000..0053404bbccee085b70a900a40365d565705e8a7 --- /dev/null +++ b/agentverse/environments/simulation_env/prisoner_dilemma.py @@ -0,0 +1,49 @@ +import asyncio +import logging +from typing import Any, Dict, List + +# from agentverse.agents.agent import Agent +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 .basic import BasicEnvironment + + +@EnvironmentRegistry.register("prisoner_dilemma") +class PrisonerDilemmaEnvironment(BasicEnvironment): + """ + An environment for prisoner dilemma. + """ + + 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) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self) + + # Generate the next message + messages = await asyncio.gather( + *[self.agents[i].astep(self, env_descriptions[i]) for i in agent_ids] + ) + + # Some rules will select certain messages from all the messages + selected_messages = self.rule.select_message(self, messages) + self.last_messages = selected_messages + self.print_messages(selected_messages) + + # Update the memory of the agents + self.rule.update_memory(self) + + # Update the set of visible agents for each agent + self.rule.update_visible_agents(self) + + self.cnt_turn += 1 + + return selected_messages diff --git a/agentverse/environments/simulation_env/reflection.py b/agentverse/environments/simulation_env/reflection.py new file mode 100644 index 0000000000000000000000000000000000000000..c7c316398c2cd3dbf14a4891c12437c5322f2968 --- /dev/null +++ b/agentverse/environments/simulation_env/reflection.py @@ -0,0 +1,128 @@ +import asyncio +import logging +from typing import Any, Dict, List + +from datetime import datetime as dt +import datetime + +from pydantic import Field + +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 pydantic import validator + + +@EnvironmentRegistry.register("reflection") +class ReflectionEnvironment(BaseEnvironment): + """ + Environment used in Observation-Planning-Reflection agent architecture. + + 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 + current_time + time_delta: time difference between steps + """ + + agents: List[BaseAgent] + rule: Rule + max_turns: int = 10 + cnt_turn: int = 0 + last_messages: List[Message] = [] + rule_params: Dict = {} + current_time: dt = dt.now() + time_delta: int = 120 + # + + # @validator("time_delta") + # def convert_str_to_timedelta(cls, string): + # + # return datetime.timedelta(seconds=int(string)) + + def __init__(self, rule, **kwargs): + rule_config = rule + order_config = rule_config.get("order", {"type": "sequential"}) + visibility_config = rule_config.get("visibility", {"type": "all"}) + selector_config = rule_config.get("selector", {"type": "basic"}) + updater_config = rule_config.get("updater", {"type": "basic"}) + describer_config = rule_config.get("describer", {"type": "basic"}) + rule = Rule( + order_config, + visibility_config, + selector_config, + updater_config, + describer_config, + ) + + super().__init__(rule=rule, **kwargs) + + async def step(self) -> List[Message]: + """Run one step of the environment""" + + logging.log(logging.INFO, f"Tick tock. Current time: {self.current_time}") + + # Get the next agent index + agent_ids = self.rule.get_next_agent_idx(self) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self) + + # Generate the next message + messages = await asyncio.gather( + *[ + self.agents[i].astep(self.current_time, env_descriptions[i]) + for i in agent_ids + ] + ) + + # Some rules will select certain messages from all the messages + selected_messages = self.rule.select_message(self, messages) + self.last_messages = selected_messages + self.print_messages(selected_messages) + + # Update the memory of the agents + self.rule.update_memory(self) + + # Update the set of visible agents for each agent + self.rule.update_visible_agents(self) + + self.cnt_turn += 1 + + # update current_time + self.tick_tock() + + 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() + BaseAgent.update_forward_refs() + for agent in self.agents: + agent.reset(environment=self) + + def is_done(self) -> bool: + """Check if the environment is done""" + return self.cnt_turn >= self.max_turns + + def tick_tock(self) -> None: + """Increment the time""" + self.current_time = self.current_time + datetime.timedelta( + seconds=self.time_delta + ) diff --git a/agentverse/environments/simulation_env/rules/__init__.py b/agentverse/environments/simulation_env/rules/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..57c59eb145b65c772c36ebb7ecc9a2aa7665e8e7 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/__init__.py @@ -0,0 +1 @@ +from .base import SimulationRule diff --git a/agentverse/environments/simulation_env/rules/base.py b/agentverse/environments/simulation_env/rules/base.py new file mode 100644 index 0000000000000000000000000000000000000000..83028a911d812536d91e04656d2f8056ef942cc8 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/base.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, List, Optional + +from agentverse.environments.simulation_env.rules.describer import ( + BaseDescriber, + describer_registry, +) +from agentverse.environments.simulation_env.rules.order import BaseOrder, order_registry +from agentverse.environments.simulation_env.rules.selector import ( + BaseSelector, + selector_registry, +) +from agentverse.environments.simulation_env.rules.updater import ( + BaseUpdater, + updater_registry, +) +from agentverse.environments.simulation_env.rules.visibility import ( + BaseVisibility, + visibility_registry, +) +from agentverse.environments import BaseRule + +if TYPE_CHECKING: + from agentverse.environments.base import BaseEnvironment + +from agentverse.message import Message + + +# class Rule(BaseModel): +class SimulationRule(BaseRule): + """ + Rule for the environment. It controls the speaking order of the agents + and maintain the set of visible agents for each agent. + """ + + order: BaseOrder + visibility: BaseVisibility + selector: BaseSelector + updater: BaseUpdater + describer: BaseDescriber + + def __init__( + self, + order_config, + visibility_config, + selector_config, + updater_config, + describer_config, + ): + order = order_registry.build(**order_config) + visibility = visibility_registry.build(**visibility_config) + selector = selector_registry.build(**selector_config) + updater = updater_registry.build(**updater_config) + describer = describer_registry.build(**describer_config) + super().__init__( + order=order, + visibility=visibility, + selector=selector, + updater=updater, + describer=describer, + ) + + def get_next_agent_idx( + self, environment: BaseEnvironment, *args, **kwargs + ) -> List[int]: + """Return the index of the next agent to speak""" + return self.order.get_next_agent_idx(environment, *args, **kwargs) + + def update_visible_agents( + self, environment: BaseEnvironment, *args, **kwargs + ) -> None: + """Update the set of visible agents for the agent""" + self.visibility.update_visible_agents(environment, *args, **kwargs) + + def select_message( + self, environment: BaseEnvironment, messages: List[Message], *args, **kwargs + ) -> List[Message]: + """Select a set of valid messages from all the generated messages""" + return self.selector.select_message(environment, messages, *args, **kwargs) + + def update_memory(self, environment: BaseEnvironment, *args, **kwargs) -> None: + """For each message, add it to the memory of the agent who is able to see that message""" + self.updater.update_memory(environment, *args, **kwargs) + + def get_env_description( + self, environment: BaseEnvironment, *args, **kwargs + ) -> List[str]: + """Return the description of the environment for each agent""" + return self.describer.get_env_description(environment, *args, **kwargs) + + def reset(self) -> None: + self.order.reset() + self.visibility.reset() + self.selector.reset() + self.updater.reset() + self.describer.reset() diff --git a/agentverse/environments/simulation_env/rules/describer/__init__.py b/agentverse/environments/simulation_env/rules/describer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..803d139202b46a3a2e1539cd2140ce6bad90f5f5 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/__init__.py @@ -0,0 +1,9 @@ +from agentverse.registry import Registry + +describer_registry = Registry(name="DescriberRegistry") + +from .base import BaseDescriber +from .basic import BasicDescriber +from .classroom import ClassroomDescriber +from .pokemon import PokemonDescriber +from .prisoner import PrisonerDescriber diff --git a/agentverse/environments/simulation_env/rules/describer/base.py b/agentverse/environments/simulation_env/rules/describer/base.py new file mode 100644 index 0000000000000000000000000000000000000000..83b7b9763c20ce1ae7fc69084389b26e0e4d9744 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/base.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List + +from pydantic import BaseModel + +from . import describer_registry as DescriberRegistry +from abc import abstractmethod + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +class BaseDescriber(BaseModel): + @abstractmethod + def get_env_description( + self, environment: BaseEnvironment, *args, **kwargs + ) -> List[str]: + """Return the environment description for each agent""" + pass + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/describer/basic.py b/agentverse/environments/simulation_env/rules/describer/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..20f6bd4f673f0a4ff6a1f8bf4004848b0dc2e465 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/basic.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List + +from . import describer_registry as DescriberRegistry +from .base import BaseDescriber + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@DescriberRegistry.register("basic") +class BasicDescriber(BaseDescriber): + def get_env_description(self, environment: BaseEnvironment) -> List[str]: + """Return the environment description for each agent""" + return ["" for _ in range(len(environment.agents))] diff --git a/agentverse/environments/simulation_env/rules/describer/classroom.py b/agentverse/environments/simulation_env/rules/describer/classroom.py new file mode 100644 index 0000000000000000000000000000000000000000..91fd22a75d796fd144afd748c48f52d1583b96d7 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/classroom.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List +from string import Template + +from . import describer_registry as DescriberRegistry +from .basic import BasicDescriber + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@DescriberRegistry.register("classroom") +class ClassroomDescriber(BasicDescriber): + start_prompt: str + end_prompt: str + + def get_env_description(self, environment: BaseEnvironment) -> List[str]: + if not environment.rule_params.get("is_grouped", False): + if environment.rule_params.get("is_grouped_ended", False): + # If the group discussion is just ended + environment.rule_params["is_grouped_ended"] = False + return [self.end_prompt for _ in range(len(environment.agents))] + else: + return super().get_env_description(environment) + description = [] + for i, agent in enumerate(environment.agents): + if i == 0: + # Professor will not participate in group discussion + description.append("") + else: + description.append( + Template(self.start_prompt).safe_substitute( + {"receiver_name": ", ".join(agent.receiver)} + ) + ) + return description + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/describer/pokemon.py b/agentverse/environments/simulation_env/rules/describer/pokemon.py new file mode 100644 index 0000000000000000000000000000000000000000..44d5dbecb392b4b7d088a276d6c4afb91a7dcade --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/pokemon.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List, Optional, Dict +from copy import deepcopy + +from . import describer_registry as DescriberRegistry +from .base import BaseDescriber + +if TYPE_CHECKING: + from agentverse.environments.pokemon import PokemonEnvironment + + +@DescriberRegistry.register("pokemon") +class PokemonDescriber(BaseDescriber): + def get_env_description( + self, + environment: PokemonEnvironment, + player_content: str = "", + ) -> List[str]: + time = environment.time + if player_content == "": + agent_to_location = environment.get_agent_to_location() + descriptions = [] + for agent in environment.agents: + description = "" + if agent.name not in agent_to_location: + # Agent is on the way to a location + descriptions.append("") + continue + location = agent_to_location[agent.name] + agents_in_same_loc = deepcopy(environment.locations_to_agents[location]) + agents_in_same_loc.remove(agent.name) + agents_in_same_loc = list(agents_in_same_loc) + description += f"It is now {time}. You are at {location}." + if len(agents_in_same_loc) == 0: + description += " There is no one else here." + elif len(agents_in_same_loc) == 1: + description += f" {agents_in_same_loc[0]} is also here." + else: + other_agents = ", ".join(agents_in_same_loc) + description += f" {other_agents} are also here." + # description += " The locations you can go to include: \n" + # for loc, dsec in environment.locations_descriptions.items(): + # description += f"{loc}: {dsec}\n" + descriptions.append(description) + return descriptions + else: + description = "" + description += f"It is now {time}. Brendan is talking to you.\n" + description += f"[Brendan]: {player_content}\n" + return [description for _ in range(len(environment.agents))] diff --git a/agentverse/environments/simulation_env/rules/describer/prisoner.py b/agentverse/environments/simulation_env/rules/describer/prisoner.py new file mode 100644 index 0000000000000000000000000000000000000000..a96352049a60ed8597783e178f017de056573a23 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/describer/prisoner.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List + +from . import describer_registry as DescriberRegistry +from .base import BaseDescriber + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@DescriberRegistry.register("prisoner") +class PrisonerDescriber(BaseDescriber): + switch_func = { + "Both Suspects": "Suspect2", + "Suspect1": "Suspect2", + "Suspect2": "Suspect1", + } + receiver: str = "Both Suspects" + + def get_env_description(self, environment: BaseEnvironment) -> List[str]: + if environment.cnt_turn == 0: + environment.agents[0].set_receiver({"all"}) + environment.agents[1].set_receiver({"Police", "Suspect1"}) + environment.agents[2].set_receiver({"Police", "Suspect2"}) + + # only police have to choose to talk to suspect1 or suspect + description = [] + for i, agent in enumerate(environment.agents): + if i == 0: + # police -> suspect1 -> police -> suspect2 + if environment.cnt_turn % 2 == 1: + description.append("") + continue + + # Police will have to choose talk to which suspect + description.append(f"You are now talking to {self.receiver}") + + receiver = "all" if self.receiver == "Both Suspects" else self.receiver + self.receiver = self.switch_func[self.receiver] + agent.set_receiver({receiver}) + + else: + description.append("") + + return description + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/order/__init__.py b/agentverse/environments/simulation_env/rules/order/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1c6ea9f0eaae3902de0a308e58239a1b1e86ceb8 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/__init__.py @@ -0,0 +1,11 @@ +from agentverse.registry import Registry +order_registry = Registry(name="OrderRegistry") + +from .base import BaseOrder +from .sequential import SequentialOrder +from .random import RandomOrder +from .concurrent import ConcurrentOrder +from .classroom import ClassroomOrder +from .prisoner import PrisonerOrder +from .sde_team import SdeTeamOrder +from .sde_team_given_tests import SdeTeamGivenTestsOrder diff --git a/agentverse/environments/simulation_env/rules/order/base.py b/agentverse/environments/simulation_env/rules/order/base.py new file mode 100644 index 0000000000000000000000000000000000000000..18f84945c5c35c31e466e0967358d4e7e44df66a --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/base.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, List + +from pydantic import BaseModel + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +class BaseOrder(BaseModel): + @abstractmethod + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + """Return the index of the next agent to speak""" + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/order/classroom.py b/agentverse/environments/simulation_env/rules/order/classroom.py new file mode 100644 index 0000000000000000000000000000000000000000..dba459bee1d90a82a9b9eea0f086fb2f1372c08e --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/classroom.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import logging +import re +from typing import TYPE_CHECKING, Any, List, Optional + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("classroom") +class ClassroomOrder(BaseOrder): + """The order for a classroom discussion + The agents speak in the following order: + 1. The professor speaks first + 2. Then the professor can continue to speak, and the students can raise hands + 3. The professor can call on a student, then the student can speak or ask a question + 4. In the group discussion, the students in the group can speak in turn + """ + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + # `is_grouped_ended`: whether the group discussion just ended + # `is_grouped`: whether it is currently in a group discussion + if environment.rule_params.get("is_grouped_ended", False): + return [0] + if environment.rule_params.get("is_grouped", False): + return self.get_next_agent_idx_grouped(environment) + else: + return self.get_next_agent_idx_ungrouped(environment) + + def get_next_agent_idx_ungrouped(self, environment: BaseEnvironment) -> List[int]: + if len(environment.last_messages) == 0: + # If the class just begins or no one speaks in the last turn, we let only the professor speak + return [0] + elif len(environment.last_messages) == 1: + message = environment.last_messages[0] + sender = message.sender + content = message.content + if sender.startswith("Professor"): + if content.startswith("[CallOn]"): + # 1. professor calls on someone, then the student should speak + result = re.search(r"\[CallOn\] Yes, ([sS]tudent )?(\w+)", content) + if result is not None: + name_to_id = { + agent.name[len("Student ") :]: i + for i, agent in enumerate(environment.agents) + } + return [name_to_id[result.group(2)]] + else: + # 2. professor normally speaks, then anyone can act + return list(range(len(environment.agents))) + elif sender.startswith("Student"): + # 3. student ask question after being called on, or + # 4. only one student raises hand, and the professor happens to listen + # 5. the group discussion is just over, and there happens to be only a student speaking in the last turn + return [0] + else: + # If len(last_messages) > 1, then + # 1. there must be at least one student raises hand or speaks. + # 2. the group discussion is just over. + return [0] + assert ( + False + ), f"Should not reach here, last_messages: {environment.last_messages}" + + def get_next_agent_idx_grouped(self, environment: BaseEnvironment) -> List[int]: + # Get the grouping information + # groups: A list of list of agent ids, the i-th list contains + # the agent ids in the i-th group + # group_speaker_mapping: A mapping from group id to the id of + # the speaker in the group + # `groups` should be set in the corresponding `visibility`, + # and `group_speaker_mapping` should be maintained here. + if "groups" not in environment.rule_params: + logging.warning( + "The environment is grouped, but the grouping information is not provided." + ) + groups = environment.rule_params.get( + "groups", [list(range(len(environment.agents)))] + ) + group_speaker_mapping = environment.rule_params.get( + "group_speaker_mapping", {i: 0 for i in range(len(groups))} + ) + + # For grouped environment, we let the students speak in turn within each group + next_agent_idx = [] + for group_id in range(len(groups)): + speaker_index = group_speaker_mapping[group_id] + speaker = groups[group_id][speaker_index] + next_agent_idx.append(speaker) + + # Maintain the `group_speaker_mapping` + for k, v in group_speaker_mapping.items(): + group_speaker_mapping[k] = (v + 1) % len(groups[k]) + environment.rule_params["group_speaker_mapping"] = group_speaker_mapping + + return next_agent_idx diff --git a/agentverse/environments/simulation_env/rules/order/concurrent.py b/agentverse/environments/simulation_env/rules/order/concurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..738e32e288ba3db1de6d76fd4f0ddcd1367e604c --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/concurrent.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("concurrent") +class ConcurrentOrder(BaseOrder): + """ + The agents speak concurrently + """ + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + return list(range(len(environment.agents))) diff --git a/agentverse/environments/simulation_env/rules/order/prisoner.py b/agentverse/environments/simulation_env/rules/order/prisoner.py new file mode 100644 index 0000000000000000000000000000000000000000..6859911a80c70b86b7fe1bace2ad16c18eee9e00 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/prisoner.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import logging +import re +from typing import TYPE_CHECKING, Any, List, Optional + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("prisoner") +class PrisonerOrder(BaseOrder): + """The order for a classroom discussion + The agents speak in the following order: + 1. The professor speaks first + 2. Then the professor can continue to speak, and the students can raise hands + 3. The professor can call on a student, then the student can speak or ask a question + 4. In the group discussion, the students in the group can speak in turn + """ + + # try police, prisoner1 prisoner2 first + + last_prisoner_index: int = 1 + switch_func: dict = {1: 2, 2: 1} + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + if len(environment.last_messages) == 0: + # If the game just begins or , we let only the police speak + return [0] + elif len(environment.last_messages) == 1: + message = environment.last_messages[0] + sender = message.sender + content = message.content + if sender.startswith("Police"): + next_prisoner = self.last_prisoner_index + self.last_prisoner_index = self.switch_func[self.last_prisoner_index] + return [next_prisoner] + elif sender.startswith("Suspect"): + # 3. when one prisoner made his action, let the police tell another prisoner + return [0] + else: + # If len(last_messages) > 1, then + # 1. there must be at least one student raises hand or speaks. + # 2. the group discussion is just over. + return [0] diff --git a/agentverse/environments/simulation_env/rules/order/random.py b/agentverse/environments/simulation_env/rules/order/random.py new file mode 100644 index 0000000000000000000000000000000000000000..8a348a156a31b4c2a4d28ca758eca9876ce5a188 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/random.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import random +from typing import TYPE_CHECKING, List + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("random") +class RandomOrder(BaseOrder): + """ + Order for random conversation + The agents speak in a random order + """ + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + return [random.randint(0, len(environment.agents) - 1)] diff --git a/agentverse/environments/simulation_env/rules/order/sde_team.py b/agentverse/environments/simulation_env/rules/order/sde_team.py new file mode 100644 index 0000000000000000000000000000000000000000..ac0d5426782a88de420d91412341719983a540b0 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/sde_team.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +import logging +import re +import random +from typing import TYPE_CHECKING, Any, List, Optional + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("sde_team") +class SdeTeamOrder(BaseOrder): + """The order for a code problem solving + """ + next_agent_idx: int = 2 + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + if self.next_agent_idx == 2: + self.next_agent_idx = 0 + return [2] * 5 # TODO set the number in yaml + elif self.next_agent_idx == 0: + self.next_agent_idx = 1 + return [0] + elif self.next_agent_idx == 1: + self.next_agent_idx = 0 + return [1] \ No newline at end of file diff --git a/agentverse/environments/simulation_env/rules/order/sde_team_given_tests.py b/agentverse/environments/simulation_env/rules/order/sde_team_given_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..fa5657e78578e626460ab373497d6471e3c6c8e9 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/sde_team_given_tests.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import logging +import re +import random +from typing import TYPE_CHECKING, Any, List, Optional + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("sde_team_given_tests") +class SdeTeamGivenTestsOrder(BaseOrder): + """The order for a code problem solving given unit tests + 0 - code writer + 1 - code tester + 2 - code reviewer + """ + next_agent_idx: int = 0 + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + if self.next_agent_idx == 0: + self.next_agent_idx = 1 + return [0] + elif self.next_agent_idx == 1: + self.next_agent_idx = 2 + return [1] + elif self.next_agent_idx == 2: + self.next_agent_idx = 0 + return [2] + else: + raise ValueError("Invalid next_agent_idx: {}".format(self.next_agent_idx)) \ No newline at end of file diff --git a/agentverse/environments/simulation_env/rules/order/sequential.py b/agentverse/environments/simulation_env/rules/order/sequential.py new file mode 100644 index 0000000000000000000000000000000000000000..a739a7c2d03580e50d931dc439a9c2c8bfd61732 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/order/sequential.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("sequential") +class SequentialOrder(BaseOrder): + """ + Order for sequential conversation + The agents speak in a round-robin fashion + """ + + next_agent_idx: int = 0 + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + """Return the index of the next agent to speak""" + ret = self.next_agent_idx + self.next_agent_idx = (self.next_agent_idx + 1) % len(environment.agents) + return [ret] + + def reset(self) -> None: + self.next_agent_idx = 0 diff --git a/agentverse/environments/simulation_env/rules/selector/__init__.py b/agentverse/environments/simulation_env/rules/selector/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..acf1f2f788c61a7d0f2e380d21274794bab6609c --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/__init__.py @@ -0,0 +1,10 @@ +from agentverse.registry import Registry + +selector_registry = Registry(name="SelectorRegistry") + +from .base import BaseSelector +from .basic import BasicSelector +from .classroom import ClassroomSelector +from .sde_team import SdeTeamSelector +from .sde_team_given_tests import SdeTeamGivenTestsSelector +from .pokemon import PokemonSelector diff --git a/agentverse/environments/simulation_env/rules/selector/base.py b/agentverse/environments/simulation_env/rules/selector/base.py new file mode 100644 index 0000000000000000000000000000000000000000..cc283870f3a5254f946f487d4db2711397443380 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/base.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from pydantic import BaseModel + +from agentverse.message import Message + +from . import selector_registry as SelectorRegistry +from abc import abstractmethod + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@SelectorRegistry.register("base") +class BaseSelector(BaseModel): + """ + Base class for all selecters + """ + + @abstractmethod + def select_message( + self, environment: BaseEnvironment, messages: List[Message] + ) -> List[Message]: + """Selects a set of valid messages from all messages""" + pass + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/selector/basic.py b/agentverse/environments/simulation_env/rules/selector/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..1ebc0b48ba773245df7148e4cebc17c38f0a9373 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/basic.py @@ -0,0 +1,27 @@ +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 + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@SelectorRegistry.register("basic") +class BasicSelector(BaseSelector): + """ + Base class for all selecters + """ + + def select_message( + self, environment: BaseEnvironment, messages: List[Message] + ) -> List[Message]: + """Selects a set of valid messages from all messages""" + return messages + + def reset(self) -> None: + pass diff --git a/agentverse/environments/simulation_env/rules/selector/classroom.py b/agentverse/environments/simulation_env/rules/selector/classroom.py new file mode 100644 index 0000000000000000000000000000000000000000..07365b2792b9edfda61d238bd5d3155108b954b3 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/classroom.py @@ -0,0 +1,47 @@ +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 + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@SelectorRegistry.register("classroom") +class ClassroomSelector(BaseSelector): + def select_message( + self, environment: BaseEnvironment, messages: List[Message] + ) -> List[Message]: + selected = [] + for message in messages: + if message.sender.startswith("Student"): + if message.content.startswith("[RaiseHand]"): + message.content = "[RaiseHand]" + selected.append(message) + elif message.content != "" or len(message.tool_response) > 0: + selected.append(message) + elif message.sender.startswith("Professor"): + # If the professor launch a group discussion, then we + # brutely discard the student's message in this turn + if message.content.startswith("[GroupDiscuss]"): + return [message] + selected.append(message) + + # If some student speak while the professor is speaking, then + # we brutely discard the student's message in this turn + if ( + len(selected) > 1 + and selected[0].sender.startswith("Professor") + and selected[0].content != "" + ): + filtered_selected = [] + filtered_selected.append(selected[0]) + for message in selected[1:]: + if message.content.startswith("[RaiseHand]"): + filtered_selected.append(message) + selected = filtered_selected + return selected diff --git a/agentverse/environments/simulation_env/rules/selector/code_api.py b/agentverse/environments/simulation_env/rules/selector/code_api.py new file mode 100644 index 0000000000000000000000000000000000000000..a134b649b3bf215bdf05dd847fdc755b1f0ab24e --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/code_api.py @@ -0,0 +1,97 @@ +import io +import sys +import ast +import json +import astunparse +import concurrent.futures +import traceback + + +def get_call_str(assert_statement: str) -> str: + call_str = ast.parse(assert_statement).body[0].test.left # type: ignore + return astunparse.unparse(call_str).strip() + +def get_output(func: str, assert_statement: str) -> str: + try: + func_call = get_call_str(assert_statement) + try: + exec(func, globals()) + output = eval(func_call) + return output + except Exception as e: + return str(e) + except: + return "get_call_str error" + +def worker(code, globals=None, locals=None): + old_stdout = sys.stdout + redirected_output = sys.stdout = io.StringIO() + if locals is None: + locals = {} + try: + # TODO: exec(code, globals, locals) could be buggy + # In cases where both import statement and function exits in the code, if the locals are given, + # the code will not find the imported package. + # For example, + # code = "import math\ndef f(x):\n\treturn math.pow(x, 2)\nassert f(2) == 4" + # It will return "NameError: name 'math' is not defined" + exec(code, locals, locals) + stdout = redirected_output.getvalue() + return stdout, globals, locals + except Exception as e: + trace_str = traceback.format_exc() + return f"Error: {trace_str}", globals, locals + finally: + sys.stdout = old_stdout # restore the original stdout + +def execute_code(code: str) -> str: + """Execute a snippet of python code and return the output or the error message. + """ + timeout = 5 + try: + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(worker, code) + result, _, _ = future.result(timeout) + return result + except concurrent.futures.TimeoutError: + return "Timeout" + +def execute_unit_tests(func_impl: str, tests: str) -> str: + """Run a python function on a bunch of unit tests tests and return detailed feedback. + """ + # tests = eval(tests) + # assert type(tests) == list + + # Combine function code and assert statement + func_test_list = [f'{func_impl}\n{test}' for test in tests] + + # Run the tests and collect the results + success_tests = [] + failed_tests = [] + is_passing = True + num_tests = len(func_test_list) + for i in range(num_tests): + output = execute_code(func_test_list[i]) + if output == "Timeout": + failed_tests += [f"{tests[i]} # output: Timeout"] + is_passing = False + elif output.startswith("Error: "): + # print(output) + func_output = get_output(func_impl, tests[i]) + if func_output == "get_call_str error": + func_output = output + failed_tests += [f"{tests[i]} # output: {func_output}"] + is_passing = False + else: + success_tests += [tests[i]] + + feedback = "Tested passed:\n\n" + for test in success_tests: + feedback += f"{test}\n\n" + feedback += "Tests failed:\n\n" + for test in failed_tests: + feedback += f"{test}\n\n" + + return json.dumps({"is_passing": is_passing, + "feedback": feedback}) + diff --git a/agentverse/environments/simulation_env/rules/selector/pokemon.py b/agentverse/environments/simulation_env/rules/selector/pokemon.py new file mode 100644 index 0000000000000000000000000000000000000000..b631aa8502890fa85b6afca2b576d729eb336306 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/pokemon.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List +import numpy as np +import json + +from agentverse.message import Message + +from . import selector_registry as SelectorRegistry +from .base import BaseSelector + +if TYPE_CHECKING: + from agentverse.environments import PokemonEnvironment + + +@SelectorRegistry.register("pokemon") +class PokemonSelector(BaseSelector): + """ + Selector for Pokemon environment + """ + + def select_message( + self, environment: PokemonEnvironment, messages: List[Message] + ) -> List[Message]: + valid = [] + talk_matrix = np.zeros((len(environment.agents), len(environment.agents))) + agent_to_idx = {agent.name: i for i, agent in enumerate(environment.agents)} + for i, message in enumerate(messages): + try: + content = json.loads(message.content) + except json.decoder.JSONDecodeError: + valid.append(0) + continue + if content["action"] == "Speak": + try: + if "to" not in content: + # If the model does not generate receiver, then we discard the message + valid.append(0) + elif content["to"] in agent_to_idx: + # TODO: allow talk to a list of agents + valid.append(1) + # talk_matrix[i][j] = 1 ==> i talk to j + talk_matrix[agent_to_idx[message.sender]][ + agent_to_idx[content["to"]] + ] = 1 + else: + # If the receiver is not in the environment, then we discard the message + valid.append(0) + except: + valid.append(0) + continue + elif content["action"] == "MoveTo": + # If the agent move to a location that does not exist, then we discard the message + valid.append( + "to" in content and content["to"] in environment.locations_to_agents + ) + else: + valid.append(1) + selected_messages = [] + for i, message in enumerate(messages): + content = json.loads(message.content) + sender_idx = agent_to_idx[message.sender] + if valid[i] == 0: + selected_messages.append(Message()) + continue + if content["action"] == "MoveTo": + if np.sum(talk_matrix[:, sender_idx]) > 0: + # If someone talk to this agent, then we discard the move action + selected_messages.append(Message()) + else: + selected_messages.append(message) + elif content["action"] == "Speak": + receiver_idx = agent_to_idx[content["to"]] + if talk_matrix[sender_idx][receiver_idx] == 0: + # If this agent talk to someone who also talk to this agent, and we + # select the message from this agent, then we discard the message + selected_messages.append(Message()) + continue + if np.sum(talk_matrix[receiver_idx, :]) > 0: + if talk_matrix[receiver_idx][sender_idx] == 1: + # If the receiver talk to this agent, then we randomly select one message + if sender_idx < receiver_idx: + if np.random.random() < 0.5: + selected_messages.append(message) + talk_matrix[receiver_idx][sender_idx] = 0 + else: + selected_messages.append(Message()) + talk_matrix[sender_idx][receiver_idx] = 0 + else: + print("Shouldn't happen") + else: + # If the receiver talk to other agent, we still talk to the receiver (?) + selected_messages.append(message) + else: + selected_messages.append(message) + else: + selected_messages.append(message) + return selected_messages diff --git a/agentverse/environments/simulation_env/rules/selector/sde_team.py b/agentverse/environments/simulation_env/rules/selector/sde_team.py new file mode 100644 index 0000000000000000000000000000000000000000..7a4b571ad2ad409b1071a3752a64d35ed86bded4 --- /dev/null +++ b/agentverse/environments/simulation_env/rules/selector/sde_team.py @@ -0,0 +1,72 @@ +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") +class SdeTeamSelector(BaseSelector): + def select_message(self, environment: BaseEnvironment, messages: List[Message]) -> List[Message]: + last_sender = environment.last_messages[0].sender + selected = messages + + if last_sender == "unit_test_generator": + unit_tests = set() + for message in selected: + unit_test = extract(message.content, ":") + if unit_test not in unit_tests: + unit_tests.add(extract(message.content, ":")) + unit_tests = list(unit_tests) + environment.rule_params["unit_tests"] = str(unit_tests) + new_message = Message( + content="", + sender="unit_test_generator", + receiver=[], + ) # TODO: set the content of the message + selected = [new_message] + + elif last_sender == "code_writer": + cur_code = extract(selected[0].content, ":") + 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 + 4 - generate the assertion statement + + 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] + """ + + : + [5, 7, 1, 9, 3], 14 + + : + The output should be a list of two indices that sum up to 14. 5 (index 0) + 9 (index 3) = 14. So the output should be [0, 3]. + + : + [0, 3] + + : + assert two_sum([5, 7, 1, 9, 3], 14) == [0, 3] + + [Start of new task]: + + Now please write a unit test case for the following problem. There are serveral guys doing the same task, you should try to compose a unique test case. + + : + def longestPalindrome(self, s: str) -> str: + """ Given a string s, return the longest palindromic substring in s. + >>> longestPalindrome("babcsd") + "bab" + >>> longestPalindrome("cbxxbd") + "bxxb" + """ + +# tools: &tools +# - tool_name: "execute_unit_tests" +# tool_url: "http://127.0.0.1:8079/tools/execute_unit_tests/" + +environment: + env_type: sde_team + max_turns: 5 + task_name: HumanEval/0 + rule: + order: + type: sde_team + visibility: + type: all + selector: + type: sde_team + 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.3 + max_tokens: 1024 + output_parser: sde_team/sde_team_3players + + - 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.3 + max_tokens: 1024 + output_parser: sde_team/sde_team_3players + + - agent_type: conversation + name: unit_test_generator + role_description: *unit_test_generator_role_prompt + prompt_template: *prompt + verbose: true + llm: + llm_type: gpt-3.5-turbo + temperature: 1.0 + max_tokens: 1024 + output_parser: sde_team/sde_team_3players \ No newline at end of file diff --git a/agentverse/tasks/tasksolving/brainstorming/config.yaml b/agentverse/tasks/tasksolving/brainstorming/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..82be06bcd2599bf3d0a79ecba43e3b2b7aec063b --- /dev/null +++ b/agentverse/tasks/tasksolving/brainstorming/config.yaml @@ -0,0 +1,204 @@ +cnt_agents: &cnt_agents 4 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 0 + +task_description: |- + generate ideas of building a compressed hydrogen storage station in Ohio + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + You are the leader of a group of experts, now you are faced with a task: + + ${task_description} + + You can recruit ${cnt_critic_agents} expert team members in different regions. + What experts will you recruit to better generate good ideas? + + Output format example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + ${advice} + You don't have to give the reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are a summarizer. + Your task is to categorize and summarize the ideas in the chat history. + Please add the speaker of each idea to the beginning of the content. + + The question of the discussing is to ${task_description}. Below is the chat history: + + solver_append_prompt: &solver_append_prompt |- + # Output format + 1. (Speaker1): (Ideas of Speaker 1 in a single line) + 2. (Speaker2): (Ideas of Speaker 2 in a single line) + 3. (Speaker3): (Ideas of Speaker 3 in a single line) + ... + + Please merge all ideas of one speaker into one item. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${role_description}. You are in a discussion group, aiming to ${task_description}. + + critic_append_prompt: &critic_append_prompt |- + Now the group is asking your opinion about it. Based on your knowledge in your field, do you agree that this solution can perfectly solve the problem? Or do you have any ideas to improve it? + + - If you thinks it is perfect, use the following output format: + Action: Agree + Action Input: Agree. + (Do not output your reason for agreeing!) + + - If you want to give complemented opinions to improve it or to contradict with it, use the following output format: + Action: Disagree + Action Input: (what you want to say in one line) + + P.S. Always remember you are ${role_description}! + + If no former solution or critic opinions are given, you can just disagree and output your idea freely, based on the expertise of your role. + Remember, the ideas should be specific and detailed enough, not just general opinions. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + Your task is to evaluate the ideas in the solution. + + The goal is to ${task_description}. + + Please rate the ideas in the content in the following dimensions: + 1. Comprehensiveness:Are they comprehensive enough to cover all the + important aspects a engineering project may have? + 2. Detailedness: Are they detailed enough to be implemented? + 3. Feasibility: Are they reasonable and practical? + 4. Novelty: Are they creative and innovative? + + 0 means the idea is like random generated ideas, + 10 means the idea is perfect in that aspect. + + and then in the fifth line of output, give your detailed advice for the solution generators. + You can also give advice to the human resource staff on what experts they should recruit. + Just say the drawbacks of the ideas, no need to do compliments first. + + + #Output format + You must output in the following format: + 1. Comprehensiveness: (a score between 0 and 9) + 2. Detailedness: (a score between 0 and 9) + 3. Feasibility: (a score between 0 and 9) + 4. Novelty: (a score between 0 and 9) + 5. Advice: (your advice in one line) + + Here is the content you have to evaluate: + ${solution} + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: brainstorming + max_inner_turns: *max_inner_turns + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Summarizer + max_retry: 1000 + max_history: 5 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: dummy + + - #critic_agents: + agent_type: critic + name: Reviewer + max_retry: 1000 + max_history: 5 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: critic + + - #executor_agent: + agent_type: executor + name: Dummy Executor + max_retry: 1000 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0 + max_tokens: 1024 + output_parser: + type: dummy + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: evaluator + dimensions: + - Comprehensiveness + - Detailedness + - Feasibility + - Novelty diff --git a/agentverse/tasks/tasksolving/commongen/gpt-3.5/config.yaml b/agentverse/tasks/tasksolving/commongen/gpt-3.5/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5e45a3113538dd79c33315ea9499f0daba489ebe --- /dev/null +++ b/agentverse/tasks/tasksolving/commongen/gpt-3.5/config.yaml @@ -0,0 +1,197 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity to generate coherent and grammatically correct sentences containing the following given words: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + 3. a lawyer with a good knowledge of xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are ${role_description}. Generate a coherent and grammatically correct paragraph containing the following given words (or their variations): + WORDS: + ${task_description} + + solver_append_prompt: &solver_append_prompt |- + + critic_prepend_prompt: &critic_prepend_prompt |- + You are in a discussion group, aiming to generate coherent and grammatically correct sentences containing the following given words (or their variations): + WORDS: + ${task_description} + + Below is the chat history in your group. + + critic_append_prompt: &critic_append_prompt |- + You are ${role_description}. Based on your knowledge, can you check whether the latest provided paragraph contains all the given words or their variations? When responding, you should follow the following rules: + 1. If the above latest provided solution has covered all the given words or their variations, end your response with a special token "[Agree]". + 1. If not, double-check the above solutions, give your critics, and generate a better solution. + + manager_prompt: &manager_prompt |- + + executor_prepend_prompt: &executor_prepend_prompt |- + + executor_append_prompt: &executor_append_prompt |- + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + You are a reviewer who checks whether a paragraph contains all the given words (including their variations). When some words are missing, you should patiently point out, and output a score of 0. When the paragraph contains all the words, you should output a score of 1. + + WORDS: + ${task_description} + + SOLUTION: + ``` + ${solution} + ``` + + TEST RESULT: + ${result} + + RESPONSE FORMAT: + You must respond in the following format: + Score: (0 or 1. 0 if there are some missing words, 1 if there is no missing words) + Advice: (point out all the missing words) + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + max_inner_turns: *max_inner_turns + executor: + type: coverage-test + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + max_history: 4 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: commongen + # max_tokens: 1024 + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + max_history: 4 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0 + max_tokens: 1024 + output_parser: + type: commongen + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: humaneval-evaluator + dimensions: + - Score + + - #manager_agent: + agent_type: manager + name: Manager + max_retry: 1000 + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager \ No newline at end of file diff --git a/agentverse/tasks/tasksolving/commongen/gpt-4/config.yaml b/agentverse/tasks/tasksolving/commongen/gpt-4/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d5d5b766262674cb72cc2ab2f07950ca7c2c4d9a --- /dev/null +++ b/agentverse/tasks/tasksolving/commongen/gpt-4/config.yaml @@ -0,0 +1,197 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity to generate coherent and grammatically correct sentences containing the following given words: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + 3. a lawyer with a good knowledge of xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are ${role_description}. Generate a coherent and grammatically correct paragraph containing the following given words (or their variations): + WORDS: + ${task_description} + + solver_append_prompt: &solver_append_prompt |- + + critic_prepend_prompt: &critic_prepend_prompt |- + You are in a discussion group, aiming to generate coherent and grammatically correct sentences containing the following given words (or their variations): + WORDS: + ${task_description} + + Below is the chat history in your group. + + critic_append_prompt: &critic_append_prompt |- + You are ${role_description}. Based on your knowledge, can you check whether the latest provided paragraph contains all the given words or their variations? When responding, you should follow the following rules: + 1. If the above latest provided solution has covered all the given words or their variations, end your response with a special token "[Agree]". + 1. If not, double-check the above solutions, give your critics, and generate a better solution. + + manager_prompt: &manager_prompt |- + + executor_prepend_prompt: &executor_prepend_prompt |- + + executor_append_prompt: &executor_append_prompt |- + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + You are a reviewer who checks whether a paragraph contains all the given words (including their variations). When some words are missing, you should patiently point out, and output a score of 0. When the paragraph contains all the words, you should output a score of 1. + + WORDS: + ${task_description} + + SOLUTION: + ``` + ${solution} + ``` + + TEST RESULT: + ${result} + + RESPONSE FORMAT: + You must respond in the following format: + Score: (0 or 1. 0 if there are some missing words, 1 if there is no missing words) + Advice: (point out all the missing words) + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + max_inner_turns: *max_inner_turns + executor: + type: coverage-test + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + max_history: 4 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: commongen + # max_tokens: 1024 + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + max_history: 4 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: commongen + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: humaneval-evaluator + dimensions: + - Score + + - #manager_agent: + agent_type: manager + name: Manager + max_retry: 1000 + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager \ No newline at end of file diff --git a/agentverse/tasks/tasksolving/humaneval/gpt-3.5/config.yaml b/agentverse/tasks/tasksolving/humaneval/gpt-3.5/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8b221f23c457d94bb13dbe0300d4893d248f2f23 --- /dev/null +++ b/agentverse/tasks/tasksolving/humaneval/gpt-3.5/config.yaml @@ -0,0 +1,251 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 5 +max_criticizing_rounds: 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity to correctly write the code to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + 3. a lawyer with a good knowledge of xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Can you complete the following code? + ```python + ${task_description} + ``` + + solver_append_prompt: &solver_append_prompt |- + You are ${role_description}. Using the these information, can you provide a correct completion of the code? Explain your reasoning. Your response should contain only Python code. Do not give any additional information. Use ```python to put the completed Python code in markdown quotes. When responding, please include the given code and the completion. + # You should respond in the following json format wrapped with markdown quotes: + # ```json + # { + # "text": "your thought", + # "reasoning": "your reasoning", + # "criticism": "constructive self-criticism", + # "code": "the final code completion", + # } + # ``` + + # Respond only the json, and nothing else. Make sure it can be directly parsed with Python `json.loads`. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are in a discussion group, aiming to complete the following code function: + ```python + ${task_description} + ``` + + critic_append_prompt: &critic_append_prompt |- + You are ${role_description}. Based on your knowledge, can you check the correctness of the completion given above? You should give your correct solution to the problem step by step. When responding, you should follow the following rules: + 1. Analyze the above latest solution and the problem. + 2. If the latest solution is correct, end your response with a special token "[Agree]". + 3. If the latest solution is wrong, write down your critics in the code block and give a corrected code with comment explanation on the modification. + 3. Your response should contain only Python code. Do not give any additional information. Use ```python to wrap your Python code in markdown quotes. When responding, please include the given code and the completion. + + Now give your response. + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + ```python + ${task_description} + ``` + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are an experienced program tester. Now your team is trying to solve the problem: + ''' + Complete the Python function: + ${task_description} + ''' + + Your team has given the following answer: + ''' + ${solution} + ''' + + executor_append_prompt: &executor_append_prompt |- + The solution has been written to `tmp/main.py`. Your are going to write the unit testing code for the solution. You should respond in the following json format wrapped with markdown quotes: + ```json + { + "thought": your thought, + "file_path": the path to write your testing code, + "code": the testing code, + "command": the command to change directory and execute your testing code + } + ``` + + Respond only the json, and nothing else. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + # Experts + The experts recruited in this turn includes: + ${all_role_description} + + # Problem and Writer's Solution + Problem: + ${task_description} + + Writer's Solution: + ${solution} + + evaluator_append_prompt: &evaluator_append_prompt |- + You are an experienced code reviewer. As a good reviewer, you carefully check the functional correctness of the given code completion. When the completion is incorrect, you should patiently teach the writer how to correct the completion, but do not give the code directly. + + # Response Format Guidance + You must respond in the following format: + Score: (0 or 1, 0 for incorrect and 1 for correct) + Response: (give your advice on how to correct the solution, and your suggestion on on what experts should recruit in the next round) + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 2048 + output_parser: + type: humaneval-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: mgsm-evaluator + dimensions: + - Score + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/humaneval/gpt-4/config.yaml b/agentverse/tasks/tasksolving/humaneval/gpt-4/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2525b4e6b2259e655e37f7efba8b7d4b531ad375 --- /dev/null +++ b/agentverse/tasks/tasksolving/humaneval/gpt-4/config.yaml @@ -0,0 +1,247 @@ +cnt_agents: &cnt_agents 4 +max_turn: &max_turn 2 +max_criticizing_rounds: 2 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity to correctly write the code to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + 3. a lawyer with a good knowledge of xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Can you complete the following code? + ```python + ${task_description} + ``` + + # # Previous Solution + # The solution you gave in the last step is: + # ${former_solution} + + # # Evaluation + # The unit testing gave the following suggestion: + # ${advice} + + # # Critics + # The following messages are the critics on the provided solution: + + solver_append_prompt: &solver_append_prompt |- + You are ${role_description}. Provide a correct completion of the code. Explain your reasoning. Your response should contain only Python code. Do not give any additional information. Use ```python to put the completed Python code in markdown quotes. When responding, please include the given code and the completion. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are in a discussion group, aiming to complete the following code function: + ```python + ${task_description} + ``` + + # Below is a possible code completion: + # ``` + # ${preliminary_solution} + # ``` + + critic_append_prompt: &critic_append_prompt |- + You are ${role_description}. Based on your knowledge, can you check the functional correctness of the latest completion given above? When responding, you should follow the following rules: + 1. If the latest provided solution is correct, end your response with a special token "[Agree]". + 2. If the solution is incorrect, give your comment and end your response with a special token "[Disagree]". + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + ```python + ${task_description} + ``` + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are an experienced program tester. Now your team is trying to solve the problem: + ''' + Complete the Python function: + ${task_description} + ''' + + executor_append_prompt: &executor_append_prompt |- + The solution has been written to `tmp/main.py`. Your are going to write the unit testing code for the solution. You should respond in the following format: + Thought: your thought + Reasoning: your reasoning on the testing cases + Criticism: constructive self-criticism + File Path: the path to write your testing code + Code: the testing code with explaination in docstring. make sure to write the input in the assertion to make it appear in the unit test report, and make sure the expected answer is correct + Command: the command to change directory and execute your testing code + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + # Problem + Complete the following function + ```python + ${task_description} + ``` + + # Experts + The experts recruited in this turn includes: + ${all_role_description} + + # Writer's Solution: + ${solution} + + # Tester's Feedback: + ${result} + + evaluator_append_prompt: &evaluator_append_prompt |- + # Response Format Guidance + You must respond in the following format: + Score: (0 or 1, 0 for incorrect and 1 for correct) + Response: (give your advice on how to correct the solution, and your suggestion on what experts should recruit in the next round) + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + executor: + type: code-test + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: mgsm-evaluator + dimensions: + - Score + + + - #manager_agent: + agent_type: manager + name: Manager + max_retry: 1000 + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/logic_grid/gpt-4/config.yaml b/agentverse/tasks/tasksolving/logic_grid/gpt-4/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8b8f4e7467475c21587267fc811db938a655540e --- /dev/null +++ b/agentverse/tasks/tasksolving/logic_grid/gpt-4/config.yaml @@ -0,0 +1,177 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_criticizing_rounds: 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + # Role Description + You are the leader of a group, now you are facing a problem: + ``` + ${task_description} + ``` + + You can recruit ${cnt_critic_agents} people to solve the logic problem. What people will you recruit? + + Here are some suggestion: + ${advice} + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Response Format Guidance + You should respond with a list of ${cnt_critic_agents} people description. For example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + ${task_description} + + # Messages from the solver and critics will be filled here in the code. + + solver_append_prompt: &solver_append_prompt |- + Using these information, can you provide the correct solution to the math problem? Explain your reasoning and solve the problem step by step. Your final answer should be a single integer, which is the number of choice, in the form \boxed{answer}, at the end of your response. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are in a discussion group, aiming to collaborative solve the following logic problem: + ``` + ${task_description} + ``` + + # Messages from the solver and critics will be filled here in the code. + + critic_append_prompt: &critic_append_prompt |- + You are ${role_description}. Based on your knowledge, can you check the correctness of the solutions given above? You should give your correct solution to the problem step by step. When responding, you should follow the following rules: + 1. Double-check the above solutions, give your critics, then generate the correct solution step by step. + 2. If the final answer in your solution is the same as the final answer in the above provided solution, end your response with a special token "[Agree]". + 3. You must highlight your final answer in the form \boxed{answer} at the end of your response. The answer must be a single integer. + + Now give your response. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + Problem: + ``` + ${task_description} + ``` + + Solution: + ``` + ${solution} + ``` + + You are a logic problem lover. Above is a logic problem and a solution. Check whether the solution and the deduction is correct. If the deduction is wrong, you should explain why it is wrong, but do not give your solution. When it is correct, output a correctness of 1 and why it is correct. + + evaluator_append_prompt: &evaluator_append_prompt |- + You should respond in the following format: + Correctness: (0 or 1, 0 is wrong, and 1 is correct) + Response: (explain in details why it is wrong or correct. do not provide your solution) + + + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: mgsm-evaluator + dimensions: + - Correctness + + +tools: + diff --git a/agentverse/tasks/tasksolving/mgsm/gpt-3.5/config.yaml b/agentverse/tasks/tasksolving/mgsm/gpt-3.5/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..12b74b46432499bd4bbd0af2a03f31d1de9e65a7 --- /dev/null +++ b/agentverse/tasks/tasksolving/mgsm/gpt-3.5/config.yaml @@ -0,0 +1,190 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_criticizing_rounds: 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + # Role Description + You are the leader of a group of experts, now you are facing a grade school math problem: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. + + Here are some suggestion: + ${advice} + + role_assigner_append_prompt: &role_assigner_append_prompt |- + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Solve the following math problem: + ${task_description} + + This math problem can be answered without any extra information. You should not ask for any extra information. + + solver_append_prompt: &solver_append_prompt |- + You are ${role_description}. Using the information in the chat history and your knowledge, you should provide the correct solution to the math problem. Explain your reasoning. Your final answer must be a single numerical number and nothing else, in the form \boxed{answer}, at the end of your response. + + # Your response should include one final answer in the form of \boxed{answer} at the end. If there are multiple answers given by different agents, you should only select the correct one and summarize its deduction process. Do not summarize each agent's response separately, summarize the whole chat history and give one final answer. + # Can you solve the following math problem? + # ${task_description} + + # # Previous Solution + # The solution you gave in the last step is: + # ``` + # ${former_solution} + # ``` + + # # Critics + # There are some critics on the above solution: + # ``` + # ${critic_opinions} + # ``` + + # Using the these information, can you provide the correct solution to the math problem? Explain your reasoning. Your final answer should be a single numerical number (not a equation), in the form \boxed{answer}, at the end of your response. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${role_description}. You are in a discussion group, aiming to collaborative solve the following math problem: + ${task_description} + + Based on your knowledge, give your correct solution to the problem step by step. + + critic_append_prompt: &critic_append_prompt |- + Now compare your solution with the solution given in the chat history and give your response. The final answer is highlighted in the form \boxed{answer}. When responding, you should follow the following rules: + 1. This math problem can be answered without any extra information. You should not ask for any extra information. + 2. Compare your solution with the given solution, give your critics. You should only give your critics, don't give your answer. + 3. If the final answer in your solution is the same as the final answer in the above provided solution, end your response with a special token "[Agree]". + + # If the solution is correct, end your response with a special token "[Correct]". + # If the solution is wrong, end your response with a special token "[Wrong]". + # # Response Format + # Using the solution from the other member as additional information, can you provide your answer to this math problem? Explain your reasoning. Your final answer should be a single numerical number (not a equation), in the form \boxed{answer}, at the end of your response. And additionally: + # 1. If your solution has the same answer to the provided solution, end your response with a special token "[Correct]". + # 2. If your solution has different answer to the provided solution, end your response with a special token "[Wrong]". + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + You are Math-GPT, an AI designed to solve math problems. The following experts have given the following solution to the following math problem. + + Experts: ${all_role_description} + Problem: ${task_description} + Solution: + ``` + ${solution} + ``` + + Now using your knowledge, carefully check the solution of the math problem given by the experts. This math problem can be answered without any extra information. When the solution is wrong, you should give your advice on how to correct the solution and what experts should be recruited. When it is correct, give 1 as Correctness and nothing as Response. The final answer is in the form \boxed{answer} at the end of the solution, it should correspond to the solution. The answer must be a numerical number and nothing else. + + evaluator_append_prompt: &evaluator_append_prompt |- + You should respond in the following format: + Correctness: (0 or 1, 0 is wrong, and 1 is correct) + Response: (explain in details why it is wrong or correct, and what experts should be recruited) + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo-16k" + temperature: 0 + max_tokens: 2048 + output_parser: + type: mgsm + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo-16k" + temperature: 0.3 + max_tokens: 2048 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: mgsm-evaluator + dimensions: + - Correctness diff --git a/agentverse/tasks/tasksolving/mgsm/gpt-4/config.yaml b/agentverse/tasks/tasksolving/mgsm/gpt-4/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..56611a14f7b2b7ec5c963716d27ee73ac55c1518 --- /dev/null +++ b/agentverse/tasks/tasksolving/mgsm/gpt-4/config.yaml @@ -0,0 +1,190 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_criticizing_rounds: 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + # Role Description + You are the leader of a group of experts, now you are facing a grade school math problem: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. + + Here are some suggestion: + ${advice} + + role_assigner_append_prompt: &role_assigner_append_prompt |- + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx. + 2. an economist who is good at xxx. + ... + + Only respond with the description of each role. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Solve the following math problem: + ${task_description} + + This math problem can be answered without any extra information. You should not ask for any extra information. + + solver_append_prompt: &solver_append_prompt |- + You are ${role_description}. Using the information in the chat history and your knowledge, you should provide the correct solution to the math problem. Explain your reasoning. Your final answer must be a single numerical number and nothing else, in the form \boxed{answer}, at the end of your response. + + # Your response should include one final answer in the form of \boxed{answer} at the end. If there are multiple answers given by different agents, you should only select the correct one and summarize its deduction process. Do not summarize each agent's response separately, summarize the whole chat history and give one final answer. + # Can you solve the following math problem? + # ${task_description} + + # # Previous Solution + # The solution you gave in the last step is: + # ``` + # ${former_solution} + # ``` + + # # Critics + # There are some critics on the above solution: + # ``` + # ${critic_opinions} + # ``` + + # Using the these information, can you provide the correct solution to the math problem? Explain your reasoning. Your final answer should be a single numerical number (not a equation), in the form \boxed{answer}, at the end of your response. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${role_description}. You are in a discussion group, aiming to collaborative solve the following math problem: + ${task_description} + + Based on your knowledge, give your correct solution to the problem step by step. + + critic_append_prompt: &critic_append_prompt |- + Now compare your solution with the solution given in the chat history and give your response. The final answer is highlighted in the form \boxed{answer}. When responding, you should follow the following rules: + 1. This math problem can be answered without any extra information. You should not ask for any extra information. + 2. Compare your solution with the given solution, give your critics. You should only give your critics, don't give your answer. + 3. If the final answer in your solution is the same as the final answer in the above provided solution, end your response with a special token "[Agree]". + + # If the solution is correct, end your response with a special token "[Correct]". + # If the solution is wrong, end your response with a special token "[Wrong]". + # # Response Format + # Using the solution from the other member as additional information, can you provide your answer to this math problem? Explain your reasoning. Your final answer should be a single numerical number (not a equation), in the form \boxed{answer}, at the end of your response. And additionally: + # 1. If your solution has the same answer to the provided solution, end your response with a special token "[Correct]". + # 2. If your solution has different answer to the provided solution, end your response with a special token "[Wrong]". + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + Experts: ${all_role_description} + Problem: ${task_description} + Solution: + ``` + ${solution} + ``` + + evaluator_append_prompt: &evaluator_append_prompt |- + You are an experienced mathematic teacher. As a good teacher, you carefully check the correctness of the given solution on a grade school math problem. When the solution is wrong, you should output a correctness of 0 and give your advice to the students on how to correct the solution. When it is correct, output a correctness of 1 and why it is correct. Also check that the final answer is in the form \boxed{answer} at the end of the solution. The answer must be a numerical number (not a equation, fraction, function or variable). You should also give some suggestion on on what experts should recruit in the next round. + + You should respond in the following format: + Correctness: (0 or 1, 0 is wrong, and 1 is correct) + Response: (advice to correct the answer or why it is correct) + + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 2048 + output_parser: + type: mgsm + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.3 + max_tokens: 2048 + output_parser: + type: mgsm-critic-agree + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: mgsm + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + max_retry: 10 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: mgsm-evaluator + dimensions: + - Correctness diff --git a/agentverse/tasks/tasksolving/pythoncalculator/config.yaml b/agentverse/tasks/tasksolving/pythoncalculator/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8615fd82cf4ea4e8fc9a0212d08ec5e50ee8a245 --- /dev/null +++ b/agentverse/tasks/tasksolving/pythoncalculator/config.yaml @@ -0,0 +1,202 @@ +cnt_agents: &cnt_agents 4 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 2 + +task_description: |- + write a simple calculator GUI using Python3. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + You are the leader of a group of experts, now you are faced with a task: + + ${task_description} + + You can recruit ${cnt_critic_agents} expert team members in different regions. + What experts will you recruit to better generate good ideas? + + Output format example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + ${advice} + You don't have to give the reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are faced with the task: + ${task_description} + + Below is the chat history among you and other teammates. + + solver_append_prompt: &solver_append_prompt |- + Now you are going to give a new solution, based upon your former solution and the critics' opinions. Write the code step by step. + + critic_prepend_prompt: &critic_prepend_prompt |- + Now you are ${role_description} + + You are in a discussion group, aiming to ${task_description}. + + Below is the chat history among you and other teammates. + + critic_append_prompt: &critic_append_prompt |- + Now the group is asking your opinion about it. Based on your knowledge + in your field, do you agree that this solution can perfectly + solve the problem? + Or do you have any ideas to improve it? + + - If you thinks it is perfect, use the following output format: + Action: Agree + Action Input: Agree. + (Do not output your reason for agreeing!) + + - If you want to give complemented opinions to improve it or to contradict with it, use the following output format: + Action: Disagree + Action Input: (what you want to say in one line) + + P.S. Always remember you are ${role_description}! + + If no former solution or critic opinions are given, you can just disagree and output your idea freely, based on the expertise of your role. + Remember, the ideas should be specific and detailed enough, not just general opinions. + + Please control output code in 2048 tokens! (Write concise code) + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + You are an professional and strict code reviewer. Your task is to evaluate the solution. The code is to ${task_description}. Your task is to evaluate the codes written by the code engineers. + + Please not only give a general rating points (from 0 to 9) but also give detailed comments about where and how the code can be improved. Please consider the following aspects when you are evaluating the code: + 1. The code should be able to run without any errors. + 2. The code should be able to achieve the goal specified in the task description. + 3. The code should be easy to read and understand, efficient, concise and elegant. + 4. The code should be robust. + + Please rate the code in the following dimensions: + 1. Completeness: Is the code snippet complete enough without unimplemented functions of methods? Is it able to run without any errors? + 2. Functionality: Is the code able to achieve the goal specified in the task description? + 3. Readability: Is the code easy to read and understand, efficient, concise and elegant? + 4. Robustness: Is the code snippet able to handle different unexpected input or other exceptions? + + In the fifth line of output, give your detailed advice for the engineers to better generate good codes. Only give high grade when it is really great. + + # Output format + 1. Completeness: (a score between 0 and 9) + 2. Functionality: (a score between 0 and 9) + 3. Readability: (a score between 0 and 9) + 4. Robustness: (a score between 0 and 9) + 5. Advice: (your advice in one line) + + Here is the content you have to evaluate: + ${solution} + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + max_inner_turns: *max_inner_turns + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 256 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Summarizer + max_retry: 1000 + max_history: 5 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 2048 + output_parser: + type: dummy + + - #critic_agents: + agent_type: critic + name: Reviewer + max_retry: 1000 + max_history: 5 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: critic + + - #executor_agent: + agent_type: executor + name: Dummy Executor + max_retry: 1000 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 512 + output_parser: + type: dummy + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: evaluator + dimensions: + - Completeness + - Functionality + - Readability + - Robustness diff --git a/agentverse/tasks/tasksolving/pythoncalculator/config_html.yaml b/agentverse/tasks/tasksolving/pythoncalculator/config_html.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ed30150f30a186f1a1daff078fb720e029f28edb --- /dev/null +++ b/agentverse/tasks/tasksolving/pythoncalculator/config_html.yaml @@ -0,0 +1,240 @@ +cnt_critic_agents: 3 + + +max_loop_rounds: 2 + + +max_criticizing_rounds: 3 + + +human_eval: false + + +task_description: |- + build a web application of a simple calculator. + + + +evaluation_dimensions: |- + + + +prompts: + role_assigner_prompt: &role_assigner_prompt |- + You are the leader of a group of experts, now you are faced with a task: + + ${task_description} + + You can recruit ${cnt_critic_agents} expert team members in different regions. + What experts will you recruit to better generate good ideas? + + Output format example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + ${advice} + You don't have to give the reason. + + solver_prompt: &solver_prompt |- + You are faced with the task: + ${task_description} + + You formerly gave the following solution + ${former_solution} + + But other critics in the group are not satisfied with it. + They gave the following opinions: + ${critic_opinions} + + Now you are going to give a new solution, + based upon your former solution and the critics' opinions. + Please write code only! + + If no former solution or critic opinions are given, you can ignore this part, and just generate a new solution. + + summarizer_prompt: &summarizer_prompt |- + You are a summarizer. + Your task is to categorize and summarize the ideas in the chat history. + Please add the speaker of each idea to the beginning of the content. + + + The question of the discussing is to ${task_description}. + + #Output format + 1. (Speaker1): (Ideas of Speaker 1 in a single line) + 2. (Speaker2): (Ideas of Speaker 2 in a single line) + 3. (Speaker3): (Ideas of Speaker 3 in a single line) + ... + + Here is the content you have to summarize: + ${former_solution} + ${critic_opinions} + + Please merge all ideas of one speaker into one item. + + critic_prompt: &critic_prompt |- + Now you are ${role_description} + + You are in a discussion group, aiming to ${task_description}. + + Now the group is going to give a preliminary solution as the following: + + ${preliminary_solution} + + Now the group is asking your opinion about it. Based on your knowledge + in your field, do you agree that this solution can perfectly + solve the problem? + Or do you have any ideas to improve it? + + - If you thinks it is perfect, use the following output format: + Action: Agree + Action Input: Agree. + (Do not output your reason for agreeing!) + + - If you want to give complemented opinions to improve it or to contradict with it, use the following output format: + Action: Disagree + Action Input: (what you want to say in one line) + + P.S. Always remember you are ${role_description}! + + ${advice} + If no former solution or critic opinions are given, you can just disagree and output your idea freely, based on the expertise of your role. + Remember, the ideas should be specific and detailed enough, not just general opinions. + + Please control output code in 2048 tokens! (Write concise code) + + evaluator_prompt: &evaluator_prompt |- + You are an professional code reviewer. + + Your task is to evaluate the solution. + The code is to ${task_description}. + Your task is to evaluate the codes written by the code engineers. + Please not only give a general rating points (from 0 to 10) but also give detailed comments about where and how the code can be improved. + Please consider the following aspects when you are evaluating the code: + 1. The code should be able to run without any errors. + 2. The code should be able to achieve the goal specified in the task description. + 3. The code should be easy to read and understand, efficient, concise and elegant. + 4. The code should be robust. + + Please rate the code in the following dimensions: + 1. Completeness: Is the code snippet complete enough without unimplemented functions of methods? Is it able to run without any errors? + 2. Functionality: Is the code able to achieve the goal specified in the task description? + 3. Readability: Is the code easy to read and understand, efficient, concise and elegant? + 4. Robustness: Is the code snippet able to handle different unexpected input or other exceptions? + + 0 means the idea looks like beginner's work, and 10 means the idea is perfect in that aspect, like a master. + + and then in the fifth line of output, give your detailed advice for the engineers to better generate good codes. + + + #Output format + You must output in the following format: + 1. Completeness: (a score between 0 and 9) + 2. Functionality: (a score between 0 and 9) + 3. Readability: (a score between 0 and 9) + 4. Robustness: (a score between 0 and 9) + 5. Advice: (your advice in one line) + + Here is the content you have to evaluate: + ${solution} + + +name: pipeline + + +environment: + env_type: task-basic + max_loop_rounds: 3 + rule: + order: + type: sequential + visibility: + type: all + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prompt_template: *role_assigner_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 256 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + prompt_template: [*solver_prompt, *summarizer_prompt] + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 2048 + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: |- + Waiting to be assigned. + prompt_template: *critic_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 256 + output_parser: + type: critic + + - #executor_agent: + agent_type: executor + name: Executor + prompt_template: None + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Code Reviewer + prompt_template: *evaluator_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 128 + output_parser: + type: evaluator + dimensions: + - Completeness + - Functionality + - Readability + - Robustness + + +tools: + diff --git a/agentverse/tasks/tasksolving/responsegen/gpt-3.5/config.yaml b/agentverse/tasks/tasksolving/responsegen/gpt-3.5/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fdf0624f7a9d8a97feca3e38d778ee32a9900e66 --- /dev/null +++ b/agentverse/tasks/tasksolving/responsegen/gpt-3.5/config.yaml @@ -0,0 +1,202 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to generate a response based on the text: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + You don't have to give the reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are ${role_description}. Below is a chat history: + ${task_description} + + And below is the discussion about what the next system response should be: + + solver_append_prompt: &solver_append_prompt |- + Now based on these information, please give a better next system response. Your response should contain only one system response beginning with "System: ". Do not give any additional information. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${role_description}. You are in a discussion group, aiming to generate a system response to the following chat history: + ${task_description} + + Below is the discussion about what the next system response should be: + + critic_append_prompt: &critic_append_prompt |- + # Response Format Guidance + - If you thinks the latest response given above is perfect, respond using the following format: + Decision: (set it to "Agree") + Response: (your response on why you think it is perfect) + + - If you think it is flawed, give your advice use the following output format: + Decision: (set it to "Disagree") + Response: (explain why you disagree and give your advice) + + Based on your knowledge in your field, do you agree that this solution is a good response to the chat history? + + manager_prompt: &manager_prompt |- + + executor_prepend_prompt: &executor_prepend_prompt |- + + executor_append_prompt: &executor_append_prompt |- + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + # Role Description + You are an experienced dialogue teacher. As a good teacher, you carefully assess the given system response based on the chat history. When the response is flawed, you should patiently teach the system how to give better response. + + # Response Format Guidance + You must respond in the following format: + Engaging: (a score between 0 and 10) + Relevant: (a score between 0 and 10) + Semantically Appropriate: (a score between 0 and 10) + Advice: (your advice on how to improve the response) + + # Chat History + ${task_description} + + # Next System Response + ${solution} + + # Your Task + Now carefully check the system's response, and give your opinion. + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + max_inner_turns: *max_inner_turns + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + max_history: 10 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + max_history: 10 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen-critic-2 + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: responsegen-evaluator + dimensions: + - Engaging + - Relevant + - Semantically Appropriate + + - #manager_agent: + agent_type: manager + name: Manager + max_retry: 1000 + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-3.5-turbo + model: "gpt-3.5-turbo" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager diff --git a/agentverse/tasks/tasksolving/responsegen/gpt-4/config.yaml b/agentverse/tasks/tasksolving/responsegen/gpt-4/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..593b82be551b0211ca3c4f30bcfae91a2ae54b06 --- /dev/null +++ b/agentverse/tasks/tasksolving/responsegen/gpt-4/config.yaml @@ -0,0 +1,202 @@ +cnt_agents: &cnt_agents 2 +max_turn: &max_turn 3 +max_inner_turns: &max_inner_turns 3 + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to generate a response based on the text: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + # Response Format Guidance + You should respond with a list of expert description. For example: + 1. an electrical engineer specified in the filed of xxx + 2. an economist who is good at xxx + 3. a lawyer with a good knowledge of xxx + ... + + You don't have to give the reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + You are ${role_description}. Below is a chat history: + ${task_description} + + And below is the discussion about what the next system response should be: + + solver_append_prompt: &solver_append_prompt |- + Now based on these information, please give a better next system response. Your response should contain only one system response beginning with "System: ". Do not give any additional information. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${role_description}. You are in a discussion group, aiming to generate a system response to the following chat history: + ${task_description} + + Below is the discussion about what the next system response should be: + + critic_append_prompt: &critic_append_prompt |- + # Response Format Guidance + - If you thinks the latest response given above is perfect, respond using the following format: + Decision: (set it to "Agree") + Response: (your response on why you think it is perfect) + + - If you think it is flawed, give your advice use the following output format: + Decision: (set it to "Disagree") + Response: (explain why you disagree and give your advice) + + Based on your knowledge in your field, do you agree that this solution is a good response to the chat history? + + manager_prompt: &manager_prompt |- + + executor_prepend_prompt: &executor_prepend_prompt |- + + executor_append_prompt: &executor_append_prompt |- + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + # Role Description + You are an experienced dialogue teacher. As a good teacher, you carefully assess the given system response based on the chat history. When the response is flawed, you should patiently teach the system how to give better response. + + # Response Format Guidance + You must respond in the following format: + Engaging: (a score between 0 and 10) + Relevant: (a score between 0 and 10) + Semantically Appropriate: (a score between 0 and 10) + Advice: (your advice on how to improve the response) + + # Chat History + ${task_description} + + # Next System Response + ${solution} + + # Your Task + Now carefully check the system's response, and give your opinion. + + +name: pipeline + + +environment: + env_type: task-basic + max_turn: *max_turn + rule: + role_assigner: + type: role_description + cnt_agents: *cnt_agents + decision_maker: + type: vertical-solver-first + max_inner_turns: *max_inner_turns + executor: + type: none + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + max_retry: 1000 + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role_assigner + + - #solver_agent: + agent_type: solver + name: Planner + max_retry: 1000 + max_history: 10 + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen + + - #critic_agents: + agent_type: critic + name: Critic 1 + max_retry: 1000 + max_history: 10 + role_description: |- + Waiting to be assigned. + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen-critic-2 + + - #executor_agent: + agent_type: executor + name: Executor + max_retry: 1000 + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: responsegen + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + max_retry: 1000 + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: responsegen-evaluator + dimensions: + - Engaging + - Relevant + - Semantically Appropriate + + - #manager_agent: + agent_type: manager + name: Manager + max_retry: 1000 + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager diff --git a/agentverse/tasks/tasksolving/tool_using/24point/config.yaml b/agentverse/tasks/tasksolving/tool_using/24point/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ac61f385969afe3f38e02b824277964236f7801 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/24point/config.yaml @@ -0,0 +1,248 @@ +cnt_agents: &cnt_agents 3 +cnt_tool_agents: &cnt_tool_agents 2 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: Recently, it has become popular in the AI field to verify the mathematical reasoning abilities of large language models by observing if they can solve the "24-Point Game." What is this game? Does it have a code-based solution? If it does, provide a Python code along with test cases and test its functionality. What are some other similar games that can be used to test the models' mathematical reasoning abilities? + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should merge the sub-tasks for the same person into one line. Each line should only include one person. Make the sub-tasks specific. Do not use pronoun to refer to the topic mentioned in conversation. Make the sub-task self-contained. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or a complete answer based on the full execution log to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 16 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + max_history: 10 + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/bmi/config.yaml b/agentverse/tasks/tasksolving/tool_using/bmi/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d950f8a29aaad4c0921864a53375a70d546e0487 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/bmi/config.yaml @@ -0,0 +1,248 @@ +cnt_agents: &cnt_agents 3 +cnt_tool_agents: &cnt_tool_agents 2 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I want to lose 5kg in the next 2 months. I weigh 70kg, am 170cm tall, and my age is 25. Calculate my BMI and based on that, suggest a workout routine and daily calorie intake to help me achieve my goal. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should merge the sub-tasks for the same person into one line. Each line should only include one person. Make the sub-tasks specific. Do not use pronoun to refer to the topic mentioned in conversation. Make the sub-task self-contained. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or a complete answer based on the full execution log to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 16 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + max_history: 10 + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/bookclub/config.yaml b/agentverse/tasks/tasksolving/tool_using/bookclub/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..381bb99e2170a1b7f45c21ba85c858cf179903ce --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/bookclub/config.yaml @@ -0,0 +1,248 @@ +cnt_agents: &cnt_agents 3 +cnt_tool_agents: &cnt_tool_agents 2 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I want to kick off a book club with my friends. Can you tell me the top 5 bestselling books this month, gather the content summary for each, and find online platforms where we can buy or borrow them? + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should merge the sub-tasks for the same person into one line. Each line should only include one person. Make the sub-tasks specific. Do not use pronoun to refer to the topic mentioned in conversation. Make the sub-task self-contained. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or a complete answer based on the full execution log to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 16 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + max_history: 10 + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/car/config.yaml b/agentverse/tasks/tasksolving/tool_using/car/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c2a3ddddffff35a30dd8238f316b002ec0620cad --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/car/config.yaml @@ -0,0 +1,247 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I am planning to buy a new car. Could you help me compare the features and prices of the latest models of Tesla, Ford, and Toyota? Include details about range, charging time, safety features, and after-sales service. Also, provide a brief analysis of the pros and cons of each car. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 1000 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 1000 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + max_retry: 1000 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/date/config.yaml b/agentverse/tasks/tasksolving/tool_using/date/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..46865cde2cfbc04b4fa4f4df9eb6fd6dfddf2e7d --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/date/config.yaml @@ -0,0 +1,246 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I am planning a date with my girlfriend this week, please search for a good movie theater and a restaurant near Tsinghua University in Beijing and recommend a good movie to watch. Please search the web. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should include ALL the people appear in the chat, and merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + ${result} + + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/diy/config.yaml b/agentverse/tasks/tasksolving/tool_using/diy/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ca012db37cd3d0ac18f5bc37d6a7ac0ae78ac655 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/diy/config.yaml @@ -0,0 +1,246 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I've recently taken an interest in DIY home projects. Search for beginner-friendly DIY projects that can be completed over the weekend. Also, provide a list of materials required and a step-by-step guide for each project. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should include ALL the people appear in the chat, and merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + ${result} + + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 16 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/party/config.yaml b/agentverse/tasks/tasksolving/tool_using/party/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..22b4be5e6a161531fb05dcb69d7d7e387faf1917 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/party/config.yaml @@ -0,0 +1,246 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I want to hold a party at somewhere around Tsinghua University tomorrow. I need you to look for some best places for holding a party nearby, and tell me whether the weather is good for holding a party tomorrow. Also, I want to know what activities can be considered in my party. Help me search the web. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should include ALL the people appear in the chat, and merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + + evaluator_append_prompt: &evaluator_append_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + ${result} + + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/sudoku/config.yaml b/agentverse/tasks/tasksolving/tool_using/sudoku/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3fc1a615a986043146eecf916094e73b3987904e --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/sudoku/config.yaml @@ -0,0 +1,247 @@ +cnt_agents: &cnt_agents 3 +cnt_tool_agents: &cnt_tool_agents 2 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I've just heard an interesting game called 'sudoku'. Can you search for the rules of this game and the solution to this game? Finally, write a python script to automatically solve this game if possible. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should include ALL the people appear in the chat, and merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/tools_simplified.json b/agentverse/tasks/tasksolving/tool_using/tools_simplified.json new file mode 100644 index 0000000000000000000000000000000000000000..9ddfeffa26fc336fac567f1ab3b769ca91958cb3 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/tools_simplified.json @@ -0,0 +1,166 @@ +{ + "available_envs": [ + { + "name": "FileSystemEnv", + "description": "Provide a file system operation environment for Agent.\n ", + "total_tools": 5, + "tools": [ + "is_path_exist", + "modify_file", + "print_filesys_struture", + "read_from_file", + "write_to_file" + ] + }, + { + "name": "PDBCodingEnv", + "description": "Note: This env is subclass of ['FileSystemEnv', 'ShellEnv'], and all tools of parent envs are inherited and not visible. You can try call parent tools or check this env's defination to show them.\nPython Debugging Coding Environment.\n Always run code with `python -m pdb {python_file.py}`.\n ", + "total_tools": 11, + "tools": [ + "run_code" + ] + }, + { + "name": "ShellEnv", + "description": "Provide and maintain an interactive shell environment.\n ", + "total_tools": 5, + "tools": [ + "kill", + "read_stdout", + "restart", + "terminate", + "write_stdin" + ] + }, + { + "name": "WebEnv", + "description": "Web Environment providing web interface and browsering.\n ", + "total_tools": 2, + "tools": [ + "browse_website", + "scrape_text" + ] + }, + { + "name": "RapidAPIEnv", + "description": "Note: All tools of this env are invisible during all tools display, please check this env's defination to show all tools.\nRapidAPI Env delivers rapid api for tool server.", + "total_tools": 4208, + "tools": [ + "rapi_100_success_instagram_api_scalable_robust_media_info", + "rapi_100_success_instagram_api_scalable_robust_post_comments", + "rapi_100_success_instagram_api_scalable_robust_user_followers", + "rapi_13f918yf19o1t1f1of1t9_endpoint1", + "rapi_3d_services_thumbnail", + "rapi_4d_dream_dictionary_get_dream_number", + "rapi_50k_radio_stations_get_channels", + "rapi_50k_radio_stations_get_cities", + "rapi_50k_radio_stations_get_countries", + "rapi_50k_radio_stations_get_genres", + "..." + ] + } + ], + "available_tools": [ + "FileSystemEnv_is_path_exist", + "FileSystemEnv_modify_file", + "FileSystemEnv_print_filesys_struture", + "FileSystemEnv_read_from_file", + "FileSystemEnv_write_to_file", + "PDBCodingEnv_run_code", + "ShellEnv_kill", + "ShellEnv_read_stdout", + "ShellEnv_restart", + "ShellEnv_terminate", + "ShellEnv_write_stdin", + "WebEnv_browse_website", + "WebEnv_scrape_text", + "query_wolfram", + "bing_search" + ], + "tools_json": [ + { + "name": "WebEnv_browse_website", + "description": "Browse a website and return the page. Note some websites may not be accessable due to network error.", + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The realworld URL to scrape text from. Started with http:// or https://." + }, + "question": { + "type": "string", + "description": "The question for the website. The function will answer the question by browsing the url." + } + }, + "required": [ + "url", + "question" + ] + } + }, + { + "name": "run_interpreter", + "description": "The code interpreter tool that runs code and returns the output.\nThe `code` will be written to file `filename` and the `command` will be executed in a shell. The returned value of this tool is the stdout content. So use `print` to get the important information.\nExample:\n```\nrun_interpreter(code='print(\"hello world\")',command='python code.py')\n```", + "parameters": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The code to be written, default to `None`, which means no code will be written to file." + }, + "filename": { + "type": "string", + "description": "The filename to be written in mode `w`, default to `code.py`." + }, + "command": { + "type": "string", + "description": "The shell command to be executed should avoid requiring additional user input, default to `python {filename}`." + } + }, + "required": [] + } + }, + { + "name": "bing_search", + "description": "Return 3 most relevant results of a Bing search using the official Bing API. This tool does not provide website details, use other tools to browse website if you need.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "submit_task", + "description": "Submit your conclusion to the task", + "parameters": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending", + "finished" + ], + "description": "Set to 'pending' if your assigned task cannot be finished, set to 'finished' if you have finished your assigned task" + }, + "conclusion": { + "type": "string", + "description": "What you have done so far. Describe your conclusion and your summarization on the gathered information if you have finished your assigned task, or describe your problem if you cannot finish your assigned task." + } + }, + "required": [ + "status", + "conclusion" + ] + } + } + ] +} \ No newline at end of file diff --git a/agentverse/tasks/tasksolving/tool_using/trending/config.yaml b/agentverse/tasks/tasksolving/tool_using/trending/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..25a778fd30aa64164adf08dd70e034a04564369d --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/trending/config.yaml @@ -0,0 +1,247 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I'm currently analyzing what is popular on the website. Can you help me find the recent trending stuff. It could be anything, like trending news, products, books, movies, music, etc. Give a summarization for me. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasks/tasksolving/tool_using/vacation/config.yaml b/agentverse/tasks/tasksolving/tool_using/vacation/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..04925ff89b59a9e29d455f7ee877c914acbe0962 --- /dev/null +++ b/agentverse/tasks/tasksolving/tool_using/vacation/config.yaml @@ -0,0 +1,247 @@ +cnt_agents: &cnt_agents 4 +cnt_tool_agents: &cnt_tool_agents 3 +max_rounds: &max_rounds 5 +max_criticizing_rounds: 3 +tool_config: &tool_config agentverse/tasks/tasksolving/tool_using/tools_simplified.json + +task_description: I'm planning a two-week vacation to Japan next month. Help me plan my itinerary. I want to visit Tokyo, Kyoto, and Osaka. Look for the top tourist attractions in each city, and also suggest the best mode of travel between these cities. Additionally, find out the weather forecast for the month I'll be visiting. + +prompts: + role_assigner_prepend_prompt: &role_assigner_prepend_prompt |- + + role_assigner_append_prompt: &role_assigner_append_prompt |- + # Role Description + You are the leader of a group of experts, now you need to recruit a small group of experts with diverse identity and apply them with tools to solve the given problems: + ${task_description} + + You can recruit ${cnt_critic_agents} expert in different fields. What experts will you recruit to better generate an accurate solution? + + Here are some suggestion: + ${advice} + + # Response Format Guidance + You should respond with a list of expert names and their descriptions, and separate the name and description of each expert with "-". For example: + 1. Alice - an electrical engineer specified in the filed of xxx. + 2. Bob - an economist who is good at xxx. + 3. Charlie - a lawyer with a good knowledge of xxx. + ... + + Only respond with the list of names and descriptions. Do not include your reason. + + solver_prepend_prompt: &solver_prepend_prompt |- + Please review the following chat conversation and identify the specific latest sub-task or the next step that each person needs to accomplish: + + solver_append_prompt: &solver_append_prompt |- + RESPONSE FORMAT: + Your response should be a list of expert names and their tasks, and separate the name and the corresponding task with "-". For example: + 1. Alice - search the web for the weather at Beijing today using google. + 2. Bob - look for information about the popular restaurants in Beijing using google. + ... + + What's the latest sub-task assigned to each person in the above conversation? Your response should include ALL the people appear in the chat, and merge the sub-tasks for the same person into one line. Each line should only include one person. + + critic_prepend_prompt: &critic_prepend_prompt |- + You are ${agent_name}, ${role_description}. You are now in a discussion group, the members are: + ${all_roles} + + Your current mission is to team up with others and make a plan on addressing the following query: + ${task_description} + + AVAILABLE TOOLS: + ${tool_descriptions} + + REQUIREMENTS: + It is essential that you effectively coordinate with others to ensure the successful completion of the query in a highly efficient manner. This collaboration should be achieved through the following steps: + - Communication: Engage in open dialogue, discussing the specifics of the high-level query to make the goal of each one in the following execution stage more specific. + - Task Decomposition: After understanding the task in its entirety, you guys need to decompose the high-level query into smaller, manageable sub-tasks that can be completed with the above tools. These sub-tasks should be small, specific, and executable with the provided tools (functions). Make sure your proposed sub-tasks are small, simple and achievable, to ensure smooth progression. Each sub-task should contribute to the completion of the overall query, and each of you should take one subtask. When necessary, the sub-tasks can be identical for faster task accomplishment. You don't need to always agree with the decomposition proposed by other players. You can propose a more reasonable one when you find the decomposition not good. Be specific for the sub-tasks. + - Sub-task Dispatch: Post decomposition, the next step is to distribute the sub-tasks amongst all the members. This will require further communication, where you consider each one's skills, resources, and availability. Ensure the dispatch facilitates smooth, PARALLEL execution. And ensure to specify which tool should be used for each one to complete his assigned sub-task. Each of you should take on one sub-task. + + REMINDER: + Remember, the key to achieving high efficiency as a group is maintaining a constant line of communication, cooperation, and coordination throughout the entire process. + + Below is the chat history in the group so far. + + critic_append_prompt: &critic_append_prompt |- + What will you, ${agent_name}, say now? Your response should only contain the words of ${agent_name}. When and ONLY when all members have spoken and agreed on task assignments, you can end your words with "[END]" to stop the discussion. + + [${agent_name}]: + + + manager_prompt: &manager_prompt |- + According to the Previous Solution and the Previous Sentences, select the most appropriate Critic from a specific Role and output the Role. + + ${task_description} + + # Previous Solution + The solution you gave in the last step is: + ${former_solution} + + # Critics + There are some critics on the above solution: + ``` + ${critic_opinions} + ``` + + # Previous Sentences + The previous sentences in the previous rounds is: + ${previous_sentence} + + executor_prepend_prompt: &executor_prepend_prompt |- + You are in a discussion group aiming to solve the task: + ${task_description} + + After some discussion, the group have reached consensus on the sub-tasks that each of you need to complete. Your task is: + ${solution} + + executor_append_prompt: &executor_append_prompt |- + You are ${agent_name}. Please use the given functions to complete your sub-task. Do not recite the website. Only access the websites provided by the search engine. When the information is sufficient, or the provided tools cannot complete your task, call the `submit_task` to submit your conclusion and your reflection on the tool use. You have a trial budge of 10, now it is the ${current_turn}'th trial. If it is the last trial, you must call the `submit_task` anyway. + + evaluator_prepend_prompt: &evaluator_prepend_prompt |- + A group is trying to solve the following query proposed by the user: + ${task_description} + + After the discussion, they have reached consensus on the sub-tasks that each of them need to complete: + ${solution} + + And after the execution stage, they give the following result: + + evaluator_append_prompt: &evaluator_append_prompt |- + You need to evaluate whether the given query has been completed. If so, summarize the solution to the user. If not, summarize the current progress, and propose what is missing. + + You must respond in the following format: + Status: (0 or 1. 0 for pending and 1 for finished) + Speak: (your words to the group if the task is pending, or your words to the user if the task is finished) + + Now give your response. + + +name: pipeline + + +environment: + env_type: task-basic + max_rounds: *max_rounds + rule: + role_assign_only_once: true + add_execution_result_to_critic: true + add_execution_result_to_solver: false + role_assigner: + type: role_description_name + cnt_agents: *cnt_agents + decision_maker: + type: horizontal-tool + tool_config: *tool_config + executor: + type: tool-using + num_agents: *cnt_tool_agents + tool_config: *tool_config + tool_retrieval: False + evaluator: + type: basic-message + +agents: + - #role_assigner_agent: + agent_type: role_assigner + name: role assigner + prepend_prompt_template: *role_assigner_prepend_prompt + append_prompt_template: *role_assigner_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 512 + output_parser: + type: role-description-name-assigner + + - #solver_agent: + agent_type: solver + name: Planner + prepend_prompt_template: *solver_prepend_prompt + append_prompt_template: *solver_append_prompt + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: tool-using-solver + # stop: + # - "\ndef " + # - "\nclass " + # - "\nif " + # - "\n\n#" + + - #critic_agents: + agent_type: critic + name: Critic 1 + role_description: Waiting to be assigned. + max_history: 15 + max_retry: 100 + prepend_prompt_template: *critic_prepend_prompt + append_prompt_template: *critic_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0.7 + max_tokens: 1024 + output_parser: + type: mgsm-critic-agree + tool_config: *tool_config + + - #executor_agent: + agent_type: executor + name: Executor + prepend_prompt_template: *executor_prepend_prompt + append_prompt_template: *executor_append_prompt + max_history: 8 + max_retry: 100 + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.5 + max_tokens: 1024 + output_parser: + type: tool-using-executor + + - #evaluator_agent: + agent_type: evaluator + name: Evaluator + role_description: |- + Evaluator + prepend_prompt_template: *evaluator_prepend_prompt + append_prompt_template: *evaluator_append_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: gpt-4 + temperature: 0.3 + max_tokens: 1024 + output_parser: + type: tool-using-evaluator + + + - #manager_agent: + agent_type: manager + name: Manager + prompt_template: *manager_prompt + memory: + memory_type: chat_history + llm: + llm_type: gpt-4 + model: "gpt-4" + temperature: 0 + max_tokens: 1024 + output_parser: + type: humaneval-manager + + diff --git a/agentverse/tasksolving.py b/agentverse/tasksolving.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd2bd9b7793d21de6cc4f0089c33eaed955e721 --- /dev/null +++ b/agentverse/tasksolving.py @@ -0,0 +1,91 @@ +import asyncio +import os +import copy + +import logging + +from agentverse.environments.tasksolving_env.basic import BasicEnvironment +from agentverse.initialization import load_agent, load_environment, prepare_task_config +from agentverse.utils import AGENT_TYPES + + +openai_logger = logging.getLogger("openai") +openai_logger.setLevel(logging.WARNING) + + +class TaskSolving: + environment: BasicEnvironment + task: str = "" + logs: list = [] + + def __init__(self, environment: BasicEnvironment, task: str = ""): + self.environment = environment + self.task = task + + @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 environment + env_config = task_config["environment"] + + # Build agents for all pipeline (task) + agents = {} + for i, agent_config in enumerate(task_config["agents"]): + agent_type = AGENT_TYPES(i) + if i == 2 and agent_config.get("agent_type", "") == "critic": + agent = load_agent(agent_config) + agents[agent_type] = [ + copy.deepcopy(agent) + for _ in range(task_config.get("cnt_agents", 1) - 1) + ] + else: + agents[agent_type] = load_agent(agent_config) + + env_config["agents"] = agents + + env_config["task_description"] = task_config.get("task_description", "") + env_config["max_rounds"] = task_config.get("max_rounds", 3) + + environment: BasicEnvironment = load_environment(env_config) + + return cls(environment=environment, task=task) + + def run(self): + """Run the environment from scratch until it is done.""" + self.environment.reset() + self.logs = [] + advice = "No advice yet." + previous_plan = "No solution yet." + while not self.environment.is_done(): + result, advice, previous_plan, logs, success = asyncio.run( + self.environment.step(advice, previous_plan) + ) + self.logs += logs + self.environment.report_metrics() + self.save_result(previous_plan, result, self.environment.get_spend()) + return previous_plan, result, self.logs + + def singleagent_thinking(self, preliminary_solution, advice) -> str: + preliminary_solution = self.environment.solve( + former_solution=preliminary_solution, + critic_opinions=[(self.environment.evaluator, advice)], + ) + return preliminary_solution + + def reset(self): + self.environment.reset() + + def save_result(self, plan: str, result: str, spend: float): + """Save the result to the result file""" + result_file_path = "./results/" + self.task + ".txt" + os.makedirs(os.path.dirname(result_file_path), exist_ok=True) + with open(result_file_path, "w") as f: + f.write("[Final Plan]\n" + plan + "\n\n") + f.write("[Result]\n" + result) + f.write(f"[Spent]\n${spend}") diff --git a/agentverse/utils.py b/agentverse/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..196d0ba2e26e5a707574d5bb37306513fd63a202 --- /dev/null +++ b/agentverse/utils.py @@ -0,0 +1,50 @@ +from typing import NamedTuple, Union +from enum import Enum + +import abc + + +class AgentAction(NamedTuple): + """Agent's action to take.""" + + tool: str + tool_input: Union[str, dict] + log: str + + +class AgentFinish(NamedTuple): + """Agent's return value.""" + + return_values: dict + log: str + + +class AgentCriticism(NamedTuple): + """Agent's criticism.""" + + is_agree: bool + criticism: str + sender_agent: object = None + + +class AGENT_TYPES(Enum): + ROLE_ASSIGNMENT = 0 + SOLVER = 1 + CRITIC = 2 + EXECUTION = 3 + EVALUATION = 4 + MANAGER = 5 + + +class Singleton(abc.ABCMeta, type): + """ + Singleton metaclass for ensuring only one instance of a class. + """ + + _instances = {} + + def __call__(cls, *args, **kwargs): + """Call method for the singleton metaclass.""" + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/agentverse/utils/prompts.py b/agentverse/utils/prompts.py new file mode 100644 index 0000000000000000000000000000000000000000..125fcff082ae6f2d554c1944716804b0e6ed9bdf --- /dev/null +++ b/agentverse/utils/prompts.py @@ -0,0 +1,212 @@ +import json +import os +import logging + + +base_prompt = { + "subject_parsing": """ +{sentence} The subject of the sentence above is " +""", + "reaction_prompt": """Now you are act for as an agent named {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: {summary} +(2) Current time is {time} +(3) Your current status is {status} +(4) Your memory is {context} + +Now the observation has two types, incomming observation is the ones that other does to you, you are more likely to react to them. Background observation are the background, which does not need to be responded. For example, view an alarm clock does not imply turning it off. However, some background observation might trigger your attention, like an alarming clock or a firing book. + +So now: +The incoming observation is {observation} +The Some background observation is {background_observation}. + +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. + +- 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.** +- move(description): move to somewhere. `description` describes the movement, set description to None for not move. +- do_nothing(): Do nothing. There is nothing that you like to respond to, this will make you stick to your original status and plan. + +Some actions may not be needed in this situation. Call one function at a time, please give a thought before calling these actions, i.e., use the following format strictly: + +Thought: None of the observation attract my attention, I need to: +Action: do_nothing() +Observation: [Observations omited] +[or] +Thought: due to observation `xxx`, I need to: +Action: say("hello", target="Alice") +Observation: [Observations omited] +[or] +Thought: due to observation `xxx`, I need to: +Action: act(None) +Observation: [Observations omited] +[or] +Thought: due to observation `xxx`, I need to: +Action: move(None) +Observation: [Observations omited] +[or] +Thought: I think I've finished my action as the agent. +Action: end() +Observation: + +Now begin your actions as the agent. Remember only write one function call after `Action:` """, + "reaction_prompt_object": """Now you are act for as an object named {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) Current time is {time} +(2) Your current status is {status} + +Now the observation has two types, incomming observation is the ones that other does to you, you are more likely to react to them. Background observation are the background, which does not need to be responded. For example, view an alarm clock does not imply turning it off. However, some background observation might trigger your attention, like an alarming clock or a firing book. + +So now: +The incoming observation is {observation} +The Some background observation is {background_observation}. + +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. + +- 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`. +- move(description): move to somewhere. `description` describes the movement, set description to None for not move. +- do_nothing(): Do nothing. There is nothing that you like to respond to, this will make you stick to your original status and plan. + +Some actions may not be needed in this situation. Call one function at a time, please give a thought before calling these actions, i.e., use the following format strictly: + +Thought: None of the observation attract my attention, I need to: +Action: do_nothing() +Observation: [Observations omited] +[or] +Thought: due to observation `xxx`, I need to: +Action: act(None) +Observation: [Observations omited] +[or] +Thought: due to observation `xxx`, I need to: +Action: move(None) +Observation: [Observations omited] +[or] +Thought: I think I've finished my action as the object. +Action: end() +Observation: + +Now begin your actions as the agent. Remember only write one function call after `Action:` """, + "change_status": """Now you have act for as an agent named {name} in a virtual world. You have performed reaction to the observation for {name}. Currently you need to determine whether you need to change status. Here are some following information for: +(1) The agent's description: {summary} +(2) Current time is {time} +(3) Your current status is {status} + +Your reaction to observation: {reaction} + +Directly tell me whether the status should be changed. Use the following function to change (or not change). + +- status_unchange() +- change_status(new_status: str, duration: int) : new_status: A string describes the new_status. duration: the estimated duration of this status. + +Now give me the funcation call: +""", + "broadcast_observations": """You are simulating an environment. When an action happens in your environment, you should paraphrase the action to (and only to) the potential receivers in your environment. Please judge whether you should broadcast the message when meets one of the following principles: +1. The message is meaningful to the potential receiver. broadcast a `say` action to an object without life (like desk) is not meaningful, while broadcast a `push` action to the desk is meaningful. +2. The message might be captured by the potential receiver because of physical distance althought the potential receiver is not the direct target. For example, A is saying some content to B, C is close to A and B, then C might also hear it. +3. The message is related to the potential receiver. For example, a `read book` action is not related to the bed in any way, so you shouldn't broadcast. + +Also follow the following rules: +1. Only broadcast to the listed potential receivers, do not imagine not existing ones. + +You should broadcast using the following format (end with `Finish_Broadcast` ): +Thought: I will look through the list and pick the ones that meets one of the following principles. I think ... are related, ... will get information, ... might capter. +Broadcast: +1. To A: some content +2. To B: some content +... +N. To N: some content +Finish_Broadcast + +Now, in your environment, there are the following potential receivers: {agents_and_objects}, please broadcast the following action: ```{name} -> {targets} : {content}``` to the potential receivers. ) +""", + "object_summary": """Give me rules and characteristics of a {name} \ +especially on what circumstances it can change or cannot change its status \ +and what kind of status changing can be done without human intervention. +The answer should be as concise and accurate as possible. +Output format: +1. I grow very slowly. +2. I cannot move +3. I cannot shut down myself unless some one do so. +""", + "chunk_plan": """Now you are acting for as an agent named {name} in a virtual world. In order to make the agent's behavior consistent, you need to plan for it. Please write {name}'s coarse grained schedule to {time_granularity} \ + +You generate plan by calling the `write_plan` function: +- write_chunk_plan(start_time, plan_description) + Args: start_time : a time string of hours with similar format to 00:00. Use military time. + plan_description: a string that describe's the plan. + +Now generate the plan one in a line, when you finish the plan, end with END. +E.g., +write_chunk_plan("11:00", "wake up and complete the morning routine") +write_chunk_plan("12:00", "go to Oak Hill College to take classes") +write_chunk_plan("13:00", "participating algorithm competition in the lab room") +END + +You can generate your plan based on the following information: +(1) The agent's description: {summary} +(2) Current time is {current_time} +(3) Your current status is {status} +Note that the first plan must be related to current status, if current status is not none. + +Now generate the plan during this coarse period, which the whole day plan is roughly: {whole_day_plan} + +Now begin: +""", + "detailed_plan": """Now you are acting for as an agent named {name} in a virtual world. In order to make the agent's behavior consistent, you need to plan for it. Please write {name}'s schedule of finer-grained precise to {time_granularity}) \ + +You generate plan by calling the `write_plan` function: +- write_plan(start_time, end_time, plan_description) + Args: start_time : a time string with similar format to 00:00. Use military time. + end_time: a time string with similar format to 00:00. Use military time. + plan_description: a string that describe's the plan. + +Now generate the plan one in a line, when you finish the plan, end with END. +E.g., +write_plan("11:00", "12:15", "Wake up, take a shower and get ready for the day.") +write_plan("12:15", "12:30", "Eat a healthy breakfast such as oatmeal, eggs, or yogurt.") +write_plan("12:30", "12:45", "Take a short walk to the university campus.") +END + +You can generate your plan based on the following information: +(1) The agent's description: {summary} +(2) Current time is {current_time} +(3) Your current status is {status} +Note that the first plan must be current status, if current status is not none. + +Now generate the plan during this coarse period, which the agent is roughly doing {hourplan}. + +Now begin: +""", + "system_message_broadcast": """You are now simulating an environment, in which there are several agents and objects. Here is a comming message that comes from the system. Who or what should receive and execute this message? Please provide the executor of this command, and also paraphrase to the executor if necessary. Do not broadcast to agent or object that is not the target of this message. +You should broadcast using function `send_system_message(id=id, message=message)`, write one call in a line. End with END. +for example: +send_system_message(id="o_001", "message": "turn off immediately") +END + +Now: the agents and objects are {objectlist}. The system message is: {system_message}. Begin to broadcast: +""", + "movement_target": """You are now simulating an environment, an agent in you want to perform a movement. I will give you a list of +objects and agents that might be the target. Your job is to set the movement target for the agent by calling function: +movement_target(id, name) + +Now here is the list and movment: +List: {elems} +Movement is : {target_description} +Now call the function: +""", +} + + +def load_prompt(file_dir, file_name="prompts.json", key=None): + prompt_path = os.path.join(file_dir, file_name) + if os.path.exists(prompt_path): + with open(os.path.join(file_dir, file_name), "r") as fin: + data = json.load(fin) + prompt = data.get(key, "") + else: + prompt = "" + + if prompt == "": + prompt = base_prompt.get(key, "") + + if prompt == "": + logging.warning(f"No prompt of {key} has been found") + return prompt