Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(enh) AgentSkills: fix \n handling with file editing (iPython) #3904

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
20 changes: 10 additions & 10 deletions agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
from itertools import islice

from agenthub.codeact_agent.action_parser import CodeActResponseParser
Expand Down Expand Up @@ -141,15 +142,14 @@ def get_observation_message(self, obs: Observation) -> Message | None:
)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, IPythonRunCellObservation):
text = obs_prefix + obs.content
# replace base64 images with a placeholder
splitted = text.split('\n')
for i, line in enumerate(splitted):
if '![image](data:image/png;base64,' in line:
splitted[i] = (
'![image](data:image/png;base64, ...) already displayed to user'
)
text = '\n'.join(splitted)
# Replace base64 images with a placeholder using regex
image_pattern = r'!\[image\]\(data:image/png;base64,[^)]+\).*\n'
text = re.sub(
image_pattern,
'![image](data:image/png;base64, ...) already displayed to user\n',
obs.content,
)
text = obs_prefix + '\n'.join(text.split('\n'))
text = truncate_content(text, max_message_chars)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, AgentDelegateObservation):
Expand All @@ -160,7 +160,7 @@ def get_observation_message(self, obs: Observation) -> Message | None:
text += '\n[Error occurred in processing last action]'
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, UserRejectObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text = obs_prefix + truncate_content(obs.content, max_message_chars)
text += '\n[Last action has been rejected by the user]'
return Message(role='user', content=[TextContent(text=text)])
else:
Expand Down
2 changes: 1 addition & 1 deletion evaluation/utils/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def model_dump(self, *args, **kwargs):
def model_dump_json(self, *args, **kwargs):
dumped = super().model_dump_json(*args, **kwargs)
dumped_dict = json.loads(dumped)
logger.debug(f'Dumped metadata: {dumped_dict}')
# avoid leaking sensitive information
dumped_dict['llm_config'] = self.llm_config.to_safe_dict()
logger.debug(f'Dumped metadata: {dumped_dict}')
return json.dumps(dumped_dict)


Expand Down
2 changes: 1 addition & 1 deletion openhands/events/serialization/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def event_to_memory(event: 'Event', max_message_chars: int) -> dict:

def truncate_content(content: str, max_chars: int) -> str:
"""Truncate the middle of the observation content if it is too long."""
if len(content) <= max_chars:
if len(content) <= max_chars or max_chars == 0:
return content

# truncate the middle and include a message to the LLM about it
Expand Down
19 changes: 13 additions & 6 deletions openhands/runtime/plugins/agent_skills/file_ops/file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,19 +705,24 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
)
return

start = file_content.find(to_replace)
# Replace escaped newlines with actual newlines
to_replace_normalized = to_replace.replace(r'\n', '\n')
xingyaoww marked this conversation as resolved.
Show resolved Hide resolved

start = file_content.find(to_replace_normalized)
if start != -1:
# Convert start from index to line number
start_line_number = file_content[:start].count('\n') + 1
end_line_number = start_line_number + len(to_replace.splitlines()) - 1
end_line_number = (
start_line_number + len(to_replace_normalized.splitlines()) - 1
)
else:

def _fuzzy_transform(s: str) -> str:
# remove all space except newline
return re.sub(r'[^\S\n]+', '', s)
return re.sub(r'(?<!\\)[^\S\n]+', '', s.replace('\\n', '\n'))
xingyaoww marked this conversation as resolved.
Show resolved Hide resolved

# perform a fuzzy search (remove all spaces except newlines)
to_replace_fuzzy = _fuzzy_transform(to_replace)
to_replace_fuzzy = _fuzzy_transform(to_replace_normalized)
file_content_fuzzy = _fuzzy_transform(file_content)
# find the closest match
start = file_content_fuzzy.find(to_replace_fuzzy)
Expand All @@ -728,7 +733,9 @@ def _fuzzy_transform(s: str) -> str:
return
# Convert start from index to line number for fuzzy match
start_line_number = file_content_fuzzy[:start].count('\n') + 1
end_line_number = start_line_number + len(to_replace.splitlines()) - 1
end_line_number = (
start_line_number + len(to_replace_normalized.splitlines()) - 1
)

