Handle error observations in codeact (#3383)

* Handle error observations in codeact

* Remove comments
This commit is contained in:
Graham Neubig
2024-08-14 09:47:31 -04:00
committed by GitHub
parent 19bc06198d
commit 7d331acffa
5 changed files with 125 additions and 5 deletions

View File

@@ -22,6 +22,7 @@ from opendevin.events.observation import (
CmdOutputObservation,
IPythonRunCellObservation,
)
from opendevin.events.observation.error import ErrorObservation
from opendevin.events.observation.observation import Observation
from opendevin.events.serialization.event import truncate_content
from opendevin.llm.llm import LLM
@@ -169,7 +170,14 @@ class CodeActAgent(Agent):
str(obs.outputs), max_message_chars
)
return Message(role='user', content=[TextContent(text=text)])
return None
elif isinstance(obs, ErrorObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += '\n[Error occurred in processing last action]'
return Message(role='user', content=[TextContent(text=text)])
else:
# If an observation message is not returned, it will cause an error
# when the LLM tries to return the next message
raise ValueError(f'Unknown observation type: {type(obs)}')
def reset(self) -> None:
"""Resets the CodeAct Agent."""

View File

@@ -176,11 +176,11 @@ class AgentController:
elif isinstance(event, ModifyTaskAction):
self.state.root_task.set_subtask_state(event.task_id, event.state)
elif isinstance(event, AgentFinishAction):
self.state.outputs = event.outputs # type: ignore[attr-defined]
self.state.outputs = event.outputs
self.state.metrics.merge(self.state.local_metrics)
await self.set_agent_state_to(AgentState.FINISHED)
elif isinstance(event, AgentRejectAction):
self.state.outputs = event.outputs # type: ignore[attr-defined]
self.state.outputs = event.outputs
self.state.metrics.merge(self.state.local_metrics)
await self.set_agent_state_to(AgentState.REJECTED)
elif isinstance(event, Observation):

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass, field
from typing import Any
from opendevin.core.schema import ActionType
@@ -35,7 +36,15 @@ class AgentSummarizeAction(Action):
@dataclass
class AgentFinishAction(Action):
outputs: dict = field(default_factory=dict)
"""An action where the agent finishes the task.
Attributes:
outputs (dict): The outputs of the agent, for instance "content".
thought (str): The agent's explanation of its actions.
action (str): The action type, namely ActionType.FINISH.
"""
outputs: dict[str, Any] = field(default_factory=dict)
thought: str = ''
action: str = ActionType.FINISH

View File

@@ -7,7 +7,13 @@ from .observation import Observation
@dataclass
class AgentDelegateObservation(Observation):
"""This data class represents the result from delegating to another agent"""
"""This data class represents the result from delegating to another agent.
Attributes:
content (str): The content of the observation.
outputs (dict): The outputs of the delegated agent.
observation (str): The type of observation.
"""
outputs: dict
observation: str = ObservationType.DELEGATE

View File

@@ -0,0 +1,97 @@
from unittest.mock import Mock
import pytest
from agenthub.codeact_agent.codeact_agent import CodeActAgent
from opendevin.core.config import LLMConfig
from opendevin.core.message import TextContent
from opendevin.events.observation.commands import (
CmdOutputObservation,
IPythonRunCellObservation,
)
from opendevin.events.observation.delegate import AgentDelegateObservation
from opendevin.events.observation.error import ErrorObservation
from opendevin.llm.llm import LLM
@pytest.fixture
def agent() -> CodeActAgent:
agent = CodeActAgent(llm=LLM(LLMConfig()))
agent.llm = Mock()
agent.llm.config = Mock()
agent.llm.config.max_message_chars = 100
return agent
def test_cmd_output_observation_message(agent: CodeActAgent):
obs = CmdOutputObservation(
command='echo hello', content='Command output', command_id=1, exit_code=0
)
result = agent.get_observation_message(obs)
assert result is not None
assert result.role == 'user'
assert len(result.content) == 1
assert isinstance(result.content[0], TextContent)
assert 'OBSERVATION:' in result.content[0].text
assert 'Command output' in result.content[0].text
assert 'Command 1 finished with exit code 0' in result.content[0].text
def test_ipython_run_cell_observation_message(agent: CodeActAgent):
obs = IPythonRunCellObservation(
code='plt.plot()',
content='IPython output\n![image](data:image/png;base64,ABC123)',
)
result = agent.get_observation_message(obs)
assert result is not None
assert result.role == 'user'
assert len(result.content) == 1
assert isinstance(result.content[0], TextContent)
assert 'OBSERVATION:' in result.content[0].text
assert 'IPython output' in result.content[0].text
assert (
'![image](data:image/png;base64, ...) already displayed to user'
in result.content[0].text
)
assert 'ABC123' not in result.content[0].text
def test_agent_delegate_observation_message(agent: CodeActAgent):
obs = AgentDelegateObservation(
content='Content', outputs={'content': 'Delegated agent output'}
)
result = agent.get_observation_message(obs)
assert result is not None
assert result.role == 'user'
assert len(result.content) == 1
assert isinstance(result.content[0], TextContent)
assert 'OBSERVATION:' in result.content[0].text
assert 'Delegated agent output' in result.content[0].text
def test_error_observation_message(agent: CodeActAgent):
obs = ErrorObservation('Error message')
result = agent.get_observation_message(obs)
assert result is not None
assert result.role == 'user'
assert len(result.content) == 1
assert isinstance(result.content[0], TextContent)
assert 'OBSERVATION:' in result.content[0].text
assert 'Error message' in result.content[0].text
assert 'Error occurred in processing last action' in result.content[0].text
def test_unknown_observation_message(agent: CodeActAgent):
obs = Mock()
with pytest.raises(ValueError) as excinfo:
agent.get_observation_message(obs)
assert 'Unknown observation type:' in str(excinfo.value)