mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-10 23:58:06 -05:00
Integrate ActionHistory into Agent + BaseAgent
This commit is contained in:
@@ -24,6 +24,7 @@ from autogpt.logs.log_cycle import (
|
||||
LogCycleHandler,
|
||||
)
|
||||
from autogpt.models.agent_actions import (
|
||||
Action,
|
||||
ActionErrorResult,
|
||||
ActionInterruptedByHuman,
|
||||
ActionResult,
|
||||
@@ -111,8 +112,8 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
kwargs["append_messages"] = []
|
||||
kwargs["append_messages"].append(budget_msg)
|
||||
|
||||
# Include message history in base prompt
|
||||
kwargs["with_message_history"] = True
|
||||
# # Include message history in base prompt
|
||||
# kwargs["with_message_history"] = True
|
||||
|
||||
return super().construct_base_prompt(*args, **kwargs)
|
||||
|
||||
@@ -124,7 +125,7 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
self.ai_config.ai_name,
|
||||
self.created_at,
|
||||
self.cycle_count,
|
||||
self.history.raw(),
|
||||
self.message_history.raw(),
|
||||
FULL_MESSAGE_HISTORY_FILE_NAME,
|
||||
)
|
||||
self.log_cycle_handler.log_cycle(
|
||||
@@ -146,7 +147,7 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
|
||||
if command_name == "human_feedback":
|
||||
result = ActionInterruptedByHuman(user_input)
|
||||
self.history.add(
|
||||
self.message_history.add(
|
||||
"user",
|
||||
"I interrupted the execution of the command you proposed "
|
||||
f"to give you some feedback: {user_input}",
|
||||
@@ -190,10 +191,13 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
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
|
||||
# history_tlength = count_string_tokens(
|
||||
# str(self.message_history.summary_message()), self.llm.name
|
||||
# )
|
||||
history_tlength = count_string_tokens(
|
||||
self.event_history.generate_list(), self.llm.name
|
||||
)
|
||||
if result_tlength + memory_tlength > self.send_token_limit:
|
||||
if result_tlength + history_tlength > self.send_token_limit:
|
||||
result = ActionErrorResult(
|
||||
reason=f"Command {command_name} returned too much output. "
|
||||
"Do not execute this command again with the same arguments."
|
||||
@@ -209,7 +213,7 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
|
||||
# Check if there's a result from the command append it to the message
|
||||
if result.status == "success":
|
||||
self.history.add(
|
||||
self.message_history.add(
|
||||
"system",
|
||||
f"Command {command_name} returned: {result.results}",
|
||||
"action_result",
|
||||
@@ -225,7 +229,10 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
):
|
||||
message = message.rstrip(".") + f". {result.error.hint}"
|
||||
|
||||
self.history.add("system", message, "action_result")
|
||||
self.message_history.add("system", message, "action_result")
|
||||
|
||||
# Update action history
|
||||
self.event_history.register_result(result)
|
||||
|
||||
return result
|
||||
|
||||
@@ -264,6 +271,15 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
assistant_reply_dict,
|
||||
NEXT_ACTION_FILE_NAME,
|
||||
)
|
||||
|
||||
self.event_history.register_action(
|
||||
Action(
|
||||
name=command_name,
|
||||
args=arguments,
|
||||
reasoning=assistant_reply_dict["thoughts"]["reasoning"],
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from autogpt.llm.base import ChatModelResponse, ChatSequence, Message
|
||||
from autogpt.llm.providers.openai import OPEN_AI_CHAT_MODELS, get_openai_command_specs
|
||||
from autogpt.llm.utils import count_message_tokens, create_chat_completion
|
||||
from autogpt.memory.message_history import MessageHistory
|
||||
from autogpt.models.agent_actions import ActionResult
|
||||
from autogpt.models.agent_actions import ActionHistory, ActionResult
|
||||
from autogpt.prompts.generator import PromptGenerator
|
||||
from autogpt.prompts.prompt import DEFAULT_TRIGGERING_PROMPT
|
||||
|
||||
@@ -93,7 +93,9 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
defaults to 75% of `llm.max_tokens`.
|
||||
"""
|
||||
|
||||
self.history = MessageHistory(
|
||||
self.event_history = ActionHistory()
|
||||
|
||||
self.message_history = MessageHistory(
|
||||
self.llm,
|
||||
max_summary_tlength=summary_max_tlength or self.send_token_limit // 6,
|
||||
)
|
||||
@@ -177,6 +179,16 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
reserve_tokens: Number of tokens to reserve for content that is added later
|
||||
"""
|
||||
|
||||
if self.event_history:
|
||||
prepend_messages.append(
|
||||
Message(
|
||||
"system",
|
||||
"# Progress\n"
|
||||
"So far, the following things have happened:\n"
|
||||
f"{self.event_history.generate_list()}",
|
||||
)
|
||||
)
|
||||
|
||||
prompt = ChatSequence.for_model(
|
||||
self.llm.name,
|
||||
[Message("system", self.system_prompt)] + prepend_messages,
|
||||
@@ -184,7 +196,7 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
|
||||
if with_message_history:
|
||||
# Reserve tokens for messages to be appended later, if any
|
||||
reserve_tokens += self.history.max_summary_tlength
|
||||
reserve_tokens += self.message_history.max_summary_tlength
|
||||
if append_messages:
|
||||
reserve_tokens += count_message_tokens(append_messages, self.llm.name)
|
||||
|
||||
@@ -192,10 +204,10 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
# Trim remaining historical messages and add them to the running summary.
|
||||
history_start_index = len(prompt)
|
||||
trimmed_history = add_history_upto_token_limit(
|
||||
prompt, self.history, self.send_token_limit - reserve_tokens
|
||||
prompt, self.message_history, self.send_token_limit - reserve_tokens
|
||||
)
|
||||
if trimmed_history:
|
||||
new_summary_msg, _ = self.history.trim_messages(
|
||||
new_summary_msg, _ = self.message_history.trim_messages(
|
||||
list(prompt), self.config
|
||||
)
|
||||
prompt.insert(history_start_index, new_summary_msg)
|
||||
@@ -359,8 +371,8 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
# Save assistant reply to message history
|
||||
self.history.append(prompt[-1])
|
||||
self.history.add(
|
||||
self.message_history.append(prompt[-1])
|
||||
self.message_history.add(
|
||||
"assistant", llm_response.content, "ai_response"
|
||||
) # FIXME: support function calls
|
||||
|
||||
@@ -370,7 +382,7 @@ class BaseAgent(metaclass=ABCMeta):
|
||||
)
|
||||
except InvalidAgentResponseError as e:
|
||||
# TODO: tune this message
|
||||
self.history.add(
|
||||
self.message_history.add(
|
||||
"system",
|
||||
f"Your response could not be parsed: {e}"
|
||||
"\n\nRemember to only respond using the specified format above!",
|
||||
|
||||
@@ -285,7 +285,7 @@ class PlanningAgent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
|
||||
result_tlength = count_string_tokens(str(result), self.llm.name)
|
||||
memory_tlength = count_string_tokens(
|
||||
str(self.history.summary_message()), self.llm.name
|
||||
str(self.message_history.summary_message()), self.llm.name
|
||||
)
|
||||
if result_tlength + memory_tlength > self.send_token_limit:
|
||||
result = ActionErrorResult(
|
||||
@@ -303,7 +303,7 @@ class PlanningAgent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
|
||||
# Check if there's a result from the command append it to the message
|
||||
if result.status == "success":
|
||||
self.history.add(
|
||||
self.message_history.add(
|
||||
"system",
|
||||
f"Command {command_name} returned: {result.results}",
|
||||
"action_result",
|
||||
@@ -316,7 +316,7 @@ class PlanningAgent(ContextMixin, WorkspaceMixin, BaseAgent):
|
||||
and result.error.hint
|
||||
):
|
||||
message = message.rstrip(".") + f". {result.error.hint}"
|
||||
self.history.add("system", message, "action_result")
|
||||
self.message_history.add("system", message, "action_result")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Iterator, Literal, Optional
|
||||
|
||||
from autogpt.prompts.utils import format_numbered_list
|
||||
|
||||
|
||||
@dataclass
|
||||
class Action:
|
||||
@@ -20,7 +22,12 @@ class ActionSuccessResult:
|
||||
status: Literal["success"] = "success"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Action succeeded and returned: `{self.results}`"
|
||||
results = (
|
||||
f'"""{self.results}"""'
|
||||
if type(self.results) == str and any(s in self.results for s in ("\n", '"'))
|
||||
else f'"{self.results}"'
|
||||
)
|
||||
return f"Action succeeded, and returned: {results}"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -30,7 +37,7 @@ class ActionErrorResult:
|
||||
status: Literal["error"] = "error"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Action failed: `{self.reason}`"
|
||||
return f"Action failed: '{self.reason}'"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -50,9 +57,14 @@ class ActionHistory:
|
||||
|
||||
@dataclass
|
||||
class CycleRecord:
|
||||
action: Action | None
|
||||
action: Action
|
||||
result: ActionResult | None
|
||||
|
||||
def __str__(self):
|
||||
executed_action = f"You executed `{self.action.format_call()}`."
|
||||
action_result = f" Result: {self.result}" if self.result else ""
|
||||
return executed_action + action_result
|
||||
|
||||
cursor: int
|
||||
cycles: list[CycleRecord]
|
||||
|
||||
@@ -80,13 +92,11 @@ class ActionHistory:
|
||||
|
||||
def register_action(self, action: Action) -> None:
|
||||
if not self.current_record:
|
||||
self.cycles.append(self.CycleRecord(None, None))
|
||||
self.cycles.append(self.CycleRecord(action, None))
|
||||
assert self.current_record
|
||||
elif self.current_record.action:
|
||||
raise ValueError("Action for current cycle already set")
|
||||
|
||||
self.current_record.action = action
|
||||
|
||||
def register_result(self, result: ActionResult) -> None:
|
||||
if not self.current_record:
|
||||
raise RuntimeError("Cannot register result for cycle without action")
|
||||
@@ -94,3 +104,7 @@ class ActionHistory:
|
||||
raise ValueError("Result for current cycle already set")
|
||||
|
||||
self.current_record.result = result
|
||||
self.cursor = len(self.cycles)
|
||||
|
||||
def generate_list(self) -> str:
|
||||
return format_numbered_list(self.cycles)
|
||||
|
||||
@@ -13,6 +13,8 @@ if TYPE_CHECKING:
|
||||
from autogpt.config import AIConfig, AIDirectives, Config
|
||||
from autogpt.models.command_registry import CommandRegistry
|
||||
|
||||
from .utils import format_numbered_list
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -124,19 +126,6 @@ class PromptGenerator:
|
||||
if best_practice not in self.best_practices:
|
||||
self.best_practices.append(best_practice)
|
||||
|
||||
def _generate_numbered_list(self, items: list[str], start_at: int = 1) -> str:
|
||||
"""
|
||||
Generate a numbered list containing the given items.
|
||||
|
||||
Params:
|
||||
items (list): A list of items to be numbered.
|
||||
start_at (int, optional): The number to start the sequence with; defaults to 1.
|
||||
|
||||
Returns:
|
||||
str: The formatted numbered list.
|
||||
"""
|
||||
return "\n".join(f"{i}. {item}" for i, item in enumerate(items, start_at))
|
||||
|
||||
def construct_system_prompt(self, agent: BaseAgent) -> str:
|
||||
"""Constructs a system prompt containing the most important information for the AI.
|
||||
|
||||
@@ -257,15 +246,15 @@ class PromptGenerator:
|
||||
return [
|
||||
"## Constraints\n"
|
||||
"You operate within the following constraints:\n"
|
||||
f"{self._generate_numbered_list(self.constraints + additional_constraints)}",
|
||||
f"{format_numbered_list(self.constraints + additional_constraints)}",
|
||||
"## Resources\n"
|
||||
"You can leverage access to the following resources:\n"
|
||||
f"{self._generate_numbered_list(self.resources + additional_resources)}",
|
||||
f"{format_numbered_list(self.resources + additional_resources)}",
|
||||
"## Commands\n"
|
||||
"You have access to the following commands:\n"
|
||||
f"{self.list_commands(agent)}",
|
||||
"## Best practices\n"
|
||||
f"{self._generate_numbered_list(self.best_practices + additional_best_practices)}",
|
||||
f"{format_numbered_list(self.best_practices + additional_best_practices)}",
|
||||
]
|
||||
|
||||
def list_commands(self, agent: BaseAgent) -> str:
|
||||
@@ -284,6 +273,6 @@ class PromptGenerator:
|
||||
]
|
||||
|
||||
# Add commands from plugins etc.
|
||||
command_strings += [str(cmd) for cmd in self.commands]
|
||||
command_strings += [str(cmd) for cmd in self.commands.values()]
|
||||
|
||||
return self._generate_numbered_list(command_strings)
|
||||
return format_numbered_list(command_strings)
|
||||
|
||||
5
autogpt/prompts/utils.py
Normal file
5
autogpt/prompts/utils.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
def format_numbered_list(items: list[Any], start_at: int = 1) -> str:
|
||||
return "\n".join(f"{i}. {str(item)}" for i, item in enumerate(items, start_at))
|
||||
@@ -3,7 +3,7 @@ from autogpt.agents.agent import Agent, execute_command
|
||||
|
||||
def test_agent_initialization(agent: Agent):
|
||||
assert agent.ai_config.ai_name == "Base"
|
||||
assert agent.history.messages == []
|
||||
assert agent.message_history.messages == []
|
||||
assert agent.cycle_budget is None
|
||||
assert "You are Base" in agent.system_prompt
|
||||
|
||||
|
||||
Reference in New Issue
Block a user