Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent loop v2: Planning & Task Management (part 2) #5077

Merged
merged 31 commits into from
Aug 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b59f6ce
Add categories to command registry
Pwuts Jul 26, 2023
1390c83
Fix tests
Pwuts Jul 26, 2023
b2772b8
Merge branch 'command-categories' into performance/planning
Pwuts Jul 26, 2023
2b81524
Clean up prompt generation
Pwuts Jul 27, 2023
38c6fb6
Add debug logging to AIConfig autogeneration
Pwuts Jul 27, 2023
031519b
Clarify prompting and add support for multiple thought processes to A…
Pwuts Jul 29, 2023
f06b756
Merge branch 'master' into cleanup-prompt-generation
Pwuts Jul 29, 2023
63c08c0
Merge branch 'master' into performance/planning
Pwuts Jul 29, 2023
f317cd7
Merge branch 'cleanup-prompt-generation' into performance/planning
Pwuts Jul 29, 2023
e811586
WIP: PlanningAgent
Pwuts Jul 29, 2023
c1d759d
Merge branch 'master' into performance/planning
Pwuts Jul 30, 2023
3d7bdd7
Disable message history by default on BaseAgent
Pwuts Jul 30, 2023
248a43d
Add CommandOutput and ThoughtProcessOutput type aliases
Pwuts Jul 30, 2023
b72bc87
Fix interrupts in main.py
Pwuts Jul 30, 2023
5b4c33a
Use custom exceptions and clean up exception/error handling
Pwuts Jul 30, 2023
e6d0cdb
Merge branch 'structured-thought-processing' into performance/planning
Pwuts Jul 30, 2023
67efb0c
Remove duplicate agent_history.py
Pwuts Jul 30, 2023
bcfdbfa
Update PlanningAgent from upstream
Pwuts Jul 30, 2023
191c3ef
WIP: Support for dynamic in-prompt context
Pwuts Jul 31, 2023
a1d2e5f
Merge branch 'master' into structured-thought-processing
Pwuts Jul 31, 2023
b9d15b6
WIP: response formats for PlanningAgent three-stage cycle
Pwuts Jul 31, 2023
304ec77
Merge branch 'master' into performance/planning
Pwuts Aug 9, 2023
548888a
Remove browsing overlay & separate browsing from extraction code
Pwuts Aug 9, 2023
ffc478d
Merge branch 'master' into structured-thought-processing
Pwuts Aug 9, 2023
fdfc6c3
Fix human feedback
Pwuts Aug 10, 2023
7b146a0
Fix tests
Pwuts Aug 10, 2023
f9cb030
Merge branch 'structured-thought-processing' into performance/planning
Pwuts Aug 10, 2023
fd1e868
Include history in Agent prompt generation
Pwuts Aug 14, 2023
c89bf58
Code improvements in agent.py
Pwuts Aug 15, 2023
319ad2e
Add ask_user command and revise system prompt
Pwuts Aug 19, 2023
fb8205e
Merge branch 'master' into performance/planning
Pwuts Aug 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion agbenchmark/benchmarks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import sys
from pathlib import Path
from typing import Tuple
Expand Down
217 changes: 139 additions & 78 deletions autogpt/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import json
import time
from datetime import datetime
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
from autogpt.config import AIConfig, Config
from autogpt.llm.base import ChatModelResponse, ChatSequence
from autogpt.memory.vector import VectorMemory
from autogpt.models.command_registry import CommandRegistry

from autogpt.agents.utils.exceptions import (
AgentException,
CommandExecutionError,
InvalidAgentResponseError,
UnknownCommandError,
)
from autogpt.json_utils.utilities import extract_dict_from_response, validate_dict
from autogpt.llm.api_manager import ApiManager
from autogpt.llm.base import Message
Expand All @@ -23,9 +29,17 @@
USER_INPUT_FILE_NAME,
LogCycleHandler,
)
from autogpt.models.agent_actions import (
ActionErrorResult,
ActionInterruptedByHuman,
ActionResult,
ActionSuccessResult,
)
from autogpt.models.command import CommandOutput
from autogpt.models.context_item import ContextItem
from autogpt.workspace import Workspace

from .base import AgentThoughts, BaseAgent, CommandArgs, CommandName
from .base import BaseAgent


class Agent(BaseAgent):
Expand Down Expand Up @@ -97,6 +111,9 @@
kwargs["append_messages"] = []
kwargs["append_messages"].append(budget_msg)

# Include message history in base prompt
kwargs["with_message_history"] = True

return super().construct_base_prompt(*args, **kwargs)

def on_before_think(self, *args, **kwargs) -> ChatSequence:
Expand All @@ -121,15 +138,19 @@

