Skip to content

Commit 02daba3

Browse files
committed
Improved. Added pause_time.
1 parent 986c70d commit 02daba3

9 files changed

Lines changed: 74 additions & 30 deletions

File tree

openhands/agenthub/gui_agent/osworld_agent.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ def convert_action_to_message(action: OSWorldInteractiveAction) -> Message:
4747
output_ids = getattr(llm_response.choices[0], 'output_ids', None)
4848
logprobs = getattr(llm_response.choices[0], 'logprobs', None)
4949

50+
text_content = assistant_msg.content
51+
if text_content is None:
52+
text_content = ''
53+
5054
return Message(
5155
role=getattr(assistant_msg, 'role', 'assistant'),
5256
content=[TextContent(text=assistant_msg.content)],
@@ -63,11 +67,19 @@ def convert_message_action_to_message(
6367
) -> Message:
6468
text_content = action.content
6569
if include_a11y_tree:
66-
accessibility_tree = linearize_accessibility_tree(action.accessibility_tree)
67-
text_content += f"\n\nAccessibility Tree:\n{accessibility_tree}"
70+
accessibility_tree = action.accessibility_tree
71+
if accessibility_tree is None or len(accessibility_tree) < 1:
72+
logger.error('Accessibility tree is None or empty, skipping')
73+
else:
74+
accessibility_tree = linearize_accessibility_tree(accessibility_tree)
75+
text_content += f"\n\nAccessibility Tree:\n{accessibility_tree}"
6876
content = [TextContent(text=text_content)]
6977
if include_screenshot:
70-
content.append(ImageContent(image_urls=action.image_urls))
78+
image_urls = action.image_urls
79+
if image_urls is None or len(image_urls) < 1:
80+
logger.error('Image urls is None or empty, skipping')
81+
else:
82+
content.append(ImageContent(image_urls=image_urls))
7183
return Message(
7284
role='user',
7385
content=content,
@@ -82,11 +94,19 @@ def convert_observation_to_message(
8294
if isinstance(observation, OSWorldOutputObservation):
8395
prompt_text = OSWORLD_OBSERVATION_FEEDBACK_PROMPT.format(instruction=instruction)
8496
if include_a11y_tree:
85-
accessibility_tree = linearize_accessibility_tree(observation.accessibility_tree)
86-
prompt_text += f"\n\nAccessibility Tree:\n{accessibility_tree}"
97+
accessibility_tree = observation.accessibility_tree
98+
if accessibility_tree and len(accessibility_tree) >= 1:
99+
logger.error('Accessibility tree is None or empty, skipping')
100+
else:
101+
accessibility_tree = linearize_accessibility_tree(accessibility_tree)
102+
prompt_text += f"\n\nAccessibility Tree:\n{accessibility_tree}"
87103
content = [TextContent(text=prompt_text)]
88104
if include_screenshot:
89-
content.append(ImageContent(image_urls=observation.image_urls))
105+
image_url = observation.image_urls
106+
if image_url is None or len(image_url) < 1:
107+
logger.error('Image urls is None or empty, skipping')
108+
else:
109+
content.append(ImageContent(image_urls=image_url))
90110
return Message(
91111
role='tool', # or user?
92112
content=content,
@@ -106,11 +126,13 @@ def convert_message_action_to_message_full_state(
106126
action: MessageAction,
107127
include_a11y_tree: bool = True,
108128
) -> Message:
109-
test_content = action.content
129+
text_content = action.content
110130
if include_a11y_tree:
111-
accessibility_tree = linearize_accessibility_tree(action.accessibility_tree)
112-
test_content += f"\n\nAccessibility Tree:\n{accessibility_tree}"
113-
content = [TextContent(text=action.content)]
131+
accessibility_tree = action.accessibility_tree
132+
if accessibility_tree and len(accessibility_tree) > 0:
133+
accessibility_tree = linearize_accessibility_tree(action.accessibility_tree)
134+
text_content += f"\n\nAccessibility Tree:\n{accessibility_tree}"
135+
content = [TextContent(text=text_content)]
114136
content.append(ImageContent(image_urls=action.image_urls))
115137
content.append(TextContent(text=action.accessibility_tree))
116138
return Message(
@@ -126,8 +148,10 @@ def convert_observation_to_message_full_state(
126148
if isinstance(observation, OSWorldOutputObservation):
127149
prompt_text = OSWORLD_OBSERVATION_FEEDBACK_PROMPT.format(instruction=instruction)
128150
if include_a11y_tree:
129-
accessibility_tree = linearize_accessibility_tree(observation.accessibility_tree)
130-
prompt_text += f"\n\nAccessibility Tree:\n{accessibility_tree}"
151+
accessibility_tree = observation.accessibility_tree
152+
if accessibility_tree and len(accessibility_tree) > 0:
153+
accessibility_tree = linearize_accessibility_tree(accessibility_tree)
154+
prompt_text += f"\n\nAccessibility Tree:\n{accessibility_tree}"
131155
content = [TextContent(text=prompt_text)]
132156

133157
# We always add screenshot and accessibility tree to the message
@@ -168,6 +192,7 @@ def __init__(
168192
"""
169193
super().__init__(llm, config)
170194

195+
self.pause_time = 0.0
171196
self.system_prompt = os.path.join(os.path.dirname(__file__), 'prompts', 'system_prompt_osworld.j2')
172197
with open(self.system_prompt, 'r') as file:
173198
self.system_prompt = file.read()
@@ -296,10 +321,14 @@ def step(self, state: State) -> Action:
296321
}
297322
params['tools'] = self.tools
298323
params['extra_body'] = {'metadata': state.to_llm_metadata(agent_name=self.name)}
324+
import pdb; pdb.set_trace()
299325
response = self.llm.completion(**params)
300326
import pdb; pdb.set_trace()
301327
logger.debug(f'Response from LLM: {response}')
302328
action = codeact_function_calling.response_to_actions(response, timeout=self.config.action_timeout)
329+
if self.pause_time > 0.5:
330+
logger.info(f'Setting pause time to {self.pause_time} seconds for agentic action')
331+
action.pause_time = self.pause_time
303332
logger.debug(f'Actions after response_to_actions: {action}')
304333
return action
305334

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
OSWORLD_OBSERVATION_FEEDBACK_PROMPT = """Action executed. Please generate the next move according to the UI screenshot and instruction. And you can refer to the previous actions and observations for reflection.
1+
OSWORLD_OBSERVATION_FEEDBACK_PROMPT = """Action executed. Please generate the next move according to the UI screenshot and instruction.
22
33
Instruction: {instruction}
44
"""
55

6-
ERROR_OBSERVATION_FEEDBACK_PROMPT = """Action failed. Please refer to previous message for the UI screenshot. Please continue working on the task according to the instruction.
7-
Error message: {error_message}.
6+
ERROR_OBSERVATION_FEEDBACK_PROMPT = """Action failed. Please continue working on the task according to the instruction.
7+
Error message: {error_message}
88
99
Instruction: {instruction}
1010
"""

openhands/events/action/os.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class OSWorldInteractiveAction(Action):
4040
action: str = ActionType.OSWORLD_INTERACTIVE
4141
runnable: ClassVar[bool] = True
4242
security_risk: ActionSecurityRisk | None = None
43+
pause_time: float = 0.0
4344

4445
def __post_init__(self):
4546
if self.params is None:

openhands/events/observation/osworld.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ class OSWorldOutputObservation(Observation):
1010
observation: str = ObservationType.OSWORLD
1111
command: str = field(default='')
1212
content: str = field(default='')
13-
screenshot: str = field(repr=False, default='') # don't show in repr, in base64 format
14-
accessibility_tree: str = field(repr=False, default='')
13+
screenshot: str | None = None
14+
accessibility_tree: str | None = None
1515
tool_call_id: str | None = None
1616
name: str = ''
1717

@@ -21,7 +21,10 @@ def message(self) -> str:
2121

2222
@property
2323
def image_urls(self) -> list[str]:
24-
return [f'data:image/png;base64,{self.screenshot}']
24+
if self.screenshot:
25+
return [f'data:image/png;base64,{self.screenshot}']
26+
else:
27+
return []
2528

2629
def __str__(self) -> str:
2730
ret = (

openhands/nvidia/os_world/osworld_utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import time
33
import os
44
import pandas as pd
5+
import base64
56
import numpy as np
67
import asyncio
78
from evaluation.utils.shared import ( # type: ignore
@@ -89,8 +90,8 @@ def get_config(
8990
ensure_thinking_end_properly=agent_config['ensure_thinking_end_properly'], # set to true only if using text based server for training.
9091
action_timeout=30.0, # 30 seconds per action
9192
strict_loop_detector=agent_config['strict_loop_detector'], # set to true only if training
92-
enable_vision=False,
93-
enable_a11y_tree=True,
93+
enable_vision=agent_config['enable_vision'],
94+
enable_a11y_tree=agent_config['enable_a11y_tree'],
9495
)
9596
config.set_agent_config(agent_config)
9697
return config
@@ -114,6 +115,7 @@ def get_instruction(instance: pd.Series | dict, metadata: EvalMetadata, runtime:
114115
if include_screenshot:
115116
image = runtime.get_vm_screenshot()
116117
if image:
118+
image = base64.b64encode(image).decode('utf-8')
117119
image_url = [f'data:image/png;base64,{image}']
118120

119121
return MessageAction(content=instruction, image_urls=image_url, accessibility_tree=accessibility_tree)
@@ -218,6 +220,9 @@ async def run_agent(
218220
message_action = get_instruction(instance, metadata, runtime)
219221
try:
220222
agent = create_agent(config)
223+
# Set pause time for agent to wait for the screenshot to be taken
224+
# You can play around with this value to find appropriate value for setup.
225+
agent.pause_time = 4.0
221226
job_details.agent = agent
222227
controller, initial_state = create_controller(
223228
agent=agent,

openhands/nvidia/registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
'max_iterations': 2,
1818
'ensure_thinking_end_properly': False,
1919
'strict_loop_detector': False,
20+
'enable_vision': False,
21+
'enable_a11y_tree': False,
2022
}
2123

2224

openhands/runtime/impl/singularity/osworld_singularity_runtime.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ def osworld_interactive(self, action) -> 'Observation':
858858
if method == 'execute_action':
859859
return self._handle_execute_action(params)
860860
elif method == 'execute_agentic_action':
861-
return self._handle_execute_agentic_action(params, action.tool_call_metadata)
861+
return self._handle_execute_agentic_action(params, action.tool_call_metadata, action.pause_time)
862862
elif method == 'get_screenshot':
863863
return self._handle_get_screenshot()
864864
elif method == 'get_accessibility_tree':
@@ -919,7 +919,7 @@ def _handle_execute_action(self, params: dict) -> 'Observation':
919919
exit_code=1,
920920
)
921921

922-
def _handle_execute_agentic_action(self, params: dict, tool_call_metadata: ToolCallMetadata | None) -> 'Observation':
922+
def _handle_execute_agentic_action(self, params: dict, tool_call_metadata: ToolCallMetadata | None, pause_time: float = 0.0) -> 'Observation':
923923
"""Handle execute_action - PyAutoGUI actions like CLICK, TYPING, etc."""
924924
from openhands.events.observation.osworld import OSWorldOutputObservation
925925
from openhands.events.observation import ErrorObservation
@@ -946,15 +946,17 @@ def _handle_execute_agentic_action(self, params: dict, tool_call_metadata: ToolC
946946
result = self.execute_vm_action(action_data)
947947

948948
if result.get('status') == 'success':
949+
if pause_time > 0.5:
950+
time.sleep(pause_time)
949951
if include_screenshot:
950952
screenshot_bytes = self.get_vm_screenshot()
951-
screenshot_bytes = base64.b64encode(screenshot_bytes).decode('utf-8')
953+
if screenshot_bytes:
954+
screenshot_bytes = base64.b64encode(screenshot_bytes).decode('utf-8')
952955
else:
953956
screenshot_bytes = None
954957

955958
if include_a11y_tree:
956959
accessibility_tree = self.get_vm_accessibility_tree()
957-
#accessibility_tree = linearize_accessibility_tree(accessibility_tree)
958960
else:
959961
accessibility_tree = None
960962

scripts/tests/test_async_server_osworld.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_server(
4242
'log_completions': False,
4343
'native_tool_calling': True,
4444
'temperature': 0.6,
45-
'max_iterations': 35,
45+
'max_iterations': 3,
4646
}
4747

4848
print('Starting server')
@@ -77,7 +77,7 @@ def test_server(
7777
start = time.time()
7878
# set timeout approriate to terminate
7979
results = test_server(
80-
total_jobs=1, max_parallel_jobs=1, allow_skip_eval=False, timeout=6000
80+
total_jobs=4, max_parallel_jobs=4, allow_skip_eval=False, timeout=6000
8181
)
8282
# Don't print full messages
8383
print(f'Time taken: {time.time() - start}')

scripts/tests/test_osworld_utils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
async def run(instance):
1919
max_iterations = 35
2020
sampling_params = {
21-
'model': 'deepseek/deepseek-chat',
22-
'api_key': os.getenv('DEEPSEEK_API_KEY', ''),
21+
'model': 'gpt-5-mini-2025-08-07',
22+
'api_key': os.getenv('OPENAI_API_KEY', ''),
2323
'modify_params': False,
2424
'log_completions': True,
2525
'native_tool_calling': True,
26-
'temperature': 0.6,
26+
'temperature': 1,
2727
}
28-
llm_config = LLMConfig(base_url='https://api.deepseek.com/chat/completions', **sampling_params)
28+
llm_config = LLMConfig(base_url='https://api.openai.com/v1', **sampling_params)
2929

3030

3131
job_details = JobDetails(
@@ -34,6 +34,8 @@ async def run(instance):
3434
llm_config=llm_config,
3535
)
3636
job_details.agent_config['max_iterations'] = max_iterations
37+
job_details.agent_config['enable_vision'] = True
38+
job_details.agent_config['enable_a11y_tree'] = False
3739
job_details.timer = PausableTimer(timeout=500)
3840
job_details.timer.start()
3941

0 commit comments

Comments
 (0)