Skip to content

Commit

Permalink
Removed config from agent controller (All-Hands-AI#3038)
Browse files Browse the repository at this point in the history
* Removed config from agent controller

* Fix tests

* Increase budget

* Update tests

* Update prompts

* Add missing prompt

* Fix mistaken deletions

* Fix browsing test

* Fixed browse tests
  • Loading branch information
neubig committed Jul 22, 2024
1 parent c3d4f64 commit 4099e48
Show file tree
Hide file tree
Showing 24 changed files with 847 additions and 104 deletions.
24 changes: 12 additions & 12 deletions opendevin/controller/agent_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State, TrafficControlState
from opendevin.controller.stuck import StuckDetector
from opendevin.core.config import config
from opendevin.core.config import LLMConfig
from opendevin.core.exceptions import (
LLMMalformedActionError,
LLMNoActionError,
Expand Down Expand Up @@ -38,8 +38,6 @@
)
from opendevin.llm.llm import LLM

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."
Expand All @@ -53,6 +51,7 @@ class AgentController:
event_stream: EventStream
state: State
confirmation_mode: bool
agent_to_llm_config: dict[str, LLMConfig]
agent_task: Optional[asyncio.Task] = None
parent: 'AgentController | None' = None
delegate: 'AgentController | None' = None
Expand All @@ -62,10 +61,11 @@ def __init__(
self,
agent: Agent,
event_stream: EventStream,
max_iterations: int,
max_budget_per_task: float | None = None,
agent_to_llm_config: dict[str, LLMConfig] | None = None,
sid: str = 'default',
max_iterations: int | None = MAX_ITERATIONS,
confirmation_mode: bool = False,
max_budget_per_task: float | None = MAX_BUDGET_PER_TASK,
initial_state: State | None = None,
is_delegate: bool = False,
headless_mode: bool = True,
Expand All @@ -75,9 +75,11 @@ def __init__(
Args:
agent: The agent instance to control.
event_stream: The event stream to publish events to.
sid: The session ID of the agent.
max_iterations: The maximum number of iterations the agent can run.
max_budget_per_task: The maximum budget (in USD) allowed per task, beyond which the agent will stop.
agent_to_llm_config: A dictionary mapping agent names to LLM configurations in the case that
we delegate to a different agent.
sid: The session ID of the agent.
initial_state: The initial state of the controller.
is_delegate: Whether this controller is a delegate.
headless_mode: Whether the agent is run in headless mode.
Expand All @@ -94,16 +96,13 @@ def __init__(
)

# state from the previous session, state from a parent agent, or a fresh state
max_iterations = (
max_iterations if max_iterations is not None else MAX_ITERATIONS
)
self.set_initial_state(
state=initial_state,
max_iterations=max_iterations,
confirmation_mode=confirmation_mode,
)

self.max_budget_per_task = max_budget_per_task
self.agent_to_llm_config = agent_to_llm_config if agent_to_llm_config else {}

# stuck helper
self._stuck_detector = StuckDetector(self.state)
Expand Down Expand Up @@ -253,7 +252,7 @@ def get_agent_state(self):

async def start_delegate(self, action: AgentDelegateAction):
agent_cls: Type[Agent] = Agent.get_cls(action.agent)
llm_config = config.get_llm_config_from_agent(action.agent)
llm_config = self.agent_to_llm_config.get(action.agent, self.agent.llm.config)
llm = LLM(config=llm_config)
delegate_agent = agent_cls(llm=llm)
state = State(
Expand All @@ -274,6 +273,7 @@ async def start_delegate(self, action: AgentDelegateAction):
event_stream=self.event_stream,
max_iterations=self.state.max_iterations,
max_budget_per_task=self.max_budget_per_task,
agent_to_llm_config=self.agent_to_llm_config,
initial_state=state,
is_delegate=True,
)
Expand Down Expand Up @@ -423,7 +423,7 @@ def get_state(self):
def set_initial_state(
self,
state: State | None,
max_iterations: int = MAX_ITERATIONS,
max_iterations: int,
confirmation_mode: bool = False,
):
# state from the previous session, state from a parent agent, or a new state
Expand Down
4 changes: 4 additions & 0 deletions opendevin/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ def get_agent_config(self, name='agent') -> AgentConfig:
def set_agent_config(self, value: AgentConfig, name='agent'):
self.agents[name] = value

def get_agent_to_llm_config_map(self) -> dict[str, LLMConfig]:
"""Get a map of agent names to llm configs."""
return {name: self.get_llm_config_from_agent(name) for name in self.agents}

def get_llm_config_from_agent(self, name='agent') -> LLMConfig:
agent_config: AgentConfig = self.get_agent_config(name)
llm_config_name = agent_config.llm_config
Expand Down
5 changes: 3 additions & 2 deletions opendevin/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def read_task_from_stdin() -> str:
async def run_agent_controller(
agent: Agent,
task_str: str,
max_iterations: int | None = None,
max_budget_per_task: float | None = None,
max_iterations: int,
max_budget_per_task: float,
exit_on_message: bool = False,
fake_user_response_fn: Callable[[State | None], str] | None = None,
sandbox: Sandbox | None = None,
Expand Down Expand Up @@ -75,6 +75,7 @@ async def run_agent_controller(
agent=agent,
max_iterations=max_iterations,
max_budget_per_task=max_budget_per_task,
agent_to_llm_config=config.get_agent_to_llm_config_map(),
event_stream=event_stream,
initial_state=initial_state,
headless_mode=headless_mode,
Expand Down
25 changes: 21 additions & 4 deletions opendevin/server/session/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from opendevin.controller import AgentController
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.config import SandboxConfig
from opendevin.core.config import LLMConfig, SandboxConfig
from opendevin.core.logger import opendevin_logger as logger
from opendevin.events.stream import EventStream
from opendevin.runtime import DockerSSHBox, get_runtime_cls
Expand Down Expand Up @@ -37,6 +37,8 @@ async def start(
agent: Agent,
confirmation_mode: bool,
max_iterations: int,
max_budget_per_task: float | None = None,
agent_to_llm_config: dict[str, LLMConfig] | None = None,
):
"""Starts the agent session.
Expand All @@ -48,7 +50,13 @@ async def start(
'Session already started. You need to close this session and start a new one.'
)
await self._create_runtime(runtime_name, sandbox_config)
await self._create_controller(agent, confirmation_mode, max_iterations)
await self._create_controller(
agent,
confirmation_mode,
max_iterations,
max_budget_per_task=max_budget_per_task,
agent_to_llm_config=agent_to_llm_config,
)

async def close(self):
if self._closed:
Expand All @@ -74,7 +82,12 @@ async def _create_runtime(self, runtime_name: str, sandbox_config: SandboxConfig
await self.runtime.ainit()

async def _create_controller(
self, agent: Agent, confirmation_mode: bool, max_iterations: int
self,
agent: Agent,
confirmation_mode: bool,
max_iterations: int,
max_budget_per_task: float | None = None,
agent_to_llm_config: dict[str, LLMConfig] | None = None,
):
"""Creates an AgentController instance."""
if self.controller is not None:
Expand All @@ -100,14 +113,18 @@ async def _create_controller(
event_stream=self.event_stream,
agent=agent,
max_iterations=int(max_iterations),
max_budget_per_task=max_budget_per_task,
agent_to_llm_config=agent_to_llm_config,
confirmation_mode=confirmation_mode,
# AgentSession is designed to communicate with the frontend, so we don't want to
# run the agent in headless mode.
headless_mode=False,
)
try:
agent_state = State.restore_from_session(self.sid)
self.controller.set_initial_state(agent_state)
self.controller.set_initial_state(
agent_state, max_iterations, confirmation_mode
)
logger.info(f'Restored agent state from session, sid: {self.sid}')
except Exception as e:
print('Error restoring state', e)
2 changes: 2 additions & 0 deletions opendevin/server/session/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ async def _initialize_agent(self, data: dict):
agent=agent,
confirmation_mode=confirmation_mode,
max_iterations=max_iterations,
max_budget_per_task=self.config.max_budget_per_task,
agent_to_llm_config=self.config.get_agent_to_llm_config_map(),
)
except Exception as e:
logger.exception(f'Error creating controller: {e}')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ possible next action to accomplish your goal. Your answer will be interpreted
and executed by a program, make sure to follow the formatting instructions.

# Goal:
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.. I should start with: Get the content on "http://localhost:8000"
Sure! Let me browse the server's homepage at http://localhost:8000 to find the ultimate answer to life.. I should start with: Get the content on "http://localhost:8000"

# Action Space

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ possible next action to accomplish your goal. Your answer will be interpreted
and executed by a program, make sure to follow the formatting instructions.

# Goal:
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.. I should start with: Get the content on "http://localhost:8000"
Sure! Let me browse the server's homepage at http://localhost:8000 to find the ultimate answer to life.. I should start with: Get the content on "http://localhost:8000"

# Action Space

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ possible next action to accomplish your goal. Your answer will be interpreted
and executed by a program, make sure to follow the formatting instructions.

# Goal:
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.. I should start with: Get the content on "http://localhost:8000"
Sure! Let me browse the server's homepage at http://localhost:8000 to find the ultimate answer to life.. I should start with: Get the content on "http://localhost:8000"

# Action Space

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,12 +405,12 @@ Browse localhost:8000, and tell me the ultimate answer to life. Do not ask me fo


<execute_browse>
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.. I should start with: Get the content on "http://localhost:8000"
Sure! Let me browse the server's homepage at http://localhost:8000 to find the ultimate answer to life.. I should start with: Get the content on "http://localhost:8000"
</execute_browse>

----------

OBSERVATION:
{'content': 'The ultimate answer to life, the universe, and everything is: OpenDevin is all you need!'}
{'content': 'The answer to life, the universe, and everything is: OpenDevin is all you need!'}

ENVIRONMENT REMINDER: You have 8 turns left to complete the task. When finished reply with <finish></finish>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.
Sure! Let me browse the server's homepage at http://localhost:8000 to find the ultimate answer to life.

<execute_browse>
Get the content on "http://localhost:8000"
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
To accomplish our goal of retrieving the ultimate answer to life from localhost:8000, I need to start by accessing the content on that page. Here's my thought process and action:

In order to accomplish my goal, I need to navigate to the specified URL using the goto action.

In order to accomplish my goal, I need to navigate to the server's homepage at http://localhost:8000 to find the ultimate answer to life.
```goto("http://localhost:8000"
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
To accomplish our goal of retrieving the ultimate answer to life, we need to click on the "Click me" button. The button has a bid of 10, so we should click on it.

Here's the action I'll take:

In order to accomplish my goal, I need to click on the button with bid 10
In order to accomplish my goal, I need to click on the button with bid 10 to reveal the answer to life, the universe, and everything.
```click("10"
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
Based on the current Accessibility Tree and the previous actions taken, it appears that we have already successfully navigated to the webpage and clicked the button to reveal the answer. The answer is now visible on the page.
In order to accomplish my goal, I need to read the static text that reveals the answer to life, the universe, and everything. The text is already visible and states "The answer is OpenDevin is all you need!"

To accomplish our goal of retrieving the ultimate answer to life, I will now send this information to the user:

```
send_msg_to_user("The ultimate answer to life, the universe, and everything is: OpenDevin is all you need!"
```send_msg_to_user("The answer to life, the universe, and everything is: OpenDevin is all you need!"
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
Thank you for providing the content from localhost:8000. Based on the information retrieved, I can now answer your question about the ultimate answer to life.
The content of the server's homepage at http://localhost:8000 is:

The ultimate answer to life, according to the content on localhost:8000, is:

"OpenDevin is all you need!"

This appears to be a playful reference to the popular science fiction series "The Hitchhiker's Guide to the Galaxy" by Douglas Adams, where the answer to the ultimate question of life, the universe, and everything is famously "42". In this case, it's been adapted to promote OpenDevin as the ultimate solution.
"The answer to life, the universe, and everything is: OpenDevin is all you need!"

If you have any further questions or tasks, feel free to ask!
<finish></finish>
25 changes: 14 additions & 11 deletions tests/integration/mock/CodeActSWEAgent/test_edits/prompt_003.log
Original file line number Diff line number Diff line change
Expand Up @@ -613,26 +613,29 @@ OBSERVATION:

----------

I will now correct the typos in the `bad.txt` file.
Let's correct the typos in the `bad.txt` file.

1. "stupid typoo" should be "stupid typo".
2. "No mor typos!" should be "No more typos!".

I will make these changes now.
<execute_ipython>
edit_file_by_replace(
"bad.txt",
to_replace="This is a stupid typoo.",
new_content="This is a stupid typo."
to_replace="This is a stupid typoo.\nNo mor typos!",
new_content="This is a stupid typo.\nNo more typos!"
)
</execute_ipython>

----------

OBSERVATION:
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a stupid typo.
2|Really?
3|No mor typos!
4|Enjoy!
(this is the end of the file)
[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
[No exact match found in bad.txt for
```
This is a stupid typoo.
No mor typos!
```
]


ENVIRONMENT REMINDER: You have 12 turns left to complete the task.
Loading

0 comments on commit 4099e48

Please sign in to comment.