def execute(
self,
command_name: str | None,
command_args: dict[str, str] | None,
user_input: str | None,
) -> str:
# Execute command
if command_name is not None and command_name.lower().startswith("error"):
result = f"Could not execute command: {command_name}{command_args}"
elif command_name == "human_feedback":
result = f"Human feedback: {user_input}"
command_name: str,
command_args: dict[str, str] = {},
user_input: str = "",
) -> ActionResult:
result: ActionResult

if command_name == "human_feedback":
result = ActionInterruptedByHuman(user_input)
self.history.add(

Check warning on line 149 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L148-L149

Added lines #L148 - L149 were not covered by tests
"user",
"I interrupted the execution of the command you proposed "
f"to give you some feedback: {user_input}",
)
self.log_cycle_handler.log_cycle(
self.ai_config.ai_name,
self.created_at,
Expand All @@ -143,65 +164,101 @@
if not plugin.can_handle_pre_command():
continue
command_name, arguments = plugin.pre_command(command_name, command_args)
command_result = execute_command(
command_name=command_name,
arguments=command_args,
agent=self,
)
result = f"Command {command_name} returned: " f"{command_result}"

result_tlength = count_string_tokens(str(command_result), self.llm.name)
try:
return_value = execute_command(
command_name=command_name,
arguments=command_args,
agent=self,
)

# Intercept ContextItem if one is returned by the command
if type(return_value) == tuple and isinstance(
return_value[1], ContextItem
):
context_item = return_value[1]

Check warning on line 179 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L179

Added line #L179 was not covered by tests
# return_value = return_value[0]
logger.debug(

Check warning on line 181 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L181

Added line #L181 was not covered by tests
f"Command {command_name} returned a ContextItem: {context_item}"
)
# self.context.add(context_item)

# HACK: use content of ContextItem as return value, for legacy support
return_value = context_item.content

Check warning on line 187 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L187

Added line #L187 was not covered by tests

result = ActionSuccessResult(return_value)
except AgentException as e:
result = ActionErrorResult(e.message, e)

Check warning on line 191 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L191

Added line #L191 was not covered by tests

logger.debug(f"Command result: {result}")

result_tlength = count_string_tokens(str(result), self.llm.name)
memory_tlength = count_string_tokens(
str(self.history.summary_message()), self.llm.name
)
if result_tlength + memory_tlength > self.send_token_limit:
result = f"Failure: command {command_name} returned too much output. \
Do not execute this command again with the same arguments."
result = ActionErrorResult(

Check warning on line 200 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L200

Added line #L200 was not covered by tests
reason=f"Command {command_name} returned too much output. "
"Do not execute this command again with the same arguments."
)

for plugin in self.config.plugins:
if not plugin.can_handle_post_command():
continue
result = plugin.post_command(command_name, result)
if result.status == "success":
result.results = plugin.post_command(command_name, result.results)

Check warning on line 209 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L209

Added line #L209 was not covered by tests
elif result.status == "error":
result.reason = plugin.post_command(command_name, result.reason)

Check warning on line 211 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L211

Added line #L211 was not covered by tests

# Check if there's a result from the command append it to the message
if result is None:
self.history.add("system", "Unable to execute command", "action_result")
else:
self.history.add("system", result, "action_result")
if result.status == "success":
self.history.add(
"system",
f"Command {command_name} returned: {result.results}",
"action_result",
)
elif result.status == "error":
message = f"Command {command_name} failed: {result.reason}"

Check warning on line 221 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L221

Added line #L221 was not covered by tests

# Append hint to the error message if the exception has a hint
if (
result.error
and isinstance(result.error, AgentException)
and result.error.hint
):
message = message.rstrip(".") + f". {result.error.hint}"

Check warning on line 229 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L229

Added line #L229 was not covered by tests

self.history.add("system", message, "action_result")

Check warning on line 231 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L231

Added line #L231 was not covered by tests

return result

def parse_and_process_response(
self, llm_response: ChatModelResponse, *args, **kwargs
) -> tuple[CommandName | None, CommandArgs | None, AgentThoughts]:
) -> Agent.ThoughtProcessOutput:
if not llm_response.content:
raise SyntaxError("Assistant response has no text content")
raise InvalidAgentResponseError("Assistant response has no text content")

Check warning on line 239 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L239

Added line #L239 was not covered by tests

assistant_reply_dict = extract_dict_from_response(llm_response.content)

valid, errors = validate_dict(assistant_reply_dict, self.config)
if not valid:
raise SyntaxError(
"Validation of response failed:\n "
+ ";\n ".join([str(e) for e in errors])
)
response_content = llm_response.content

for plugin in self.config.plugins:
if not plugin.can_handle_post_planning():
continue
assistant_reply_dict = plugin.post_planning(assistant_reply_dict)
response_content = plugin.post_planning(response_content)

Check warning on line 246 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L246

Added line #L246 was not covered by tests

