Add AgentFactory and replace AI Goals by AI Directives + Task

This commit is contained in:
Reinier van der Leer
2023-10-08 10:13:23 -07:00
parent 12656646ae
commit 36e2dae6b0
10 changed files with 687 additions and 437 deletions

View File

@@ -0,0 +1,116 @@
from typing import Optional
from autogpt.agent_manager import AgentManager
from autogpt.agents.agent import Agent, AgentConfiguration, AgentSettings
from autogpt.commands import COMMAND_CATEGORIES
from autogpt.config import AIProfile, AIDirectives, Config
from autogpt.core.resource.model_providers import ChatModelProvider
from autogpt.logs.config import configure_chat_plugins
from autogpt.logs.helpers import print_attribute
from autogpt.models.command_registry import CommandRegistry
from autogpt.plugins import scan_plugins
def create_agent(
task: str,
ai_profile: AIProfile,
app_config: Config,
llm_provider: ChatModelProvider,
directives: Optional[AIDirectives] = None,
) -> Agent:
if not task:
raise ValueError("No task specified for new agent")
if not directives:
directives = AIDirectives.from_file(app_config.prompt_settings_file)
agent = _configure_agent(
task=task,
ai_profile=ai_profile,
directives=directives,
app_config=app_config,
llm_provider=llm_provider,
)
agent.state.agent_id = AgentManager.generate_id(agent.ai_profile.ai_name)
return agent
def configure_agent_with_state(
state: AgentSettings,
app_config: Config,
llm_provider: ChatModelProvider,
) -> Agent:
return _configure_agent(
state=state,
app_config=app_config,
llm_provider=llm_provider,
)
def _configure_agent(
app_config: Config,
llm_provider: ChatModelProvider,
task: str = "",
ai_profile: Optional[AIProfile] = None,
directives: Optional[AIDirectives] = None,
state: Optional[AgentSettings] = None,
) -> Agent:
if not (state or task and ai_profile and directives):
raise TypeError(
"Either (state) or (task, ai_profile, directives) must be specified"
)
app_config.plugins = scan_plugins(app_config, app_config.debug_mode)
configure_chat_plugins(app_config)
# Create a CommandRegistry instance and scan default folder
command_registry = CommandRegistry.with_command_modules(
modules=COMMAND_CATEGORIES,
config=app_config,
)
agent_state = state or create_agent_state(
task=task,
ai_profile=ai_profile,
directives=directives,
app_config=app_config,
)
# TODO: configure memory
print_attribute("Configured Browser", app_config.selenium_web_browser)
return Agent(
settings=agent_state,
llm_provider=llm_provider,
command_registry=command_registry,
legacy_config=app_config,
)
def create_agent_state(
task: str,
ai_profile: AIProfile,
directives: AIDirectives,
app_config: Config,
) -> AgentSettings:
agent_prompt_config = Agent.default_settings.prompt_config.copy(deep=True)
agent_prompt_config.use_functions_api = app_config.openai_functions
return AgentSettings(
name=Agent.default_settings.name,
description=Agent.default_settings.description,
task=task,
ai_profile=ai_profile,
directives=directives,
config=AgentConfiguration(
fast_llm=app_config.fast_llm,
smart_llm=app_config.smart_llm,
allow_fs_access=not app_config.restrict_to_workspace,
use_functions_api=app_config.openai_functions,
plugins=app_config.plugins,
),
prompt_config=agent_prompt_config,
history=Agent.default_settings.history.copy(deep=True),
)

View File

@@ -0,0 +1,29 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from autogpt.agents.agent import Agent
from autogpt.config import AIDirectives, Config
from autogpt.core.resource.model_providers.schema import ChatModelProvider
from .configurators import _configure_agent
from .profile_generator import generate_agent_profile_for_task
async def generate_agent_for_task(
task: str,
app_config: "Config",
llm_provider: "ChatModelProvider",
) -> "Agent":
base_directives = AIDirectives.from_file(app_config.ai_settings_file)
ai_profile, task_directives = await generate_agent_profile_for_task(
task=task,
app_config=app_config,
llm_provider=llm_provider,
)
return _configure_agent(
task=task,
ai_profile=ai_profile,
directives=base_directives + task_directives,
app_config=app_config,
llm_provider=llm_provider,
)

View File

