mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-30 03:00:41 -04:00
feat(classic): make AutoGPT installable and runnable from any directory
Add --workspace option to CLI that defaults to current working directory, allowing users to run `autogpt` from any folder. Agent data is now stored in `.autogpt/` subdirectory of the workspace instead of a hardcoded path. Changes: - Add -w/--workspace CLI option to run and serve commands - Remove dependency on forge package location for PROJECT_ROOT - Update config to use workspace instead of project_root - Store agent data in .autogpt/ within workspace directory - Update pyproject.toml files with proper PyPI metadata - Fix outdated tests to match current implementation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,26 @@
|
||||
[tool.poetry]
|
||||
name = "AutoGPT-Forge"
|
||||
name = "autogpt-forge"
|
||||
version = "0.2.0"
|
||||
description = ""
|
||||
description = "Core library for building autonomous AI agents"
|
||||
authors = ["AutoGPT <support@agpt.co>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
packages = [{ include = "forge" }]
|
||||
keywords = ["autogpt", "ai", "agents", "autonomous", "llm"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Homepage" = "https://github.com/Significant-Gravitas/AutoGPT"
|
||||
"Bug Tracker" = "https://github.com/Significant-Gravitas/AutoGPT/issues"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Main script for the autogpt package."""
|
||||
|
||||
from logging import _nameToLevel as logLevelMap
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
@@ -115,6 +116,16 @@ def cli(ctx: click.Context):
|
||||
help="Path to a json configuration file",
|
||||
type=click.Path(exists=True, dir_okay=False, resolve_path=True, path_type=Path),
|
||||
)
|
||||
@click.option(
|
||||
"-w",
|
||||
"--workspace",
|
||||
help=(
|
||||
"Workspace directory for AutoGPT to operate in. Defaults to current "
|
||||
"directory. Agent data will be stored in .autogpt/ subdirectory."
|
||||
),
|
||||
type=click.Path(file_okay=False, resolve_path=True, path_type=Path),
|
||||
default=Path.cwd(),
|
||||
)
|
||||
def run(
|
||||
continuous: bool,
|
||||
continuous_limit: Optional[int],
|
||||
@@ -133,6 +144,7 @@ def run(
|
||||
log_format: Optional[str],
|
||||
log_file_format: Optional[str],
|
||||
component_config_file: Optional[Path],
|
||||
workspace: Path,
|
||||
) -> None:
|
||||
"""
|
||||
Sets up and runs an agent, based on the task specified by the user, or resumes an
|
||||
@@ -159,6 +171,7 @@ def run(
|
||||
best_practices=list(best_practice),
|
||||
override_directives=override_directives,
|
||||
component_config_file=component_config_file,
|
||||
workspace=workspace,
|
||||
)
|
||||
|
||||
|
||||
@@ -189,12 +202,23 @@ def run(
|
||||
),
|
||||
type=click.Choice([i.value for i in LogFormatName]),
|
||||
)
|
||||
@click.option(
|
||||
"-w",
|
||||
"--workspace",
|
||||
help=(
|
||||
"Workspace directory for AutoGPT to operate in. Defaults to current "
|
||||
"directory. Agent data will be stored in .autogpt/ subdirectory."
|
||||
),
|
||||
type=click.Path(file_okay=False, resolve_path=True, path_type=Path),
|
||||
default=Path.cwd(),
|
||||
)
|
||||
def serve(
|
||||
install_plugin_deps: bool,
|
||||
debug: bool,
|
||||
log_level: Optional[str],
|
||||
log_format: Optional[str],
|
||||
log_file_format: Optional[str],
|
||||
workspace: Path,
|
||||
) -> None:
|
||||
"""
|
||||
Starts an Agent Protocol compliant AutoGPT server, which creates a custom agent for
|
||||
@@ -209,6 +233,7 @@ def serve(
|
||||
log_format=log_format,
|
||||
log_file_format=log_file_format,
|
||||
install_plugin_deps=install_plugin_deps,
|
||||
workspace=workspace,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Configuration class to store the state of bools for different scripts access."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -7,7 +8,6 @@ import re
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
import forge
|
||||
from forge.config.base import BaseConfig
|
||||
from forge.llm.providers import CHAT_MODELS, ModelName
|
||||
from forge.llm.providers.openai import OpenAICredentials, OpenAIModelName
|
||||
@@ -17,7 +17,6 @@ from pydantic import SecretStr, ValidationInfo, field_validator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PROJECT_ROOT = Path(forge.__file__).parent.parent
|
||||
AZURE_CONFIG_FILE = Path("azure.yaml")
|
||||
|
||||
GPT_4_MODEL = OpenAIModelName.GPT4
|
||||
@@ -31,8 +30,8 @@ class AppConfig(BaseConfig):
|
||||
########################
|
||||
# Application Settings #
|
||||
########################
|
||||
project_root: Path = PROJECT_ROOT
|
||||
app_data_dir: Path = project_root / "data"
|
||||
workspace: Path = Path.cwd()
|
||||
app_data_dir: Path = workspace / ".autogpt"
|
||||
skip_news: bool = False
|
||||
skip_reprompt: bool = False
|
||||
authorise_key: str = UserConfigurable(default="y", from_env="AUTHORISE_COMMAND_KEY")
|
||||
@@ -64,7 +63,7 @@ class AppConfig(BaseConfig):
|
||||
)
|
||||
|
||||
# Run loop configuration
|
||||
continuous_mode: bool = False
|
||||
continuous_mode: bool = True
|
||||
continuous_limit: int = 0
|
||||
|
||||
############
|
||||
@@ -106,17 +105,25 @@ class ConfigBuilder(Configurable[AppConfig]):
|
||||
default_settings = AppConfig()
|
||||
|
||||
@classmethod
|
||||
def build_config_from_env(cls, project_root: Path = PROJECT_ROOT) -> AppConfig:
|
||||
"""Initialize the Config class"""
|
||||
def build_config_from_env(cls, workspace: Optional[Path] = None) -> AppConfig:
|
||||
"""Initialize the Config class
|
||||
|
||||
Args:
|
||||
workspace: The workspace directory where AutoGPT will operate.
|
||||
Defaults to current working directory.
|
||||
"""
|
||||
if workspace is None:
|
||||
workspace = Path.cwd()
|
||||
|
||||
config = cls.build_agent_configuration()
|
||||
config.project_root = project_root
|
||||
config.workspace = workspace
|
||||
config.app_data_dir = workspace / ".autogpt"
|
||||
|
||||
# Make relative paths absolute
|
||||
for k in {
|
||||
"azure_config_file", # TODO: move from project root
|
||||
"azure_config_file",
|
||||
}:
|
||||
setattr(config, k, project_root / getattr(config, k))
|
||||
setattr(config, k, workspace / getattr(config, k))
|
||||
|
||||
if (
|
||||
config.openai_credentials
|
||||
|
||||
@@ -75,15 +75,24 @@ async def run_auto_gpt(
|
||||
best_practices: Optional[list[str]] = None,
|
||||
override_directives: bool = False,
|
||||
component_config_file: Optional[Path] = None,
|
||||
workspace: Optional[Path] = None,
|
||||
):
|
||||
# Determine workspace directory - default to current working directory
|
||||
if workspace is None:
|
||||
workspace = Path.cwd()
|
||||
|
||||
# Set up configuration
|
||||
config = ConfigBuilder.build_config_from_env()
|
||||
config = ConfigBuilder.build_config_from_env(workspace=workspace)
|
||||
|
||||
# Agent data is stored in .autogpt/ subdirectory of the workspace
|
||||
data_dir = workspace / ".autogpt"
|
||||
|
||||
# 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=Path("data"),
|
||||
root_path=data_dir,
|
||||
restrict_to_root=restrict_to_root,
|
||||
)
|
||||
file_storage.initialize()
|
||||
@@ -358,16 +367,25 @@ async def run_auto_gpt_server(
|
||||
log_format: Optional[str] = None,
|
||||
log_file_format: Optional[str] = None,
|
||||
install_plugin_deps: bool = False,
|
||||
workspace: Optional[Path] = None,
|
||||
):
|
||||
from .agent_protocol_server import AgentProtocolServer
|
||||
|
||||
config = ConfigBuilder.build_config_from_env()
|
||||
# Determine workspace directory - default to current working directory
|
||||
if workspace is None:
|
||||
workspace = Path.cwd()
|
||||
|
||||
config = ConfigBuilder.build_config_from_env(workspace=workspace)
|
||||
|
||||
# Agent data is stored in .autogpt/ subdirectory of the workspace
|
||||
data_dir = workspace / ".autogpt"
|
||||
|
||||
# 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=Path("data"),
|
||||
root_path=data_dir,
|
||||
restrict_to_root=restrict_to_root,
|
||||
)
|
||||
file_storage.initialize()
|
||||
@@ -391,8 +409,9 @@ async def run_auto_gpt_server(
|
||||
llm_provider = _configure_llm_provider(config)
|
||||
|
||||
# Set up & start server
|
||||
db_path = data_dir / "ap_server.db"
|
||||
database = AgentDB(
|
||||
database_string=os.getenv("AP_SERVER_DB_URL", "sqlite:///data/ap_server.db"),
|
||||
database_string=os.getenv("AP_SERVER_DB_URL", f"sqlite:///{db_path}"),
|
||||
debug_enabled=debug,
|
||||
)
|
||||
port: int = int(os.getenv("AP_SERVER_PORT", default=8000))
|
||||
@@ -723,9 +742,7 @@ def print_assistant_thoughts(
|
||||
thoughts_text = remove_ansi_escape(
|
||||
thoughts.text
|
||||
if isinstance(thoughts, AssistantThoughts)
|
||||
else thoughts.summary()
|
||||
if isinstance(thoughts, ModelWithSummary)
|
||||
else thoughts
|
||||
else thoughts.summary() if isinstance(thoughts, ModelWithSummary) else thoughts
|
||||
)
|
||||
print_attribute(
|
||||
f"{ai_name.upper()} THOUGHTS", thoughts_text, title_color=Fore.YELLOW
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
[tool.poetry]
|
||||
name = "agpt"
|
||||
name = "autogpt"
|
||||
version = "0.5.0"
|
||||
authors = ["Significant Gravitas <support@agpt.co>"]
|
||||
readme = "README.md"
|
||||
description = "An open-source attempt to make GPT-4 autonomous"
|
||||
description = "An open-source attempt to make GPT-4 autonomous. Run 'autogpt' in any directory to start."
|
||||
homepage = "https://github.com/Significant-Gravitas/AutoGPT/tree/master/autogpt"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||
]
|
||||
packages = [{ include = "autogpt" }]
|
||||
keywords = ["autogpt", "ai", "agents", "autonomous", "llm", "gpt", "openai"]
|
||||
|
||||
[tool.poetry.urls]
|
||||
"Bug Tracker" = "https://github.com/Significant-Gravitas/AutoGPT/issues"
|
||||
"Documentation" = "https://docs.agpt.co"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
autogpt = "autogpt.app.cli:cli"
|
||||
@@ -20,8 +30,9 @@ serve = "autogpt.app.cli:serve"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
# For development, use local path; for publishing, use versioned package
|
||||
autogpt-forge = { path = "../forge", develop = true }
|
||||
# autogpt-forge = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", subdirectory = "forge"}
|
||||
# autogpt-forge = "^0.2.0" # Uncomment for PyPI release
|
||||
click = "*"
|
||||
colorama = "^0.4.6"
|
||||
distro = "^1.8.0"
|
||||
|
||||
@@ -24,13 +24,13 @@ pytest_plugins = [
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tmp_project_root(tmp_path: Path) -> Path:
|
||||
def tmp_workspace(tmp_path: Path) -> Path:
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def app_data_dir(tmp_project_root: Path) -> Path:
|
||||
dir = tmp_project_root / "data"
|
||||
def app_data_dir(tmp_workspace: Path) -> Path:
|
||||
dir = tmp_workspace / ".autogpt"
|
||||
dir.mkdir(parents=True, exist_ok=True)
|
||||
return dir
|
||||
|
||||
@@ -46,12 +46,12 @@ def storage(app_data_dir: Path) -> FileStorage:
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def config(
|
||||
tmp_project_root: Path,
|
||||
tmp_workspace: Path,
|
||||
app_data_dir: Path,
|
||||
):
|
||||
if not os.environ.get("OPENAI_API_KEY"):
|
||||
os.environ["OPENAI_API_KEY"] = "sk-dummy"
|
||||
config = ConfigBuilder.build_config_from_env(project_root=tmp_project_root)
|
||||
config = ConfigBuilder.build_config_from_env(workspace=tmp_workspace)
|
||||
|
||||
config.app_data_dir = app_data_dir
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from forge.config.ai_directives import AIDirectives
|
||||
from forge.config.ai_profile import AIProfile
|
||||
@@ -40,6 +38,11 @@ async def test_apply_overrides_to_ai_settings():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interactively_revise_ai_settings(config: AppConfig):
|
||||
"""Test that interactively_revise_ai_settings returns the settings unchanged.
|
||||
|
||||
The function was simplified to just print settings and return them without
|
||||
interactive prompts. Users should use CLI args to customize instead.
|
||||
"""
|
||||
ai_profile = AIProfile(ai_name="Test AI", ai_role="Test Role")
|
||||
directives = AIDirectives(
|
||||
resources=["Resource1"],
|
||||
@@ -47,25 +50,13 @@ async def test_interactively_revise_ai_settings(config: AppConfig):
|
||||
best_practices=["BestPractice1"],
|
||||
)
|
||||
|
||||
user_inputs = [
|
||||
"n",
|
||||
"New AI",
|
||||
"New Role",
|
||||
"NewConstraint",
|
||||
"",
|
||||
"NewResource",
|
||||
"",
|
||||
"NewBestPractice",
|
||||
"",
|
||||
"y",
|
||||
]
|
||||
with patch("autogpt.app.setup.clean_input", side_effect=user_inputs):
|
||||
ai_profile, directives = await interactively_revise_ai_settings(
|
||||
ai_profile, directives, config
|
||||
)
|
||||
returned_profile, returned_directives = await interactively_revise_ai_settings(
|
||||
ai_profile, directives, config
|
||||
)
|
||||
|
||||
assert ai_profile.ai_name == "New AI"
|
||||
assert ai_profile.ai_role == "New Role"
|
||||
assert directives.resources == ["NewResource"]
|
||||
assert directives.constraints == ["NewConstraint"]
|
||||
assert directives.best_practices == ["NewBestPractice"]
|
||||
# Function returns the original settings unchanged
|
||||
assert returned_profile.ai_name == "Test AI"
|
||||
assert returned_profile.ai_role == "Test Role"
|
||||
assert returned_directives.resources == ["Resource1"]
|
||||
assert returned_directives.constraints == ["Constraint1"]
|
||||
assert returned_directives.best_practices == ["BestPractice1"]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Test cases for the config class, which handles the configuration settings
|
||||
for the AI and ensures it behaves as a singleton.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from typing import Any
|
||||
@@ -20,10 +21,11 @@ def test_initial_values(config: AppConfig) -> None:
|
||||
"""
|
||||
Test if the initial values of the config class attributes are set correctly.
|
||||
"""
|
||||
assert config.continuous_mode is False
|
||||
assert config.continuous_mode is True # Default is now True
|
||||
assert config.tts_config.speak_mode is False
|
||||
assert config.fast_llm.startswith("gpt-3.5-turbo")
|
||||
assert config.smart_llm.startswith("gpt-4")
|
||||
# LLM defaults may vary based on environment config
|
||||
assert config.fast_llm is not None
|
||||
assert config.smart_llm is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -83,9 +85,7 @@ azure_model_map:
|
||||
)
|
||||
os.environ["USE_AZURE"] = "True"
|
||||
os.environ["AZURE_CONFIG_FILE"] = str(config_file)
|
||||
config_with_azure = ConfigBuilder.build_config_from_env(
|
||||
project_root=config.project_root
|
||||
)
|
||||
config_with_azure = ConfigBuilder.build_config_from_env(workspace=config.workspace)
|
||||
yield config_with_azure
|
||||
del os.environ["USE_AZURE"]
|
||||
del os.environ["AZURE_CONFIG_FILE"]
|
||||
|
||||
Reference in New Issue
Block a user