Skip to content

Commit

Permalink
Minor fixes for new Agent framework (All-Hands-AI#158)
Browse files Browse the repository at this point in the history
* add message if exit happens before finish

* fix kill command

* fix browse action

* fix sandbox

* update requirements

* add status code

* refactor controller

* add try to callbacks

* fix background log collection

* format logs a bit more nicely

* run background procs in same container

* fix close command

* rename sockt

* fix up kills

* fix lint issues

* fix ruff

---------

Co-authored-by: Xingyao Wang <xingyao6@illinois.edu>
  • Loading branch information
rbren and xingyaoww committed Mar 26, 2024
1 parent 4305276 commit 486ec2d
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 112 deletions.
16 changes: 11 additions & 5 deletions agenthub/langchains_agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from agenthub.langchains_agent.utils.memory import LongTermMemory

from opendevin.action import (
NullAction,
CmdRunAction,
CmdKillAction,
BrowseURLAction,
Expand Down Expand Up @@ -128,16 +129,18 @@ def step(self, state: State) -> Action:

# Translate state to action_dict
for prev_action, obs in state.updated_info:
d = None
if isinstance(obs, CmdOutputObservation):
if obs.error:
d = {"action": "error", "args": {"output": obs.content}}
else:
d = {"action": "output", "args": {"output": obs.content}}
else:
d = {"action": "output", "args": {"output": obs.content}}
self._add_event(d)

raise ValueError(f"Unknown observation type: {obs}")
if d is not None:
self._add_event(d)

d = None
if isinstance(prev_action, CmdRunAction):
d = {"action": "run", "args": {"command": prev_action.command}}
elif isinstance(prev_action, CmdKillAction):
Expand All @@ -154,9 +157,12 @@ def step(self, state: State) -> Action:
d = {"action": "think", "args": {"thought": prev_action.thought}}
elif isinstance(prev_action, AgentFinishAction):
d = {"action": "finish"}
elif isinstance(prev_action, NullAction):
d = None
else:
raise NotImplementedError(f"Unknown action type: {prev_action}")
self._add_event(d)
raise ValueError(f"Unknown action type: {prev_action}")
if d is not None:
self._add_event(d)

state.updated_info = []

Expand Down
18 changes: 13 additions & 5 deletions opendevin/action/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ class BrowseURLAction(ExecutableAction):
url: str

def run(self, *args, **kwargs) -> BrowserOutputObservation:
response = requests.get(self.url)
return BrowserOutputObservation(
content=response.text,
url=self.url
)
try:
response = requests.get(self.url)
return BrowserOutputObservation(
content=response.text,
status_code=response.status_code,
url=self.url
)
except requests.exceptions.RequestException as e:
return BrowserOutputObservation(
content=str(e),
error=True,
url=self.url
)

@property
def message(self) -> str:
Expand Down
91 changes: 54 additions & 37 deletions opendevin/controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from .command_manager import CommandManager

def print_with_indent(text: str):
print("\t"+text.replace("\n","\n\t"), flush=True)

class AgentController:
def __init__(
Expand Down Expand Up @@ -47,45 +49,60 @@ def add_observation(self, observation: Observation):
self.state_updated_info.append((NullAction(), observation))

async def start_loop(self, task_instruction: str):
try:
self.agent.instruction = task_instruction
for i in range(self.max_iterations):
print("STEP", i, flush=True)
finished = False
self.agent.instruction = task_instruction
for i in range(self.max_iterations):
try:
finished = await self.step(i)
except Exception as e:
print("Error in loop", e, flush=True)
break
if finished:
break
if not finished:
print("Exited before finishing", flush=True)

state: State = self.get_current_state()
action: Action = self.agent.step(state)

print("ACTION", action, flush=True)
for _callback_fn in self.callbacks:
_callback_fn(action)

if isinstance(action, AgentFinishAction):
print("FINISHED", flush=True)
break
if isinstance(action, (FileReadAction, FileWriteAction)):
action_cls = action.__class__
_kwargs = action.__dict__
_kwargs["base_path"] = self.workdir
action = action_cls(**_kwargs)
print(action, flush=True)
print("---", flush=True)
async def step(self, i: int):
print("\n\n==============", flush=True)
print("STEP", i, flush=True)
log_obs = self.command_manager.get_background_obs()
for obs in log_obs:
self.add_observation(obs)
await self._run_callbacks(obs)
print_with_indent("\nBACKGROUND LOG:\n%s" % obs)

state: State = self.get_current_state()
action: Action = self.agent.step(state)

if action.executable:
observation: Observation = action.run(self)
else:
print("ACTION NOT EXECUTABLE", flush=True)
observation = NullObservation("")
print("OBSERVATION", observation, flush=True)
self.state_updated_info.append((action, observation))

print(observation, flush=True)
for _callback_fn in self.callbacks:
_callback_fn(observation)
print_with_indent("\nACTION:\n%s" % action)
await self._run_callbacks(action)

print("==============", flush=True)
if isinstance(action, AgentFinishAction):
print_with_indent("\nFINISHED")
return True
if isinstance(action, (FileReadAction, FileWriteAction)):
action_cls = action.__class__
_kwargs = action.__dict__
_kwargs["base_path"] = self.workdir
action = action_cls(**_kwargs)
print(action, flush=True)
if action.executable:
observation: Observation = action.run(self)
else:
observation = NullObservation("")
print_with_indent("\nOBSERVATION:\n%s" % observation)
self.state_updated_info.append((action, observation))
await self._run_callbacks(observation)

await asyncio.sleep(0.001)
except Exception as e:
print("Error in loop", e, flush=True)
pass

async def _run_callbacks(self, event):
if event is None:
return
for callback in self.callbacks:
idx = self.callbacks.index(callback)
try:
callback(event)
except Exception as e:
print("Callback error:" + str(idx), e, flush=True)
pass
await asyncio.sleep(0.001) # Give back control for a tick, so we can await in callbacks
48 changes: 18 additions & 30 deletions opendevin/controller/command_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,9 @@
from opendevin.observation import CmdOutputObservation
from opendevin.sandbox.sandbox import DockerInteractive


class BackgroundCommand:
def __init__(self, id: int, command: str, dir: str):
self.command = command
self.id = id
self.shell = DockerInteractive(id=str(id), workspace_dir=dir)
self.shell.execute_in_background(command)

def get_logs(self) -> str:
# TODO: get an exit code if process is exited
return self.shell.read_logs()


class CommandManager:
def __init__(self, dir):
self.cur_id = 0
self.directory = dir
self.background_commands = {}
self.shell = DockerInteractive(id="default", workspace_dir=dir)

def run_command(self, command: str, background=False) -> CmdOutputObservation:
Expand All @@ -32,35 +17,38 @@ def run_command(self, command: str, background=False) -> CmdOutputObservation:
def _run_immediately(self, command: str) -> CmdOutputObservation:
exit_code, output = self.shell.execute(command)
return CmdOutputObservation(
command_id=-1,
content=output,
command_id=self.cur_id,
command=command,
exit_code=exit_code
)

def _run_background(self, command: str) -> CmdOutputObservation:
bg_cmd = BackgroundCommand(self.cur_id, command, self.directory)
self.cur_id += 1
self.background_commands[bg_cmd.id] = bg_cmd
bg_cmd = self.shell.execute_in_background(command)
return CmdOutputObservation(
content=f"Background command started. To stop it, send a `kill` action with id {bg_cmd.id}",
content=f"Background command started. To stop it, send a `kill` action with id {bg_cmd.id}",
command_id=bg_cmd.id,
command=command,
exit_code=0
)

def kill_command(self, id: int):
# TODO: get log events before killing
self.background_commands[id].shell.close()
del self.background_commands[id]
def kill_command(self, id: int) -> CmdOutputObservation:
cmd = self.shell.kill_background(id)
return CmdOutputObservation(
content=f"Background command with id {id} has been killed.",
command_id=id,
command=cmd.command,
exit_code=0
)

def get_background_obs(self) -> List[CmdOutputObservation]:
obs = []
for _id, cmd in self.background_commands.items():
output = cmd.get_logs()
obs.append(
CmdOutputObservation(
content=output, command_id=_id, command=cmd.command
for _id, cmd in self.shell.background_commands.items():
output = cmd.read_logs()
if output is not None and output != "":
obs.append(
CmdOutputObservation(
content=output, command_id=_id, command=cmd.command
)
)
)
return obs
2 changes: 2 additions & 0 deletions opendevin/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class BrowserOutputObservation(Observation):
"""

url: str
status_code: int = 200
error: bool = False

@property
def message(self) -> str:
Expand Down
Loading

0 comments on commit 486ec2d

Please sign in to comment.