ret_str = _edit_file_impl(
file_name,
Expand Down Expand Up @@ -829,7 +836,7 @@ def search_dir(search_term: str, dir_path: str = './') -> None:

if num_files > 100:
print(
f'More than {num_files} files matched for "{search_term}" in {dir_path}. Please narrow your search.'
f'{num_files} files matched for "{search_term}" in {dir_path}. Please narrow your search.'
)
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,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:
. I should start with: Get the content on "http://localhost:8000"
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"

# Action Space

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,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:
. I should start with: Get the content on "http://localhost:8000"
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"

# Action Space

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,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:
. I should start with: Get the content on "http://localhost:8000"
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"

# Action Space

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


<execute_browse>
. I should start with: Get the content on "http://localhost:8000"
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"
</execute_browse>

----------

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


ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Certainly! I'll browse localhost:8000 and retrieve the ultimate answer to life for you.

<execute_browse>
Get the content on "http://localhost:8000"
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
To accomplish my goal of getting the content on "http://localhost:8000", I need to use the goto action to navigate to that URL. Here's the action with my chain of thought:
Thank you for providing the instructions and example. I understand that I need to start by retrieving the content from "http://localhost:8000". To do this, I'll use the `goto` action. Here's my thought process and the action:

In order to accomplish my goal of accessing the content on "http://localhost:8000", I need to navigate to that URL using the goto action.
In order to accomplish my goal of browsing localhost:8000 and retrieving the ultimate answer to life, I need to first navigate to the specified URL.

```goto("http://localhost:8000"
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
To accomplish the goal of revealing the answer, I need to click on the "Click me" button. The button has a bid of 10.
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 our goal, I need to click on the button with bid 10
```click("10"
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
Based on the current Accessibility Tree and the previous actions taken, it appears that we have already navigated to the correct page and clicked the button to reveal the answer. The answer is now visible on the page.
Based on the current state of the page and the previous actions taken, it appears that we have already successfully navigated to the desired URL and clicked the button to reveal the answer. The accessibility tree shows that the answer has been revealed:

To accomplish our goal of retrieving the ultimate answer to life, I will now send a message to the user with the revealed answer:
StaticText 'The answer is OpenHands is all you need!'

Since the goal was to get the content on the page and reveal the answer, we have successfully completed the task. There's no need for any further actions.
This text contains the ultimate answer we were looking for. Now, I should report this information back to you.

However, to formally conclude and report our findings to the user, we should send a message summarizing what we've discovered. Here's the action I propose:

In order to report our findings to the user, I will send a message with the revealed answer.
```send_msg_to_user("The answer to life, the universe, and everything has been revealed: OpenHands is all you need!"
```
send_msg_to_user("The ultimate answer to life, the universe, and everything is: OpenHands is all you need!"
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
Based on the content retrieved from localhost:8000, I can provide you with the ultimate answer to life:

The ultimate answer to life, the universe, and everything is: OpenHands is all you need!

This appears to be the answer provided by the server at localhost:8000. It's a playful reference to Douglas Adams' "The Hitchhiker's Guide to the Galaxy," where the answer to life, the universe, and everything is famously 42. In this case, it's been customized to highlight OpenHands.
Based on the content retrieved from localhost:8000, the ultimate answer to life, the universe, and everything is: OpenHands is all you need!

<finish></finish>
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@


----------

A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
The assistant can use a Python environment with <execute_ipython>, e.g.:
<execute_ipython>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@


----------

A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
The assistant can use a Python environment with <execute_ipython>, e.g.:
<execute_ipython>
Expand Down Expand Up @@ -410,7 +406,7 @@ Fix typos in bad.txt. Do not ask me for confirmation at any point.

----------

Certainly! I'll fix the typos in the bad.txt file. Let's start by opening the file and examining its contents.
Certainly! I'll fix the typos in bad.txt. First, let's open the file to see its contents:
<execute_ipython>
open_file('bad.txt')
</execute_ipython>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@


----------

A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
The assistant can use a Python environment with <execute_ipython>, e.g.:
<execute_ipython>
Expand Down Expand Up @@ -410,7 +406,7 @@ Fix typos in bad.txt. Do not ask me for confirmation at any point.

----------

Certainly! I'll fix the typos in the bad.txt file. Let's start by opening the file and examining its contents.
Certainly! I'll fix the typos in bad.txt. First, let's open the file to see its contents:
<execute_ipython>
open_file('bad.txt')
</execute_ipython>
Expand All @@ -430,18 +426,12 @@ OBSERVATION:

----------

I'll now fix the typos in the bad.txt file. There are three typos to correct:

1. "typoo" should be "typo"
2. "stupid" is not appropriate, we'll change it to "silly"
3. "mor" should be "more"

Let's make these changes:
I'll now fix the typos in the file:
<execute_ipython>
edit_file_by_replace(
'bad.txt',
to_replace="This is a stupid typoo.",
new_content="This is a silly typo."
to_replace="This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!",
new_content="This is a stupid typo.\nReally?\nNo more typos!\nEnjoy!"
)
</execute_ipython>

Expand All @@ -450,9 +440,9 @@ edit_file_by_replace(
OBSERVATION:
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a silly typo.
1|This is a stupid typo.
2|Really?
3|No mor typos!
3|No more 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.]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Certainly! I'll fix the typos in the bad.txt file. Let's start by opening the file and examining its contents.
Certainly! I'll fix the typos in bad.txt. First, let's open the file to see its contents:

<execute_ipython>
open_file('bad.txt')
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
I'll now fix the typos in the bad.txt file. There are three typos to correct:

1. "typoo" should be "typo"
2. "stupid" is not appropriate, we'll change it to "silly"
3. "mor" should be "more"

Let's make these changes:
I'll now fix the typos in the file:

<execute_ipython>
edit_file_by_replace(
'bad.txt',
to_replace="This is a stupid typoo.",
new_content="This is a silly typo."
to_replace="This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!",
new_content="This is a stupid typo.\nReally?\nNo more typos!\nEnjoy!"
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Now, let's fix the remaining typo:
The typos in bad.txt have been fixed. Here's a summary of the changes:

<execute_ipython>
edit_file_by_replace(
'bad.txt',
to_replace="No mor typos!",
new_content="No more typos!"
)
1. "typoo" was corrected to "typo" in the first line.
2. "mor" was corrected to "more" in the third line.

The file now contains the correct text without any typos. The task is complete.

<finish></finish>
Original file line number Diff line number Diff line change
Expand Up @@ -406,19 +406,17 @@ Use Jupyter IPython to write a text file containing 'hello world' to '/workspace

----------

Certainly! I'll use Jupyter IPython to create a text file containing 'hello world' in the specified location.
Certainly! I'll use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'.
<execute_ipython>
create_file('/workspace/test.txt')
with open('/workspace/test.txt', 'w') as f:
f.write('hello world')
print("File created successfully.")
</execute_ipython>

----------

OBSERVATION:
[File: /workspace/test.txt (1 lines total)]
(this is the beginning of the file)
1|
(this is the end of the file)
[File /workspace/test.txt created.]
File created successfully.
[Jupyter current working directory: /workspace]
[Jupyter Python interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.11/bin/python]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,37 +406,32 @@ Use Jupyter IPython to write a text file containing 'hello world' to '/workspace

----------

Certainly! I'll use Jupyter IPython to create a text file containing 'hello world' in the specified location.
Certainly! I'll use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'.
<execute_ipython>
create_file('/workspace/test.txt')
with open('/workspace/test.txt', 'w') as f:
f.write('hello world')
print("File created successfully.")
</execute_ipython>

----------

OBSERVATION:
[File: /workspace/test.txt (1 lines total)]
(this is the beginning of the file)
1|
(this is the end of the file)
[File /workspace/test.txt created.]
File created successfully.
[Jupyter current working directory: /workspace]
[Jupyter Python interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.11/bin/python]

----------

Now that the file has been created, I'll append the content 'hello world' to it.
Great! The file has been created successfully. Let's verify its contents:
<execute_ipython>
append_file('/workspace/test.txt', 'hello world')
with open('/workspace/test.txt', 'r') as f:
print(f.read())
</execute_ipython>

----------

OBSERVATION:
[File: /workspace/test.txt (1 lines total after edit)]
(this is the beginning of the file)
1|hello world
(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.]
hello world
[Jupyter current working directory: /workspace]
[Jupyter Python interpreter: /openhands/poetry/openhands-ai-5O4_aCHf-py3.11/bin/python]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Certainly! I'll use Jupyter IPython to create a text file containing 'hello world' in the specified location.
Certainly! I'll use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'.

<execute_ipython>
create_file('/workspace/test.txt')
with open('/workspace/test.txt', 'w') as f:
f.write('hello world')
print("File created successfully.")
Loading
Loading