aboutsummaryrefslogtreecommitdiff
path: root/autogpts/autogpt/autogpt/core/agent/simple.py
diff options
context:
space:
mode:
Diffstat (limited to 'autogpts/autogpt/autogpt/core/agent/simple.py')
-rw-r--r--autogpts/autogpt/autogpt/core/agent/simple.py404
1 files changed, 404 insertions, 0 deletions
diff --git a/autogpts/autogpt/autogpt/core/agent/simple.py b/autogpts/autogpt/autogpt/core/agent/simple.py
new file mode 100644
index 000000000..ea113dafc
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/agent/simple.py
@@ -0,0 +1,404 @@
+import logging
+from datetime import datetime
+from pathlib import Path
+from typing import Any
+
+from pydantic import BaseModel
+
+from autogpt.core.ability import (
+ AbilityRegistrySettings,
+ AbilityResult,
+ SimpleAbilityRegistry,
+)
+from autogpt.core.agent.base import Agent
+from autogpt.core.configuration import Configurable, SystemConfiguration, SystemSettings
+from autogpt.core.memory import MemorySettings, SimpleMemory
+from autogpt.core.planning import PlannerSettings, SimplePlanner, Task, TaskStatus
+from autogpt.core.plugin.simple import (
+ PluginLocation,
+ PluginStorageFormat,
+ SimplePluginService,
+)
+from autogpt.core.resource.model_providers import (
+ CompletionModelFunction,
+ OpenAIProvider,
+ OpenAISettings,
+)
+from autogpt.core.workspace.simple import SimpleWorkspace, WorkspaceSettings
+
+
+class AgentSystems(SystemConfiguration):
+ ability_registry: PluginLocation
+ memory: PluginLocation
+ openai_provider: PluginLocation
+ planning: PluginLocation
+ workspace: PluginLocation
+
+
+class AgentConfiguration(SystemConfiguration):
+ cycle_count: int
+ max_task_cycle_count: int
+ creation_time: str
+ name: str
+ role: str
+ goals: list[str]
+ systems: AgentSystems
+
+
+class AgentSystemSettings(SystemSettings):
+ configuration: AgentConfiguration
+
+
+class AgentSettings(BaseModel):
+ agent: AgentSystemSettings
+ ability_registry: AbilityRegistrySettings
+ memory: MemorySettings
+ openai_provider: OpenAISettings
+ planning: PlannerSettings
+ workspace: WorkspaceSettings
+
+ def update_agent_name_and_goals(self, agent_goals: dict) -> None:
+ self.agent.configuration.name = agent_goals["agent_name"]
+ self.agent.configuration.role = agent_goals["agent_role"]
+ self.agent.configuration.goals = agent_goals["agent_goals"]
+
+
+class SimpleAgent(Agent, Configurable):
+ default_settings = AgentSystemSettings(
+ name="simple_agent",
+ description="A simple agent.",
+ configuration=AgentConfiguration(
+ name="Entrepreneur-GPT",
+ role=(
+ "An AI designed to autonomously develop and run businesses with "
+ "the sole goal of increasing your net worth."
+ ),
+ goals=[
+ "Increase net worth",
+ "Grow Twitter Account",
+ "Develop and manage multiple businesses autonomously",
+ ],
+ cycle_count=0,
+ max_task_cycle_count=3,
+ creation_time="",
+ systems=AgentSystems(
+ ability_registry=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.ability.SimpleAbilityRegistry",
+ ),
+ memory=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.memory.SimpleMemory",
+ ),
+ openai_provider=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route=(
+ "autogpt.core.resource.model_providers.OpenAIProvider"
+ ),
+ ),
+ planning=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.planning.SimplePlanner",
+ ),
+ workspace=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.workspace.SimpleWorkspace",
+ ),
+ ),
+ ),
+ )
+
+ def __init__(
+ self,
+ settings: AgentSystemSettings,
+ logger: logging.Logger,
+ ability_registry: SimpleAbilityRegistry,
+ memory: SimpleMemory,
+ openai_provider: OpenAIProvider,
+ planning: SimplePlanner,
+ workspace: SimpleWorkspace,
+ ):
+ self._configuration = settings.configuration
+ self._logger = logger
+ self._ability_registry = ability_registry
+ self._memory = memory
+ # FIXME: Need some work to make this work as a dict of providers
+ # Getting the construction of the config to work is a bit tricky
+ self._openai_provider = openai_provider
+ self._planning = planning
+ self._workspace = workspace
+ self._task_queue = []
+ self._completed_tasks = []
+ self._current_task = None
+ self._next_ability = None
+
+ @classmethod
+ def from_workspace(
+ cls,
+ workspace_path: Path,
+ logger: logging.Logger,
+ ) -> "SimpleAgent":
+ agent_settings = SimpleWorkspace.load_agent_settings(workspace_path)
+ agent_args = {}
+
+ agent_args["settings"] = agent_settings.agent
+ agent_args["logger"] = logger
+ agent_args["workspace"] = cls._get_system_instance(
+ "workspace",
+ agent_settings,
+ logger,
+ )
+ agent_args["openai_provider"] = cls._get_system_instance(
+ "openai_provider",
+ agent_settings,
+ logger,
+ )
+ agent_args["planning"] = cls._get_system_instance(
+ "planning",
+ agent_settings,
+ logger,
+ model_providers={"openai": agent_args["openai_provider"]},
+ )
+ agent_args["memory"] = cls._get_system_instance(
+ "memory",
+ agent_settings,
+ logger,
+ workspace=agent_args["workspace"],
+ )
+
+ agent_args["ability_registry"] = cls._get_system_instance(
+ "ability_registry",
+ agent_settings,
+ logger,
+ workspace=agent_args["workspace"],
+ memory=agent_args["memory"],
+ model_providers={"openai": agent_args["openai_provider"]},
+ )
+
+ return cls(**agent_args)
+
+ async def build_initial_plan(self) -> dict:
+ plan = await self._planning.make_initial_plan(
+ agent_name=self._configuration.name,
+ agent_role=self._configuration.role,
+ agent_goals=self._configuration.goals,
+ abilities=self._ability_registry.list_abilities(),
+ )
+ tasks = [Task.parse_obj(task) for task in plan.parsed_result["task_list"]]
+
+ # TODO: Should probably do a step to evaluate the quality of the generated tasks
+ # and ensure that they have actionable ready and acceptance criteria
+
+ self._task_queue.extend(tasks)
+ self._task_queue.sort(key=lambda t: t.priority, reverse=True)
+ self._task_queue[-1].context.status = TaskStatus.READY
+ return plan.parsed_result
+
+ async def determine_next_ability(self, *args, **kwargs):
+ if not self._task_queue:
+ return {"response": "I don't have any tasks to work on right now."}
+
+ self._configuration.cycle_count += 1
+ task = self._task_queue.pop()
+ self._logger.info(f"Working on task: {task}")
+
+ task = await self._evaluate_task_and_add_context(task)
+ next_ability = await self._choose_next_ability(
+ task,
+ self._ability_registry.dump_abilities(),
+ )
+ self._current_task = task
+ self._next_ability = next_ability.parsed_result
+ return self._current_task, self._next_ability
+
+ async def execute_next_ability(self, user_input: str, *args, **kwargs):
+ if user_input == "y":
+ ability = self._ability_registry.get_ability(
+ self._next_ability["next_ability"]
+ )
+ ability_response = await ability(**self._next_ability["ability_arguments"])
+ await self._update_tasks_and_memory(ability_response)
+ if self._current_task.context.status == TaskStatus.DONE:
+ self._completed_tasks.append(self._current_task)
+ else:
+ self._task_queue.append(self._current_task)
+ self._current_task = None
+ self._next_ability = None
+
+ return ability_response.dict()
+ else:
+ raise NotImplementedError
+
+ async def _evaluate_task_and_add_context(self, task: Task) -> Task:
+ """Evaluate the task and add context to it."""
+ if task.context.status == TaskStatus.IN_PROGRESS:
+ # Nothing to do here
+ return task
+ else:
+ self._logger.debug(f"Evaluating task {task} and adding relevant context.")
+ # TODO: Look up relevant memories (need working memory system)
+ # TODO: Eval whether there is enough information to start the task (w/ LLM).
+ task.context.enough_info = True
+ task.context.status = TaskStatus.IN_PROGRESS
+ return task
+
+ async def _choose_next_ability(
+ self,
+ task: Task,
+ ability_specs: list[CompletionModelFunction],
+ ):
+ """Choose the next ability to use for the task."""
+ self._logger.debug(f"Choosing next ability for task {task}.")
+ if task.context.cycle_count > self._configuration.max_task_cycle_count:
+ # Don't hit the LLM, just set the next action as "breakdown_task"
+ # with an appropriate reason
+ raise NotImplementedError
+ elif not task.context.enough_info:
+ # Don't ask the LLM, just set the next action as "breakdown_task"
+ # with an appropriate reason
+ raise NotImplementedError
+ else:
+ next_ability = await self._planning.determine_next_ability(
+ task, ability_specs
+ )
+ return next_ability
+
+ async def _update_tasks_and_memory(self, ability_result: AbilityResult):
+ self._current_task.context.cycle_count += 1
+ self._current_task.context.prior_actions.append(ability_result)
+ # TODO: Summarize new knowledge
+ # TODO: store knowledge and summaries in memory and in relevant tasks
+ # TODO: evaluate whether the task is complete
+
+ def __repr__(self):
+ return "SimpleAgent()"
+
+ ################################################################
+ # Factory interface for agent bootstrapping and initialization #
+ ################################################################
+
+ @classmethod
+ def build_user_configuration(cls) -> dict[str, Any]:
+ """Build the user's configuration."""
+ configuration_dict = {
+ "agent": cls.get_user_config(),
+ }
+
+ system_locations = configuration_dict["agent"]["configuration"]["systems"]
+ for system_name, system_location in system_locations.items():
+ system_class = SimplePluginService.get_plugin(system_location)
+ configuration_dict[system_name] = system_class.get_user_config()
+ configuration_dict = _prune_empty_dicts(configuration_dict)
+ return configuration_dict
+
+ @classmethod
+ def compile_settings(
+ cls, logger: logging.Logger, user_configuration: dict
+ ) -> AgentSettings:
+ """Compile the user's configuration with the defaults."""
+ logger.debug("Processing agent system configuration.")
+ configuration_dict = {
+ "agent": cls.build_agent_configuration(
+ user_configuration.get("agent", {})
+ ).dict(),
+ }
+
+ system_locations = configuration_dict["agent"]["configuration"]["systems"]
+
+ # Build up default configuration
+ for system_name, system_location in system_locations.items():
+ logger.debug(f"Compiling configuration for system {system_name}")
+ system_class = SimplePluginService.get_plugin(system_location)
+ configuration_dict[system_name] = system_class.build_agent_configuration(
+ user_configuration.get(system_name, {})
+ ).dict()
+
+ return AgentSettings.parse_obj(configuration_dict)
+
+ @classmethod
+ async def determine_agent_name_and_goals(
+ cls,
+ user_objective: str,
+ agent_settings: AgentSettings,
+ logger: logging.Logger,
+ ) -> dict:
+ logger.debug("Loading OpenAI provider.")
+ provider: OpenAIProvider = cls._get_system_instance(
+ "openai_provider",
+ agent_settings,
+ logger=logger,
+ )
+ logger.debug("Loading agent planner.")
+ agent_planner: SimplePlanner = cls._get_system_instance(
+ "planning",
+ agent_settings,
+ logger=logger,
+ model_providers={"openai": provider},
+ )
+ logger.debug("determining agent name and goals.")
+ model_response = await agent_planner.decide_name_and_goals(
+ user_objective,
+ )
+
+ return model_response.parsed_result
+
+ @classmethod
+ def provision_agent(
+ cls,
+ agent_settings: AgentSettings,
+ logger: logging.Logger,
+ ):
+ agent_settings.agent.configuration.creation_time = datetime.now().strftime(
+ "%Y%m%d_%H%M%S"
+ )
+ workspace: SimpleWorkspace = cls._get_system_instance(
+ "workspace",
+ agent_settings,
+ logger=logger,
+ )
+ return workspace.setup_workspace(agent_settings, logger)
+
+ @classmethod
+ def _get_system_instance(
+ cls,
+ system_name: str,
+ agent_settings: AgentSettings,
+ logger: logging.Logger,
+ *args,
+ **kwargs,
+ ):
+ system_locations = agent_settings.agent.configuration.systems.dict()
+
+ system_settings = getattr(agent_settings, system_name)
+ system_class = SimplePluginService.get_plugin(system_locations[system_name])
+ system_instance = system_class(
+ system_settings,
+ *args,
+ logger=logger.getChild(system_name),
+ **kwargs,
+ )
+ return system_instance
+
+
+def _prune_empty_dicts(d: dict) -> dict:
+ """
+ Prune branches from a nested dictionary if the branch only contains empty
+ dictionaries at the leaves.
+
+ Args:
+ d: The dictionary to prune.
+
+ Returns:
+ The pruned dictionary.
+ """
+ pruned = {}
+ for key, value in d.items():
+ if isinstance(value, dict):
+ pruned_value = _prune_empty_dicts(value)
+ if (
+ pruned_value
+ ): # if the pruned dictionary is not empty, add it to the result
+ pruned[key] = pruned_value
+ else:
+ pruned[key] = value
+ return pruned