@@ -0,0 +1,216 @@
import logging
from autogpt.config import AIProfile, AIDirectives, Config
from autogpt.core.configuration import SystemConfiguration, UserConfigurable
from autogpt.core.prompting import (
ChatPrompt,
LanguageModelClassification,
PromptStrategy,
)
from autogpt.core.prompting.utils import json_loads
from autogpt.core.resource.model_providers.schema import (
AssistantChatMessageDict,
ChatMessage,
ChatModelProvider,
CompletionModelFunction,
)
from autogpt.core.utils.json_schema import JSONSchema
logger = logging.getLogger(__name__)
class AgentProfileGeneratorConfiguration(SystemConfiguration):
model_classification: LanguageModelClassification = UserConfigurable(
default=LanguageModelClassification.SMART_MODEL
)
system_prompt: str = UserConfigurable(
default=(
"Your job is to respond to a user-defined task, given in triple quotes, by "
"invoking the `create_agent` function to generate an autonomous agent to "
"complete the task. "
"You should supply a role-based name for the agent (_GPT), "
"an informative description for what the agent does, and "
"1 to 5 directives in each of the categories Best Practices and Constraints, "
"that are optimally aligned with the successful completion "
"of its assigned task.\n"
"\n"
"Example Input:\n"
'"""Help me with marketing my business"""\n\n'
"Example Function Call:\n"
"```\n"
"{"
'"name": "create_agent",'
' "arguments": {'
'"name": "CMOGPT",'
' "description": "a professional digital marketer AI that assists Solopreneurs in'
" growing their businesses by providing world-class expertise in solving"
' marketing problems for SaaS, content products, agencies, and more.",'
' "directives": {'
' "best_practices": ['
'"Engage in effective problem-solving, prioritization, planning, and'
" supporting execution to address your marketing needs as your virtual Chief"
' Marketing Officer.",'
' "Provide specific, actionable, and concise advice to help you make'
" informed decisions without the use of platitudes or overly wordy"
' explanations.",'
' "Identify and prioritize quick wins and cost-effective campaigns that'
' maximize results with minimal time and budget investment.",'
' "Proactively take the lead in guiding you and offering suggestions when'
" faced with unclear information or uncertainty to ensure your marketing"
' strategy remains on track."'
"]" # best_practices
"}" # directives
"}" # arguments
"}\n"
"```"
)
)
user_prompt_template: str = UserConfigurable(default='"""{user_objective}"""')
create_agent_function: dict = UserConfigurable(
default=CompletionModelFunction(
name="create_agent",
description="Create a new autonomous AI agent to complete a given task.",
parameters={
"name": JSONSchema(
type=JSONSchema.Type.STRING,
description="A short role-based name for an autonomous agent.",
required=True,
),
"description": JSONSchema(
type=JSONSchema.Type.STRING,
description="An informative one sentence description of what the AI agent does",
required=True,
),
"directives": JSONSchema(
type=JSONSchema.Type.OBJECT,
properties={
"best_practices": JSONSchema(
type=JSONSchema.Type.ARRAY,
minItems=1,
maxItems=5,
items=JSONSchema(
type=JSONSchema.Type.STRING,
),
description=(
"One to five highly effective best practices that are"
" optimally aligned with the completion of the given task."
),
required=True,
),
"constraints": JSONSchema(
type=JSONSchema.Type.ARRAY,
minItems=1,
maxItems=5,
items=JSONSchema(
type=JSONSchema.Type.STRING,
),
description=(
"One to five highly effective constraints that are"
" optimally aligned with the completion of the given task."
),
required=True,
),
},
required=True,
),
},
).schema
)
class AgentProfileGenerator(PromptStrategy):
default_configuration: AgentProfileGeneratorConfiguration = AgentProfileGeneratorConfiguration()
def __init__(
self,
model_classification: LanguageModelClassification,
system_prompt: str,
user_prompt_template: str,
create_agent_function: dict,
):
self._model_classification = model_classification
self._system_prompt_message = system_prompt
self._user_prompt_template = user_prompt_template
self._create_agent_function = CompletionModelFunction.parse(
create_agent_function
)
@property
def model_classification(self) -> LanguageModelClassification:
return self._model_classification
def build_prompt(self, user_objective: str = "", **kwargs) -> ChatPrompt:
system_message = ChatMessage.system(self._system_prompt_message)
user_message = ChatMessage.user(
self._user_prompt_template.format(
user_objective=user_objective,
)
)
prompt = ChatPrompt(
messages=[system_message, user_message],
functions=[self._create_agent_function],
)
return prompt
def parse_response_content(
self,
response_content: AssistantChatMessageDict,
) -> tuple[AIProfile, AIDirectives]:
"""Parse the actual text response from the objective model.
Args:
response_content: The raw response content from the objective model.
Returns:
The parsed response.
"""
try:
arguments = json_loads(response_content["function_call"]["arguments"])
ai_profile = AIProfile(
ai_name=arguments["name"],
ai_role=arguments["description"],
)
ai_directives = AIDirectives(
best_practices=arguments["directives"]["best_practices"],
constraints=arguments["directives"]["constraints"],
resources=[],
)
except KeyError:
logger.debug(f"Failed to parse this response content: {response_content}")
raise
return ai_profile, ai_directives
async def generate_agent_profile_for_task(
task: str,
app_config: Config,
llm_provider: ChatModelProvider,
) -> tuple[AIProfile, AIDirectives]:
"""Generates an AIConfig object from the given string.
Returns:
AIConfig: The AIConfig object tailored to the user's input
"""
agent_profile_generator = AgentProfileGenerator(
**AgentProfileGenerator.default_configuration.dict() # HACK
)
prompt = agent_profile_generator.build_prompt(task)
# Call LLM with the string as user input
output = (
await llm_provider.create_chat_completion(
prompt.messages,
model_name=app_config.smart_llm,
functions=prompt.functions,
)
).response
# Debug LLM Output
logger.debug(f"AI Config Generator Raw Output: {output}")
# Parse the output
ai_profile, ai_directives = agent_profile_generator.parse_response_content(output)
return ai_profile, ai_directives

