mirror of
https://github.com/aljazceru/Auto-GPT.git
synced 2025-12-28 19:34:30 +01:00
392 lines
14 KiB
Python
392 lines
14 KiB
Python
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 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.content["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.content
|
|
|
|
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.content
|
|
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: Evaluate whether there is enough information to start the task (language model call).
|
|
task.context.enough_info = True
|
|
task.context.status = TaskStatus.IN_PROGRESS
|
|
return task
|
|
|
|
async def _choose_next_ability(self, task: Task, ability_schema: list[dict]):
|
|
"""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_schema
|
|
)
|
|
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.content
|
|
|
|
@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
|