mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user