View File

@@ -137,6 +137,9 @@ class BaseAgentSettings(SystemSettings):
)
"""Directives (general instructional guidelines) for the agent."""
task: str = "Terminate immediately" # FIXME: placeholder for forge.sdk.schema.Task
"""The user-given task that the agent is working on."""
config: BaseAgentConfiguration = Field(default_factory=BaseAgentConfiguration)
"""The configuration for this BaseAgent subsystem instance."""
@@ -165,7 +168,7 @@ class BaseAgent(Configurable[BaseAgentSettings], ABC):
self.state = settings
self.config = settings.config
self.ai_profile = settings.ai_profile
self.ai_directives = settings.directives
self.directives = settings.directives
self.event_history = settings.history
self.legacy_config = legacy_config
@@ -288,13 +291,14 @@ class BaseAgent(Configurable[BaseAgentSettings], ABC):
if not plugin.can_handle_post_prompt():
continue
plugin.post_prompt(scratchpad)
ai_directives = self.ai_directives.copy(deep=True)
ai_directives = self.directives.copy(deep=True)
ai_directives.resources += scratchpad.resources
ai_directives.constraints += scratchpad.constraints
ai_directives.best_practices += scratchpad.best_practices
extra_commands += list(scratchpad.commands.values())
prompt = self.prompt_strategy.build_prompt(
task=self.state.task,
ai_profile=self.ai_profile,
ai_directives=ai_directives,
commands=get_openai_command_specs(

View File

@@ -193,6 +193,7 @@ class OneShotAgentPromptStrategy(PromptStrategy):
def build_prompt(
self,
*,
task: str,
ai_profile: AIProfile,
ai_directives: AIDirectives,
commands: list[CompletionModelFunction],
@@ -223,6 +224,9 @@ class OneShotAgentPromptStrategy(PromptStrategy):
)
system_prompt_tlength = count_message_tokens(ChatMessage.system(system_prompt))
user_task = f'"""{task}"""'
user_task_tlength = count_message_tokens(ChatMessage.user(user_task))
response_format_instr = self.response_format_instruction(
self.config.use_functions_api
)
@@ -238,6 +242,7 @@ class OneShotAgentPromptStrategy(PromptStrategy):
max_tokens=(
max_prompt_tokens
- system_prompt_tlength
- user_task_tlength
- final_instruction_tlength
- count_message_tokens(extra_messages)
),
@@ -250,6 +255,7 @@ class OneShotAgentPromptStrategy(PromptStrategy):
prompt = ChatPrompt(
messages=[
ChatMessage.system(system_prompt),
ChatMessage.user(user_task),
*extra_messages,
final_instruction_msg,
],
@@ -278,7 +284,12 @@ class OneShotAgentPromptStrategy(PromptStrategy):
best_practices=format_numbered_list(ai_directives.best_practices),
)
]
+ self._generate_goals_info(ai_profile.ai_goals)
+ [
"## Your Task\n"
"The user will specify a task for you to execute, in triple quotes,"
" in the next message. Your job is to complete the task while following"
" your directives as given above, and terminate when your task is done."
]
)
# Join non-empty parts together into paragraph format
@@ -395,24 +406,6 @@ class OneShotAgentPromptStrategy(PromptStrategy):
]
return []
def _generate_goals_info(self, goals: list[str]) -> list[str]:
"""Generates the goals information part of the prompt.
Returns:
str: The goals information part of the prompt.
"""
if goals:
return [
"\n".join(
[
"## Goals",
"For your task, you must fulfill the following goals:",
*[f"{i+1}. {goal}" for i, goal in enumerate(goals)],
]
)
]
return []
def _generate_commands_list(self, commands: list[CompletionModelFunction]) -> str:
"""Lists the commands available to the agent.

View File

@@ -84,10 +84,39 @@ import click
help="AI role override",
)
@click.option(
"--ai-goal",
"--constraint",
type=str,
multiple=True,
help="AI goal override; may be used multiple times to pass multiple goals",
help=(
"Add or override AI constraints to include in the prompt;"
" may be used multiple times to pass multiple constraints"
),
)
@click.option(
"--resource",
type=str,
multiple=True,
help=(
"Add or override AI resources to include in the prompt;"
" may be used multiple times to pass multiple resources"
),
)
@click.option(
"--best-practice",
type=str,
multiple=True,
help=(
"Add or override AI best practices to include in the prompt;"
" may be used multiple times to pass multiple best practices"
),
)
@click.option(
"--override-directives",
is_flag=True,
help=(
"If specified, --constraint, --resource and --best-practice will override"
" the AI's directives instead of being appended to them"
),
)
@click.pass_context
def main(
@@ -109,7 +138,10 @@ def main(
install_plugin_deps: bool,
ai_name: Optional[str],
ai_role: Optional[str],
ai_goal: tuple[str],
resource: tuple[str],
constraint: tuple[str],
best_practice: tuple[str],
override_directives: bool,
) -> None:
"""
Welcome to AutoGPT an experimental open-source application showcasing the capabilities of the GPT-4 pushing the boundaries of AI.
@@ -136,9 +168,12 @@ def main(
skip_news=skip_news,
workspace_directory=workspace_directory,
install_plugin_deps=install_plugin_deps,
ai_name=ai_name,
ai_role=ai_role,
ai_goals=ai_goal,
override_ai_name=ai_name,
override_ai_role=ai_role,
resources=list(resource),
constraints=list(constraint),
best_practices=list(best_practice),
override_directives=override_directives,
)

