Skip to content

Commit

Permalink
Implement watchdog feature for dynamic switching between smart & fast…
Browse files Browse the repository at this point in the history
… LLMs
  • Loading branch information
Pwuts committed Aug 30, 2023
1 parent a4ef53c commit e437065
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 14 deletions.
8 changes: 4 additions & 4 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ OPENAI_API_KEY=your-openai-api-key
### LLM MODELS
################################################################################

## SMART_LLM - Smart language model (Default: gpt-4)
# SMART_LLM=gpt-4
## SMART_LLM - Smart language model (Default: gpt-4-0314)
# SMART_LLM=gpt-4-0314

## FAST_LLM - Fast language model (Default: gpt-3.5-turbo)
# FAST_LLM=gpt-3.5-turbo
## FAST_LLM - Fast language model (Default: gpt-3.5-turbo-16k)
# FAST_LLM=gpt-3.5-turbo-16k

## EMBEDDING_MODEL - Model to use for creating embeddings
# EMBEDDING_MODEL=text-embedding-ada-002
Expand Down
3 changes: 2 additions & 1 deletion autogpt/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

from .base import BaseAgent
from .features.context import ContextMixin
from .features.watchdog import WatchdogMixin
from .features.workspace import WorkspaceMixin
from .utils.exceptions import (
AgentException,
Expand All @@ -46,7 +47,7 @@
logger = logging.getLogger(__name__)


class Agent(ContextMixin, WorkspaceMixin, BaseAgent):
class Agent(ContextMixin, WorkspaceMixin, WatchdogMixin, BaseAgent):
"""Agent class for interacting with Auto-GPT."""

def __init__(
Expand Down
13 changes: 8 additions & 5 deletions autogpt/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

if TYPE_CHECKING:
from autogpt.config import AIConfig, Config
from autogpt.llm.base import ChatModelInfo, ChatModelResponse
from autogpt.models.command_registry import CommandRegistry

from autogpt.agents.utils.exceptions import InvalidAgentResponseError
from autogpt.config.ai_directives import AIDirectives
from autogpt.llm.base import ChatModelResponse, ChatSequence, Message
from autogpt.llm.base import 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
Expand Down Expand Up @@ -83,10 +84,6 @@ def __init__(
self.cycle_count = 0
"""The number of cycles that the agent has run since its initialization."""

llm_name = self.config.smart_llm if self.big_brain else self.config.fast_llm
self.llm = OPEN_AI_CHAT_MODELS[llm_name]
"""The LLM that the agent uses to think."""

self.send_token_limit = send_token_limit or self.llm.max_tokens * 3 // 4
"""
The token limit for prompt construction. Should leave room for the completion;
Expand All @@ -111,6 +108,12 @@ def system_prompt(self) -> str:
"""
return self.prompt_generator.construct_system_prompt(self)

@property
def llm(self) -> ChatModelInfo:
"""The LLM that the agent uses to think."""
llm_name = self.config.smart_llm if self.big_brain else self.config.fast_llm
return OPEN_AI_CHAT_MODELS[llm_name]

def think(
self,
instruction: Optional[str] = None,
Expand Down
59 changes: 59 additions & 0 deletions autogpt/agents/features/watchdog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from __future__ import annotations

import logging
from contextlib import ExitStack

from autogpt.models.agent_actions import ActionHistory

from ..base import BaseAgent

logger = logging.getLogger(__name__)


class WatchdogMixin:
"""
Mixin that adds a watchdog feature to an agent class. Whenever the agent starts
looping, the watchdog will switch from the FAST_LLM to the SMART_LLM and re-think.
"""

event_history: ActionHistory

def __init__(self, **kwargs) -> None:
# Initialize other bases first, because we need the event_history from BaseAgent
super(WatchdogMixin, self).__init__(**kwargs)

if not isinstance(self, BaseAgent):
raise NotImplementedError(
f"{__class__.__name__} can only be applied to BaseAgent derivatives"
)

def think(self, *args, **kwargs) -> BaseAgent.ThoughtProcessOutput:
command_name, command_args, thoughts = super(WatchdogMixin, self).think(
*args, **kwargs
)

if not self.big_brain and len(self.event_history) > 1:

This comment has been minimized.

Copy link
@Boostrix

Boostrix Oct 4, 2023

Contributor

Hi pwuts, am I missing something or is this much simpler than the originally proposed patch which was literally hashing all/most agent state ? I am only seeing command + params being taken into account here ? How would this work inside a valid situation, such as looping or re-trying some action that previously failed ?

This comment has been minimized.

Copy link
@Pwuts

Pwuts Nov 16, 2023

Author Member

You're mostly right. It doesn't account for "valid looping scenarios" currently, although it will only crack down on single-step loops (so directly repetitive commands). Multi-step loops are not caught or prohibited (which can both be good and bad).

For the cases I tested with, this worked well enough that I decided not to make it more complicated "than necessary". I'll be happy to reconsider if there are clear examples where this implementation breaks an effective workflow.

# Detect repetitive commands
previous_cycle = self.event_history.cycles[self.event_history.cursor - 1]
if (
command_name == previous_cycle.action.name
and command_args == previous_cycle.action.args
):
logger.info(
f"Repetitive command detected ({command_name}), re-thinking with SMART_LLM..."
)
with ExitStack() as stack:

@stack.callback
def restore_state() -> None:
# Executed after exiting the ExitStack context
self.big_brain = False

# Remove partial record of current cycle
self.event_history.rewind()

# Switch to SMART_LLM and re-think
self.big_brain = True
return self.think(*args, **kwargs)

return command_name, command_args, thoughts
2 changes: 1 addition & 1 deletion autogpt/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Config(SystemSettings, arbitrary_types_allowed=True):
workspace_path: Optional[Path] = None
file_logger_path: Optional[Path] = None
# Model configuration
fast_llm: str = "gpt-3.5-turbo"
fast_llm: str = "gpt-3.5-turbo-16k"
smart_llm: str = "gpt-4-0314"
temperature: float = 0
openai_functions: bool = False
Expand Down
17 changes: 17 additions & 0 deletions autogpt/models/agent_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ def register_result(self, result: ActionResult) -> None:
self.current_record.result = result
self.cursor = len(self.cycles)

def rewind(self, number_of_cycles: int = 0) -> None:
"""Resets the history to an earlier state.
Params:
number_of_cycles (int): The number of cycles to rewind. Default is 0.
When set to 0, it will only reset the current cycle.
"""
# Remove partial record of current cycle
if self.current_record:
if self.current_record.action and not self.current_record.result:
self.cycles.pop(self.cursor)

# Rewind the specified number of cycles
if number_of_cycles > 0:
self.cycles = self.cycles[:-number_of_cycles]
self.cursor = len(self.cycles)

def fmt_list(self) -> str:
return format_numbered_list(self.cycles)

Expand Down
8 changes: 5 additions & 3 deletions autogpt/models/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ def __init__(
def __call__(self, *args, agent: BaseAgent, **kwargs) -> Any:
if callable(self.enabled) and not self.enabled(agent.config):
if self.disabled_reason:
return f"Command '{self.name}' is disabled: {self.disabled_reason}"
return f"Command '{self.name}' is disabled"
raise RuntimeError(
f"Command '{self.name}' is disabled: {self.disabled_reason}"
)
raise RuntimeError(f"Command '{self.name}' is disabled")

if callable(self.available) and not self.available(agent):
return f"Command '{self.name}' is not available"
raise RuntimeError(f"Command '{self.name}' is not available")

return self.method(*args, **kwargs, agent=agent)

Expand Down

0 comments on commit e437065

Please sign in to comment.