Skip to content

Commit

Permalink
specialize global planner agent by removing editing skills and update…
Browse files Browse the repository at this point in the history
… few-shot examples
  • Loading branch information
ryanhoangt committed Sep 9, 2024
1 parent 39d8d0a commit fc44e17
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 172 deletions.
3 changes: 2 additions & 1 deletion agenthub/coact_agent/planner/action_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ def parse(self, action_str: str) -> Action:
assert (
self.global_plan is not None
), 'self.global_plan should not be None when parse is called'
# thought = action_str.replace(self.global_plan.group(0), '').strip()
thought = action_str.replace(self.global_plan.group(0), '').strip()
global_plan_actions = self.global_plan.group(1).strip()
return AgentDelegateAction(
agent='CoActExecutorAgent',
thought=thought,
inputs={'task': global_plan_actions},
action_suffix='global_plan',
) # FIXME: check to use a more proper key e.g. 'global_plan' instead of 'task'
19 changes: 17 additions & 2 deletions agenthub/coact_agent/planner/planner_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from agenthub.codeact_agent.codeact_agent import CodeActAgent
from openhands.core.config import AgentConfig
from openhands.llm.llm import LLM
from openhands.runtime.plugins.agent_skills import AgentSkillsRequirement
from openhands.runtime.plugins.agent_skills.agentskills import (
DOCUMENTATION_DICT as AGENTSKILLS_DOCS_DICT,
)
from openhands.utils.prompt import PromptManager


Expand All @@ -15,8 +17,21 @@ def __init__(self, llm: LLM, config: AgentConfig) -> None:
super().__init__(llm, config)

self.action_parser = PlannerResponseParser()

# Planner agent can do everything except file-editing operations
planner_agentskills_exclude = [
'create_file',
'edit_file_by_replace',
'insert_content_at_line',
'append_file',
]
planner_agentskills = [
v
for k, v in AGENTSKILLS_DOCS_DICT.items()
if k not in planner_agentskills_exclude
]
self.prompt_manager = PromptManager(
prompt_dir=os.path.join(os.path.dirname(__file__)),
agent_skills_docs=AgentSkillsRequirement.documentation,
agent_skills_docs=''.join(planner_agentskills),
micro_agent=self.micro_agent,
)
7 changes: 1 addition & 6 deletions agenthub/coact_agent/planner/system_prompt.j2
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ Apart from the standard Python library, the agent can also use the following fun
{{ agent_skills_docs }}
IMPORTANT:
- `open_file` only returns the first 100 lines of the file by default! The agent MUST use `scroll_down` repeatedly to read the full file BEFORE making edits!
- The agent shall adhere to THE `edit_file_by_replace`, `append_file` and `insert_content_at_line` FUNCTIONS REQUIRING PROPER INDENTATION. If the agent would like to add the line ' print(x)', it must fully write the line out, with all leading spaces before the code!
- Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
- Any code issued should be less than 50 lines to avoid context being cut off!
- After EVERY `create_file` the method `append_file` shall be used to write the FIRST content!
- For `edit_file_by_replace` NEVER provide empty parameters!
- For `edit_file_by_replace` the file must be read fully before any replacements!
{% endset %}
{% set SYSTEM_SUFFIX %}
Responses should be concise.
Expand All @@ -50,6 +44,7 @@ The agent should utilize full file paths and the `pwd` command to prevent path-r
The agent must avoid apologies and thanks in its responses.
The planner agent must NOT perform the plan by itself and must delegate the plan to the local agent using <execute_global_plan>.
The planner agent can only use <execute_browse> to browse the Internet if it's required to come up with the plan. To delegate the plan, please only use <execute_global_plan>.
The planner agent can navigate the codebase to locate the necessary information to construct the plan.
After the local agent successfully completes the plan without any issues, you can collate the result and if it is good, you can exit.

{% endset %}
Expand Down
259 changes: 96 additions & 163 deletions agenthub/coact_agent/planner/user_prompt.j2
Original file line number Diff line number Diff line change
@@ -1,45 +1,69 @@
{% set DEFAULT_EXAMPLE %}
--- START OF EXAMPLE ---
--- START OF EXAMPLE 1 ---

USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.
USER: I want to go to the user 'ryanhoangtt' github profile using browser and check how many repos they have, then write the number to the new 'repo_count.txt' file. Use a plan that has the least number of steps.

ASSISTANT:
Sure! Let me create a Python file `app.py`:
<execute_ipython>
create_file('app.py')
</execute_ipython>
I have enough information, let's create a plan to achieve the task:
<execute_global_plan>
The user message is: "I want to go to the user 'ryanhoangtt' github profile using browser and check how many repos they have, then write the number to the new 'repo_count.txt' file. Use a plan that has the least number of steps."
# Phases
## Phase 1
- description: Browse the GitHub profile of the user 'ryanhoangtt' to find the number of repositories.
- expected_state: The number of repositories is identified.
## Phase 2
- description: Create a new file `repo_count.txt` and write the number of repositories to it.
- expected_state: \"The file `repo_count.txt` is created and contains the number of repositories.
</execute_global_plan>

