fix(agent): Fix & improve agent self-termination and resumption mechanism

- Add `AgentFinished` exception (subclass of `AgentTerminated`)
- Raise `AgentFinished` instead of `AgentTerminated` in `finish` method
- Remove resumption patch from PR #6990 in `BaseAgent`
- Clean up implementation of `finish` in `AgentProtocolServer`
- Add resumption mechanism in `run_auto_gpt` (main.py)
pull/7014/head
Reinier van der Leer 2024-03-13 20:42:12 +01:00
parent fd2c26188f
commit da4f013a5d
No known key found for this signature in database
GPG Key ID: CDC1180FDAE06193
5 changed files with 50 additions and 37 deletions

View File

@ -177,14 +177,6 @@ class BaseAgent(Configurable[BaseAgentSettings], ABC):
self.legacy_config = legacy_config
"""LEGACY: Monolithic application configuration."""
# In case the agent is resumed, cursor is set to the last episode
if self.event_history:
# To prevent errors, when the last action is "finish", we register a result
# And move cursor to the next action
if self.event_history.current_episode.action.name == "finish":
self.event_history.register_result(ActionSuccessResult())
self.event_history.cursor = len(self.event_history)
self.llm_provider = llm_provider
self.prompt_strategy = prompt_strategy

View File

@ -18,6 +18,10 @@ class AgentTerminated(AgentException):
"""The agent terminated or was terminated"""
class AgentFinished(AgentTerminated):
"""The agent self-terminated"""
class ConfigurationError(AgentException):
"""Error caused by invalid, incompatible or otherwise incorrect configuration"""

View File

@ -30,6 +30,7 @@ from sentry_sdk import set_user
from autogpt.agent_factory.configurators import configure_agent_with_state
from autogpt.agent_factory.generators import generate_agent_for_task
from autogpt.agent_manager import AgentManager
from autogpt.agents.utils.exceptions import AgentFinished
from autogpt.commands.system import finish
from autogpt.commands.user_interaction import ask_user
from autogpt.config import Config
@ -230,26 +231,6 @@ class AgentProtocolServer:
task=task, step=step, relative_path=path
)
if step.is_last and execute_command == finish.__name__:
assert execute_command_args
additional_output = {}
task_total_cost = agent.llm_provider.get_incurred_cost()
if task_total_cost > 0:
additional_output["task_total_cost"] = task_total_cost
logger.info(
f"Total LLM cost for task {task_id}: "
f"${round(task_total_cost, 2)}"
)
step = await self.db.update_step(
task_id=task_id,
step_id=step.step_id,
output=execute_command_args["reason"],
additional_output=additional_output,
)
return step
if execute_command == ask_user.__name__: # HACK
execute_result = ActionSuccessResult(outputs=user_input)
agent.event_history.register_result(execute_result)
@ -261,11 +242,31 @@ class AgentProtocolServer:
step_id=step.step_id,
status="running",
)
# Execute previously proposed action
execute_result = await agent.execute(
command_name=execute_command,
command_args=execute_command_args,
)
try:
# Execute previously proposed action
execute_result = await agent.execute(
command_name=execute_command,
command_args=execute_command_args,
)
except AgentFinished:
additional_output = {}
task_total_cost = agent.llm_provider.get_incurred_cost()
if task_total_cost > 0:
additional_output["task_total_cost"] = task_total_cost
logger.info(
f"Total LLM cost for task {task_id}: "
f"${round(task_total_cost, 2)}"
)
step = await self.db.update_step(
task_id=task_id,
step_id=step.step_id,
output=execute_command_args["reason"],
additional_output=additional_output,
)
await agent.save_state()
return step
else:
assert user_input
execute_result = await agent.execute(

View File

@ -16,8 +16,6 @@ from typing import TYPE_CHECKING, Optional
from colorama import Fore, Style
from forge.sdk.db import AgentDB
from autogpt.file_storage import FileStorageBackendName, get_storage
if TYPE_CHECKING:
from autogpt.agents.agent import Agent
@ -26,6 +24,7 @@ from autogpt.agent_factory.profile_generator import generate_agent_profile_for_t
from autogpt.agent_manager import AgentManager
from autogpt.agents import AgentThoughts, CommandArgs, CommandName
from autogpt.agents.utils.exceptions import AgentTerminated, InvalidAgentResponseError
from autogpt.commands.system import finish
from autogpt.config import (
AIDirectives,
AIProfile,
@ -35,8 +34,10 @@ from autogpt.config import (
)
from autogpt.core.resource.model_providers.openai import OpenAIProvider
from autogpt.core.runner.client_lib.utils import coroutine
from autogpt.file_storage import FileStorageBackendName, get_storage
from autogpt.logs.config import configure_chat_plugins, configure_logging
from autogpt.logs.helpers import print_attribute, speak
from autogpt.models.action_history import ActionInterruptedByHuman
from autogpt.plugins import scan_plugins
from scripts.install_plugin_deps import install_plugin_dependencies
@ -217,6 +218,21 @@ async def run_auto_gpt(
replace_directives=override_directives,
)
if (
agent.event_history.current_episode
and agent.event_history.current_episode.action.name == finish.__name__
and not agent.event_history.current_episode.result
):
# Agent was resumed after `finish` -> rewrite result of `finish` action
finish_reason = agent.event_history.current_episode.action.args["reason"]
print(f"Agent previously self-terminated; reason: '{finish_reason}'")
new_assignment = await clean_input(
config, "Please give a follow-up question or assignment:"
)
agent.event_history.register_result(
ActionInterruptedByHuman(feedback=new_assignment)
)
# If any of these are specified as arguments,
# assume the user doesn't want to revise them
if not any(

View File

@ -6,7 +6,7 @@ import logging
from typing import TYPE_CHECKING
from autogpt.agents.features.context import get_agent_context
from autogpt.agents.utils.exceptions import AgentTerminated, InvalidArgumentError
from autogpt.agents.utils.exceptions import AgentFinished, InvalidArgumentError
from autogpt.command_decorator import command
from autogpt.core.utils.json_schema import JSONSchema
@ -44,7 +44,7 @@ def finish(reason: str, agent: Agent) -> None:
A result string from create chat completion. A list of suggestions to
improve the code.
"""
raise AgentTerminated(reason)
raise AgentFinished(reason)
@command(