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
parent
fd2c26188f
commit
da4f013a5d
|
@ -177,14 +177,6 @@ class BaseAgent(Configurable[BaseAgentSettings], ABC):
|
||||||
self.legacy_config = legacy_config
|
self.legacy_config = legacy_config
|
||||||
"""LEGACY: Monolithic application configuration."""
|
"""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.llm_provider = llm_provider
|
||||||
|
|
||||||
self.prompt_strategy = prompt_strategy
|
self.prompt_strategy = prompt_strategy
|
||||||
|
|
|
@ -18,6 +18,10 @@ class AgentTerminated(AgentException):
|
||||||
"""The agent terminated or was terminated"""
|
"""The agent terminated or was terminated"""
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFinished(AgentTerminated):
|
||||||
|
"""The agent self-terminated"""
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationError(AgentException):
|
class ConfigurationError(AgentException):
|
||||||
"""Error caused by invalid, incompatible or otherwise incorrect configuration"""
|
"""Error caused by invalid, incompatible or otherwise incorrect configuration"""
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ from sentry_sdk import set_user
|
||||||
from autogpt.agent_factory.configurators import configure_agent_with_state
|
from autogpt.agent_factory.configurators import configure_agent_with_state
|
||||||
from autogpt.agent_factory.generators import generate_agent_for_task
|
from autogpt.agent_factory.generators import generate_agent_for_task
|
||||||
from autogpt.agent_manager import AgentManager
|
from autogpt.agent_manager import AgentManager
|
||||||
|
from autogpt.agents.utils.exceptions import AgentFinished
|
||||||
from autogpt.commands.system import finish
|
from autogpt.commands.system import finish
|
||||||
from autogpt.commands.user_interaction import ask_user
|
from autogpt.commands.user_interaction import ask_user
|
||||||
from autogpt.config import Config
|
from autogpt.config import Config
|
||||||
|
@ -230,26 +231,6 @@ class AgentProtocolServer:
|
||||||
task=task, step=step, relative_path=path
|
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
|
if execute_command == ask_user.__name__: # HACK
|
||||||
execute_result = ActionSuccessResult(outputs=user_input)
|
execute_result = ActionSuccessResult(outputs=user_input)
|
||||||
agent.event_history.register_result(execute_result)
|
agent.event_history.register_result(execute_result)
|
||||||
|
@ -261,11 +242,31 @@ class AgentProtocolServer:
|
||||||
step_id=step.step_id,
|
step_id=step.step_id,
|
||||||
status="running",
|
status="running",
|
||||||
)
|
)
|
||||||
# Execute previously proposed action
|
|
||||||
execute_result = await agent.execute(
|
try:
|
||||||
command_name=execute_command,
|
# Execute previously proposed action
|
||||||
command_args=execute_command_args,
|
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:
|
else:
|
||||||
assert user_input
|
assert user_input
|
||||||
execute_result = await agent.execute(
|
execute_result = await agent.execute(
|
||||||
|
|
|
@ -16,8 +16,6 @@ from typing import TYPE_CHECKING, Optional
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
from forge.sdk.db import AgentDB
|
from forge.sdk.db import AgentDB
|
||||||
|
|
||||||
from autogpt.file_storage import FileStorageBackendName, get_storage
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from autogpt.agents.agent import Agent
|
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.agent_manager import AgentManager
|
||||||
from autogpt.agents import AgentThoughts, CommandArgs, CommandName
|
from autogpt.agents import AgentThoughts, CommandArgs, CommandName
|
||||||
from autogpt.agents.utils.exceptions import AgentTerminated, InvalidAgentResponseError
|
from autogpt.agents.utils.exceptions import AgentTerminated, InvalidAgentResponseError
|
||||||
|
from autogpt.commands.system import finish
|
||||||
from autogpt.config import (
|
from autogpt.config import (
|
||||||
AIDirectives,
|
AIDirectives,
|
||||||
AIProfile,
|
AIProfile,
|
||||||
|
@ -35,8 +34,10 @@ from autogpt.config import (
|
||||||
)
|
)
|
||||||
from autogpt.core.resource.model_providers.openai import OpenAIProvider
|
from autogpt.core.resource.model_providers.openai import OpenAIProvider
|
||||||
from autogpt.core.runner.client_lib.utils import coroutine
|
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.config import configure_chat_plugins, configure_logging
|
||||||
from autogpt.logs.helpers import print_attribute, speak
|
from autogpt.logs.helpers import print_attribute, speak
|
||||||
|
from autogpt.models.action_history import ActionInterruptedByHuman
|
||||||
from autogpt.plugins import scan_plugins
|
from autogpt.plugins import scan_plugins
|
||||||
from scripts.install_plugin_deps import install_plugin_dependencies
|
from scripts.install_plugin_deps import install_plugin_dependencies
|
||||||
|
|
||||||
|
@ -217,6 +218,21 @@ async def run_auto_gpt(
|
||||||
replace_directives=override_directives,
|
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,
|
# If any of these are specified as arguments,
|
||||||
# assume the user doesn't want to revise them
|
# assume the user doesn't want to revise them
|
||||||
if not any(
|
if not any(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from autogpt.agents.features.context import get_agent_context
|
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.command_decorator import command
|
||||||
from autogpt.core.utils.json_schema import JSONSchema
|
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
|
A result string from create chat completion. A list of suggestions to
|
||||||
improve the code.
|
improve the code.
|
||||||
"""
|
"""
|
||||||
raise AgentTerminated(reason)
|
raise AgentFinished(reason)
|
||||||
|
|
||||||
|
|
||||||
@command(
|
@command(
|
||||||
|
|
Loading…
Reference in New Issue