response = None, None, assistant_reply_dict
assistant_reply_dict = extract_dict_from_response(response_content)

# Print Assistant thoughts
if assistant_reply_dict != {}:
# Get command name and arguments
try:
command_name, arguments = extract_command(
assistant_reply_dict, llm_response, self.config
)
response = command_name, arguments, assistant_reply_dict
except Exception as e:
logger.error("Error: \n", str(e))
_, errors = validate_dict(assistant_reply_dict, self.config)
if errors:
raise InvalidAgentResponseError(
"Validation of response failed:\n "
+ ";\n ".join([str(e) for e in errors])
)

# Get command name and arguments
command_name, arguments = extract_command(
assistant_reply_dict, llm_response, self.config
)
response = command_name, arguments, assistant_reply_dict

self.log_cycle_handler.log_cycle(
self.ai_config.ai_name,
Expand Down Expand Up @@ -233,48 +290,46 @@
"""
if config.openai_functions:
if assistant_reply.function_call is None:
return "Error:", {"message": "No 'function_call' in assistant reply"}
raise InvalidAgentResponseError("No 'function_call' in assistant reply")

Check warning on line 293 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L293

Added line #L293 was not covered by tests
assistant_reply_json["command"] = {
"name": assistant_reply.function_call.name,
"args": json.loads(assistant_reply.function_call.arguments),
}
try:
if "command" not in assistant_reply_json:
return "Error:", {"message": "Missing 'command' object in JSON"}

if not isinstance(assistant_reply_json, dict):
return (
"Error:",
{
"message": f"The previous message sent was not a dictionary {assistant_reply_json}"
},
raise InvalidAgentResponseError(

Check warning on line 300 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L300

Added line #L300 was not covered by tests
f"The previous message sent was not a dictionary {assistant_reply_json}"
)

if "command" not in assistant_reply_json:
raise InvalidAgentResponseError("Missing 'command' object in JSON")

Check warning on line 305 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L305

Added line #L305 was not covered by tests

command = assistant_reply_json["command"]
if not isinstance(command, dict):
return "Error:", {"message": "'command' object is not a dictionary"}
raise InvalidAgentResponseError("'command' object is not a dictionary")

Check warning on line 309 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L309

Added line #L309 was not covered by tests

if "name" not in command:
return "Error:", {"message": "Missing 'name' field in 'command' object"}
raise InvalidAgentResponseError("Missing 'name' field in 'command' object")

Check warning on line 312 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L312

Added line #L312 was not covered by tests

command_name = command["name"]

# Use an empty dictionary if 'args' field is not present in 'command' object
arguments = command.get("args", {})

return command_name, arguments

except json.decoder.JSONDecodeError:
return "Error:", {"message": "Invalid JSON"}
# All other errors, return "Error: + error message"
raise InvalidAgentResponseError("Invalid JSON")

Check warning on line 322 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L322

Added line #L322 was not covered by tests

except Exception as e:
return "Error:", {"message": str(e)}
raise InvalidAgentResponseError(str(e))

Check warning on line 325 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L325

Added line #L325 was not covered by tests


def execute_command(
command_name: str,
arguments: dict[str, str],
agent: Agent,
) -> Any:
) -> CommandOutput:
"""Execute the command and return the result

Args:
Expand All @@ -285,22 +340,28 @@
Returns:
str: The result of the command
"""
try:
# Execute a native command with the same name or alias, if it exists
if command := agent.command_registry.get_command(command_name):
# Execute a native command with the same name or alias, if it exists
if command := agent.command_registry.get_command(command_name):
try:
return command(**arguments, agent=agent)

# Handle non-native commands (e.g. from plugins)
for command in agent.ai_config.prompt_generator.commands:
if (
command_name == command.label.lower()
or command_name == command.name.lower()
):
except AgentException:
raise

Check warning on line 348 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L348

Added line #L348 was not covered by tests
except Exception as e:
raise CommandExecutionError(str(e))

Check warning on line 350 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L350

Added line #L350 was not covered by tests

# Handle non-native commands (e.g. from plugins)
for command in agent.ai_config.prompt_generator.commands:
if (
command_name == command.label.lower()
or command_name == command.name.lower()
):
try:
return command.function(**arguments)
except AgentException:
raise
except Exception as e:
raise CommandExecutionError(str(e))

Check warning on line 363 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L361-L363

Added lines #L361 - L363 were not covered by tests

raise RuntimeError(
f"Cannot execute '{command_name}': unknown command."
" Do not try to use this command again."
)
except Exception as e:
return f"Error: {str(e)}"
raise UnknownCommandError(

Check warning on line 365 in autogpt/agents/agent.py

View check run for this annotation

Codecov / codecov/patch

autogpt/agents/agent.py#L365

Added line #L365 was not covered by tests
f"Cannot execute command '{command_name}': unknown command."
)
Loading
Loading