From 571985a6dd6bd8ad94f142329e47e28108f6d2df Mon Sep 17 00:00:00 2001 From: Mahdi Karami Date: Sat, 17 Feb 2024 14:55:41 +0330 Subject: [PATCH] feat(agent): add agent group feature * Add `divider_and_conquer` strategy for using in agent members * Add `AgentMember` class * Add `AgentGroup` class * Add `hire_agent` and `create_agent` and `create_task` * Add `test_agent_group` as an intergration test with VCR * Add `AgentTaskStatus` for flow of doing tasks * Add `autogpt_multi_agent.sh` file to run autogpt in multi agent mode --- .../autogpt/autogpt/agents/agent_group.py | 216 +++++ .../autogpt/autogpt/agents/agent_member.py | 319 +++++++ .../prompt_strategies/divide_and_conquer.py | 557 ++++++++++++ .../autogpt/agents/utils/exceptions.py | 7 + autogpts/autogpt/autogpt/app/cli.py | 194 ++++ autogpts/autogpt/autogpt/app/main.py | 490 +++++++++- .../autogpt/autogpt/commands/create_agent.py | 52 ++ .../autogpt/autogpt/commands/create_task.py | 65 ++ .../autogpt/autogpt/commands/finish_task.py | 52 ++ .../autogpt/autogpt/commands/request_agent.py | 55 ++ autogpts/autogpt/autogpt_multi_agent.sh | 29 + autogpts/autogpt/poetry.lock | 46 +- autogpts/autogpt/pyproject.toml | 5 +- .../tests/integration/test_agent_group.py | 64 ++ .../test_agent_group_happy_scenario.yaml | 834 ++++++++++++++++++ 15 files changed, 2959 insertions(+), 26 deletions(-) create mode 100644 autogpts/autogpt/autogpt/agents/agent_group.py create mode 100644 autogpts/autogpt/autogpt/agents/agent_member.py create mode 100644 autogpts/autogpt/autogpt/agents/prompt_strategies/divide_and_conquer.py create mode 100644 autogpts/autogpt/autogpt/commands/create_agent.py create mode 100644 autogpts/autogpt/autogpt/commands/create_task.py create mode 100644 autogpts/autogpt/autogpt/commands/finish_task.py create mode 100644 autogpts/autogpt/autogpt/commands/request_agent.py create mode 100755 autogpts/autogpt/autogpt_multi_agent.sh create mode 100644 autogpts/autogpt/tests/integration/test_agent_group.py create mode 100644 tests/vcr_cassettes/test_agent_group_happy_scenario/test_agent_group_happy_scenario.yaml diff --git a/autogpts/autogpt/autogpt/agents/agent_group.py b/autogpts/autogpt/autogpt/agents/agent_group.py new file mode 100644 index 00000000000..e25d3482be5 --- /dev/null +++ b/autogpts/autogpt/autogpt/agents/agent_group.py @@ -0,0 +1,216 @@ +import logging +from typing import Optional +from autogpt.agents.prompt_strategies.divide_and_conquer import ( + DivideAndConquerAgentPromptStrategy, +) +from autogpt.file_storage import get_storage +from autogpt.file_storage.base import FileStorage +from autogpt.models.command_registry import CommandRegistry +from forge.sdk.model import TaskRequestBody +from autogpt.config.config import Config, ConfigBuilder +from autogpt.agents.agent import Agent, AgentConfiguration +from autogpt.agent_manager.agent_manager import AgentManager +from autogpt.agents.agent_member import AgentMember, AgentMemberSettings, AgentTask, AgentTaskSettings +from autogpt.agent_factory.profile_generator import AgentProfileGenerator +from autogpt.core.resource.model_providers.schema import ChatModelProvider + +logger = logging.getLogger(__name__) + + +class AgentGroup: + + leader: AgentMember + members: dict[str, AgentMember] + + def assign_group_to_members(self): + self.leader.recursive_assign_group(self) + + def reload_members(self): + members = self.leader.get_list_of_all_your_team_members() + members_dict = {} + for agent_member in members: + members_dict[agent_member.id] = agent_member + self.members = members_dict + + def __init__(self, leader: AgentMember): + self.leader = leader + self.assign_group_to_members() + self.reload_members() + + async def create_task(self, task: TaskRequestBody): + await self.leader.create_task(task) + + @staticmethod + def configure_agent_group_with_state( + state: AgentMemberSettings, + app_config: Config, + file_storage: FileStorage, + llm_provider: ChatModelProvider, + ) -> 'AgentGroup': + commands = [ + "autogpt.commands.create_task", + "autogpt.commands.execute_code", + "autogpt.commands.file_operations", + "autogpt.commands.user_interaction", + "autogpt.commands.web_search", + "autogpt.commands.web_selenium", + "autogpt.commands.finish_task", + "autogpt.commands.image_gen", + ] + if state.create_agent: + commands.insert(0, "autogpt.commands.create_agent") + else: + commands.insert(0, "autogpt.commands.request_agent") + + leader = AgentMember( + settings=state, + llm_provider=llm_provider, + ) + agents, tasks = AgentGroup.create_agents_and_tasks_dict_from_state( + leader, file_storage, llm_provider + ) + members = AgentGroup.get_agents_tree_from_state(agents, tasks, state) + if state.recruiter_id: + leader.recruiter = agents[state.recruiter_id] + leader.members = members + return AgentGroup(leader=leader) + + @staticmethod + def get_agents_tree_from_state( + agents: dict[str, AgentMember], tasks: dict[str, AgentTask], agent: AgentMember + ): + members = [] + for member_id in agent.state.members: + members_of_member = AgentGroup.get_agents_and_tasks_from_state( + agents, tasks, agents[member_id] + ) + agents[member_id].members = members_of_member + agent_tasks = [] + for task_id in agent.state.tasks: + if tasks[task_id].parent_task_id: + tasks[task_id].parent_task = tasks[tasks[task_id].parent_task_id] + agent_tasks.append( + tasks[task_id] + ) + agents[member_id].tasks = agent_tasks + members.append(agents[member_id]) + return members + + @staticmethod + def create_agents_and_tasks_dict_from_state( + agent: "AgentMember", file_storage: FileStorage, llm_provider: ChatModelProvider + ): + agent_manager = AgentManager(file_storage) + members = [] + sub_tasks = [] + for member_id in agent.state.members: + member_state = agent_manager.load_agent_state(member_id) + agent_member = AgentMember( + settings=member_state, + llm_provider=llm_provider, + ) + members, sub_tasks = AgentGroup.get_agents_and_tasks_from_state( + agent_member, file_storage, llm_provider + ) + members[agent_member.id] = agent_member + for sub_task in agent.state.tasks: + sub_tasks[sub_task.task_id] = AgentTask( + task_id=sub_task.task_id, + input=sub_task.input, + parent_task_id=sub_task.parent_task_id, + ) + return members, sub_tasks + + +async def create_agent_member( + role: str, + initial_prompt: str, + llm_provider: ChatModelProvider, + boss: Optional["AgentMember"] = None, + recruiter: Optional["AgentMember"] = None, + create_agent: bool = False, +) -> AgentMember: + config = ConfigBuilder.build_config_from_env() + config.logging.plain_console_output = True + + config.continuous_mode = False + config.continuous_limit = 20 + config.noninteractive_mode = True + config.memory_backend = "no_memory" + settings = await generate_agent_settings_for_task( + role=role, + initial_prompt=initial_prompt, + boss_id=boss.id if boss else None, + recruiter_id=recruiter.id if recruiter else None, + tasks=[], + members=[], + create_agent=create_agent, + task=initial_prompt, + app_config=config, + llm_provider=llm_provider, + ) + + agent_member = AgentMember( + settings=settings, + boss=boss, + recruiter=recruiter, + llm_provider=llm_provider, + ) + + if boss: + boss.members.append(agent_member) + boss.group.reload_members() + + await agent_member.save_state() + return agent_member + + +async def generate_agent_settings_for_task( + role: str, + initial_prompt: str, + boss_id: Optional[str], + recruiter_id: Optional[str], + tasks: list[AgentTaskSettings], + members: list[str], + create_agent: bool, + task: str, + llm_provider: ChatModelProvider, + app_config, +) -> AgentMemberSettings: + agent_profile_generator = AgentProfileGenerator( + **AgentProfileGenerator.default_configuration.dict() # HACK + ) + + prompt = agent_profile_generator.build_prompt(task) + output = ( + await llm_provider.create_chat_completion( + prompt.messages, + model_name=app_config.smart_llm, + functions=prompt.functions, + ) + ).response + + ai_profile, ai_directives = agent_profile_generator.parse_response_content(output) + + return AgentMemberSettings( + role=role, + initial_prompt=initial_prompt, + boss_id=boss_id, + recruiter_id=recruiter_id, + tasks=tasks, + members=members, + create_agent=create_agent, + name=Agent.default_settings.name, + description=Agent.default_settings.description, + task=task, + ai_profile=ai_profile, + directives=ai_directives, + config=AgentConfiguration( + fast_llm=app_config.fast_llm, + smart_llm=app_config.smart_llm, + allow_fs_access=not app_config.restrict_to_workspace, + use_functions_api=app_config.openai_functions, + plugins=app_config.plugins, + ), + history=Agent.default_settings.history.copy(deep=True), + ) diff --git a/autogpts/autogpt/autogpt/agents/agent_member.py b/autogpts/autogpt/autogpt/agents/agent_member.py new file mode 100644 index 00000000000..9a07d3eedf4 --- /dev/null +++ b/autogpts/autogpt/autogpt/agents/agent_member.py @@ -0,0 +1,319 @@ +import copy +import logging +from enum import Enum +import uuid +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from autogpt.agent_factory.configurators import create_agent +from autogpt.commands import COMMAND_CATEGORIES +from autogpt.config.config import ConfigBuilder +from autogpt.file_storage import get_storage +from forge.sdk.model import Task, TaskRequestBody +from autogpt.agents.base import BaseAgentSettings +from autogpt.agents.agent import Agent, AgentConfiguration +from autogpt.models.command_registry import CommandRegistry +from autogpt.models.action_history import Action, ActionResult +from autogpt.llm.providers.openai import get_openai_command_specs +from autogpt.agents.utils.prompt_scratchpad import PromptScratchpad +from autogpt.core.resource.model_providers.openai import OpenAIProvider +from autogpt.core.prompting.schema import ( + ChatMessage, + ChatPrompt, + CompletionModelFunction, +) +from autogpt.core.resource.model_providers.schema import ( + AssistantChatMessage, + ChatModelProvider, +) +from autogpt.agents.prompt_strategies.divide_and_conquer import ( + CommandRequest, + DivideAndConquerAgentPromptConfiguration, + DivideAndConquerAgentPromptStrategy, +) + +logger = logging.getLogger(__name__) + +class AgentTaskSettings(BaseModel): + input: str + task_id: str + parent_task_id: Optional[str] + status: str + sub_tasks: list[str] + +class AgentMemberSettings(BaseAgentSettings): + config: AgentConfiguration = Field(default_factory=AgentConfiguration) + prompt_config: DivideAndConquerAgentPromptConfiguration = Field( + default_factory=( + lambda: DivideAndConquerAgentPromptStrategy.default_configuration.copy( + deep=True + ) + ) + ) + role: str + initial_prompt: str + boss_id: Optional[str] + recruiter_id: Optional[str] + create_agent: bool + members: list[str] + tasks: list[AgentTaskSettings] + +class ProposeActionResult: + commands: list[CommandRequest] + agent: "AgentMember" + + def __init__(self, commands: list[CommandRequest], agent: "AgentMember") -> None: + self.commands = commands + self.agent = agent + + +class CommandActionResult: + action_result: ActionResult + command: str + + def __init__(self, action_result: ActionResult, command: str) -> None: + self.action_result = action_result + self.command = command + + +class AgentMember(Agent): + + id: str + role: str + initial_prompt: str + boss: Optional["AgentMember"] + recruiter: Optional["AgentMember"] + tasks: list["AgentTask"] + members: list["AgentMember"] + create_agent: bool + group: "AgentGroup" + + def recursive_assign_group(self, group: "AgentGroup"): + self.group = group + for members in self.members: + members.recursive_assign_group(group) + + def get_list_of_all_your_team_members(self) -> list["AgentMember"]: + members = [] + members.append(self) + for member in self.members: + members.extend(member.get_list_of_all_your_team_members()) + return members + + def __init__( + self, + llm_provider: ChatModelProvider, + settings: AgentMemberSettings, + boss: Optional["AgentMember"] = None, + recruiter: Optional["AgentMember"] = None, + tasks: list['AgentTask'] = [], + members: list['AgentMember'] = [], + ): + config = ConfigBuilder.build_config_from_env() + config.logging.plain_console_output = True + + config.continuous_mode = False + config.continuous_limit = 20 + config.noninteractive_mode = True + config.memory_backend = "no_memory" + + file_storage = get_storage( + config.file_storage_backend, root_path="data_group", restrict_to_root=False + ) + file_storage.initialize() + commands = [ + "autogpt.commands.create_task", + "autogpt.commands.execute_code", + "autogpt.commands.file_operations", + "autogpt.commands.user_interaction", + "autogpt.commands.web_search", + "autogpt.commands.web_selenium", + "autogpt.commands.finish_task", + "autogpt.commands.image_gen", + ] + if create_agent: + commands.insert(0, "autogpt.commands.create_agent") + else: + commands.insert(0, "autogpt.commands.request_agent") + + command_registry = CommandRegistry.with_command_modules(commands, config) + + hugging_chat_settings = OpenAIProvider.default_settings.copy(deep=True) + hugging_chat_settings.credentials = config.openai_credentials + + super().__init__(settings, llm_provider, command_registry, file_storage, config) + + self.role = settings.role + self.initial_prompt = settings.initial_prompt + self.boss = boss + self.recruiter = recruiter + self.tasks = tasks + self.members = members + self.create_agent = settings.create_agent + self.prompt_strategy = DivideAndConquerAgentPromptStrategy( + configuration=settings.prompt_config, + logger=logger, + ) + + def build_prompt( + self, + scratchpad: PromptScratchpad, + tasks: list["AgentTask"], + extra_commands: Optional[list[CompletionModelFunction]] = None, + extra_messages: Optional[list[ChatMessage]] = None, + **extras, + ) -> ChatPrompt: + """Constructs a prompt using `self.prompt_strategy`. + + Params: + scratchpad: An object for plugins to write additional prompt elements to. + (E.g. commands, constraints, best practices) + extra_commands: Additional commands that the agent has access to. + extra_messages: Additional messages to include in the prompt. + """ + if not extra_commands: + extra_commands = [] + if not extra_messages: + extra_messages = [] + + # Apply additions from plugins + for plugin in self.config.plugins: + if not plugin.can_handle_post_prompt(): + continue + plugin.post_prompt(scratchpad) + ai_directives = self.directives.copy(deep=True) + ai_directives.resources += scratchpad.resources + ai_directives.constraints += scratchpad.constraints + ai_directives.best_practices += scratchpad.best_practices + extra_commands += list(scratchpad.commands.values()) + + prompt = self.prompt_strategy.build_prompt( + include_os_info=True, + tasks=tasks, + agent_member=self, + ai_profile=self.ai_profile, + ai_directives=ai_directives, + commands=get_openai_command_specs( + self.command_registry.list_available_commands(self) + ) + + extra_commands, + event_history=self.event_history, + max_prompt_tokens=self.send_token_limit, + count_tokens=lambda x: self.llm_provider.count_tokens(x, self.llm.name), + count_message_tokens=lambda x: self.llm_provider.count_message_tokens( + x, self.llm.name + ), + extra_messages=extra_messages, + **extras, + ) + + return prompt + + async def execute_commands( + self, commands: list["CommandRequest"] + ) -> list[CommandActionResult]: + results = [] + + for command in commands: + self.event_history.register_action( + Action( + name=command.command, + args=command.args, + reasoning="", + ) + ) + result = await self.execute( + command_name=command.command, + command_args=command.args, + user_input=command.user_input, + ) + results.append( + CommandActionResult(action_result=result, command=command.command) + ) + + return results + + async def create_task(self, task_request: TaskRequestBody): + try: + task = AgentTask( + input=task_request.input, + additional_input=task_request.additional_input, + status=AgentTaskStatus.INITIAL.value, + created_at=datetime.now(), + modified_at=datetime.now(), + task_id=str(uuid.uuid4()), + sub_tasks=[], + ) + + self.tasks.append(task) + except Exception as e: + logger.error(f"Error occurred while creating task: {e}") + + async def recursive_propose_action(self) -> list[ProposeActionResult]: + result = [ + ProposeActionResult(agent=self, commands=await self.single_propose_action()) + ] + for agent_member in self.members: + result = result + (await agent_member.recursive_propose_action()) + return result + + async def single_propose_action(self) -> list[CommandRequest]: + current_tasks = [] + for task in self.tasks: + if task.status == AgentTaskStatus.REJECTED: + task.status = AgentTaskStatus.INITIAL + + elif task.status == AgentTaskStatus.DOING: + # sub_tasks_done = all(sub_task.status == AgentTaskStatus.DONE for sub_task in task.sub_tasks) + # # sub_tasks_checking = any(sub_task.status == AgentTaskStatus.CHECKING for sub_task in task.sub_tasks) + + # if sub_tasks_done: + # task.status = AgentTaskStatus.CHECKING + # elif sub_tasks_checking: + current_tasks.append(task) + + elif task.status == AgentTaskStatus.INITIAL: + current_tasks.append(task) + task.status = AgentTaskStatus.DOING + + commands: list[CommandRequest] = [] + if current_tasks: + self._prompt_scratchpad = PromptScratchpad() + logger.info(f"tasks: {str(current_tasks)}") + prompt = self.build_prompt( + scratchpad=self._prompt_scratchpad, tasks=current_tasks + ) + result = await self.llm_provider.create_chat_completion( + prompt.messages, + model_name=self.config.smart_llm, + functions=prompt.functions, + completion_parser=lambda r: self.parse_and_process_response( + r, + prompt, + scratchpad=self._prompt_scratchpad, + ), + ) + commands = result.parsed_result + await self.save_state() + return commands + + def parse_and_process_response( + self, llm_response: AssistantChatMessage, *args, **kwargs + ) -> list[CommandRequest]: + result = self.prompt_strategy.parse_response_content(llm_response) + return result + + +class AgentTaskStatus(Enum): + INITIAL = "INITIAL" + DOING = "DOING" + CHECKING = "CHECKING" + REJECTED = "REJECTED" + DONE = "DONE" + + +class AgentTask(Task): + parent_task_id: Optional[str] + status: AgentTaskStatus + parent_task: Optional["AgentTask"] + sub_tasks: list["AgentTask"] diff --git a/autogpts/autogpt/autogpt/agents/prompt_strategies/divide_and_conquer.py b/autogpts/autogpt/autogpt/agents/prompt_strategies/divide_and_conquer.py new file mode 100644 index 00000000000..3956d71c24f --- /dev/null +++ b/autogpts/autogpt/autogpt/agents/prompt_strategies/divide_and_conquer.py @@ -0,0 +1,557 @@ +from __future__ import annotations + +import json +import platform +import re +from logging import Logger +from typing import TYPE_CHECKING, Callable, Optional + +import distro + +if TYPE_CHECKING: + from autogpt.models.action_history import Episode + +from autogpt.agents.utils.exceptions import InvalidAgentResponseError +from autogpt.config import AIDirectives, AIProfile +from autogpt.core.configuration.schema import SystemConfiguration, UserConfigurable +from autogpt.core.prompting import ( + ChatPrompt, + LanguageModelClassification, + PromptStrategy, +) +from autogpt.core.resource.model_providers.schema import ( + AssistantChatMessage, + ChatMessage, + CompletionModelFunction, +) +from autogpt.core.utils.json_schema import JSONSchema +from autogpt.json_utils.utilities import extract_dict_from_response +from autogpt.prompts.utils import format_numbered_list, indent + + +class CommandRequest: + command: str + args: dict[str, str] + user_input: str + + def __init__(self, command: str, args: dict[str, str], user_input: str = "") -> None: + self.command = command + self.args = args + self.user_input = user_input + +class DivideAndConquerAgentPromptConfiguration(SystemConfiguration): + DEFAULT_BODY_TEMPLATE: str = ( + "## Constraints\n" + "You operate within the following constraints:\n" + "{constraints}\n" + "\n" + "## Resources\n" + "You can leverage access to the following resources:\n" + "{resources}\n" + "\n" + "## Commands\n" + "These are the ONLY commands you can use." + " Any action you perform must be possible through list of these commands:\n" + "{commands}\n" + "\n" + "## Best practices\n" + "{best_practices}" + ) + + DEFAULT_CHOOSE_ACTION_INSTRUCTION: str = ( + "Determine list of commands to use next based on the given goals " + "and the progress you have made so far, " + "and respond using the JSON schema specified previously:" + ) + + DEFAULT_RESPONSE_SCHEMA = JSONSchema( + type=JSONSchema.Type.OBJECT, + properties={ + "thoughts": JSONSchema( + type=JSONSchema.Type.OBJECT, + required=True, + properties={ + "observations": JSONSchema( + description=( + "Relevant observations from your last action (if any)" + ), + type=JSONSchema.Type.STRING, + required=False, + ), + "text": JSONSchema( + description="Thoughts", + type=JSONSchema.Type.STRING, + required=True, + ), + "reasoning": JSONSchema( + type=JSONSchema.Type.STRING, + required=True, + ), + "self_criticism": JSONSchema( + description="Constructive self-criticism", + type=JSONSchema.Type.STRING, + required=True, + ), + "plan": JSONSchema( + description=( + "Short markdown-style bullet list that conveys the " + "long-term plan" + ), + type=JSONSchema.Type.STRING, + required=True, + ), + "speak": JSONSchema( + description="Summary of thoughts, to say to user", + type=JSONSchema.Type.STRING, + required=True, + ), + }, + ), + "commands": JSONSchema( + description=( + "give 1 to 5 commands based on tasks with DOING status to make progress in tasks and show task_id of that task. " + "some tips about commands:\n" + "- We can't have command with unclear identities.\n" + # "1 to 5 commands that make tasks progressive. These commands for tasks whose status is DOING. The structure should be like this " + # "list[dict[name:str, args:dict[str,str], task_id: str ]] for completing a list of tasks. " + # "Each command should be assigned to a task whose status is DOING by task_id field. " + # "You need to choose some commands to progress tasks. your list shouldn't be empty. " + # "args field to should be a dictionary of keys and values. It's so important to break " + # "tasks and hire or create agents as much as you can by create_task or create_agent or request_agent. " + # "commands arguments should not be abstract and not clear. Commands part should not be empty " + # "and in each step you should give some comamnds to make tasks prggresive" + ), + type=JSONSchema.Type.ARRAY, + required=True, + items=JSONSchema( + type=JSONSchema.Type.OBJECT, + properties={ + "name": JSONSchema( + type=JSONSchema.Type.STRING, + required=True, + ), + "args": JSONSchema( + type=JSONSchema.Type.OBJECT, + required=True, + ), + }, + ), + ), + # "tasks": JSONSchema( + # description=( + # "check all tasks that I sent you (not sub_tasks part) with CHECKING status. In this part you should say status of tasks " + # "that I sent(not sub_tasks parts). send them with these statuses: REJECTED, DONE with this structure list[dict[task_id: str, status: str, reason: str?]] " + # "(REJECTED: This status shows that the task need to be imporved by the owner of task and agent member needs to work on it more. need to say reject reason in reason field" + # "DONE: This status shows that the result of task is OK and the task is done)" + # ), + # type=JSONSchema.Type.ARRAY, + # required=True, + # items=JSONSchema( + # type=JSONSchema.Type.OBJECT, + # properties={ + # "task_id": JSONSchema( + # type=JSONSchema.Type.STRING, + # required=True, + # ), + # "status": JSONSchema( + # type=JSONSchema.Type.STRING, + # required=True, + # ), + # "reason": JSONSchema( + # type=JSONSchema.Type.STRING, + # required=False, + # ), + # }, + # ), + # ), + }, + ) + + body_template: str = UserConfigurable(default=DEFAULT_BODY_TEMPLATE) + response_schema: dict = UserConfigurable( + default_factory=DEFAULT_RESPONSE_SCHEMA.to_dict + ) + choose_action_instruction: str = UserConfigurable( + default=DEFAULT_CHOOSE_ACTION_INSTRUCTION + ) + use_functions_api: bool = UserConfigurable(default=False) + + ######### + # State # + ######### + # progress_summaries: dict[tuple[int, int], str] = Field( + # default_factory=lambda: {(0, 0): ""} + # ) + + +class DivideAndConquerAgentPromptStrategy(PromptStrategy): + default_configuration: DivideAndConquerAgentPromptConfiguration = ( + DivideAndConquerAgentPromptConfiguration() + ) + + def __init__( + self, + configuration: DivideAndConquerAgentPromptConfiguration, + logger: Logger, + ): + self.config = configuration + self.response_schema = JSONSchema.from_dict(configuration.response_schema) + self.logger = logger + + @property + def model_classification(self) -> LanguageModelClassification: + return LanguageModelClassification.FAST_MODEL # FIXME: dynamic switching + + def build_prompt( + self, + *, + tasks: list["AgentTask"], + agent_member: "AgentMember", + ai_profile: AIProfile, + ai_directives: AIDirectives, + commands: list[CompletionModelFunction], + event_history: list[Episode], + include_os_info: bool, + max_prompt_tokens: int, + count_tokens: Callable[[str], int], + count_message_tokens: Callable[[ChatMessage | list[ChatMessage]], int], + extra_messages: Optional[list[ChatMessage]] = None, + **extras, + ) -> ChatPrompt: + """Constructs and returns a prompt with the following structure: + 1. System prompt + 2. Message history of the agent, truncated & prepended with running summary + as needed + 3. `cycle_instruction` + """ + if not extra_messages: + extra_messages = [] + + system_prompt = self.build_system_prompt( + ai_profile=ai_profile, + ai_directives=ai_directives, + commands=commands, + include_os_info=include_os_info, + agent=agent_member, + ) + system_prompt_tlength = count_message_tokens(ChatMessage.system(system_prompt)) + + tasks_list = [] + for task in tasks: + task_dict = { + "task_id": task.task_id, + "task_detail": task.input, + "status": task.status.value, + "sub_tasks": task.sub_tasks, + } + tasks_list.append(task_dict) + tasks_prompt = json.dumps(tasks_list, ensure_ascii=False, indent=4) + + user_task = f'"""{tasks_prompt}"""' + user_task_tlength = count_message_tokens(ChatMessage.user(user_task)) + + response_format_instr = self.response_format_instruction( + self.config.use_functions_api + ) + extra_messages.append(ChatMessage.system(response_format_instr)) + + final_instruction_msg = ChatMessage.user(self.config.choose_action_instruction) + final_instruction_tlength = count_message_tokens(final_instruction_msg) + + if event_history: + progress = self.compile_progress( + event_history, + count_tokens=count_tokens, + max_tokens=( + max_prompt_tokens + - system_prompt_tlength + - user_task_tlength + - final_instruction_tlength + - count_message_tokens(extra_messages) + ), + ) + extra_messages.insert( + 0, + ChatMessage.system(f"## Progress\n\n{progress}"), + ) + + prompt = ChatPrompt( + messages=[ + ChatMessage.system(system_prompt), + ChatMessage.user(user_task), + *extra_messages, + final_instruction_msg, + ], + ) + + return prompt + + def _generate_members_detail(self, agent: "AgentMember") -> list[str]: + result = "## Members detail:\n" + if agent.members: + for emp in agent.members: + result += ( + "{agent_id: " + + emp.id + + ", description: " + + emp.ai_profile.ai_role.rstrip(".") + + "}\n" + ) + else: + result += "you don't have any member to assign task" + return [result] + + def build_system_prompt( + self, + ai_profile: AIProfile, + ai_directives: AIDirectives, + commands: list[CompletionModelFunction], + include_os_info: bool, + agent: "AgentMember", + ) -> str: + system_prompt_parts = ( + self._generate_intro_prompt(agent.id, ai_profile) + + (self._generate_os_info() if include_os_info else []) + + self._generate_members_detail(agent) + + [ + self.config.body_template.format( + constraints=format_numbered_list( + ai_directives.constraints + + self._generate_budget_constraint(ai_profile.api_budget) + ), + resources=format_numbered_list(ai_directives.resources), + commands=self._generate_commands_list(commands), + best_practices=format_numbered_list(ai_directives.best_practices), + ) + ] + + [ + "## tasks\n" + "The user will specify tasks for you to execute," + "in triple quotes, in the next message. " + "Your job is to use the command list to do things " + "to make progress in tasks. complete the task while " + "following your directives as given above, and terminate when your task is done. " + "It's good practice to hire or create other agents to break tasks and make them better." + ] + ) + + # Join non-empty parts together into paragraph format + return "\n\n".join(filter(None, system_prompt_parts)).strip("\n") + + def compile_progress( + self, + episode_history: list[Episode], + max_tokens: Optional[int] = None, + count_tokens: Optional[Callable[[str], int]] = None, + ) -> str: + if max_tokens and not count_tokens: + raise ValueError("count_tokens is required if max_tokens is set") + + steps: list[str] = [] + tokens: int = 0 + n_episodes = len(episode_history) + + for i, episode in enumerate(reversed(episode_history)): + # Use full format for the latest 4 steps, summary or format for older steps + if i < 4 or episode.summary is None: + step_content = indent(episode.format(), 2).strip() + else: + step_content = episode.summary + + step = f"* Step {n_episodes - i}: {step_content}" + + if max_tokens and count_tokens: + step_tokens = count_tokens(step) + if tokens + step_tokens > max_tokens: + break + tokens += step_tokens + + steps.insert(0, step) + + return "\n\n".join(steps) + + def response_format_instruction(self, use_functions_api: bool) -> str: + response_schema = self.response_schema.copy(deep=True) + if ( + use_functions_api + and response_schema.properties + and "commands" in response_schema.properties + ): + del response_schema.properties["commands"] + + # Unindent for performance + response_format = re.sub( + r"\n\s+", + "\n", + response_schema.to_typescript_object_interface("Response"), + ) + + instruction = ( + "Respond with pure JSON containing your thoughts, " "and invoke a tool." + if use_functions_api + else "Respond with pure JSON." + ) + + return ( + f"{instruction} " + "The JSON object should be compatible with the TypeScript type `Response` " + f"from the following:\n{response_format}" + ) + + def _generate_intro_prompt(self, agent_id: str, ai_profile: AIProfile) -> list[str]: + """Generates the introduction part of the prompt. + + Returns: + list[str]: A list of strings forming the introduction part of the prompt. + """ + return [ + f"You are {ai_profile.ai_name}, {ai_profile.ai_role.rstrip('.')} with agent_id {agent_id}.", + "Your decisions must always be made independently without seeking " + "user assistance. Play to your strengths as an LLM and pursue " + "simple strategies with no legal complications.", + ] + + def _generate_os_info(self) -> list[str]: + """Generates the OS information part of the prompt. + + Params: + config (Config): The configuration object. + + Returns: + str: The OS information part of the prompt. + """ + os_name = platform.system() + os_info = ( + platform.platform(terse=True) + if os_name != "Linux" + else distro.name(pretty=True) + ) + return [f"The OS you are running on is: {os_info}"] + + def _generate_budget_constraint(self, api_budget: float) -> list[str]: + """Generates the budget information part of the prompt. + + Returns: + list[str]: The budget information part of the prompt, or an empty list. + """ + if api_budget > 0.0: + return [ + f"It takes money to let you run. " + f"Your API budget is ${api_budget:.3f}" + ] + return [] + + def _generate_commands_list(self, commands: list[CompletionModelFunction]) -> str: + """Lists the commands available to the agent. + + Params: + agent: The agent for which the commands are being listed. + + Returns: + str: A string containing a numbered list of commands. + """ + try: + return format_numbered_list([cmd.fmt_line() for cmd in commands]) + except AttributeError: + self.logger.warning(f"Formatting commands failed. {commands}") + raise + + def parse_response_content( + self, + response: AssistantChatMessage, + ) -> list[CommandRequest]: + if not response.content: + raise InvalidAgentResponseError("Assistant response has no text content") + + self.logger.debug( + "LLM response content:" + + ( + f"\n{response.content}" + if "\n" in response.content + else f" '{response.content}'" + ) + ) + assistant_reply_dict = extract_dict_from_response(response.content) + self.logger.debug( + "Validating object extracted from LLM response:\n" + f"{json.dumps(assistant_reply_dict, indent=4)}" + ) + + _, errors = self.response_schema.validate_object( + object=assistant_reply_dict, + logger=self.logger, + ) + if errors: + raise InvalidAgentResponseError( + "Validation of response failed:\n " + + ";\n ".join([str(e) for e in errors]) + ) + + # Get commands name and arguments + commands = extract_command( + assistant_reply_dict, response, self.config.use_functions_api + ) + return commands + + +############# +# Utilities # +############# + + +def extract_command( + assistant_reply_json: dict, + assistant_reply: AssistantChatMessage, + use_openai_functions_api: bool, +) -> list[CommandRequest]: + """Parse the response and return the commands name and arguments + + Args: + assistant_reply_json (dict): The response object from the AI + assistant_reply (AssistantChatMessage): The model response from the AI + config (Config): The config object + + Returns: + list: The commands name and arguments + + Raises: + json.decoder.JSONDecodeError: If the response is not valid JSON + + Exception: If any other error occurs + """ + if use_openai_functions_api: + if not assistant_reply.tool_calls: + raise InvalidAgentResponseError("No 'tool_calls' in assistant reply") + assistant_reply_json["commands"] = [] + for tool_call in assistant_reply.tool_calls: + command = { + "name": tool_call.function.name, + "args": json.loads(tool_call.function.arguments), + } + assistant_reply_json["commands"].append(command) + try: + if not isinstance(assistant_reply_json, dict): + raise InvalidAgentResponseError( + f"The previous message sent was not a dictionary {assistant_reply_json}" + ) + + if "commands" not in assistant_reply_json: + raise InvalidAgentResponseError("Missing 'commands' object in JSON") + + commands = assistant_reply_json["commands"] + if not isinstance(commands, list): + raise InvalidAgentResponseError("'commands' object is not a list") + + commands_list = [] + for command_data in commands: + command_obj = CommandRequest( + command=command_data["name"], args=command_data.get("args", {}) + ) + commands_list.append(command_obj) + return commands_list + + except json.decoder.JSONDecodeError: + raise InvalidAgentResponseError("Invalid JSON") + + except Exception as e: + raise InvalidAgentResponseError(str(e)) diff --git a/autogpts/autogpt/autogpt/agents/utils/exceptions.py b/autogpts/autogpt/autogpt/agents/utils/exceptions.py index 211c8443c00..49fefda59f9 100644 --- a/autogpts/autogpt/autogpt/agents/utils/exceptions.py +++ b/autogpts/autogpt/autogpt/agents/utils/exceptions.py @@ -22,6 +22,13 @@ class AgentFinished(AgentTerminated): """The agent self-terminated""" +class TaskFinished(Exception): + """The task self-terminated""" + def __init__(self, task_id: str, reason: str) -> None: + self.task_id = task_id + self.reason = reason + + class ConfigurationError(AgentException): """Error caused by invalid, incompatible or otherwise incorrect configuration""" diff --git a/autogpts/autogpt/autogpt/app/cli.py b/autogpts/autogpt/autogpt/app/cli.py index e6ca0a78387..09583744025 100644 --- a/autogpts/autogpt/autogpt/app/cli.py +++ b/autogpts/autogpt/autogpt/app/cli.py @@ -282,6 +282,200 @@ def serve( install_plugin_deps=install_plugin_deps, ) +@cli.command() +@click.option("-c", "--continuous", is_flag=True, help="Enable Continuous Mode") +@click.option( + "-l", + "--continuous-limit", + type=int, + help="Defines the number of times to run in continuous mode", +) +@click.option("--speak", is_flag=True, help="Enable Speak Mode") +@click.option("--gpt3only", is_flag=True, help="Enable GPT3.5 Only Mode") +@click.option("--gpt4only", is_flag=True, help="Enable GPT4 Only Mode") +@click.option( + "-b", + "--browser-name", + help="Specifies which web-browser to use when using selenium to scrape the web.", +) +@click.option( + "--allow-downloads", + is_flag=True, + help="Dangerous: Allows AutoGPT to download files natively.", +) +@click.option( + # TODO: this is a hidden option for now, necessary for integration testing. + # We should make this public once we're ready to roll out agent specific workspaces. + "--workspace-directory", + "-w", + type=click.Path(file_okay=False), + hidden=True, +) +@click.option( + "--install-plugin-deps", + is_flag=True, + help="Installs external dependencies for 3rd party plugins.", +) +@click.option( + "--skip-news", + is_flag=True, + help="Specifies whether to suppress the output of latest news on startup.", +) +@click.option( + "--skip-reprompt", + "-y", + is_flag=True, + help="Skips the re-prompting messages at the beginning of the script", +) +@click.option( + "--ai-settings", + "-C", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help=( + "Specifies which ai_settings.yaml file to use, relative to the AutoGPT" + " root directory. Will also automatically skip the re-prompt." + ), +) +@click.option( + "--ai-name", + type=str, + help="AI name override", +) +@click.option( + "--ai-role", + type=str, + help="AI role override", +) +@click.option( + "--prompt-settings", + "-P", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help="Specifies which prompt_settings.yaml file to use.", +) +@click.option( + "--constraint", + type=str, + multiple=True, + help=( + "Add or override AI constraints to include in the prompt;" + " may be used multiple times to pass multiple constraints" + ), +) +@click.option( + "--resource", + type=str, + multiple=True, + help=( + "Add or override AI resources to include in the prompt;" + " may be used multiple times to pass multiple resources" + ), +) +@click.option( + "--best-practice", + type=str, + multiple=True, + help=( + "Add or override AI best practices to include in the prompt;" + " may be used multiple times to pass multiple best practices" + ), +) +@click.option( + "--member-description", + type=str, + multiple=True, + help=( + "Add members description and detail" + ), +) +@click.option( + "--override-directives", + is_flag=True, + help=( + "If specified, --constraint, --resource and --best-practice will override" + " the AI's directives instead of being appended to them" + ), +) +@click.option( + "--debug", is_flag=True, help="Implies --log-level=DEBUG --log-format=debug" +) +@click.option("--log-level", type=click.Choice([*logLevelMap.keys()])) +@click.option( + "--log-format", + help=( + "Choose a log format; defaults to 'simple'." + " Also implies --log-file-format, unless it is specified explicitly." + " Using the 'structured_google_cloud' format disables log file output." + ), + type=click.Choice([i.value for i in LogFormatName]), +) +@click.option( + "--log-file-format", + help=( + "Override the format used for the log file output." + " Defaults to the application's global --log-format." + ), + type=click.Choice([i.value for i in LogFormatName]), +) +def run_multi_agent( + continuous: bool, + continuous_limit: Optional[int], + speak: bool, + gpt3only: bool, + gpt4only: bool, + browser_name: Optional[str], + allow_downloads: bool, + workspace_directory: Optional[Path], + install_plugin_deps: bool, + skip_news: bool, + skip_reprompt: bool, + ai_settings: Optional[Path], + ai_name: Optional[str], + ai_role: Optional[str], + prompt_settings: Optional[Path], + resource: tuple[str], + constraint: tuple[str], + best_practice: tuple[str], + override_directives: bool, + debug: bool, + log_level: Optional[str], + log_format: Optional[str], + log_file_format: Optional[str], + member_description: tuple[str], +) -> None: + """ + Sets up and runs an agent, based on the task specified by the user, or resumes an + existing agent. + """ + # Put imports inside function to avoid importing everything when starting the CLI + from autogpt.app.main import run_multi_agents_auto_gpt + + run_multi_agents_auto_gpt( + continuous=continuous, + continuous_limit=continuous_limit, + ai_settings=ai_settings, + prompt_settings=prompt_settings, + skip_reprompt=skip_reprompt, + speak=speak, + debug=debug, + log_level=log_level, + log_format=log_format, + log_file_format=log_file_format, + gpt3only=gpt3only, + gpt4only=gpt4only, + browser_name=browser_name, + allow_downloads=allow_downloads, + skip_news=skip_news, + workspace_directory=workspace_directory, + install_plugin_deps=install_plugin_deps, + override_ai_name=ai_name, + override_ai_role=ai_role, + resources=list(resource), + constraints=list(constraint), + best_practices=list(best_practice), + override_directives=override_directives, + member_descriptions=list(member_description), + ) + if __name__ == "__main__": cli() diff --git a/autogpts/autogpt/autogpt/app/main.py b/autogpts/autogpt/autogpt/app/main.py index 59ea781a540..3037821d7da 100644 --- a/autogpts/autogpt/autogpt/app/main.py +++ b/autogpts/autogpt/autogpt/app/main.py @@ -16,6 +16,11 @@ from colorama import Fore, Style from forge.sdk.db import AgentDB +from forge.sdk.model import TaskRequestBody +from autogpt.agents.agent_group import AgentGroup, create_agent_member +from autogpt.agents.agent_member import ProposeActionResult, AgentTaskSettings +from autogpt.agents.prompt_strategies.divide_and_conquer import CommandRequest + if TYPE_CHECKING: from autogpt.agents.agent import Agent @@ -418,7 +423,6 @@ async def run_auto_gpt_server( f"${round(sum(b.total_cost for b in server._task_budgets.values()), 2)}" ) - def _configure_openai_provider(config: Config) -> OpenAIProvider: """Create a configured OpenAIProvider object. @@ -799,3 +803,487 @@ def print_assistant_thoughts( def remove_ansi_escape(s: str) -> str: return s.replace("\x1B", "") + + +@coroutine +async def run_multi_agents_auto_gpt( + continuous: bool = False, + continuous_limit: Optional[int] = None, + ai_settings: Optional[Path] = None, + prompt_settings: Optional[Path] = None, + skip_reprompt: bool = False, + speak: bool = False, + debug: bool = False, + log_level: Optional[str] = None, + log_format: Optional[str] = None, + log_file_format: Optional[str] = None, + gpt3only: bool = False, + gpt4only: bool = False, + browser_name: Optional[str] = None, + allow_downloads: bool = False, + skip_news: bool = False, + workspace_directory: Optional[Path] = None, + install_plugin_deps: bool = False, + override_ai_name: Optional[str] = None, + override_ai_role: Optional[str] = None, + resources: Optional[list[str]] = None, + constraints: Optional[list[str]] = None, + best_practices: Optional[list[str]] = None, + override_directives: bool = False, + member_descriptions: list[str] = [], +): + # Set up configuration + config = ConfigBuilder.build_config_from_env() + # Storage + local = config.file_storage_backend == FileStorageBackendName.LOCAL + restrict_to_root = not local or config.restrict_to_workspace + file_storage = get_storage( + config.file_storage_backend, + root_path="data_group", + restrict_to_root=restrict_to_root, + ) + file_storage.initialize() + + # TODO: fill in llm values here + assert_config_has_openai_api_key(config) + + apply_overrides_to_config( + config=config, + continuous=continuous, + continuous_limit=continuous_limit, + ai_settings_file=ai_settings, + prompt_settings_file=prompt_settings, + skip_reprompt=skip_reprompt, + speak=speak, + debug=debug, + log_level=log_level, + log_format=log_format, + log_file_format=log_file_format, + gpt3only=gpt3only, + gpt4only=gpt4only, + browser_name=browser_name, + allow_downloads=allow_downloads, + skip_news=skip_news, + ) + + # Set up logging module + configure_logging( + **config.logging.dict(), + tts_config=config.tts_config, + ) + + llm_provider = _configure_gpt_4_free_provider(config) + + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + if config.continuous_mode: + for line in get_legal_warning().split("\n"): + logger.warning( + extra={ + "title": "LEGAL:", + "title_color": Fore.RED, + "preserve_color": True, + }, + msg=markdown_to_ansi_style(line), + ) + + if not config.skip_news: + print_motd(config, logger) + print_git_branch_info(logger) + print_python_version_info(logger) + print_attribute("Smart LLM", config.smart_llm) + print_attribute("Fast LLM", config.fast_llm) + print_attribute("Browser", config.selenium_web_browser) + if config.continuous_mode: + print_attribute("Continuous Mode", "ENABLED", title_color=Fore.YELLOW) + if continuous_limit: + print_attribute("Continuous Limit", config.continuous_limit) + if config.tts_config.speak_mode: + print_attribute("Speak Mode", "ENABLED") + if ai_settings: + print_attribute("Using AI Settings File", ai_settings) + if prompt_settings: + print_attribute("Using Prompt Settings File", prompt_settings) + if config.allow_downloads: + print_attribute("Native Downloading", "ENABLED") + + if install_plugin_deps: + install_plugin_dependencies() + + config.plugins = scan_plugins(config) + configure_chat_plugins(config) + agent_manager = AgentManager(file_storage) + existing_agents = agent_manager.list_agents() + load_existing_agent = "" + if existing_agents: + print( + "Existing agent groups\n---------------\n" + + "\n".join(f"{i} - {id}" for i, id in enumerate(existing_agents, 1)) + ) + load_existing_agent = await clean_input( + config, + "Enter the number or name of the agent group to run," + " or hit enter to create a new one:", + ) + if re.match(r"^\d+$", load_existing_agent): + load_existing_agent = existing_agents[int(load_existing_agent) - 1] + elif load_existing_agent and load_existing_agent not in existing_agents: + logger.info( + f"Unknown agent '{load_existing_agent}', " + f"creating a new one instead.", + extra={"color": Fore.YELLOW}, + ) + load_existing_agent = "" + agent_group = None + + ################################## + # Resume an Existing Agent Group # + ################################## + agent_state = None + if load_existing_agent: + agent_state = None + while True: + answer = await clean_input(config, "Resume? [Y/n]") + if answer == "" or answer.lower() == "y": + agent_state = agent_manager.load_agent_state(load_existing_agent) + break + elif answer.lower() == "n": + break + + if agent_state: + agent_group = AgentGroup.configure_agent_group_with_state( + state=agent_state, + app_config=config, + file_storage=file_storage, + llm_provider=llm_provider, + ) + + # if ( + # agent.event_history.current_episode + # and agent.event_history.current_episode.action.name == finish.__name__ + # and not agent.event_history.current_episode.result + # ): + # # Agent was resumed after `finish` -> rewrite result of `finish` action + # finish_reason = agent.event_history.current_episode.action.args["reason"] + # print(f"Agent previously self-terminated; reason: '{finish_reason}'") + # new_assignment = await clean_input( + # config, "Please give a follow-up question or assignment:" + # ) + # agent.event_history.register_result( + # ActionInterruptedByHuman(feedback=new_assignment) + # ) + + # If any of these are specified as arguments, + # assume the user doesn't want to revise them + # if not any( + # [ + # override_ai_name, + # override_ai_role, + # resources, + # constraints, + # best_practices, + # ] + # ): + # ai_profile, ai_directives = await interactively_revise_ai_settings( + # ai_profile=agent.state.ai_profile, + # directives=agent.state.directives, + # app_config=config, + # ) + # else: + # logger.info("AI config overrides specified through CLI; skipping revision") + + ###################### + # Set up a new Agent Group # + ###################### + if not agent_group: + task = await clean_input( + config, + "Enter the task that you want AutoGPT to execute," + " with as much detail as possible:", + ) + + agent_members = [] + if not len(member_descriptions) == 0: + for description in member_descriptions: + description_params = description.split(":") + agent_member = await create_agent_member( + role=description_params[0], + initial_prompt=description_params[1], + create_agent=bool(description_params[2]), + llm_provider=llm_provider, + ) + agent_members.append(agent_member) + + for member_index in range(len(agent_members)): + description_params = member_descriptions[member_index].split(":") + if len(description_params) >= 4 and description_params[3] != "": + agent_members[member_index].boss = agent_members[ + int(description_params[3]) + ] + if len(description_params) >= 5 and description_params[4] != "": + agent_members[member_index].recruiter = agent_members[ + int(description_params[4]) + ] + + for member_index in range(len(agent_members)): + for sub_member_index in range(len(agent_members)): + if agent_members[sub_member_index].boss != None: + if ( + agent_members[sub_member_index].boss.id + == agent_members[member_index].id + ): + agent_members[member_index].members.append( + agent_members[sub_member_index] + ) + else: + leader = await create_agent_member( + role="leader", + initial_prompt=f"someone can do {task}", + create_agent=True, + llm_provider=llm_provider, + ) + agent_members.append(leader) + + agent_group = AgentGroup(leader=agent_members[0]) + await agent_group.create_task(TaskRequestBody(input=task)) + + ################# + # Run the Agent # + ################# + await run_interaction_loop_for_agent_group(agent_group) + + +async def run_interaction_loop_for_agent_group( + agent_group: "AgentGroup", +) -> None: + """Run the main interaction loop for the agent group. + + Args: + agent_group: The agent group to run the interaction loop for. + + Returns: + None + """ + # These contain both application config and agent config, so grab them here. + legacy_config = agent_group.leader.legacy_config + ai_profile = agent_group.leader.ai_profile + logger = logging.getLogger(__name__) + + cycle_budget = cycles_remaining = _get_cycle_budget( + legacy_config.continuous_mode, legacy_config.continuous_limit + ) + spinner = Spinner( + "Thinking...", plain_output=legacy_config.logging.plain_console_output + ) + stop_reason = None + + def graceful_agent_interrupt(signum: int, frame: Optional[FrameType]) -> None: + nonlocal cycle_budget, cycles_remaining, spinner, stop_reason + if stop_reason: + logger.error("Quitting immediately...") + sys.exit() + if cycles_remaining in [0, 1]: + logger.warning("Interrupt signal received: shutting down gracefully.") + logger.warning( + "Press Ctrl+C again if you want to stop AutoGPT immediately." + ) + stop_reason = AgentTerminated("Interrupt signal received") + else: + restart_spinner = spinner.running + if spinner.running: + spinner.stop() + + logger.error( + "Interrupt signal received: stopping continuous command execution." + ) + cycles_remaining = 1 + if restart_spinner: + spinner.start() + + def handle_stop_signal() -> None: + if stop_reason: + raise stop_reason + + # Set up an interrupt signal for the agent. + signal.signal(signal.SIGINT, graceful_agent_interrupt) + + ######################### + # Application Main Loop # + ######################### + + # Keep track of consecutive failures of the agent + consecutive_failures = 0 + + while cycles_remaining > 0: + logger.debug(f"Cycle budget: {cycle_budget}; remaining: {cycles_remaining}") + + ######## + # Plan # + ######## + handle_stop_signal() + # Have the agent determine the next action to take. + commands_result = [] + with spinner: + try: + commands_result = await agent_group.leader.recursive_propose_action() + except InvalidAgentResponseError as e: + logger.warning(f"The agent's thoughts could not be parsed: {e}") + consecutive_failures += 1 + if consecutive_failures >= 3: + logger.error( + "The agent failed to output valid thoughts" + f" {consecutive_failures} times in a row. Terminating..." + ) + raise AgentTerminated( + "The agent failed to output valid thoughts" + f" {consecutive_failures} times in a row." + ) + continue + + consecutive_failures = 0 + + ############### + # Update User # + ############### + # Print the assistant's thoughts and the next command to the user. + update_user_in_group_mode(commands_result) + + ################## + # Get user input # + ################## + handle_stop_signal() + if cycles_remaining == 1: # Last cycle + user_feedback, user_input, new_cycles_remaining = await get_user_feedback( + legacy_config, + ai_profile, + ) + + if user_feedback == UserFeedback.AUTHORIZE: + if new_cycles_remaining is not None: + # Case 1: User is altering the cycle budget. + if cycle_budget > 1: + cycle_budget = new_cycles_remaining + 1 + # Case 2: User is running iteratively and + # has initiated a one-time continuous cycle + cycles_remaining = new_cycles_remaining + 1 + else: + # Case 1: Continuous iteration was interrupted -> resume + if cycle_budget > 1: + logger.info( + f"The cycle budget is {cycle_budget}.", + extra={ + "title": "RESUMING CONTINUOUS EXECUTION", + "title_color": Fore.MAGENTA, + }, + ) + # Case 2: The agent used up its cycle budget -> reset + cycles_remaining = cycle_budget + 1 + logger.info( + "-=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-=", + extra={"color": Fore.MAGENTA}, + ) + elif user_feedback == UserFeedback.EXIT: + logger.warning("Exiting...") + exit() + else: # user_feedback == UserFeedback.TEXT + commands_result = [ + ProposeActionResult( + agent=agent_group.leader, + commands=[ + CommandRequest( + command="human_feedback", + args={}, + user_input=user_input, + ) + ], + ) + ] + else: + user_input = "" + # First log new-line so user can differentiate sections better in console + print() + if cycles_remaining != math.inf: + # Print authorized commands left value + print_attribute( + "AUTHORIZED_COMMANDS_LEFT", cycles_remaining, title_color=Fore.CYAN + ) + + ################### + # Execute Command # + ################### + # Decrement the cycle counter first to reduce the likelihood of a SIGINT + # happening during command execution, setting the cycles remaining to 1, + # and then having the decrement set it to 0, exiting the application. + if ( + len(commands_result[0].commands) > 0 + and commands_result[0].commands[0].command != "human_feedback" + ): + cycles_remaining -= 1 + + if not commands_result: + continue + + handle_stop_signal() + + if commands_result: + for command_result in commands_result: + command_action_results = await command_result.agent.execute_commands( + command_result.commands + ) + command_result.agent.state.members = [] + for member in command_result.agent.members: + command_result.agent.state.members.append(member.id) + for task in command_result.agent.tasks: + sub_tasks = [] + for sub_task in task.sub_tasks: + sub_tasks.append(sub_task.task_id) + command_result.agent.state.tasks.append( + AgentTaskSettings( + task_id=task.task_id, + input=task.input, + parent_task_id=task.parent_task_id, + status=task.status.value, + sub_tasks=sub_tasks, + ) + ) + for command_action_result in command_action_results: + result = command_action_result.action_result + command = command_action_result.command + if result.status == "success": + logger.info( + result, + extra={"title": "SYSTEM:", "title_color": Fore.YELLOW}, + ) + elif result.status == "error": + logger.warning( + f"Command {command} returned an error: " + f"{result.error or result.reason}" + ) + + +def update_user_in_group_mode( + commands_result: list[ProposeActionResult], +) -> None: + """Prints the commands result. + + Args: + commands_result: list of results for all of the agents + """ + logger = logging.getLogger(__name__) + + print() + for command_result in commands_result: + logger.info( + f"Agent = {Fore.CYAN}{command_result.agent.role}{Style.RESET_ALL} " + ) + for command in command_result.commands: + logger.info( + f"COMMAND = {Fore.CYAN}{remove_ansi_escape(command.command)}{Style.RESET_ALL} " + f"ARGUMENTS = {Fore.CYAN}{command.args}{Style.RESET_ALL}", + extra={ + "title": "NEXT ACTION:", + "title_color": Fore.CYAN, + "preserve_color": True, + }, + ) diff --git a/autogpts/autogpt/autogpt/commands/create_agent.py b/autogpts/autogpt/autogpt/commands/create_agent.py new file mode 100644 index 00000000000..825e2b864cd --- /dev/null +++ b/autogpts/autogpt/autogpt/commands/create_agent.py @@ -0,0 +1,52 @@ +"""Commands to search the web with""" + +from __future__ import annotations + +from autogpt.command_decorator import command +from autogpt.agents.agent_member import AgentMember +from autogpt.core.utils.json_schema import JSONSchema +from autogpt.agents.agent_group import create_agent_member + +COMMAND_CATEGORY = "create_agent" +COMMAND_CATEGORY_TITLE = "Create agent" + + +@command( + "create_agent", + "Create a new agent member for someone. The prompt for this step should be create someone to do this task.", + { + "prompt": JSONSchema( + type=JSONSchema.Type.STRING, + description="The description for agent that one to be created", + required=True, + ), + "role": JSONSchema( + type=JSONSchema.Type.STRING, + description="role of agent member that one be created", + required=True, + ), + "boss_id": JSONSchema( + type=JSONSchema.Type.STRING, + description="The agent who will be boss of new agent id", + required=True, + ), + }, +) +async def create_agent(prompt: str, role: str, agent: AgentMember, boss_id: str) -> str: + """Create new agent for some one + + Args: + prompt (str): The description for agent that one to be created. + role (str): role of agent member that one be created. + + """ + try: + group = agent.group + boss = group.members[boss_id] + await create_agent_member( + role=role, initial_prompt=prompt, boss=boss, llm_provider=agent.llm_provider + ) + return f"{role} created" + except Exception as ex: + print(ex) + return f"can't create {role}" diff --git a/autogpts/autogpt/autogpt/commands/create_task.py b/autogpts/autogpt/autogpt/commands/create_task.py new file mode 100644 index 00000000000..265b73d4032 --- /dev/null +++ b/autogpts/autogpt/autogpt/commands/create_task.py @@ -0,0 +1,65 @@ +"""Commands to search the web with""" + +from __future__ import annotations +from datetime import datetime +from typing import Optional +import uuid + +from autogpt.command_decorator import command +from autogpt.core.utils.json_schema import JSONSchema +from autogpt.agents.agent_member import AgentMember, AgentTask, AgentTaskStatus + +COMMAND_CATEGORY = "create_task" +COMMAND_CATEGORY_TITLE = "Create task" + + +@command( + "create_task", + "Create new task for yourself or one of your members. Show the assignee " + "by agent_id of yourself or your members. the task description should be matched " + "with the assignee description. If you can't find someone for this create or hire a new agent for this one.", + { + "task": JSONSchema( + type=JSONSchema.Type.STRING, + description="The description for task that will be created", + required=True, + ), + "agent_id": JSONSchema( + type=JSONSchema.Type.STRING, + description="The id of agent will be the owner of this task", + required=True, + ), + "parent_task_id": JSONSchema( + type=JSONSchema.Type.STRING, + description="The task id that is cause of this task", + required=False, + ), + }, +) +async def create_task( + task: str, agent_id: str, agent: AgentMember, parent_task_id: Optional[str] = None +): + """Create new agent for some one + + Args: + task (str): The description of that task + agent_id (str): The id of the agent will be the owner of this task. + parent_task_id (str): The task id that is the cause of this task. + """ + try: + taskObject = AgentTask( + input=task, + status=AgentTaskStatus.INITIAL.value, + created_at=datetime.now(), + modified_at=datetime.now(), + task_id=str(uuid.uuid4()), + sub_tasks=[], + parent_task_id=parent_task_id, + ) + print(agent.group.members) + owner_of_task = agent.group.members[agent_id] + owner_of_task.tasks.append(taskObject) + return f"{task} created" + except Exception as ex: + print(ex) + return f"can't create {task}" diff --git a/autogpts/autogpt/autogpt/commands/finish_task.py b/autogpts/autogpt/autogpt/commands/finish_task.py new file mode 100644 index 00000000000..6aa44b4702d --- /dev/null +++ b/autogpts/autogpt/autogpt/commands/finish_task.py @@ -0,0 +1,52 @@ +"""Commands to control the internal state of the program""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from autogpt.command_decorator import command +from autogpt.agents.utils.exceptions import TaskFinished +from autogpt.core.utils.json_schema import JSONSchema + +COMMAND_CATEGORY = "finish" +COMMAND_CATEGORY_TITLE = "Finish" + + +if TYPE_CHECKING: + from autogpt.agents.agent import Agent + + +logger = logging.getLogger(__name__) + + +@command( + "finish_task", + "Use this to shut down once you have completed your task," + " or when there are insurmountable problems that make it impossible" + " for you to finish your task.", + { + "reason": JSONSchema( + type=JSONSchema.Type.STRING, + description="A summary to the user of how the goals were accomplished", + required=True, + ), + "task_id": JSONSchema( + type=JSONSchema.Type.STRING, + description="The task id that is gone", + required=True, + ), + }, +) +def finish(reason: str, task_id: str, agent: Agent) -> None: + """ + A function that finish a task + + Parameters: + reason (str): A summary to the user of how the goals were accomplished. + task_id (str): The task id that is gone. + Returns: + A result string from create chat completion. A list of suggestions to + improve the code. + """ + raise TaskFinished(task_id, reason) diff --git a/autogpts/autogpt/autogpt/commands/request_agent.py b/autogpts/autogpt/autogpt/commands/request_agent.py new file mode 100644 index 00000000000..7e93a294331 --- /dev/null +++ b/autogpts/autogpt/autogpt/commands/request_agent.py @@ -0,0 +1,55 @@ +"""Commands to search the web with""" + +from __future__ import annotations + +from forge.sdk.model import TaskRequestBody +from autogpt.command_decorator import command +from autogpt.agents.agent_member import AgentMember +from autogpt.core.utils.json_schema import JSONSchema + +COMMAND_CATEGORY = "request_agent" +COMMAND_CATEGORY_TITLE = "Request an agent" + +@command( + "request_agent", + "Request a new agent member for someone. The prompt for this step should be create someone to do this task.", + { + "prompt": JSONSchema( + type=JSONSchema.Type.STRING, + description="The description for agent that one to be created", + required=True, + ), + "role": JSONSchema( + type=JSONSchema.Type.STRING, + description="Role of agent member that one be created", + required=True, + ), + "boss_id": JSONSchema( + type=JSONSchema.Type.STRING, + description="The agent id that is going to be the boss of new agent", + required=True, + ), + }, +) +async def request_agent(prompt: str, role:str, agent: AgentMember, boss_id: str) -> str: + """Request new agent for some one + + Args: + prompt (str): The description for agent that one to be created. + role (str): role of agent member that one be created. + boss_id (str): The agent id that is going to be the boss of new agent. + + """ + try: + if agent.recruiter != None: + await agent.recruiter.create_task(task_request=TaskRequestBody(input=f"hire someone with {role} and this prompt: {prompt} for agent with id {boss_id}")) + return f"create task for recruiter to hire {role}" + elif agent.boss != None: + await agent.boss.create_task(task_request=TaskRequestBody(input=f"hire someone with {role} and this prompt: {prompt} for agent with id {boss_id}")) + return f"create task for boss to hire {role}" + else: + raise Exception("We can't hire someone ") + except Exception as ex: + print(ex) + return f"can't create {role}" + diff --git a/autogpts/autogpt/autogpt_multi_agent.sh b/autogpts/autogpt/autogpt_multi_agent.sh new file mode 100755 index 00000000000..7f5b326e31f --- /dev/null +++ b/autogpts/autogpt/autogpt_multi_agent.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +function find_python_command() { + if command -v python3 &> /dev/null + then + echo "python3" + elif command -v python &> /dev/null + then + echo "python" + else + echo "Python not found. Please install Python." + exit 1 + fi +} + +PYTHON_CMD=$(find_python_command) + +if $PYTHON_CMD -c "import sys; sys.exit(sys.version_info < (3, 10))"; then + if ! $PYTHON_CMD scripts/check_requirements.py; then + echo + poetry install --without dev + echo + echo "Finished installing packages! Starting AutoGPT..." + echo + fi + poetry run multi_agent "$@" +else + echo "Python 3.10 or higher is required to run Auto GPT." +fi diff --git a/autogpts/autogpt/poetry.lock b/autogpts/autogpt/poetry.lock index 176be5e63cb..bde92b6d9d8 100644 --- a/autogpts/autogpt/poetry.lock +++ b/autogpts/autogpt/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "abstract-singleton" @@ -338,14 +338,11 @@ uvicorn = "^0.23.2" webdriver-manager = "^4.0.1" [package.extras] -benchmark = ["agbenchmark @ git+https://github.com/Significant-Gravitas/AutoGPT.git#subdirectory=benchmark"] +benchmark = ["agbenchmark @ file:///home/mehdi/all/repositories/github.com/AutoGPT/benchmark"] [package.source] -type = "git" -url = "https://github.com/Significant-Gravitas/AutoGPT.git" -reference = "ab05b7ae70754c063909" -resolved_reference = "ab05b7ae70754c06390982d237d86dc7290cd1aa" -subdirectory = "autogpts/forge" +type = "directory" +url = "../forge" [[package]] name = "backoff" @@ -1527,26 +1524,28 @@ files = [ [[package]] name = "curl-cffi" -version = "0.5.10" +version = "0.6.2" description = "libcurl ffi bindings for Python, with impersonation support" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "curl_cffi-0.5.10-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:892603dab5e56fb72bfff7ae969136138971f63f63defe98232e1ec55cb0f1c6"}, - {file = "curl_cffi-0.5.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9937b8e13b1a6963c63e155b6621ec74649965105efedb919bc226fe731861cc"}, - {file = "curl_cffi-0.5.10-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b537595b9610a4dd0927c09823925b4e32b1ce0fd04385bfc5bb72ab830720e6"}, - {file = "curl_cffi-0.5.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b2bc8822d23415f6533c8b750475e9bbc76764025fe1dcb5866dc033607fd7b"}, - {file = "curl_cffi-0.5.10-cp37-abi3-win_amd64.whl", hash = "sha256:f9a1874b860c4e8db49bdfd9b9d4dc39999a1397d271ec78624c35c838e9e92a"}, - {file = "curl_cffi-0.5.10.tar.gz", hash = "sha256:55bac4b73e2d80ceeaabea33270fc8ca6ace594128a46710242f2e688b4f8bfc"}, + {file = "curl_cffi-0.6.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:23b8a2872b160718c04b06b1f8aa4fb1a2f4f94bce7040493515e081a27cad19"}, + {file = "curl_cffi-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ad3c1cf5360810825ec4bc3da425f26ee4098878a615dab9d309a99afd883ba9"}, + {file = "curl_cffi-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d01de6ed737ad1924aaa0198195b9020c38e77ce90ea3d72b9eacf4938c7adf"}, + {file = "curl_cffi-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37e513cc149d024a2d625e202f2cc9d4423d2937343ea2e06f797d99779e62dc"}, + {file = "curl_cffi-0.6.2-cp38-abi3-win32.whl", hash = "sha256:12e829af97cbf7c1d5afef177e786f6f404ddf163b08897a1ed087cadbeb4837"}, + {file = "curl_cffi-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:3791b7a9ae4cb1298165300f2dc2d60a86779f055570ae83163fc2d8a74bf714"}, + {file = "curl_cffi-0.6.2.tar.gz", hash = "sha256:9ee519e960b5fc6e0bbf13d0ecba9ce5f6306cb929354504bf03cc30f59a8f63"}, ] [package.dependencies] +certifi = "*" cffi = ">=1.12.0" [package.extras] build = ["cibuildwheel", "wheel"] -dev = ["autoflake (==1.4)", "black (==22.8.0)", "coverage (==6.4.1)", "cryptography (==38.0.3)", "flake8 (==6.0.0)", "flake8-bugbear (==22.7.1)", "flake8-pie (==0.15.0)", "httpx (==0.23.1)", "isort (==5.10.1)", "mypy (==0.971)", "pytest (==7.1.2)", "pytest-asyncio (==0.19.0)", "pytest-trio (==0.7.0)", "trio (==0.21.0)", "trio-typing (==0.7.0)", "trustme (==0.9.0)", "types-certifi (==2021.10.8.2)", "uvicorn (==0.18.3)"] -test = ["cryptography (==38.0.3)", "httpx (==0.23.1)", "pytest (==7.1.2)", "pytest-asyncio (==0.19.0)", "pytest-trio (==0.7.0)", "trio (==0.21.0)", "trio-typing (==0.7.0)", "trustme (==0.9.0)", "types-certifi (==2021.10.8.2)", "uvicorn (==0.18.3)"] +dev = ["autoflake (==1.4)", "coverage (==6.4.1)", "cryptography (==38.0.3)", "flake8 (==6.0.0)", "flake8-bugbear (==22.7.1)", "flake8-pie (==0.15.0)", "httpx (==0.23.1)", "mypy (==0.971)", "nest-asyncio (==1.6.0)", "pytest (==7.1.2)", "pytest-asyncio (==0.19.0)", "pytest-trio (==0.7.0)", "ruff (==0.1.14)", "trio (==0.21.0)", "trio-typing (==0.7.0)", "trustme (==0.9.0)", "types-certifi (==2021.10.8.2)", "uvicorn (==0.18.3)", "websockets (==11.0.3)"] +test = ["cryptography (==38.0.3)", "fastapi (==0.100.0)", "httpx (==0.23.1)", "nest-asyncio (==1.6.0)", "proxy.py (==2.4.3)", "pytest (==7.1.2)", "pytest-asyncio (==0.19.0)", "pytest-trio (==0.7.0)", "python-multipart (==0.0.6)", "trio (==0.21.0)", "trio-typing (==0.7.0)", "trustme (==0.9.0)", "types-certifi (==2021.10.8.2)", "uvicorn (==0.18.3)", "websockets (==11.0.3)"] [[package]] name = "cycler" @@ -4449,18 +4448,18 @@ grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "playsound" @@ -5319,6 +5318,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -7248,4 +7248,4 @@ benchmark = ["agbenchmark"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f693821e204aaf64d946ac45d7c0e447f02c2a3f173f939dac2fcc8e797a142f" +content-hash = "294c758260913f103492e9f6ff7583329268a7828991500d8bd3ecf26d5117f0" diff --git a/autogpts/autogpt/pyproject.toml b/autogpts/autogpt/pyproject.toml index 919e1101815..b1405eb030f 100644 --- a/autogpts/autogpt/pyproject.toml +++ b/autogpts/autogpt/pyproject.toml @@ -17,14 +17,15 @@ packages = [{ include = "autogpt" }] [tool.poetry.scripts] autogpt = "autogpt.app.cli:cli" +multi_agent = "autogpt.app.cli:run_multi_agent" serve = "autogpt.app.cli:serve" [tool.poetry.dependencies] python = "^3.10" auto-gpt-plugin-template = {git = "https://github.com/Significant-Gravitas/Auto-GPT-Plugin-Template", rev = "0.1.0"} -# autogpt-forge = { path = "../forge" } -autogpt-forge = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", rev = "ab05b7ae70754c063909", subdirectory = "autogpts/forge"} +autogpt-forge = { path = "../forge" } +# autogpt-forge = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", rev = "ab05b7ae70754c063909", subdirectory = "autogpts/forge"} beautifulsoup4 = "^4.12.2" boto3 = "^1.33.6" charset-normalizer = "^3.1.0" diff --git a/autogpts/autogpt/tests/integration/test_agent_group.py b/autogpts/autogpt/tests/integration/test_agent_group.py new file mode 100644 index 00000000000..b5a9c2122d0 --- /dev/null +++ b/autogpts/autogpt/tests/integration/test_agent_group.py @@ -0,0 +1,64 @@ +import asyncio +import logging +import uuid +import requests +import pytest +from unittest.mock import patch +from autogpt.agents.agent_group import AgentGroup, create_agent_member +from autogpt.core.resource.model_providers.openai import OPEN_AI_CHAT_MODELS, _tool_calls_compat_extract_calls +from autogpt.core.resource.model_providers.schema import ChatModelResponse +from forge.sdk.model import TaskRequestBody +from autogpt.core.resource.model_providers.schema import AssistantChatMessage +logging.basicConfig( + level=logging.DEBUG) +logger = logging.getLogger(__name__) + +async def run_tasks(agents): + for agent in agents: + asyncio.create_task(agent.run_tasks()) + +async def main(): + ceo = await create_agent_member( + role="ceo", + initial_prompt="you are ceo of a software game company" + ) + + hr_lead = await create_agent_member( + role="hr_lead", + initial_prompt="you are hr_lead of a company You'll recruite agents when we need it", + boss=ceo, + create_agent=True + ) + + ceo.recruiter = hr_lead + + agentGroup = AgentGroup( + leader=ceo + ) + + await agentGroup.create_task(TaskRequestBody(input="create best shooter game in the world", additional_input={})) + + await run_tasks([ceo, hr_lead]) + + while True: + await asyncio.sleep(1) + running_tasks = [task for task in asyncio.all_tasks() if not task.done()] + if not running_tasks: + break +def empty_file(file_path): + try: + with open(file_path, 'w') as f: + f.truncate(0) + print(f"The file '{file_path}' has been emptied successfully.") + except Exception as e: + print(f"An error occurred: {e}") + +@pytest.mark.asyncio +@pytest.mark.vcr("/home/mahdi/all/repositories/github.com/autogpt/AutoGPT/autogpts/autogpt/tests/vcr_cassettes/test_agent_group_happy_scenario/test_agent_group_happy_scenario.yaml") +async def test_agent_group_happy_scenario(): + empty_file("/home/mahdi/all/repositories/github.com/autogpt/AutoGPT/autogpts/autogpt/agetn_group.db") + expected_uuids_for_tasks = ['11111111-1111-1111-1111-111111111121', '11111111-1111-1111-1111-111111111122','11111111-1111-1111-1111-111111111123','11111111-1111-1111-1111-111111111124'] + expected_uuids_for_agents = ['33333333-3333-3333-3333-333333333331', '33333333-3333-3333-3333-333333333332'] + with patch('forge.sdk.db.uuid4', side_effect=expected_uuids_for_tasks): + with patch('autogpt.agents.agent_member.uuid4', side_effect=expected_uuids_for_agents): + await main() diff --git a/tests/vcr_cassettes/test_agent_group_happy_scenario/test_agent_group_happy_scenario.yaml b/tests/vcr_cassettes/test_agent_group_happy_scenario/test_agent_group_happy_scenario.yaml new file mode 100644 index 00000000000..122e33569dd --- /dev/null +++ b/tests/vcr_cassettes/test_agent_group_happy_scenario/test_agent_group_happy_scenario.yaml @@ -0,0 +1,834 @@ +interactions: +- request: + body: '{"messages": [{"content": "Your job is to respond to a user-defined task, + given in triple quotes, by invoking the `create_agent` function to generate + an autonomous agent to complete the task. You should supply a role-based name + for the agent (_GPT), an informative description for what the agent does, and + 1 to 5 directives in each of the categories Best Practices and Constraints, + that are optimally aligned with the successful completion of its assigned task.\n\nExample + Input:\n\"\"\"Help me with marketing my business\"\"\"\n\nExample Call:\n```\n[\n {\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"create_agent\",\n \"arguments\": + {\n \"name\": \"CMOGPT\",\n \"description\": \"a + professional digital marketer AI that assists Solopreneurs in growing their + businesses by providing world-class expertise in solving marketing problems + for SaaS, content products, agencies, and more.\",\n \"directives\": + {\n \"best_practices\": [\n \"Engage + in effective problem-solving, prioritization, planning, and supporting execution + to address your marketing needs as your virtual Chief Marketing Officer.\",\n \"Provide + specific, actionable, and concise advice to help you make informed decisions + without the use of platitudes or overly wordy explanations.\",\n \"Identify + and prioritize quick wins and cost-effective campaigns that maximize results + with minimal time and budget investment.\",\n \"Proactively + take the lead in guiding you and offering suggestions when faced with unclear + information or uncertainty to ensure your marketing strategy remains on track.\"\n ],\n \"constraints\": + [\n \"Do not suggest illegal or unethical plans or strategies.\",\n \"Take + reasonable budgetary limits into account.\"\n ]\n }\n }\n }\n }\n]\n```", + "role": "system"}, {"content": "\"\"\"you are ceo of a software game company\"\"\"", + "role": "user"}], "model": "gpt-4-turbo-preview", "tool_choice": {"function": + {"name": "create_agent"}, "type": "function"}, "tools": [{"function": {"description": + "Create a new autonomous AI agent to complete a given task.", "name": "create_agent", + "parameters": {"properties": {"description": {"description": "An informative + one sentence description of what the AI agent does", "type": "string"}, "directives": + {"properties": {"best_practices": {"description": "One to five highly effective + best practices that are optimally aligned with the completion of the given task", + "items": {"type": "string"}, "maxItems": 5, "minItems": 1, "type": "array"}, + "constraints": {"description": "One to five reasonable and efficacious constraints + that are optimally aligned with the completion of the given task", "items": + {"type": "string"}, "maxItems": 5, "minItems": 1, "type": "array"}}, "required": + ["best_practices", "constraints"], "type": "object"}, "name": {"description": + "A short role-based name for an autonomous agent.", "type": "string"}}, "required": + ["name", "description", "directives"], "type": "object"}}, "type": "function"}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3299' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: ' { "id": "chatcmpl-abc123", "object": "chat.completion", "created": + 1677858242, "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "[{\"type\":\"function\",\"function\":{\"name\":\"create_agent\",\"arguments\":{\"name\":\"CEOGPT\",\"description\":\"An + autonomous CEO AI tailored for managing a software game company. It provides + strategic direction, fosters innovation, and ensures operational efficiency + to drive the companys success.\",\"directives\":{\"best_practices\":[\"Foster + a culture of creativity, innovation, and collaboration within the company to + continually enhance game development and user experience.\",\"Develop a comprehensive + market analysis strategy to understand target audiences, industry trends, and + competitors, ensuring informed decision-making.\",\"Implement agile development + methodologies to iterate quickly, respond to market changes, and deliver high-quality + games on time and within budget.\",\"Leverage data analytics and user feedback + to refine game mechanics, optimize monetization strategies, and enhance player + engagement.\",\"Establish strong partnerships with key stakeholders, including + game developers, publishers, and platform providers, to expand distribution + channels and reach new markets.\"],\"constraints\":[\"Adhere to industry regulations + and standards, ensuring compliance with legal requirements and ethical practices + in all aspects of game development and publishing.\",\"Maintain a balanced approach + between creative vision and financial sustainability, avoiding excessive risk-taking + that could jeopardize the companys long-term viability.\"]}}}}]", "tool_calls":[{"id":"random_id","type":"function","function":{"name":"create_agent","arguments":"{\"name\":\"CEOGPT\",\"description\":\"An + autonomous CEO AI tailored for managing a software game company. It provides + strategic direction, fosters innovation, and ensures operational efficiency + to drive the companys success.\",\"directives\":{\"best_practices\":[\"Foster + a culture of creativity, innovation, and collaboration within the company to + continually enhance game development and user experience.\",\"Develop a comprehensive + market analysis strategy to understand target audiences, industry trends, and + competitors, ensuring informed decision-making.\",\"Implement agile development + methodologies to iterate quickly, respond to market changes, and deliver high-quality + games on time and within budget.\",\"Leverage data analytics and user feedback + to refine game mechanics, optimize monetization strategies, and enhance player + engagement.\",\"Establish strong partnerships with key stakeholders, including + game developers, publishers, and platform providers, to expand distribution + channels and reach new markets.\"],\"constraints\":[\"Adhere to industry regulations + and standards, ensuring compliance with legal requirements and ethical practices + in all aspects of game development and publishing.\",\"Maintain a balanced approach + between creative vision and financial sustainability, avoiding excessive risk-taking + that could jeopardize the companys long-term viability.\"]}}"}}] }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] } ' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85c7f8681d440a5f-AMS + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 28 Feb 2024 10:26:37 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=MLY4hTR.IFDGIdNL6l.o8WamKFhganMDeIwdBKkD43g-1709115997-1.0-AQxZ34/9EiS3AxqSO3HceYc2wjWk7UDkYOH+j5CWDmGip9i1HfOV8MSjVEN49W8tgbaZtZfmw1uWkUMgLXxLqEs=; + path=/; expires=Wed, 28-Feb-24 10:56:37 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=Rv7Zavx4VnfUuJ5ukoYp8eLdnNR5DDfPC4t_6.Xk77E-1709115997602-0.0-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_0a522190af26733adc03a08b837e6d5b + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "Your job is to respond to a user-defined task, + given in triple quotes, by invoking the `create_agent` function to generate + an autonomous agent to complete the task. You should supply a role-based name + for the agent (_GPT), an informative description for what the agent does, and + 1 to 5 directives in each of the categories Best Practices and Constraints, + that are optimally aligned with the successful completion of its assigned task.\n\nExample + Input:\n\"\"\"Help me with marketing my business\"\"\"\n\nExample Call:\n```\n[\n {\n \"type\": + \"function\",\n \"function\": {\n \"name\": \"create_agent\",\n \"arguments\": + {\n \"name\": \"CMOGPT\",\n \"description\": \"a + professional digital marketer AI that assists Solopreneurs in growing their + businesses by providing world-class expertise in solving marketing problems + for SaaS, content products, agencies, and more.\",\n \"directives\": + {\n \"best_practices\": [\n \"Engage + in effective problem-solving, prioritization, planning, and supporting execution + to address your marketing needs as your virtual Chief Marketing Officer.\",\n \"Provide + specific, actionable, and concise advice to help you make informed decisions + without the use of platitudes or overly wordy explanations.\",\n \"Identify + and prioritize quick wins and cost-effective campaigns that maximize results + with minimal time and budget investment.\",\n \"Proactively + take the lead in guiding you and offering suggestions when faced with unclear + information or uncertainty to ensure your marketing strategy remains on track.\"\n ],\n \"constraints\": + [\n \"Do not suggest illegal or unethical plans or strategies.\",\n \"Take + reasonable budgetary limits into account.\"\n ]\n }\n }\n }\n }\n]\n```", + "role": "system"}, {"content": "\"\"\"you are hr_lead of a company You''ll recruite + agents when we need it\"\"\"", "role": "user"}], "model": "gpt-4-turbo-preview", + "tool_choice": {"function": {"name": "create_agent"}, "type": "function"}, "tools": + [{"function": {"description": "Create a new autonomous AI agent to complete + a given task.", "name": "create_agent", "parameters": {"properties": {"description": + {"description": "An informative one sentence description of what the AI agent + does", "type": "string"}, "directives": {"properties": {"best_practices": {"description": + "One to five highly effective best practices that are optimally aligned with + the completion of the given task", "items": {"type": "string"}, "maxItems": + 5, "minItems": 1, "type": "array"}, "constraints": {"description": "One to five + reasonable and efficacious constraints that are optimally aligned with the completion + of the given task", "items": {"type": "string"}, "maxItems": 5, "minItems": + 1, "type": "array"}}, "required": ["best_practices", "constraints"], "type": + "object"}, "name": {"description": "A short role-based name for an autonomous + agent.", "type": "string"}}, "required": ["name", "description", "directives"], + "type": "object"}}, "type": "function"}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '3328' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "[{\"type\":\"function\",\"function\":{\"name\":\"create_agent\",\"arguments\":{\"name\":\"HRLead_GPT\" + ,\"description\":\"An HR lead AI empowered to efficiently manage the recruitment + process, streamline hiring procedures, and ensure the companys staffing needs + are met promptly and effectively.\" ,\"directives\":{\"best_practices\":[\"Employ + systematic and fair evaluation methods to assess candidate qualifications, skills, + and cultural fit.\",\"Leverage data-driven insights to optimize the recruitment + pipeline, enhance candidate experience, and reduce time-to-hire metrics.\",\"Maintain + transparent communication with hiring managers and stakeholders regarding recruitment + progress, challenges, and opportunities.\",\"Implement proactive talent sourcing + strategies to build robust talent pools and mitigate potential skill gaps and + recruitment bottlenecks.\",\"Regularly review and refine recruitment processes + to adapt to changing market trends, technological advancements, and organizational + needs.\"],\"constraints\":[\"Adhere strictly to legal and regulatory requirements + governing hiring practices, ensuring non-discrimination and equal opportunity + principles are upheld.\",\"Respect candidate privacy and confidentiality throughout + the recruitment process, safeguarding sensitive personal information.\",\"Exercise + discretion and professionalism when handling sensitive or confidential candidate + data, refraining from unauthorized disclosure or misuse.\",\"Ensure adherence + to established budgetary allocations for recruitment activities , optimizing + resource utilization and cost-effectiveness.\",\"Avoid over-reliance on automated + decision-making processes, maintaining human oversight and intervention where + necessary to prevent bias or errors.\"]}}}}]", "tool_calls":[{"id":"random_id_2","type":"function","function":{"name":"create_agent","arguments":"{\"name\":\"HRLead_GPT\",\"description\":\"An + HR lead AI empowered to efficiently manage the recruitment process, streamline + hiring procedures, and ensure the companys staffing needs are met promptly and + effectively.\",\"directives\":{\"best_practices\":[\"Employ systematic and fair + evaluation methods to assess candidate qualifications, skills, and cultural + fit.\",\"Leverage data-driven insights to optimize the recruitment pipeline, + enhance candidate experience, and reduce time-to-hire metrics.\",\"Maintain + transparent communication with hiring managers and stakeholders regarding recruitment + progress, challenges, and opportunities.\",\"Implement proactive talent sourcing + strategies to build robust talent pools and mitigate potential skill gaps and + recruitment bottlenecks.\",\"Regularly review and refine recruitment processes + to adapt to changing market trends, technological advancements, and organizational + needs.\"],\"constraints\":[\"Adhere strictly to legal and regulatory requirements + governing hiring practices, ensuring non-discrimination and equal opportunity + principles are upheld.\",\"Respect candidate privacy and confidentiality throughout + the recruitment process, safeguarding sensitive personal information.\",\"Exercise + discretion and professionalism when handling sensitive or confidential candidate + data, refraining from unauthorized disclosure or misuse.\",\"Ensure adherence + to established budgetary allocations for recruitment activities, optimizing + resource utilization and cost-effectiveness.\",\"Avoid over-reliance on automated + decision-making processes, maintaining human oversight and intervention where + necessary to prevent bias or errors.\"]}}"}}]}, "logprobs": null, "finish_reason": + "stop", "index": 0 } ] } ' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85c91c445a125c49-AMS + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 28 Feb 2024 13:45:52 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=yY3T7KV58WZAleTXLYU4B1NiQIvAesrVSrrECWXZ4N0-1709127952-1.0-AUqfj+INOB2PdUciWO12/Vto/wUeb+zFl73MNMFZtxRfnCJ4dZjiMfXUhnzH6z6B4zqs1e92q2JaYyJ11KUtdE0=; + path=/; expires=Wed, 28-Feb-24 14:15:52 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=.Y5rAOMTuA.demTIhPTy1gL5v9jAJmIksxrB6P.kIjg-1709127952200-0.0-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_cf61793fa0e7e1769af3cf1abc9ccdd8 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "You are CEOGPT, An autonomous CEO AI tailored + for managing a software game company. It provides strategic direction, fosters + innovation, and ensures operational efficiency to drive the companys success + with agent_id 33333333-3333-3333-3333-333333333331.\n\nYour decisions must always + be made independently without seeking user assistance. Play to your strengths + as an LLM and pursue simple strategies with no legal complications.\n\nThe OS + you are running on is: Ubuntu 22.04.3 LTS\n\n## Members detail:\n{agent_id: + 33333333-3333-3333-3333-333333333332, description: An HR lead AI empowered to + efficiently manage the recruitment process, streamline hiring procedures, and + ensure the companys staffing needs are met promptly and effectively}\n\n\n## + Constraints\nYou operate within the following constraints:\n1. Adhere to industry + regulations and standards, ensuring compliance with legal requirements and ethical + practices in all aspects of game development and publishing.\n2. Maintain a + balanced approach between creative vision and financial sustainability, avoiding + excessive risk-taking that could jeopardize the companys long-term viability.\n\n## + Resources\nYou can leverage access to the following resources:\n\n\n## Commands\nThese + are the ONLY commands you can use. Any action you perform must be possible through + list of these commands:\n1. execute_python_code: Executes the given Python code + inside a single-use Docker container with access to your workspace folder. Params: + (code: string)\n2. execute_python_file: Execute an existing Python file inside + a single-use Docker container with access to your workspace folder. Params: + (filename: string, args?: Array)\n3. list_folder: List the items in + a folder. Params: (folder: string)\n4. open_file: Opens a file for editing or + continued viewing; creates it if it does not exist yet. Note: If you only need + to read or write a file once, use `write_to_file` instead.. Params: (file_path: + string)\n5. open_folder: Open a folder to keep track of its content. Params: + (path: string)\n6. read_file: Read an existing file. Params: (filename: string)\n7. + write_file: Write a file, creating it if necessary. If the file exists, it is + overwritten.. Params: (filename: string, contents: string)\n8. web_search: Searches + the web. Params: (query: string)\n9. read_webpage: Read a webpage, and extract + specific information from it. You must specify either topics_of_interest, a + question, or get_raw_content.. Params: (url: string, topics_of_interest?: Array, + question?: string, get_raw_content?: boolean)\n10. finish_task: Use this to + shut down once you have completed your task, or when there are insurmountable + problems that make it impossible for you to finish your task.. Params: (reason: + string, task_id: string)\n11. hire_agent: Hire a new agent member for someone. + The prompt for this step should be create someone to do this task.. Params: + (prompt: string, role: string, boss_id: string)\n12. create_task: Create new + task for yourself or one of your members. Show the assignee by agent_id of yourself + or your members. the task description should be matched with the assignee description. + If you can''t find someone for this create or hire a new agent for this one.. + Params: (task: string, agent_id: string, father_task_id?: string)\n\n## Best + practices\n1. Foster a culture of creativity, innovation, and collaboration + within the company to continually enhance game development and user experience.\n2. + Develop a comprehensive market analysis strategy to understand target audiences, + industry trends, and competitors, ensuring informed decision-making.\n3. Implement + agile development methodologies to iterate quickly, respond to market changes, + and deliver high-quality games on time and within budget.\n4. Leverage data + analytics and user feedback to refine game mechanics, optimize monetization + strategies, and enhance player engagement.\n5. Establish strong partnerships + with key stakeholders, including game developers, publishers, and platform providers, + to expand distribution channels and reach new markets.\n\n## tasks\nThe user + will specify tasks for you to execute,in triple quotes, in the next message. + Your job is to use the command list to do things to make progress in tasks. + complete the task while following your directives as given above, and terminate + when your task is done. It''s good practice to hire or create other agents to + break tasks and make them better.", "role": "system"}, {"content": "\"\"\"[\n {\n \"task_id\": + \"11111111-1111-1111-1111-111111111121\",\n \"task_detail\": \"create + best shooter game in the world\",\n \"status\": \"DOING\",\n \"sub_tasks\": + []\n }\n]\"\"\"", "role": "user"}, {"content": "Respond with pure JSON. The + JSON object should be compatible with the TypeScript type `Response` from the + following:\ninterface Response {\nthoughts: {\n// Relevant observations from + your last action (if any)\nobservations: string;\n// Thoughts\ntext: string;\nreasoning: + string;\n// Constructive self-criticism\nself_criticism: string;\n// Short markdown-style + bullet list that conveys the long-term plan\nplan: string;\n// Summary of thoughts, + to say to user\nspeak: string;\n};\n// list of commands for tasks whose status + is DOING. The structure should be like this list[dict[name:str, args:object, + task_id: str ]] for complete list of tasks. Each command should be assign to + a task wholse status is DOING by task_id field. You need to choose some commands + to progress tasks. your list shouldn''t be empty\ncommands: Array;\n// check + all tasks that I sent you (not sub_tasks part) with CHECKING status. In this + part you should say status of tasks that I sent(not sub_tasks parts). send them + with these statuses: REJECTED, DONE with this structure list[dict[task_id: str, + status: str, reason: str?]] (REJECTED: This status shows that the task need + to be imporved by the owner of task and agent member needs to work on it more. + need to say reject reason in reason fieldDONE: This status shows that the result + of task is OK and the task is done)\ntasks: Array;\n}", "role": "system"}, {"content": + "Determine list of commands to use next based on the given goals and the progress + you have made so far, and respond using the JSON schema specified previously:", + "role": "user"}], "model": "gpt-4-turbo-preview"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '6376' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "{\n \"thoughts\": {\n \"observations\": \"\",\n \"text\": + \"To progress in creating the best shooter game in the world, we need to focus + on key development tasks, prioritize feature implementation, and ensure efficient + resource utilization.\",\n \"reasoning\": \"By following a systematic + approach and leveraging available resources effectively, we can expedite the + game development process and deliver a high-quality product.\",\n \"self_criticism\": + \"We need to ensure that our development efforts remain aligned with the overarching + goal of creating the best shooter game while maintaining a balance between innovation + and feasibility.\",\n \"plan\": \"- Implement core gameplay mechanics\\n- + Design immersive environments\\n- Develop intuitive controls\\n- Test and iterate + for refinement\\n- Optimize performance and user experience\",\n \"speak\": + \"Our focus is on advancing the development of the best shooter game. We will + concentrate on core mechanics, environment design, controls, testing, and optimization.\"\n },\n \"commands\": + [\n {\n \"name\": \"hire_agent\",\n \"args\": {\n \"prompt\": + \"hire a CTO for our company\",\n \"role\": \"CTO\"\n , \"boss_id\": + \"33333333-3333-3333-3333-333333333331\"\n },\n \"task_id\": + \"11111111-1111-1111-1111-111111111121\"\n },\n {\n \"name\": + \"create_task\",\n \"args\": {\n \"task\": \"give + me a structure of what roles we need in our company\",\n \"agent_id\": + \"33333333-3333-3333-3333-333333333332\",\n \"father_task_id\": + \"11111111-1111-1111-1111-111111111121\"\n },\n \"task_id\": + \"11111111-1111-1111-1111-111111111121\"\n },\n {\n \"name\": + \"create_task\",\n \"args\": {\n \"task\": \"Follow + up tasks that I gave to my members\",\n \"agent_id\": \"33333333-3333-3333-3333-333333333331\",\n \"father_task_id\": + \"11111111-1111-1111-1111-111111111121\"\n },\n \"task_id\": + \"11111111-1111-1111-1111-111111111121\"\n }\n ], \"tasks\":[]}" }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] } ' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85f8a8b43fdf1959-FRA + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 05 Mar 2024 08:15:35 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=kgSxHQxZcZaRGMx2PngAkp_U.P7URfUV2EDGerqbN1I-1709626535-1.0.1.1-AMzKeFxktTz9HHgWZyYriCSfZRdZkPVY9GXctre4y0fCTJGy3LHgRtQ227gRmHXxY82Vz1OJ1Mu7UzEcEmd3JA; + path=/; expires=Tue, 05-Mar-24 08:45:35 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=QC2YgxZL4Hd2iDkpszXqABLBSnB8zu.9i7wXzUExNVU-1709626535238-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_821b37a402ea691444e58ac9e2a59269 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "The user is going to give you a text enclosed + in triple quotes. The text represents an action, the reason for its execution, + and its result. Condense the action taken and its result into one line. Preserve + any specific factual information gathered by the action.", "role": "system"}, + {"content": "\"\"\"Executed `hire_agent(prompt=''hire a CTO for our company'', + role=''CTO'', boss_id=''33333333-3333-3333-3333-333333333331'')`\n- **Reasoning:** + \"\"\n- **Status:** `success`\n- **Output:** create task for recruiter to hire + CTO\"\"\"", "role": "user"}], "model": "gpt-3.5-turbo-0125", "temperature": + 0.5}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '645' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "Action: Hired a Chief Technology Officer (CTO) for the company. \n Result: Successfully created a task for the recruiter to hire a CTO." }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] }' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85f8ccc1cae1d584-CDG + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 05 Mar 2024 08:40:11 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=mo2z9bGeG_VJvec8bYrEN1GsV6P7vf20iA_ksnaElAc-1709628011-1.0.1.1-2N1v20YCmTz5qfob27B642kGkcWFbblkR4CcaRq0x0IHcAlsxC4zAui3Oc9QJcGeTTVVsrKjQ6YOq0ooJbB2FA; + path=/; expires=Tue, 05-Mar-24 09:10:11 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_7c5e507b22f6615219aa26cd83b1e264 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "The user is going to give you a text enclosed + in triple quotes. The text represents an action, the reason for its execution, + and its result. Condense the action taken and its result into one line. Preserve + any specific factual information gathered by the action.", "role": "system"}, + {"content": "\"\"\"Executed `create_task(task=''give me a structure of what + roles we need in our company'', agent_id=''33333333-3333-3333-3333-333333333332'', + father_task_id=''11111111-1111-1111-1111-111111111121'')`\n- **Reasoning:** + \"\"\n- **Status:** `success`\n- **Output:** None\"\"\"", "role": "user"}], + "model": "gpt-3.5-turbo-0125", "temperature": 0.5}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '685' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "\"\"\"Executed `create_task(task=''give me a structure of what + roles we need in our company'', agent_id=''33333333-3333-3333-3333-333333333332'', + father_task_id=''11111111-1111-1111-1111-111111111121'')`\n- **Reasoning:** + \"\"\n- **Status:** `success`\n- **Output:** None\"\"\"" }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] }' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85f93e5babde1e14-FRA + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 05 Mar 2024 09:57:45 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=tj0Sixl86vKFmdr3xw.qE1tpLaRx9NdYIkMLyflU_O4-1709632665-1.0.1.1-LrAOWw8MlLEt25GQAEJX56kQQQSsMiuCZYPHvBNZvGOKuraBfx6YUIRPjhuixqiL7ZxyTRFOmZ7I0mlhFWO4HQ; + path=/; expires=Tue, 05-Mar-24 10:27:45 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_a437636cfe3c109bebfd777264d5aede + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "You are HRLead_GPT, An HR lead AI empowered + to efficiently manage the recruitment process, streamline hiring procedures, + and ensure the companys staffing needs are met promptly and effectively with + agent_id 33333333-3333-3333-3333-333333333332.\n\nYour decisions must always + be made independently without seeking user assistance. Play to your strengths + as an LLM and pursue simple strategies with no legal complications.\n\nThe OS + you are running on is: Ubuntu 22.04.3 LTS\n\n## Members detail:\nyou don''t + have any member to assign task\n\n## Constraints\nYou operate within the following + constraints:\n1. Adhere strictly to legal and regulatory requirements governing + hiring practices, ensuring non-discrimination and equal opportunity principles + are upheld.\n2. Respect candidate privacy and confidentiality throughout the + recruitment process, safeguarding sensitive personal information.\n3. Exercise + discretion and professionalism when handling sensitive or confidential candidate + data, refraining from unauthorized disclosure or misuse.\n4. Ensure adherence + to established budgetary allocations for recruitment activities, optimizing + resource utilization and cost-effectiveness.\n5. Avoid over-reliance on automated + decision-making processes, maintaining human oversight and intervention where + necessary to prevent bias or errors.\n\n## Resources\nYou can leverage access + to the following resources:\n\n\n## Commands\nThese are the ONLY commands you + can use. Any action you perform must be possible through list of these commands:\n1. + execute_python_code: Executes the given Python code inside a single-use Docker + container with access to your workspace folder. Params: (code: string)\n2. execute_python_file: + Execute an existing Python file inside a single-use Docker container with access + to your workspace folder. Params: (filename: string, args?: Array)\n3. + list_folder: List the items in a folder. Params: (folder: string)\n4. open_file: + Opens a file for editing or continued viewing; creates it if it does not exist + yet. Note: If you only need to read or write a file once, use `write_to_file` + instead.. Params: (file_path: string)\n5. open_folder: Open a folder to keep + track of its content. Params: (path: string)\n6. read_file: Read an existing + file. Params: (filename: string)\n7. write_file: Write a file, creating it if + necessary. If the file exists, it is overwritten.. Params: (filename: string, + contents: string)\n8. web_search: Searches the web. Params: (query: string)\n9. + read_webpage: Read a webpage, and extract specific information from it. You + must specify either topics_of_interest, a question, or get_raw_content.. Params: + (url: string, topics_of_interest?: Array, question?: string, get_raw_content?: + boolean)\n10. finish_task: Use this to shut down once you have completed your + task, or when there are insurmountable problems that make it impossible for + you to finish your task.. Params: (reason: string, task_id: string)\n11. create_agent: + Create a new agent member for someone. The prompt for this step should be create + someone to do this task.. Params: (prompt: string, role: string, boss_id: string)\n12. + create_task: Create new task for yourself or one of your members. Show the assignee + by agent_id of yourself or your members. the task description should be matched + with the assignee description. If you can''t find someone for this create or + hire a new agent for this one.. Params: (task: string, agent_id: string, father_task_id?: + string)\n\n## Best practices\n1. Employ systematic and fair evaluation methods + to assess candidate qualifications, skills, and cultural fit.\n2. Leverage data-driven + insights to optimize the recruitment pipeline, enhance candidate experience, + and reduce time-to-hire metrics.\n3. Maintain transparent communication with + hiring managers and stakeholders regarding recruitment progress, challenges, + and opportunities.\n4. Implement proactive talent sourcing strategies to build + robust talent pools and mitigate potential skill gaps and recruitment bottlenecks.\n5. + Regularly review and refine recruitment processes to adapt to changing market + trends, technological advancements, and organizational needs.\n\n## tasks\nThe + user will specify tasks for you to execute,in triple quotes, in the next message. + Your job is to use the command list to do things to make progress in tasks. + complete the task while following your directives as given above, and terminate + when your task is done. It''s good practice to hire or create other agents to + break tasks and make them better.", "role": "system"}, {"content": "\"\"\"[\n {\n \"task_id\": + \"11111111-1111-1111-1111-111111111122\",\n \"task_detail\": \"hire someone + with CTO and this prompt: hire a CTO for our company for agent with id 33333333-3333-3333-3333-333333333331\",\n \"status\": + \"DOING\",\n \"sub_tasks\": []\n }\n]\"\"\"", "role": "user"}, {"content": + "Respond with pure JSON. The JSON object should be compatible with the TypeScript + type `Response` from the following:\ninterface Response {\nthoughts: {\n// Relevant + observations from your last action (if any)\nobservations: string;\n// Thoughts\ntext: + string;\nreasoning: string;\n// Constructive self-criticism\nself_criticism: + string;\n// Short markdown-style bullet list that conveys the long-term plan\nplan: + string;\n// Summary of thoughts, to say to user\nspeak: string;\n};\n// list + of commands for tasks whose status is DOING. The structure should be like this + list[dict[name:str, args:object, task_id: str ]] for complete list of tasks. + Each command should be assign to a task wholse status is DOING by task_id field. + You need to choose some commands to progress tasks. your list shouldn''t be + empty\ncommands: Array;\n// check all tasks that I sent you (not sub_tasks part) + with CHECKING status. In this part you should say status of tasks that I sent(not + sub_tasks parts). send them with these statuses: REJECTED, DONE with this structure + list[dict[task_id: str, status: str, reason: str?]] (REJECTED: This status shows + that the task need to be imporved by the owner of task and agent member needs + to work on it more. need to say reject reason in reason fieldDONE: This status + shows that the result of task is OK and the task is done)\ntasks: Array;\n}", + "role": "system"}, {"content": "Determine list of commands to use next based + on the given goals and the progress you have made so far, and respond using + the JSON schema specified previously:", "role": "user"}], "model": "gpt-4-turbo-preview"}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '6561' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "{\"thoughts\": {\"observations\": \"\",\"text\": \"\",\"reasoning\": \"\",\"self_criticism\": \"\",\"plan\": \"\",\"speak\": \"\"},\"commands\": [{\"name\": \"create_agent\",\"args\": {\"prompt\": \"hire a CTO for our company\",\"role\": \"CTO\",\"boss_id\": \"33333333-3333-3333-3333-333333333331\"},\"task_id\": \"11111111-1111-1111-1111-111111111122\"}],\"tasks\": []} + " }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] }' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85f9b3db4bb06f72-CDG + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 05 Mar 2024 11:17:57 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=HbtdlOd5EZG_PsSCT910yl3N0z1yQt77dFsVy1OrQzo-1709637477-1.0.1.1-WjIsjzwz0BCb18SraLK_DYIiOivnFTzzc4SsmH9i3EHWOqb26AKS5uZUcOVfUr4C77IlZiFYdI78vl3Y5_kNLw; + path=/; expires=Tue, 05-Mar-24 11:47:57 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=lGVLoeQ7sdDQi9s9evV.cAmrHBEei4cS0K9sQ.oanLA-1709637477810-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_f1cd282c66ece1877b8c46cee0103b5c + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"content": "The user is going to give you a text enclosed + in triple quotes. The text represents an action, the reason for its execution, + and its result. Condense the action taken and its result into one line. Preserve + any specific factual information gathered by the action.", "role": "system"}, + {"content": "\"\"\"Executed `create_task(task=''Follow up tasks that I gave + to my members'', agent_id=''33333333-3333-3333-3333-333333333331'', father_task_id=''11111111-1111-1111-1111-111111111121'')`\n- + **Reasoning:** \"\"\n- **Status:** `success`\n- **Output:** None\"\"\"", "role": + "user"}], "model": "gpt-3.5-turbo-0125", "temperature": 0.5}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '670' + content-type: + - application/json + host: + - api.openai.com + x-stainless-arch: + - x64 + x-stainless-async: + - async:asyncio + x-stainless-lang: + - python + x-stainless-os: + - Linux + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.12 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + content: '{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1677858242, + "model": "gpt-3.5-turbo-0613", "usage": { "prompt_tokens": 13, "completion_tokens": + 7, "total_tokens": 20 }, "choices": [ { "message": { "role": "assistant", "content": + "Action: Hired a Chief Technology Officer (CTO) for the company. \n Result: Successfully created a task for the recruiter to hire a CTO." }, "logprobs": + null, "finish_reason": "stop", "index": 0 } ] }' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 85f9b3db6b3f917a-FRA + Connection: + - keep-alive + Content-Length: + - '269' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 05 Mar 2024 11:17:57 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=DlMPyOqckvAPePxi7qn2VzWDyryAov1azIL54sZx.us-1709637477-1.0.1.1-ZNXkWcn2c.me8XjEWGOo_BqDg_M474gQEo2xrFUtutP7xntr1JR93Otbrkb3ntIcwfz2mw0PNKZTO3o.eMQa4w; + path=/; expires=Tue, 05-Mar-24 11:47:57 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + alt-svc: + - h3=":443"; ma=86400 + strict-transport-security: + - max-age=15724800; includeSubDomains + vary: + - Origin + x-request-id: + - req_8eb61183cf4514f1720cde7efcfc35f5 + http_version: HTTP/1.1 + status_code: 200 +version: 1