View File

@@ -6,16 +6,24 @@ import signal
import sys
from pathlib import Path
from types import FrameType
from typing import Optional
from typing import TYPE_CHECKING, Optional
from colorama import Fore, Style
from pydantic import SecretStr
if TYPE_CHECKING:
from autogpt.agents.agent import Agent
from autogpt.agent_factory.configurators import create_agent
from autogpt.agent_factory.profile_generator import generate_agent_profile_for_task
from autogpt.agent_manager import AgentManager
from autogpt.agents import AgentThoughts, CommandArgs, CommandName
from autogpt.agents.agent import Agent, AgentConfiguration, AgentSettings
from autogpt.agents.utils.exceptions import InvalidAgentResponseError
from autogpt.app.configurator import apply_overrides_to_config
from autogpt.app.setup import interactive_ai_profile_setup
from autogpt.app.setup import (
apply_overrides_to_ai_settings,
interactively_revise_ai_settings,
)
from autogpt.app.spinner import Spinner
from autogpt.app.utils import (
clean_input,
@@ -25,7 +33,6 @@ from autogpt.app.utils import (
print_motd,
print_python_version_info,
)
from autogpt.commands import COMMAND_CATEGORIES
from autogpt.config import (
AIDirectives,
AIProfile,
@@ -33,16 +40,11 @@ from autogpt.config import (
ConfigBuilder,
assert_config_has_openai_api_key,
)
from autogpt.core.resource.model_providers import (
ChatModelProvider,
ModelProviderCredentials,
)
from autogpt.core.resource.model_providers import ModelProviderCredentials
from autogpt.core.resource.model_providers.openai import OpenAIProvider
from autogpt.core.runner.client_lib.utils import coroutine
from autogpt.llm.api_manager import ApiManager
from autogpt.logs.config import configure_chat_plugins, configure_logging
from autogpt.logs.helpers import print_attribute, speak
from autogpt.models.command_registry import CommandRegistry
from autogpt.plugins import scan_plugins
from scripts.install_plugin_deps import install_plugin_dependencies
@@ -62,11 +64,14 @@ async def run_auto_gpt(
browser_name: str,
allow_downloads: bool,
skip_news: bool,
workspace_directory: str | Path,
workspace_directory: Path,
install_plugin_deps: bool,
ai_name: Optional[str] = None,
ai_role: Optional[str] = None,
ai_goals: tuple[str] = tuple(),
override_ai_name: str = "",
override_ai_role: str = "",
resources: Optional[list[str]] = None,
constraints: Optional[list[str]] = None,
best_practices: Optional[list[str]] = None,
override_directives: bool = False,
):
config = ConfigBuilder.build_config_from_env()
@@ -123,44 +128,58 @@ async def run_auto_gpt(
config.plugins = scan_plugins(config, config.debug_mode)
configure_chat_plugins(config)
# Create a CommandRegistry instance and scan default folder
command_registry = CommandRegistry.with_command_modules(COMMAND_CATEGORIES, config)
ai_profile = await construct_main_ai_profile(
######################
# Set up a new Agent #
######################
task = await clean_input(
config,
llm_provider=llm_provider,
name=ai_name,
role=ai_role,
goals=ai_goals,
"Enter the task that you want AutoGPT to execute,"
" with as much detail as possible:",
)
ai_directives = AIDirectives.from_file(config.prompt_settings_file)
base_ai_directives = AIDirectives.from_file(config.prompt_settings_file)
agent_prompt_config = Agent.default_settings.prompt_config.copy(deep=True)
agent_prompt_config.use_functions_api = config.openai_functions
agent_settings = AgentSettings(
name=Agent.default_settings.name,
description=Agent.default_settings.description,
ai_profile, task_oriented_ai_directives = await generate_agent_profile_for_task(
task,
app_config=config,
llm_provider=llm_provider,
)
ai_directives = base_ai_directives + task_oriented_ai_directives
apply_overrides_to_ai_settings(
ai_profile=ai_profile,
directives=ai_directives,
config=AgentConfiguration(
fast_llm=config.fast_llm,
smart_llm=config.smart_llm,
allow_fs_access=not config.restrict_to_workspace,
use_functions_api=config.openai_functions,
plugins=config.plugins,
),
prompt_config=agent_prompt_config,
history=Agent.default_settings.history.copy(deep=True),
override_name=override_ai_name,
override_role=override_ai_role,
resources=resources,
constraints=constraints,
best_practices=best_practices,
replace_directives=override_directives,
)
print_attribute("Configured Browser", config.selenium_web_browser)
# If any of these are specified as arguments,
# assume the user doesn't want to revise them
if not any(
[
override_ai_name,
override_ai_role,
resources,
constraints,
best_practices,
]
):
ai_profile, ai_directives = await interactively_revise_ai_settings(
ai_profile=ai_profile,
directives=ai_directives,
app_config=config,
)
else:
logger.info("AI config overrides specified through CLI; skipping revision")
agent = Agent(
settings=agent_settings,
agent = create_agent(
task=task,
ai_profile=ai_profile,
directives=ai_directives,
app_config=config,
llm_provider=llm_provider,
command_registry=command_registry,
legacy_config=config,
)
agent.attach_fs(config.app_data_dir / "agents" / "AutoGPT") # HACK
@@ -223,7 +242,7 @@ class UserFeedback(str, enum.Enum):
async def run_interaction_loop(
agent: Agent,
agent: "Agent",
) -> None:
"""Run the main interaction loop for the agent.
@@ -482,83 +501,6 @@ async def get_user_feedback(
return user_feedback, user_input, new_cycles_remaining
async def construct_main_ai_profile(
config: Config,
llm_provider: ChatModelProvider,
name: Optional[str] = None,
role: Optional[str] = None,
goals: tuple[str] = tuple(),
) -> AIProfile:
"""Construct the prompt for the AI to respond to
Returns:
str: The prompt string
"""
logger = logging.getLogger(__name__)
ai_profile = AIProfile.load(config.ai_settings_file)
# Apply overrides
if name:
ai_profile.ai_name = name
if role:
ai_profile.ai_role = role
if goals:
ai_profile.ai_goals = list(goals)
if (
all([name, role, goals])
or config.skip_reprompt
and all([ai_profile.ai_name, ai_profile.ai_role, ai_profile.ai_goals])
):
print_attribute("Name :", ai_profile.ai_name)
print_attribute("Role :", ai_profile.ai_role)
print_attribute("Goals:", ai_profile.ai_goals)
print_attribute(
"API Budget:",
"infinite" if ai_profile.api_budget <= 0 else f"${ai_profile.api_budget}",
)
elif all([ai_profile.ai_name, ai_profile.ai_role, ai_profile.ai_goals]):
logger.info(
extra={"title": f"{Fore.GREEN}Welcome back!{Fore.RESET}"},
msg=f"Would you like me to return to being {ai_profile.ai_name}?",
)
should_continue = await clean_input(
config,
f"""Continue with the last settings?
Name: {ai_profile.ai_name}
Role: {ai_profile.ai_role}
Goals: {ai_profile.ai_goals}
API Budget: {"infinite" if ai_profile.api_budget <= 0 else f"${ai_profile.api_budget}"}
Continue ({config.authorise_key}/{config.exit_key}): """,
)
if should_continue.lower() == config.exit_key:
ai_profile = AIProfile()
if any([not ai_profile.ai_name, not ai_profile.ai_role, not ai_profile.ai_goals]):
ai_profile = await interactive_ai_profile_setup(config, llm_provider)
ai_profile.save(config.ai_settings_file)
# set the total api budget
api_manager = ApiManager()
api_manager.set_total_budget(ai_profile.api_budget)
# Agent Created, print message
logger.info(
f"{Fore.LIGHTBLUE_EX}{ai_profile.ai_name}{Fore.RESET} has been created with the following details:",
extra={"preserve_color": True},
)
# Print the ai_profile details
print_attribute("Name :", ai_profile.ai_name)
print_attribute("Role :", ai_profile.ai_role)
print_attribute("Goals:", "")
for goal in ai_profile.ai_goals:
logger.info(f"- {goal}")
return ai_profile
def print_assistant_thoughts(
ai_name: str,
assistant_reply_json_valid: dict,

View File

@@ -1,253 +1,190 @@
"""Set up the AI and its goals"""
import logging
import re
from typing import Optional
from colorama import Fore, Style
from jinja2 import Template
from autogpt.app import utils
from autogpt.config import Config
from autogpt.config.ai_profile import AIProfile
from autogpt.core.resource.model_providers import ChatMessage, ChatModelProvider
from autogpt.logs.helpers import user_friendly_output
from autogpt.prompts.default_prompts import (
DEFAULT_SYSTEM_PROMPT_AICONFIG_AUTOMATIC,
DEFAULT_TASK_PROMPT_AICONFIG_AUTOMATIC,
DEFAULT_USER_DESIRE_PROMPT,
)
from autogpt.app.utils import clean_input
from autogpt.config import AIProfile, AIDirectives, Config
from autogpt.logs.helpers import print_attribute
logger = logging.getLogger(__name__)
async def interactive_ai_profile_setup(
config: Config,
llm_provider: ChatModelProvider,
ai_profile_template: Optional[AIProfile] = None,
) -> AIProfile:
"""Prompt the user for input
def apply_overrides_to_ai_settings(
ai_profile: AIProfile,
directives: AIDirectives,
override_name: str = "",
override_role: str = "",
replace_directives: bool = False,
resources: Optional[list[str]] = None,
constraints: Optional[list[str]] = None,
best_practices: Optional[list[str]] = None,
):
if override_name:
ai_profile.ai_name = override_name
if override_role:
ai_profile.ai_role = override_role
Params:
config (Config): The Config object
ai_profile_template (AIProfile): The AIProfile object to use as a template
if replace_directives:
if resources:
directives.resources = resources
if constraints:
directives.constraints = constraints
if best_practices:
directives.best_practices = best_practices
else:
if resources:
directives.resources += resources
if constraints:
directives.constraints += constraints
if best_practices:
directives.best_practices += best_practices
async def interactively_revise_ai_settings(
ai_profile: AIProfile,
directives: AIDirectives,
app_config: Config,
):
"""Interactively revise the AI settings.
Args:
ai_profile (AIConfig): The current AI profile.
ai_directives (AIDirectives): The current AI directives.
app_config (Config): The application configuration.
Returns:
AIProfile: The AIProfile object tailored to the user's input
AIConfig: The revised AI settings.
"""
logger = logging.getLogger("revise_ai_profile")
# Construct the prompt
user_friendly_output(
title="Welcome to AutoGPT! ",
message="run with '--help' for more information.",
title_color=Fore.GREEN,
)
revised = False
ai_profile_template_provided = ai_profile_template is not None and any(
[
ai_profile_template.ai_goals,
ai_profile_template.ai_name,
ai_profile_template.ai_role,
]
)
user_desire = ""
if not ai_profile_template_provided:
# Get user desire if command line overrides have not been passed in
user_friendly_output(
title="Create an AI-Assistant:",
message="input '--manual' to enter manual mode.",
title_color=Fore.GREEN,
while True:
# Print the current AI configuration
print_ai_settings(
title="Current AI Settings" if not revised else "Revised AI Settings",
ai_profile=ai_profile,
directives=directives,
logger=logger,
)
user_desire = await utils.clean_input(
config, f"{Fore.LIGHTBLUE_EX}I want AutoGPT to{Style.RESET_ALL}: "
)
if (
await clean_input(app_config, "Continue with these settings? [Y/n]")
or app_config.authorise_key
) == app_config.authorise_key:
break
if user_desire.strip() == "":
user_desire = DEFAULT_USER_DESIRE_PROMPT # Default prompt
# If user desire contains "--manual" or we have overridden any of the AI configuration
if "--manual" in user_desire or ai_profile_template_provided:
user_friendly_output(
"",
title="Manual Mode Selected",
title_color=Fore.GREEN,
)
return await generate_aiconfig_manual(config, ai_profile_template)
else:
try:
return await generate_aiconfig_automatic(user_desire, config, llm_provider)
except Exception as e:
user_friendly_output(
title="Unable to automatically generate AI Config based on user desire.",
message="Falling back to manual mode.",
title_color=Fore.RED,
# Ask for revised ai_profile
ai_profile.ai_name = (
await clean_input(
app_config, "Enter AI name (or press enter to keep current):"
)
logger.debug(f"Error during AIProfile generation: {e}")
return await generate_aiconfig_manual(config)
async def generate_aiconfig_manual(
config: Config, ai_profile_template: Optional[AIProfile] = None
) -> AIProfile:
"""
Interactively create an AI configuration by prompting the user to provide the name, role, and goals of the AI.
This function guides the user through a series of prompts to collect the necessary information to create
an AIProfile object. The user will be asked to provide a name and role for the AI, as well as up to five
goals. If the user does not provide a value for any of the fields, default values will be used.
Params:
config (Config): The Config object
ai_profile_template (AIProfile): The AIProfile object to use as a template
Returns:
AIProfile: An AIProfile object containing the user-defined or default AI name, role, and goals.
"""
# Manual Setup Intro
user_friendly_output(
title="Create an AI-Assistant:",
message="Enter the name of your AI and its role below. Entering nothing will load"
" defaults.",
title_color=Fore.GREEN,
)
if ai_profile_template and ai_profile_template.ai_name:
ai_name = ai_profile_template.ai_name
else:
ai_name = ""
# Get AI Name from User
user_friendly_output(
title="Name your AI:",
message="For example, 'Entrepreneur-GPT'",
title_color=Fore.GREEN,
or ai_profile.ai_name
)
ai_name = await utils.clean_input(config, "AI Name: ")
if ai_name == "":
ai_name = "Entrepreneur-GPT"
user_friendly_output(
title=f"{ai_name} here!",
message="I am at your service.",
title_color=Fore.LIGHTBLUE_EX,
)
if ai_profile_template and ai_profile_template.ai_role:
ai_role = ai_profile_template.ai_role
else:
# Get AI Role from User
user_friendly_output(
title="Describe your AI's role:",
message="For example, 'an AI designed to autonomously develop and run businesses with"
" the sole goal of increasing your net worth.'",
title_color=Fore.GREEN,
)
ai_role = await utils.clean_input(config, f"{ai_name} is: ")
if ai_role == "":
ai_role = "an AI designed to autonomously develop and run businesses with the"
" sole goal of increasing your net worth."
if ai_profile_template and ai_profile_template.ai_goals:
ai_goals = ai_profile_template.ai_goals
else:
# Enter up to 5 goals for the AI
user_friendly_output(
title="Enter up to 5 goals for your AI:",
message="For example: \nIncrease net worth, Grow Twitter Account, Develop and manage"
" multiple businesses autonomously'",
title_color=Fore.GREEN,
)
logger.info("Enter nothing to load defaults, enter nothing when finished.")
ai_goals = []
for i in range(5):
ai_goal = await utils.clean_input(
config, f"{Fore.LIGHTBLUE_EX}Goal{Style.RESET_ALL} {i+1}: "
ai_profile.ai_role = (
await clean_input(
app_config, "Enter new AI role (or press enter to keep current):"
)
if ai_goal == "":
or ai_profile.ai_role
)
# Revise constraints
for i, constraint in enumerate(directives.constraints):
print_attribute(f"Constraint {i+1}:", f'"{constraint}"')
new_constraint = (
await clean_input(
app_config,
f"Enter new constraint {i+1} (press enter to keep current, or '-' to remove):",
)
or constraint
)
if new_constraint == "-":
directives.constraints.remove(constraint)
elif new_constraint:
directives.constraints[i] = new_constraint
# Add new constraints
while True:
new_constraint = await clean_input(
app_config,
"Press enter to finish, or enter a constraint to add:",
)
if not new_constraint:
break
ai_goals.append(ai_goal)
if not ai_goals:
ai_goals = [
"Increase net worth",
"Grow Twitter Account",
"Develop and manage multiple businesses autonomously",
]
directives.constraints.append(new_constraint)
# Get API Budget from User
user_friendly_output(
title="Enter your budget for API calls:",
message="For example: $1.50",
title_color=Fore.GREEN,
)
logger.info("Enter nothing to let the AI run without monetary limit")
api_budget_input = await utils.clean_input(
config, f"{Fore.LIGHTBLUE_EX}Budget{Style.RESET_ALL}: $"
)
if api_budget_input == "":
api_budget = 0.0
else:
try:
api_budget = float(api_budget_input.replace("$", ""))
except ValueError:
user_friendly_output(
level=logging.WARNING,
title="Invalid budget input.",
message="Setting budget to unlimited.",
title_color=Fore.RED,
# Revise resources
for i, resource in enumerate(directives.resources):
print_attribute(f"Resource {i+1}:", f'"{resource}"')
new_resource = (
await clean_input(
app_config,
f"Enter new resource {i+1} (press enter to keep current, or '-' to remove):",
)
or resource
)
api_budget = 0.0
if new_resource == "-":
directives.resources.remove(resource)
elif new_resource:
directives.resources[i] = new_resource
return AIProfile(
ai_name=ai_name, ai_role=ai_role, ai_goals=ai_goals, api_budget=api_budget
)
# Add new resources
while True:
new_resource = await clean_input(
app_config,
"Press enter to finish, or enter a resource to add:",
)
if not new_resource:
break
directives.resources.append(new_resource)
# Revise best practices
for i, best_practice in enumerate(directives.best_practices):
print_attribute(f"Best Practice {i+1}:", f'"{best_practice}"')
new_best_practice = (
await clean_input(
app_config,
f"Enter new best practice {i+1} (press enter to keep current, or '-' to remove):",
)
or best_practice
)
if new_best_practice == "-":
directives.best_practices.remove(best_practice)
elif new_best_practice:
directives.best_practices[i] = new_best_practice
# Add new best practices
while True:
new_best_practice = await clean_input(
app_config,
"Press enter to finish, or add a best practice to add:",
)
if not new_best_practice:
break
directives.best_practices.append(new_best_practice)
revised = True
return ai_profile, directives
async def generate_aiconfig_automatic(
user_prompt: str,
config: Config,
llm_provider: ChatModelProvider,
) -> AIProfile:
"""Generates an AIProfile object from the given string.
def print_ai_settings(
ai_profile: AIProfile,
directives: AIDirectives,
logger: logging.Logger,
title: str = "AI Settings",
):
print_attribute(title, "")
print_attribute("-" * len(title), "")
print_attribute("Name :", ai_profile.ai_name)
print_attribute("Role :", ai_profile.ai_role)
Returns:
AIProfile: The AIProfile object tailored to the user's input
"""
system_prompt = DEFAULT_SYSTEM_PROMPT_AICONFIG_AUTOMATIC
prompt_ai_profile_automatic = Template(
DEFAULT_TASK_PROMPT_AICONFIG_AUTOMATIC
).render(user_prompt=user_prompt)
# Call LLM with the string as user input
output = (
await llm_provider.create_chat_completion(
[
ChatMessage.system(system_prompt),
ChatMessage.user(prompt_ai_profile_automatic),
],
config.smart_llm,
)
).response["content"]
# Debug LLM Output
logger.debug(f"AI Config Generator Raw Output: {output}")
# Parse the output
ai_name = re.search(r"Name(?:\s*):(?:\s*)(.*)", output, re.IGNORECASE).group(1)
ai_role = (
re.search(
r"Description(?:\s*):(?:\s*)(.*?)(?:(?:\n)|Goals)",
output,
re.IGNORECASE | re.DOTALL,
)
.group(1)
.strip()
)
ai_goals = re.findall(r"(?<=\n)-\s*(.*)", output)
api_budget = 0.0 # TODO: parse api budget using a regular expression
return AIProfile(
ai_name=ai_name, ai_role=ai_role, ai_goals=ai_goals, api_budget=api_budget
)
print_attribute("Constraints:", "" if directives.constraints else "(none)")
for constraint in directives.constraints:
logger.info(f"- {constraint}")
print_attribute("Resources:", "" if directives.resources else "(none)")
for resource in directives.resources:
logger.info(f"- {resource}")
print_attribute("Best practices:", "" if directives.best_practices else "(none)")
for best_practice in directives.best_practices:
logger.info(f"- {best_practice}")

View File

@@ -39,3 +39,10 @@ class AIDirectives(BaseModel):
resources=config_params.get("resources", []),
best_practices=config_params.get("best_practices", []),
)
def __add__(self, other: "AIDirectives") -> "AIDirectives":
return AIDirectives(
resources=self.resources + other.resources,
constraints=self.constraints + other.constraints,
best_practices=self.best_practices + other.best_practices,
).copy(deep=True)

View File

@@ -2,78 +2,49 @@ from unittest.mock import patch
import pytest
from autogpt.app.setup import generate_aiconfig_automatic, interactive_ai_profile_setup
from autogpt.app.setup import (
apply_overrides_to_ai_settings,
interactively_revise_ai_settings,
)
from autogpt.config.ai_profile import AIProfile
from autogpt.config import AIDirectives, Config
@pytest.mark.vcr
@pytest.mark.requires_openai_api_key
async def test_generate_aiconfig_automatic_default(
patched_api_requestor, config, llm_provider
):
user_inputs = [""]
with patch("autogpt.app.utils.session.prompt", side_effect=user_inputs):
ai_profile = await interactive_ai_profile_setup(config, llm_provider)
@pytest.mark.asyncio
async def test_apply_overrides_to_ai_settings():
ai_profile = AIProfile(ai_name="Test AI", ai_role="Test Role")
directives = AIDirectives(resources=["Resource1"], constraints=["Constraint1"], best_practices=["BestPractice1"])
assert isinstance(ai_profile, AIProfile)
assert ai_profile.ai_name is not None
assert ai_profile.ai_role is not None
assert 1 <= len(ai_profile.ai_goals) <= 5
apply_overrides_to_ai_settings(ai_profile, directives, override_name="New AI", override_role="New Role", replace_directives=True, resources=["NewResource"], constraints=["NewConstraint"], best_practices=["NewBestPractice"])
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"]
@pytest.mark.vcr
@pytest.mark.requires_openai_api_key
async def test_generate_aiconfig_automatic_typical(
patched_api_requestor, config, llm_provider
):
user_prompt = "Help me create a rock opera about cybernetic giraffes"
ai_profile = await generate_aiconfig_automatic(user_prompt, config, llm_provider)
@pytest.mark.asyncio
async def test_interactively_revise_ai_settings(config: Config):
ai_profile = AIProfile(ai_name="Test AI", ai_role="Test Role")
directives = AIDirectives(resources=["Resource1"], constraints=["Constraint1"], best_practices=["BestPractice1"])
assert isinstance(ai_profile, AIProfile)
assert ai_profile.ai_name is not None
assert ai_profile.ai_role is not None
assert 1 <= len(ai_profile.ai_goals) <= 5
@pytest.mark.vcr
@pytest.mark.requires_openai_api_key
async def test_generate_aiconfig_automatic_fallback(
patched_api_requestor, config, llm_provider
):
user_inputs = [
"T&GF£OIBECC()!*",
"Chef-GPT",
"an AI designed to browse bake a cake.",
"Purchase ingredients",
"Bake a cake",
"y",
"New AI",
"New Role",
"NewConstraint",
"",
"NewResource",
"",
"NewBestPractice",
"",
]
with patch("autogpt.app.utils.session.prompt", side_effect=user_inputs):
ai_profile = await interactive_ai_profile_setup(config, llm_provider)
with patch("autogpt.app.utils.clean_input", side_effect=user_inputs):
ai_profile, directives = await interactively_revise_ai_settings(ai_profile, directives, config)
assert isinstance(ai_profile, AIProfile)
assert ai_profile.ai_name == "Chef-GPT"
assert ai_profile.ai_role == "an AI designed to browse bake a cake."
assert ai_profile.ai_goals == ["Purchase ingredients", "Bake a cake"]
@pytest.mark.vcr
@pytest.mark.requires_openai_api_key
async def test_prompt_user_manual_mode(patched_api_requestor, config, llm_provider):
user_inputs = [
"--manual",
"Chef-GPT",
"an AI designed to browse bake a cake.",
"Purchase ingredients",
"Bake a cake",
"",
"",
]
with patch("autogpt.app.utils.session.prompt", side_effect=user_inputs):
ai_profile = await interactive_ai_profile_setup(config, llm_provider)
assert isinstance(ai_profile, AIProfile)
assert ai_profile.ai_name == "Chef-GPT"
assert ai_profile.ai_role == "an AI designed to browse bake a cake."
assert ai_profile.ai_goals == ["Purchase ingredients", "Bake a cake"]
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"]