aboutsummaryrefslogtreecommitdiff
path: root/autogpts/autogpt/autogpt/core/ability
diff options
context:
space:
mode:
Diffstat (limited to 'autogpts/autogpt/autogpt/core/ability')
-rw-r--r--autogpts/autogpt/autogpt/core/ability/__init__.py18
-rw-r--r--autogpts/autogpt/autogpt/core/ability/base.py88
-rw-r--r--autogpts/autogpt/autogpt/core/ability/builtins/__init__.py12
-rw-r--r--autogpts/autogpt/autogpt/core/ability/builtins/create_new_ability.py107
-rw-r--r--autogpts/autogpt/autogpt/core/ability/builtins/file_operations.py170
-rw-r--r--autogpts/autogpt/autogpt/core/ability/builtins/query_language_model.py66
-rw-r--r--autogpts/autogpt/autogpt/core/ability/schema.py30
-rw-r--r--autogpts/autogpt/autogpt/core/ability/simple.py97
8 files changed, 588 insertions, 0 deletions
diff --git a/autogpts/autogpt/autogpt/core/ability/__init__.py b/autogpts/autogpt/autogpt/core/ability/__init__.py
new file mode 100644
index 000000000..3709b9277
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/__init__.py
@@ -0,0 +1,18 @@
+"""The command system provides a way to extend the functionality of the AI agent."""
+from autogpt.core.ability.base import Ability, AbilityConfiguration, AbilityRegistry
+from autogpt.core.ability.schema import AbilityResult
+from autogpt.core.ability.simple import (
+ AbilityRegistryConfiguration,
+ AbilityRegistrySettings,
+ SimpleAbilityRegistry,
+)
+
+__all__ = [
+ "Ability",
+ "AbilityConfiguration",
+ "AbilityRegistry",
+ "AbilityResult",
+ "AbilityRegistryConfiguration",
+ "AbilityRegistrySettings",
+ "SimpleAbilityRegistry",
+]
diff --git a/autogpts/autogpt/autogpt/core/ability/base.py b/autogpts/autogpt/autogpt/core/ability/base.py
new file mode 100644
index 000000000..2686c101c
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/base.py
@@ -0,0 +1,88 @@
+import abc
+from pprint import pformat
+from typing import Any, ClassVar
+
+import inflection
+from pydantic import Field
+
+from autogpt.core.configuration import SystemConfiguration
+from autogpt.core.planning.simple import LanguageModelConfiguration
+from autogpt.core.plugin.base import PluginLocation
+from autogpt.core.resource.model_providers import CompletionModelFunction
+from autogpt.core.utils.json_schema import JSONSchema
+
+from .schema import AbilityResult
+
+
+class AbilityConfiguration(SystemConfiguration):
+ """Struct for model configuration."""
+
+ location: PluginLocation
+ packages_required: list[str] = Field(default_factory=list)
+ language_model_required: LanguageModelConfiguration = None
+ memory_provider_required: bool = False
+ workspace_required: bool = False
+
+
+class Ability(abc.ABC):
+ """A class representing an agent ability."""
+
+ default_configuration: ClassVar[AbilityConfiguration]
+
+ @classmethod
+ def name(cls) -> str:
+ """The name of the ability."""
+ return inflection.underscore(cls.__name__)
+
+ @property
+ @classmethod
+ @abc.abstractmethod
+ def description(cls) -> str:
+ """A detailed description of what the ability does."""
+ ...
+
+ @property
+ @classmethod
+ @abc.abstractmethod
+ def parameters(cls) -> dict[str, JSONSchema]:
+ ...
+
+ @abc.abstractmethod
+ async def __call__(self, *args: Any, **kwargs: Any) -> AbilityResult:
+ ...
+
+ def __str__(self) -> str:
+ return pformat(self.spec)
+
+ @property
+ @classmethod
+ def spec(cls) -> CompletionModelFunction:
+ return CompletionModelFunction(
+ name=cls.name(),
+ description=cls.description,
+ parameters=cls.parameters,
+ )
+
+
+class AbilityRegistry(abc.ABC):
+ @abc.abstractmethod
+ def register_ability(
+ self, ability_name: str, ability_configuration: AbilityConfiguration
+ ) -> None:
+ ...
+
+ @abc.abstractmethod
+ def list_abilities(self) -> list[str]:
+ ...
+
+ @abc.abstractmethod
+ def dump_abilities(self) -> list[CompletionModelFunction]:
+ ...
+
+ @abc.abstractmethod
+ def get_ability(self, ability_name: str) -> Ability:
+ ...
+
+ @abc.abstractmethod
+ async def perform(self, ability_name: str, **kwargs: Any) -> AbilityResult:
+ ...
diff --git a/autogpts/autogpt/autogpt/core/ability/builtins/__init__.py b/autogpts/autogpt/autogpt/core/ability/builtins/__init__.py
new file mode 100644
index 000000000..93936dbc6
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/builtins/__init__.py
@@ -0,0 +1,12 @@
+from autogpt.core.ability.builtins.create_new_ability import CreateNewAbility
+from autogpt.core.ability.builtins.query_language_model import QueryLanguageModel
+
+BUILTIN_ABILITIES = {
+ QueryLanguageModel.name(): QueryLanguageModel,
+}
+
+__all__ = [
+ "BUILTIN_ABILITIES",
+ "CreateNewAbility",
+ "QueryLanguageModel",
+]
diff --git a/autogpts/autogpt/autogpt/core/ability/builtins/create_new_ability.py b/autogpts/autogpt/autogpt/core/ability/builtins/create_new_ability.py
new file mode 100644
index 000000000..55550cafc
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/builtins/create_new_ability.py
@@ -0,0 +1,107 @@
+import logging
+from typing import ClassVar
+
+from autogpt.core.ability.base import Ability, AbilityConfiguration
+from autogpt.core.ability.schema import AbilityResult
+from autogpt.core.plugin.simple import PluginLocation, PluginStorageFormat
+from autogpt.core.utils.json_schema import JSONSchema
+
+
+class CreateNewAbility(Ability):
+ default_configuration = AbilityConfiguration(
+ location=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.ability.builtins.CreateNewAbility",
+ ),
+ )
+
+ def __init__(
+ self,
+ logger: logging.Logger,
+ configuration: AbilityConfiguration,
+ ):
+ self._logger = logger
+ self._configuration = configuration
+
+ description: ClassVar[str] = "Create a new ability by writing python code."
+
+ parameters: ClassVar[dict[str, JSONSchema]] = {
+ "ability_name": JSONSchema(
+ description="A meaningful and concise name for the new ability.",
+ type=JSONSchema.Type.STRING,
+ required=True,
+ ),
+ "description": JSONSchema(
+ description=(
+ "A detailed description of the ability and its uses, "
+ "including any limitations."
+ ),
+ type=JSONSchema.Type.STRING,
+ required=True,
+ ),
+ "arguments": JSONSchema(
+ description="A list of arguments that the ability will accept.",
+ type=JSONSchema.Type.ARRAY,
+ items=JSONSchema(
+ type=JSONSchema.Type.OBJECT,
+ properties={
+ "name": JSONSchema(
+ description="The name of the argument.",
+ type=JSONSchema.Type.STRING,
+ ),
+ "type": JSONSchema(
+ description=(
+ "The type of the argument. "
+ "Must be a standard json schema type."
+ ),
+ type=JSONSchema.Type.STRING,
+ ),
+ "description": JSONSchema(
+ description=(
+ "A detailed description of the argument and its uses."
+ ),
+ type=JSONSchema.Type.STRING,
+ ),
+ },
+ ),
+ ),
+ "required_arguments": JSONSchema(
+ description="A list of the names of the arguments that are required.",
+ type=JSONSchema.Type.ARRAY,
+ items=JSONSchema(
+ description="The names of the arguments that are required.",
+ type=JSONSchema.Type.STRING,
+ ),
+ ),
+ "package_requirements": JSONSchema(
+ description=(
+ "A list of the names of the Python packages that are required to "
+ "execute the ability."
+ ),
+ type=JSONSchema.Type.ARRAY,
+ items=JSONSchema(
+ description=(
+ "The of the Python package that is required to execute the ability."
+ ),
+ type=JSONSchema.Type.STRING,
+ ),
+ ),
+ "code": JSONSchema(
+ description=(
+ "The Python code that will be executed when the ability is called."
+ ),
+ type=JSONSchema.Type.STRING,
+ required=True,
+ ),
+ }
+
+ async def __call__(
+ self,
+ ability_name: str,
+ description: str,
+ arguments: list[dict],
+ required_arguments: list[str],
+ package_requirements: list[str],
+ code: str,
+ ) -> AbilityResult:
+ raise NotImplementedError
diff --git a/autogpts/autogpt/autogpt/core/ability/builtins/file_operations.py b/autogpts/autogpt/autogpt/core/ability/builtins/file_operations.py
new file mode 100644
index 000000000..08dc8c7a9
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/builtins/file_operations.py
@@ -0,0 +1,170 @@
+import logging
+import os
+from typing import ClassVar
+
+from autogpt.core.ability.base import Ability, AbilityConfiguration
+from autogpt.core.ability.schema import AbilityResult, ContentType, Knowledge
+from autogpt.core.plugin.simple import PluginLocation, PluginStorageFormat
+from autogpt.core.utils.json_schema import JSONSchema
+from autogpt.core.workspace import Workspace
+
+
+class ReadFile(Ability):
+ default_configuration = AbilityConfiguration(
+ location=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.ability.builtins.ReadFile",
+ ),
+ packages_required=["unstructured"],
+ workspace_required=True,
+ )
+
+ def __init__(
+ self,
+ logger: logging.Logger,
+ workspace: Workspace,
+ ):
+ self._logger = logger
+ self._workspace = workspace
+
+ description: ClassVar[str] = "Read and parse all text from a file."
+
+ parameters: ClassVar[dict[str, JSONSchema]] = {
+ "filename": JSONSchema(
+ type=JSONSchema.Type.STRING,
+ description="The name of the file to read.",
+ ),
+ }
+
+ def _check_preconditions(self, filename: str) -> AbilityResult | None:
+ message = ""
+ try:
+ pass
+ except ImportError:
+ message = "Package charset_normalizer is not installed."
+
+ try:
+ file_path = self._workspace.get_path(filename)
+ if not file_path.exists():
+ message = f"File {filename} does not exist."
+ if not file_path.is_file():
+ message = f"{filename} is not a file."
+ except ValueError as e:
+ message = str(e)
+
+ if message:
+ return AbilityResult(
+ ability_name=self.name(),
+ ability_args={"filename": filename},
+ success=False,
+ message=message,
+ data=None,
+ )
+
+ def __call__(self, filename: str) -> AbilityResult:
+ if result := self._check_preconditions(filename):
+ return result
+
+ from unstructured.partition.auto import partition
+
+ file_path = self._workspace.get_path(filename)
+ try:
+ elements = partition(str(file_path))
+ # TODO: Lots of other potentially useful information is available
+ # in the partitioned file. Consider returning more of it.
+ new_knowledge = Knowledge(
+ content="\n\n".join([element.text for element in elements]),
+ content_type=ContentType.TEXT,
+ content_metadata={"filename": filename},
+ )
+ success = True
+ message = f"File {file_path} read successfully."
+ except IOError as e:
+ new_knowledge = None
+ success = False
+ message = str(e)
+
+ return AbilityResult(
+ ability_name=self.name(),
+ ability_args={"filename": filename},
+ success=success,
+ message=message,
+ new_knowledge=new_knowledge,
+ )
+
+
+class WriteFile(Ability):
+ default_configuration = AbilityConfiguration(
+ location=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.ability.builtins.WriteFile",
+ ),
+ packages_required=["unstructured"],
+ workspace_required=True,
+ )
+
+ def __init__(
+ self,
+ logger: logging.Logger,
+ workspace: Workspace,
+ ):
+ self._logger = logger
+ self._workspace = workspace
+
+ description: ClassVar[str] = "Write text to a file."
+
+ parameters: ClassVar[dict[str, JSONSchema]] = {
+ "filename": JSONSchema(
+ type=JSONSchema.Type.STRING,
+ description="The name of the file to write.",
+ ),
+ "contents": JSONSchema(
+ type=JSONSchema.Type.STRING,
+ description="The contents of the file to write.",
+ ),
+ }
+
+ def _check_preconditions(
+ self, filename: str, contents: str
+ ) -> AbilityResult | None:
+ message = ""
+ try:
+ file_path = self._workspace.get_path(filename)
+ if file_path.exists():
+ message = f"File {filename} already exists."
+ if len(contents):
+ message = f"File {filename} was not given any content."
+ except ValueError as e:
+ message = str(e)
+
+ if message:
+ return AbilityResult(
+ ability_name=self.name(),
+ ability_args={"filename": filename, "contents": contents},
+ success=False,
+ message=message,
+ data=None,
+ )
+
+ def __call__(self, filename: str, contents: str) -> AbilityResult:
+ if result := self._check_preconditions(filename, contents):
+ return result
+
+ file_path = self._workspace.get_path(filename)
+ try:
+ directory = os.path.dirname(file_path)
+ os.makedirs(directory)
+ with open(filename, "w", encoding="utf-8") as f:
+ f.write(contents)
+ success = True
+ message = f"File {file_path} written successfully."
+ except IOError as e:
+ success = False
+ message = str(e)
+
+ return AbilityResult(
+ ability_name=self.name(),
+ ability_args={"filename": filename},
+ success=success,
+ message=message,
+ )
diff --git a/autogpts/autogpt/autogpt/core/ability/builtins/query_language_model.py b/autogpts/autogpt/autogpt/core/ability/builtins/query_language_model.py
new file mode 100644
index 000000000..7a6ae68ee
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/builtins/query_language_model.py
@@ -0,0 +1,66 @@
+import logging
+from typing import ClassVar
+
+from autogpt.core.ability.base import Ability, AbilityConfiguration
+from autogpt.core.ability.schema import AbilityResult
+from autogpt.core.planning.simple import LanguageModelConfiguration
+from autogpt.core.plugin.simple import PluginLocation, PluginStorageFormat
+from autogpt.core.resource.model_providers import (
+ ChatMessage,
+ ChatModelProvider,
+ ModelProviderName,
+ OpenAIModelName,
+)
+from autogpt.core.utils.json_schema import JSONSchema
+
+
+class QueryLanguageModel(Ability):
+ default_configuration = AbilityConfiguration(
+ location=PluginLocation(
+ storage_format=PluginStorageFormat.INSTALLED_PACKAGE,
+ storage_route="autogpt.core.ability.builtins.QueryLanguageModel",
+ ),
+ language_model_required=LanguageModelConfiguration(
+ model_name=OpenAIModelName.GPT3,
+ provider_name=ModelProviderName.OPENAI,
+ temperature=0.9,
+ ),
+ )
+
+ def __init__(
+ self,
+ logger: logging.Logger,
+ configuration: AbilityConfiguration,
+ language_model_provider: ChatModelProvider,
+ ):
+ self._logger = logger
+ self._configuration = configuration
+ self._language_model_provider = language_model_provider
+
+ description: ClassVar[str] = (
+ "Query a language model."
+ " A query should be a question and any relevant context."
+ )
+
+ parameters: ClassVar[dict[str, JSONSchema]] = {
+ "query": JSONSchema(
+ type=JSONSchema.Type.STRING,
+ description=(
+ "A query for a language model. "
+ "A query should contain a question and any relevant context."
+ ),
+ )
+ }
+
+ async def __call__(self, query: str) -> AbilityResult:
+ model_response = await self._language_model_provider.create_chat_completion(
+ model_prompt=[ChatMessage.user(query)],
+ functions=[],
+ model_name=self._configuration.language_model_required.model_name,
+ )
+ return AbilityResult(
+ ability_name=self.name(),
+ ability_args={"query": query},
+ success=True,
+ message=model_response.response.content or "",
+ )
diff --git a/autogpts/autogpt/autogpt/core/ability/schema.py b/autogpts/autogpt/autogpt/core/ability/schema.py
new file mode 100644
index 000000000..3d20a7b92
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/schema.py
@@ -0,0 +1,30 @@
+import enum
+from typing import Any
+
+from pydantic import BaseModel
+
+
+class ContentType(str, enum.Enum):
+ # TBD what these actually are.
+ TEXT = "text"
+ CODE = "code"
+
+
+class Knowledge(BaseModel):
+ content: str
+ content_type: ContentType
+ content_metadata: dict[str, Any]
+
+
+class AbilityResult(BaseModel):
+ """The AbilityResult is a standard response struct for an ability."""
+
+ ability_name: str
+ ability_args: dict[str, str]
+ success: bool
+ message: str
+ new_knowledge: Knowledge = None
+
+ def summary(self) -> str:
+ kwargs = ", ".join(f"{k}={v}" for k, v in self.ability_args.items())
+ return f"{self.ability_name}({kwargs}): {self.message}"
diff --git a/autogpts/autogpt/autogpt/core/ability/simple.py b/autogpts/autogpt/autogpt/core/ability/simple.py
new file mode 100644
index 000000000..962413182
--- /dev/null
+++ b/autogpts/autogpt/autogpt/core/ability/simple.py
@@ -0,0 +1,97 @@
+import logging
+
+from autogpt.core.ability.base import Ability, AbilityConfiguration, AbilityRegistry
+from autogpt.core.ability.builtins import BUILTIN_ABILITIES
+from autogpt.core.ability.schema import AbilityResult
+from autogpt.core.configuration import Configurable, SystemConfiguration, SystemSettings
+from autogpt.core.memory.base import Memory
+from autogpt.core.plugin.simple import SimplePluginService
+from autogpt.core.resource.model_providers import (
+ ChatModelProvider,
+ CompletionModelFunction,
+ ModelProviderName,
+)
+from autogpt.core.workspace.base import Workspace
+
+
+class AbilityRegistryConfiguration(SystemConfiguration):
+ """Configuration for the AbilityRegistry subsystem."""
+
+ abilities: dict[str, AbilityConfiguration]
+
+
+class AbilityRegistrySettings(SystemSettings):
+ configuration: AbilityRegistryConfiguration
+
+
+class SimpleAbilityRegistry(AbilityRegistry, Configurable):
+ default_settings = AbilityRegistrySettings(
+ name="simple_ability_registry",
+ description="A simple ability registry.",
+ configuration=AbilityRegistryConfiguration(
+ abilities={
+ ability_name: ability.default_configuration
+ for ability_name, ability in BUILTIN_ABILITIES.items()
+ },
+ ),
+ )
+
+ def __init__(
+ self,
+ settings: AbilityRegistrySettings,
+ logger: logging.Logger,
+ memory: Memory,
+ workspace: Workspace,
+ model_providers: dict[ModelProviderName, ChatModelProvider],
+ ):
+ self._configuration = settings.configuration
+ self._logger = logger
+ self._memory = memory
+ self._workspace = workspace
+ self._model_providers = model_providers
+ self._abilities: list[Ability] = []
+ for (
+ ability_name,
+ ability_configuration,
+ ) in self._configuration.abilities.items():
+ self.register_ability(ability_name, ability_configuration)
+
+ def register_ability(
+ self, ability_name: str, ability_configuration: AbilityConfiguration
+ ) -> None:
+ ability_class = SimplePluginService.get_plugin(ability_configuration.location)
+ ability_args = {
+ "logger": self._logger.getChild(ability_name),
+ "configuration": ability_configuration,
+ }
+ if ability_configuration.packages_required:
+ # TODO: Check packages are installed and maybe install them.
+ pass
+ if ability_configuration.memory_provider_required:
+ ability_args["memory"] = self._memory
+ if ability_configuration.workspace_required:
+ ability_args["workspace"] = self._workspace
+ if ability_configuration.language_model_required:
+ ability_args["language_model_provider"] = self._model_providers[
+ ability_configuration.language_model_required.provider_name
+ ]
+ ability = ability_class(**ability_args)
+ self._abilities.append(ability)
+
+ def list_abilities(self) -> list[str]:
+ return [
+ f"{ability.name()}: {ability.description}" for ability in self._abilities
+ ]
+
+ def dump_abilities(self) -> list[CompletionModelFunction]:
+ return [ability.spec for ability in self._abilities]
+
+ def get_ability(self, ability_name: str) -> Ability:
+ for ability in self._abilities:
+ if ability.name() == ability_name:
+ return ability
+ raise ValueError(f"Ability '{ability_name}' not found.")
+
+ async def perform(self, ability_name: str, **kwargs) -> AbilityResult:
+ ability = self.get_ability(ability_name)
+ return await ability(**kwargs)