USER:
OBSERVATION:
[File: /workspace/app.py (1 lines total)]
(this is the beginning of the file)
1|
(this is the end of the file)
[File app.py created.]
LocalExecutorAgent finishes task with content: The GitHub profile 'ryanhoangtt' does not exist or is not accessible. Please provide a new global plan with a valid GitHub username or an alternative task.

ASSISTANT:
Now I will write the Python code for starting a web server and save it to the file `app.py`:
<execute_ipython>
EDITED_CODE="""from flask import Flask
app = Flask(__name__)
It seems the GitHub profile 'ryanhoangtt' does not exist or is not accessible. Please provide a valid GitHub username or an alternative task.

@app.route('/')
def index():
numbers = list(range(1, 11))
return str(numbers)
USER:
Sorry, the correct github handle is "ryanhoangt".

if __name__ == '__main__':
app.run(port=5000)"""
ASSISTANT:
Let's create a new plan to achieve the task:
<execute_global_plan>
# Phases
## Phase 1
- description: Browse the GitHub profile of the user 'ryanhoangt' to find the number of repositories.
- expected_state: The number of repositories is identified.
## Phase 2
- description: Create a new file `repo_count.txt` and write the number of repositories to it.
expected_state: The file `repo_count.txt` is created and contains the number of repositories.
</execute_global_plan>

insert_content_at_line(
'app.py',
1,
EDITED_CODE,
)
USER:
OBSERVATION:
LocalExecutorAgent finishes task with content: The file repo_count.txt has been successfully updated with the number of repositories. The tasks are now complete.
Summary:
- Browsed the GitHub profile of the user 'ryanhoangt' and found that the number of repositories is 129.
- Appended the number of repositories to the file repo_count.txt.

ASSISTANT:
The tasks have been successfully completed. The file repo_count.txt has been created and updated with the number of repositories (129) for the user 'ryanhoangt'.
If you have any further requests, feel free to ask. Otherwise, I will consider the task complete.
<finish></finish>

--- END OF EXAMPLE 1 ---

--- START OF EXAMPLE 2 ---

USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000. There's an existing Python file `app.py` that you can use.

ASSISTANT: Let's check the existing Python file `app.py`:
<execute_ipython>
open_file('app.py')
</execute_ipython>

USER:
OBSERVATION:
[File: /workspace/app.py (8 lines total)]
(this is the beginning of the file)
1|from flask import Flask
2|app = Flask(__name__)
Expand All @@ -49,174 +73,83 @@ OBSERVATION:
6| numbers = list(range(1, 11))
7| return str(numbers)
8|
9|if __name__ == '__main__':
10| app.run(port=5000)
(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.]

ASSISTANT:
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
<execute_bash>
python3 app.py > server.log 2>&1 &
</execute_bash>

USER:
OBSERVATION:
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1

ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
<execute_bash>
cat server.log
</execute_bash>

USER:
OBSERVATION:
Traceback (most recent call last):
File "/workspace/app.py", line 2, in <module>
from flask import Flask
ModuleNotFoundError: No module named 'flask'

ASSISTANT:
It seems that Flask is not installed. Let me install Flask for you:
<execute_bash>
pip install flask
</execute_bash>

USER:
OBSERVATION:
Defaulting to user installation because normal site-packages is not writeable
Collecting flask
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
Collecting blinker>=1.6.2
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
Collecting Werkzeug>=3.0.0
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
Collecting click>=8.1.3
Using cached click-8.1.7-py3-none-any.whl (97 kB)
Collecting itsdangerous>=2.1.2
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Requirement already satisfied: Jinja2>=3.1.2 in /home/openhands/.local/lib/python3.10/site-packages (from flask) (3.1.3)
Requirement already satisfied: MarkupSafe>=2.0 in /home/openhands/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0

ASSISTANT:
Now that Flask is installed, let me run the Python file again:
<execute_bash>
python3 app.py > server.log 2>&1 &
</execute_bash>

USER:
OBSERVATION:
[1] 124

ASSISTANT:
Let me check the server log again:
<execute_bash>
cat server.log
</execute_bash>
Great! Seems like the existing Python file `app.py` is ready to display a list of numbers from 1 to 10, but it's missing the code to start the web server. Let me create a plan to achieve the task:
<execute_global_plan>
The user message is: "Create a list of numbers from 1 to 10, and display them in a web page at port 5000. There's an existing Python file `app.py` that you can use."
# Phases
## Phase 1
- description: Edit the existing Python file `app.py` to insert the code for starting a web server, at the end of the file: """
if __name__ == '__main__':
app.run(port=5000)
"""
- expected_state: The Python file `app.py` is updated with the code to start the web server.
## Phase 2
- description: Run the Python file `app.py` to display the list of numbers on a web page at port 5000.
- expected_state: The list of numbers is displayed on a web page at port 5000.

