Bypass MAX_ITERATIONS and MAX_BUDGET_PER_TASK on web GUI (#2697)

Closes #1493

Introduced TRAFFIC_CONTROL_STATE to allow OpenDevin to switch between normal traffic limiting mode and temporarily disabled mode.
This commit is contained in:
Boxuan Li
2024-06-30 13:19:45 -07:00
committed by GitHub
parent e24c52d060
commit 8dae1f9307
2 changed files with 55 additions and 14 deletions

View File

@@ -3,7 +3,7 @@ import traceback
from typing import Optional, Type
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.controller.state.state import TRAFFIC_CONTROL_STATE, State
from opendevin.core.config import config
from opendevin.core.exceptions import (
LLMMalformedActionError,
@@ -37,6 +37,10 @@ from opendevin.events.observation import (
MAX_ITERATIONS = config.max_iterations
MAX_BUDGET_PER_TASK = config.max_budget_per_task
# note: RESUME is only available on web GUI
TRAFFIC_CONTROL_REMINDER = (
"Please click on resume button if you'd like to continue, or start a new task."
)
class AgentController:
@@ -194,6 +198,14 @@ class AgentController:
if new_state == self.state.agent_state:
return
if (
self.state.agent_state == AgentState.PAUSED
and new_state == AgentState.RUNNING
and self.state.traffic_control_state == TRAFFIC_CONTROL_STATE.THROTTLING
):
# user intends to interrupt traffic control and let the task resume temporarily
self.state.traffic_control_state = TRAFFIC_CONTROL_STATE.PAUSED
self.state.agent_state = new_state
if new_state == AgentState.STOPPED or new_state == AgentState.ERROR:
self.reset_task()
@@ -208,6 +220,8 @@ class AgentController:
def get_agent_state(self):
"""Returns the current state of the agent task."""
if self.delegate is not None:
return self.delegate.get_agent_state()
return self.state.agent_state
async def start_delegate(self, action: AgentDelegateAction):
@@ -293,22 +307,35 @@ class AgentController:
f'{self.agent.name} LEVEL {self.state.delegate_level} STEP {self.state.iteration}',
extra={'msg_type': 'STEP'},
)
if self.state.iteration >= self.state.max_iterations:
await self.report_error('Agent reached maximum number of iterations')
await self.set_agent_state_to(AgentState.ERROR)
return
if self.max_budget_per_task is not None:
if self.state.iteration >= self.state.max_iterations:
if self.state.traffic_control_state == TRAFFIC_CONTROL_STATE.PAUSED:
logger.info(
'Hitting traffic control, temporarily resume upon user request'
)
self.state.traffic_control_state = TRAFFIC_CONTROL_STATE.NORMAL
else:
self.state.traffic_control_state = TRAFFIC_CONTROL_STATE.THROTTLING
await self.report_error(
f'Agent reached maximum number of iterations, task paused. {TRAFFIC_CONTROL_REMINDER}'
)
await self.set_agent_state_to(AgentState.PAUSED)
return
elif self.max_budget_per_task is not None:
current_cost = self.state.metrics.accumulated_cost
if current_cost > self.max_budget_per_task:
await self.report_error(
f'Task budget exceeded. Current cost: {current_cost:.2f}, Max budget: {self.max_budget_per_task:.2f}'
)
await self.set_agent_state_to(AgentState.ERROR)
return
if self.state.agent_state == AgentState.ERROR:
return
if self.state.traffic_control_state == TRAFFIC_CONTROL_STATE.PAUSED:
logger.info(
'Hitting traffic control, temporarily resume upon user request'
)
self.state.traffic_control_state = TRAFFIC_CONTROL_STATE.NORMAL
else:
self.state.traffic_control_state = TRAFFIC_CONTROL_STATE.THROTTLING
await self.report_error(
f'Task budget exceeded. Current cost: {current_cost:.2f}, Max budget: {self.max_budget_per_task:.2f}, task paused. {TRAFFIC_CONTROL_REMINDER}'
)
await self.set_agent_state_to(AgentState.PAUSED)
return
self.update_state_before_step()
action: Action = NullAction()

View File

@@ -1,6 +1,7 @@
import base64
import pickle
from dataclasses import dataclass, field
from enum import Enum
from opendevin.controller.state.task import RootTask
from opendevin.core.logger import opendevin_logger as logger
@@ -16,6 +17,18 @@ from opendevin.events.observation import (
)
from opendevin.storage import get_file_store
class TRAFFIC_CONTROL_STATE(str, Enum):
# default state, no rate limiting
NORMAL = 'normal'
# task paused due to traffic control
THROTTLING = 'throttling'
# traffic control is temporarily paused
PAUSED = 'paused'
RESUMABLE_STATES = [
AgentState.RUNNING,
AgentState.PAUSED,
@@ -37,6 +50,7 @@ class State:
last_error: str | None = None
agent_state: AgentState = AgentState.LOADING
resume_state: AgentState | None = None
traffic_control_state: TRAFFIC_CONTROL_STATE = TRAFFIC_CONTROL_STATE.NORMAL
metrics: Metrics = Metrics()
# root agent has level 0, and every delegate increases the level by one
delegate_level: int = 0