Skip to content

Commit

Permalink
Add GCSWorkspace Implementation to Forge (#6510)
Browse files Browse the repository at this point in the history
* Added gcs workspace implementation

* Renamed abilites to actions (#6511)

* Renamed abilites to actions

* formatting

* schema to model
  • Loading branch information
Swiftyos authored Dec 6, 2023
1 parent 4c495ce commit 78f5ff1
Show file tree
Hide file tree
Showing 19 changed files with 398 additions and 140 deletions.
1 change: 1 addition & 0 deletions autogpts/forge/forge/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .registry import ActionRegister, Action, action, ActionParameter
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import List

from ..registry import ability
from ..registry import action

@ability(

@action(
name="list_files",
description="List files in a directory",
parameters=[
Expand All @@ -22,7 +23,7 @@ async def list_files(agent, task_id: str, path: str) -> List[str]:
return agent.workspace.list(task_id=task_id, path=str(path))


@ability(
@action(
name="write_file",
description="Write data to a file",
parameters=[
Expand Down Expand Up @@ -57,7 +58,7 @@ async def write_file(agent, task_id: str, file_path: str, data: bytes):
)


@ability(
@action(
name="read_file",
description="Read data from a file",
parameters=[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from ..forge_log import ForgeLogger
from .registry import ability
from sdk.forge_log import ForgeLogger
from .registry import action

logger = ForgeLogger(__name__)


@ability(
@action(
name="finish",
description="Use this to shut down once you have accomplished all of your goals,"
" or when there are insurmountable problems that make it impossible"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
import pydantic


class AbilityParameter(pydantic.BaseModel):
class ActionParameter(pydantic.BaseModel):
"""
This class represents a parameter for an ability.
This class represents a parameter for an action.
Attributes:
name (str): The name of the parameter.
Expand All @@ -24,22 +24,22 @@ class AbilityParameter(pydantic.BaseModel):
required: bool


class Ability(pydantic.BaseModel):
class Action(pydantic.BaseModel):
"""
This class represents an ability in the system.
This class represents an action in the system.
Attributes:
name (str): The name of the ability.
description (str): A brief description of what the ability does.
method (Callable): The method that implements the ability.
parameters (List[AbilityParameter]): A list of parameters that the ability requires.
output_type (str): The type of the output that the ability returns.
name (str): The name of the action.
description (str): A brief description of what the action does.
method (Callable): The method that implements the action.
parameters (List[ActionParameter]): A list of parameters that the action requires.
output_type (str): The type of the output that the action returns.
"""

name: str
description: str
method: Callable
parameters: List[AbilityParameter]
parameters: List[ActionParameter]
output_type: str
category: str | None = None

Expand Down Expand Up @@ -71,22 +71,22 @@ def __str__(self) -> str:
return func_summary


def ability(
name: str, description: str, parameters: List[AbilityParameter], output_type: str
def action(
name: str, description: str, parameters: List[ActionParameter], output_type: str
):
def decorator(func):
func_params = inspect.signature(func).parameters
param_names = set(
[AbilityParameter.parse_obj(param).name for param in parameters]
[ActionParameter.parse_obj(param).name for param in parameters]
)
param_names.add("agent")
param_names.add("task_id")
func_param_names = set(func_params.keys())
if param_names != func_param_names:
raise ValueError(
f"Mismatch in parameter names. Ability Annotation includes {param_names}, but function actually takes {func_param_names} in function {func.__name__} signature"
f"Mismatch in parameter names. Action Annotation includes {param_names}, but function actually takes {func_param_names} in function {func.__name__} signature"
)
func.ability = Ability(
func.action = Action(
name=name,
description=description,
parameters=parameters,
Expand All @@ -98,88 +98,88 @@ def decorator(func):
return decorator


class AbilityRegister:
class ActionRegister:
def __init__(self, agent) -> None:
self.abilities = {}
self.register_abilities()
self.agent = agent

def register_abilities(self) -> None:
for ability_path in glob.glob(
for action_path in glob.glob(
os.path.join(os.path.dirname(__file__), "**/*.py"), recursive=True
):
if not os.path.basename(ability_path) in [
if not os.path.basename(action_path) in [
"__init__.py",
"registry.py",
]:
ability = os.path.relpath(
ability_path, os.path.dirname(__file__)
action = os.path.relpath(
action_path, os.path.dirname(__file__)
).replace("/", ".")
try:
module = importlib.import_module(
f".{ability[:-3]}", package="forge.sdk.abilities"
f".{action[:-3]}", package="forge.sdk.abilities"
)
for attr in dir(module):
func = getattr(module, attr)
if hasattr(func, "ability"):
ab = func.ability
if hasattr(func, "action"):
ab = func.action

ab.category = (
ability.split(".")[0].lower().replace("_", " ")
if len(ability.split(".")) > 1
action.split(".")[0].lower().replace("_", " ")
if len(action.split(".")) > 1
else "general"
)
self.abilities[func.ability.name] = func.ability
self.abilities[func.action.name] = func.action
except Exception as e:
print(f"Error occurred while registering abilities: {str(e)}")

def list_abilities(self) -> List[Ability]:
def list_abilities(self) -> List[Action]:
return self.abilities

def list_abilities_for_prompt(self) -> List[str]:
return [str(ability) for ability in self.abilities.values()]
return [str(action) for action in self.abilities.values()]

def abilities_description(self) -> str:
abilities_by_category = {}
for ability in self.abilities.values():
if ability.category not in abilities_by_category:
abilities_by_category[ability.category] = []
abilities_by_category[ability.category].append(str(ability))
for action in self.abilities.values():
if action.category not in abilities_by_category:
abilities_by_category[action.category] = []
abilities_by_category[action.category].append(str(action))

abilities_description = ""
for category, abilities in abilities_by_category.items():
if abilities_description != "":
abilities_description += "\n"
abilities_description += f"{category}:"
for ability in abilities:
abilities_description += f" {ability}"
for action in abilities:
abilities_description += f" {action}"

return abilities_description

async def run_ability(
self, task_id: str, ability_name: str, *args: Any, **kwds: Any
async def run_action(
self, task_id: str, action_name: str, *args: Any, **kwds: Any
) -> Any:
"""
This method runs a specified ability with the provided arguments and keyword arguments.
This method runs a specified action with the provided arguments and keyword arguments.
The agent is passed as the first argument to the ability. This allows the ability to access and manipulate
The agent is passed as the first argument to the action. This allows the action to access and manipulate
the agent's state as needed.
Args:
task_id (str): The ID of the task that the ability is being run for.
ability_name (str): The name of the ability to run.
task_id (str): The ID of the task that the action is being run for.
action_name (str): The name of the action to run.
*args: Variable length argument list.
**kwds: Arbitrary keyword arguments.
Returns:
Any: The result of the ability execution.
Any: The result of the action execution.
Raises:
Exception: If there is an error in running the ability.
Exception: If there is an error in running the action.
"""
try:
ability = self.abilities[ability_name]
return await ability(self.agent, task_id, *args, **kwds)
action = self.abilities[action_name]
return await action(self.agent, task_id, *args, **kwds)
except Exception:
raise

Expand All @@ -188,6 +188,6 @@ async def run_ability(
import sys

sys.path.append("/Users/swifty/dev/forge/forge")
register = AbilityRegister(agent=None)
register = ActionRegister(agent=None)
print(register.abilities_description())
print(register.run_ability("abc", "list_files", "/Users/swifty/dev/forge/forge"))
print(register.run_action("abc", "list_files", "/Users/swifty/dev/forge/forge"))
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from __future__ import annotations

import json
Expand All @@ -7,12 +6,12 @@

from duckduckgo_search import DDGS

from ..registry import ability
from ..registry import action

DUCKDUCKGO_MAX_ATTEMPTS = 3


@ability(
@action(
name="web_search",
description="Searches the web",
parameters=[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from webdriver_manager.microsoft import EdgeChromiumDriverManager as EdgeDriverManager


from ..registry import ability
from ..registry import action
from forge.sdk.errors import *
import functools
import re
Expand Down Expand Up @@ -76,7 +76,6 @@ def format_hyperlinks(hyperlinks: list[tuple[str, str]]) -> list[str]:
return [f"{link_text} ({link_url})" for link_text, link_url in hyperlinks]



def validate_url(func: Callable[..., Any]) -> Any:
"""The method decorator validate_url is used to validate urls for any command that requires
a url as an argument"""
Expand Down Expand Up @@ -178,8 +177,6 @@ def check_local_file_access(url: str) -> bool:
return any(url.startswith(prefix) for prefix in local_prefixes)




logger = logging.getLogger(__name__)

FILE_DIR = Path(__file__).parent.parent
Expand All @@ -191,7 +188,7 @@ class BrowsingError(CommandExecutionError):
"""An error occurred while trying to browse the page"""


@ability(
@action(
name="read_webpage",
description="Read a webpage, and extract specific information from it if a question is specified. If you are looking to extract specific information from the webpage, you should specify a question.",
parameters=[
Expand All @@ -201,17 +198,19 @@ class BrowsingError(CommandExecutionError):
"type": "string",
"required": True,
},
{
{
"name": "question",
"description": "A question that you want to answer using the content of the webpage.",
"type": "string",
"required": False,
}
},
],
output_type="string",
)
@validate_url
async def read_webpage(agent, task_id: str, url: str, question: str = "") -> Tuple(str, List[str]):
async def read_webpage(
agent, task_id: str, url: str, question: str = ""
) -> Tuple(str, List[str]):
"""Browse a website and return the answer and links to the user
Args:
Expand Down Expand Up @@ -372,4 +371,3 @@ def close_browser(driver: WebDriver) -> None:
None
"""
driver.quit()

22 changes: 13 additions & 9 deletions autogpts/forge/forge/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
StepRequestBody,
Task,
TaskRequestBody,
Workspace,
PromptEngine,
chat_completion_request,
ChromaMemStore
Workspace,
PromptEngine,
chat_completion_request,
ChromaMemStore,
)
import json
from forge.actions import ActionRegister
import json
import pprint

LOG = ForgeLogger(__name__)
Expand Down Expand Up @@ -78,6 +79,7 @@ def __init__(self, database: AgentDB, workspace: Workspace):
Feel free to create subclasses of the database and workspace to implement your own storage
"""
super().__init__(database, workspace)
self.abilities = ActionRegister(self)

async def create_task(self, task_request: TaskRequestBody) -> Task:
"""
Expand Down Expand Up @@ -139,9 +141,11 @@ async def execute_step(self, task_id: str, step_request: StepRequestBody) -> Ste

step.output = "Washington D.C"

LOG.info(f"\t✅ Final Step completed: {step.step_id}. \n" +
f"Output should be placeholder text Washington D.C. You'll need to \n" +
f"modify execute_step to include LLM behavior. Follow the tutorial " +
f"if confused. ")
LOG.info(
f"\t✅ Final Step completed: {step.step_id}. \n"
+ f"Output should be placeholder text Washington D.C. You'll need to \n"
+ f"modify execute_step to include LLM behavior. Follow the tutorial "
+ f"if confused. "
)

return step
Loading

2 comments on commit 78f5ff1

@fosterushka
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, but if you rename any methods guys don't forget about docs as well !

@mansurmetocrm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed we spend a while on it to figure out , if you do renaming don't forget uptodate docs

Please sign in to comment.