USER:
OBSERVATION:
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
LocalExecutorAgent finishes task with content: The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. The tasks are now complete.
Summary:
- Edited the existing Python file `app.py` to insert the code for starting a web server.
- Ran the Python file to display the list of numbers on a web page at port 5000.

ASSISTANT:
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!

USER: Now browse the newly started server's homepage and show me the content.

ASSISTANT:
Sure! Let me browse the server's homepage at http://127.0.0.1:5000:
<execute_browse>
Get the content on "http://127.0.0.1:5000"
</execute_browse>
Sure, let me create a plan to achieve the task:
<execute_global_plan>
# Phases
## Phase 1
- description: Browse the server's homepage at http://127.0.0.1:5000
- expected_state: The content of the server's homepage is retrieved.
</execute_global_plan>

USER:
OBSERVATION:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
LocalExecutorAgent finishes task with content: The content of the server's homepage is "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]". The tasks are now complete.
Summary:
- Browsed the server's homepage at http://127.0.0.1:5000 and retrieved the content "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]".

ASSISTANT:
The content of the server's homepage is "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]". If you have any further questions, feel free to ask!

USER: Now kill the server, make it display the numbers in a table format.

ASSISTANT:
Sure! Let me stop the server first:
<execute_bash>
kill 124
</execute_bash>
Sure! Let me create a plan to achieve the task:
<execute_global_plan>
# Phases
## Phase 1
- description: Stop the server running on port 5000.
- expected_state: The server is stopped.
## Phase 2
- description: Modify the Python code to display the numbers in a table format.
- expected_state: The numbers are displayed in a table format on the web page.
## Phase 3
- description: Run the Python file again to display the numbers in a table format on the web page at port 5000.
- expected_state: The numbers are displayed in a table format on the web page.
</execute_global_plan>

USER:
OBSERVATION:
[1]+ Terminated python3 app.py > server.log 2>&1

ASSISTANT:
The server has been stopped. Let me open the Python file and modify it.
<execute_ipython>
open_file('app.py')
</execute_ipython>

USER:
[File: /workspace/app.py (10 lines total)]
(this is the beginning of the file)
1|from flask import Flask
2|app = Flask(__name__)
3|
4|@app.route('/')
5|def index():
6| numbers = list(range(1, 11))
7| return str(numbers)
8|
9|if __name__ == '__main__':
10| app.run(port=5000)
(this is the end of the file)

ASSISTANT:
I should edit the file to display the numbers in a table format. I should include correct indentation. Let me update the file:
<execute_ipython>
edit_file_by_replace(
'app.py',
to_replace=" return str(numbers)",
new_content=" return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'",
)
</execute_ipython>

USER:
Observation:
[File: /workspace/app.py (10 lines total after edit)]
(this is the beginning of the file)
1|from flask import Flask
2|app = Flask(__name__)
3|
4|@app.route('/')
5|def index():
6| numbers = list(range(1, 11))
7| return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
8|
9|if __name__ == '__main__':
10| app.run(port=5000)
(this is the end of the file)
[File updated (edited at line 7). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]

ASSISTANT:
Running the updated file:
<execute_bash>
python3 app.py > server.log 2>&1 &
</execute_bash>

USER:
Observation:
[1] 126
LocalExecutorAgent finishes task with content: The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. The tasks are now complete.
Summary:
- Stopped the server running on port 5000.
- Modified the Python code to display the numbers in a table format.
- Ran the Python file again to display the numbers in a table format on the web page at port 5000.

ASSISTANT:
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Let me know if you have any further requests!
<finish></finish>

--- END OF EXAMPLE ---
--- END OF EXAMPLE 2 ---
{% endset %}
Here is an example of how you can interact with the environment for task solving:
{{ DEFAULT_EXAMPLE }}
Expand Down
2 changes: 2 additions & 0 deletions openhands/runtime/plugins/agent_skills/agentskills.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
__all__ = file_ops.__all__ + file_reader.__all__

DOCUMENTATION = ''
DOCUMENTATION_DICT = {}
for func_name in __all__:
func = globals()[func_name]

Expand All @@ -23,3 +24,4 @@

fn_signature = f'{func.__name__}' + str(signature(func))
DOCUMENTATION += f'{fn_signature}:\n{cur_doc}\n\n'
DOCUMENTATION_DICT[func_name] = f'{fn_signature}:\n{cur_doc}\n\n'

0 comments on commit fc44e17

Please sign in to comment.