Compare commits

...

1 Commits

Author SHA1 Message Date
openhands
0b5200e632 Add reason field to AgentStateChangedObservation for ERROR state 2025-03-29 18:53:12 +00:00
4 changed files with 17 additions and 7 deletions

View File

@@ -228,7 +228,8 @@ class AgentController:
e: Exception,
):
"""React to an exception by setting the agent state to error and sending a status message."""
await self.set_agent_state_to(AgentState.ERROR)
error_message = type(e).__name__ + ': ' + str(e)
if self.status_callback is not None:
err_id = ''
if isinstance(e, AuthenticationError):
@@ -249,7 +250,9 @@ class AgentController:
elif isinstance(e, RateLimitError):
await self.set_agent_state_to(AgentState.RATE_LIMITED)
return
self.status_callback('error', err_id, type(e).__name__ + ': ' + str(e))
self.status_callback('error', err_id, error_message)
await self.set_agent_state_to(AgentState.ERROR, reason=error_message)
def step(self):
asyncio.create_task(self._step_with_exception_handling())
@@ -524,11 +527,12 @@ class AgentController:
self._pending_action = None
self.agent.reset()
async def set_agent_state_to(self, new_state: AgentState) -> None:
async def set_agent_state_to(self, new_state: AgentState, reason: str = "") -> None:
"""Updates the agent's state and handles side effects. Can emit events to the event stream.
Args:
new_state (AgentState): The new state to set for the agent.
reason (str, optional): The reason for the state change, particularly useful for ERROR state.
"""
self.log(
'info',
@@ -538,6 +542,10 @@ class AgentController:
if new_state == self.state.agent_state:
return
# Store error reason if provided
if new_state == AgentState.ERROR and reason:
self.state.last_error = reason
if new_state in (AgentState.STOPPED, AgentState.ERROR):
# sync existing metrics BEFORE resetting the agent
await self.update_state_after_step()
@@ -583,7 +591,7 @@ class AgentController:
self.state.agent_state = new_state
self.event_stream.add_event(
AgentStateChangedObservation('', self.state.agent_state),
AgentStateChangedObservation('', self.state.agent_state, reason=reason if new_state == AgentState.ERROR else ""),
EventSource.ENVIRONMENT,
)

View File

@@ -23,8 +23,7 @@ async def run_agent_until_done(
if msg_type == 'error':
logger.error(msg)
if controller:
controller.state.last_error = msg
asyncio.create_task(controller.set_agent_state_to(AgentState.ERROR))
asyncio.create_task(controller.set_agent_state_to(AgentState.ERROR, reason=msg))
else:
logger.info(msg)

View File

@@ -10,10 +10,13 @@ class AgentStateChangedObservation(Observation):
"""This data class represents the result from delegating to another agent"""
agent_state: str
reason: str = ""
observation: str = ObservationType.AGENT_STATE_CHANGED
@property
def message(self) -> str:
if self.agent_state == "error" and self.reason:
return f"Error: {self.reason}"
return ''

View File

@@ -267,7 +267,7 @@ class Session:
agent_session = self.agent_session
controller = self.agent_session.controller
if controller is not None and not agent_session.is_closed():
await controller.set_agent_state_to(AgentState.ERROR)
await controller.set_agent_state_to(AgentState.ERROR, reason=message)
self.logger.info(
'Agent status error',
extra={'signal': 'agent_status_error'},