Skip to content

Commit 803bdce

Browse files
Fix Windows prompt refinement: ensure 'bash' is replaced with 'powershell' in all prompts (OpenHands#10179)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 3eecac2 commit 803bdce

4 files changed

Lines changed: 194 additions & 3 deletions

File tree

openhands/agenthub/codeact_agent/tools/bash.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import sys
23

34
from litellm import ChatCompletionToolParam, ChatCompletionToolParamFunctionChunk
@@ -37,7 +38,16 @@
3738

3839
def refine_prompt(prompt: str):
3940
if sys.platform == 'win32':
40-
return prompt.replace('bash', 'powershell')
41+
# Replace 'bash' with 'powershell' including tool names like 'execute_bash'
42+
# First replace 'execute_bash' with 'execute_powershell' to handle tool names
43+
result = re.sub(
44+
r'\bexecute_bash\b', 'execute_powershell', prompt, flags=re.IGNORECASE
45+
)
46+
# Then replace standalone 'bash' with 'powershell'
47+
result = re.sub(
48+
r'(?<!execute_)(?<!_)\bbash\b', 'powershell', result, flags=re.IGNORECASE
49+
)
50+
return result
4151
return prompt
4252

4353

openhands/llm/fn_call_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ def get_example_for_tools(tools: list[dict]) -> str:
383383
"""
384384
example = example.lstrip()
385385

386-
return example
386+
return refine_prompt(example)
387387

388388

389389
IN_CONTEXT_LEARNING_EXAMPLE_PREFIX = get_example_for_tools

openhands/utils/prompt.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from jinja2 import Template
66

7+
from openhands.agenthub.codeact_agent.tools.bash import refine_prompt
78
from openhands.controller.state.state import State
89
from openhands.core.message import Message, TextContent
910
from openhands.events.observation.agent import MicroagentKnowledge
@@ -91,7 +92,8 @@ def _load_template(self, template_name: str) -> Template:
9192
return Template(file.read())
9293

9394
def get_system_message(self) -> str:
94-
return self.system_template.render().strip()
95+
system_message = self.system_template.render().strip()
96+
return refine_prompt(system_message)
9597

9698
def get_example_user_message(self) -> str:
9799
"""This is an initial user message that can be provided to the agent
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import sys
2+
from unittest.mock import patch
3+
4+
import pytest
5+
6+
from openhands.agenthub.codeact_agent.codeact_agent import CodeActAgent
7+
from openhands.core.config import AgentConfig
8+
from openhands.llm.llm import LLM
9+
10+
# Skip all tests in this module if not running on Windows
11+
pytestmark = pytest.mark.skipif(
12+
sys.platform != 'win32', reason='Windows prompt refinement tests require Windows'
13+
)
14+
15+
16+
@pytest.fixture
17+
def mock_llm():
18+
"""Create a mock LLM for testing."""
19+
llm = LLM(config={'model': 'gpt-4', 'api_key': 'test'})
20+
return llm
21+
22+
23+
@pytest.fixture
24+
def agent_config():
25+
"""Create a basic agent config for testing."""
26+
return AgentConfig()
27+
28+
29+
def test_codeact_agent_system_prompt_no_bash_on_windows(mock_llm, agent_config):
30+
"""Test that CodeActAgent's system prompt doesn't contain 'bash' on Windows."""
31+
# Create a CodeActAgent instance
32+
agent = CodeActAgent(llm=mock_llm, config=agent_config)
33+
34+
# Get the system prompt
35+
system_prompt = agent.prompt_manager.get_system_message()
36+
37+
# Assert that 'bash' doesn't exist in the system prompt (case-insensitive)
38+
assert 'bash' not in system_prompt.lower(), (
39+
f"System prompt contains 'bash' on Windows platform. "
40+
f"It should be replaced with 'powershell'. "
41+
f'System prompt: {system_prompt}'
42+
)
43+
44+
# Verify that 'powershell' exists instead (case-insensitive)
45+
assert 'powershell' in system_prompt.lower(), (
46+
f"System prompt should contain 'powershell' on Windows platform. "
47+
f'System prompt: {system_prompt}'
48+
)
49+
50+
51+
def test_codeact_agent_tool_descriptions_no_bash_on_windows(mock_llm, agent_config):
52+
"""Test that CodeActAgent's tool descriptions don't contain 'bash' on Windows."""
53+
# Create a CodeActAgent instance
54+
agent = CodeActAgent(llm=mock_llm, config=agent_config)
55+
56+
# Get the tools
57+
tools = agent.tools
58+
59+
# Check each tool's description and parameters
60+
for tool in tools:
61+
if tool['type'] == 'function':
62+
function_info = tool['function']
63+
64+
# Check function description
65+
description = function_info.get('description', '')
66+
assert 'bash' not in description.lower(), (
67+
f"Tool '{function_info['name']}' description contains 'bash' on Windows. "
68+
f'Description: {description}'
69+
)
70+
71+
# Check parameter descriptions
72+
parameters = function_info.get('parameters', {})
73+
properties = parameters.get('properties', {})
74+
75+
for param_name, param_info in properties.items():
76+
param_description = param_info.get('description', '')
77+
assert 'bash' not in param_description.lower(), (
78+
f"Tool '{function_info['name']}' parameter '{param_name}' "
79+
f"description contains 'bash' on Windows. "
80+
f'Parameter description: {param_description}'
81+
)
82+
83+
84+
def test_in_context_learning_example_no_bash_on_windows():
85+
"""Test that in-context learning examples don't contain 'bash' on Windows."""
86+
from openhands.agenthub.codeact_agent.tools.bash import create_cmd_run_tool
87+
from openhands.agenthub.codeact_agent.tools.finish import FinishTool
88+
from openhands.agenthub.codeact_agent.tools.str_replace_editor import (
89+
create_str_replace_editor_tool,
90+
)
91+
from openhands.llm.fn_call_converter import get_example_for_tools
92+
93+
# Create a sample set of tools
94+
tools = [
95+
create_cmd_run_tool(),
96+
create_str_replace_editor_tool(),
97+
FinishTool,
98+
]
99+
100+
# Get the in-context learning example
101+
example = get_example_for_tools(tools)
102+
103+
# Assert that 'bash' doesn't exist in the example (case-insensitive)
104+
assert 'bash' not in example.lower(), (
105+
f"In-context learning example contains 'bash' on Windows platform. "
106+
f"It should be replaced with 'powershell'. "
107+
f'Example: {example}'
108+
)
109+
110+
# Verify that 'powershell' exists instead (case-insensitive)
111+
if example: # Only check if example is not empty
112+
assert 'powershell' in example.lower(), (
113+
f"In-context learning example should contain 'powershell' on Windows platform. "
114+
f'Example: {example}'
115+
)
116+
117+
118+
def test_refine_prompt_function_works():
119+
"""Test that the refine_prompt function correctly replaces 'bash' with 'powershell'."""
120+
from openhands.agenthub.codeact_agent.tools.bash import refine_prompt
121+
122+
# Test basic replacement
123+
test_prompt = 'Execute a bash command to list files'
124+
refined_prompt = refine_prompt(test_prompt)
125+
126+
assert 'bash' not in refined_prompt.lower()
127+
assert 'powershell' in refined_prompt.lower()
128+
assert refined_prompt == 'Execute a powershell command to list files'
129+
130+
# Test multiple occurrences
131+
test_prompt = 'Use bash to run bash commands in the bash shell'
132+
refined_prompt = refine_prompt(test_prompt)
133+
134+
assert 'bash' not in refined_prompt.lower()
135+
assert (
136+
refined_prompt
137+
== 'Use powershell to run powershell commands in the powershell shell'
138+
)
139+
140+
# Test case sensitivity
141+
test_prompt = 'BASH and Bash and bash should all be replaced'
142+
refined_prompt = refine_prompt(test_prompt)
143+
144+
assert 'bash' not in refined_prompt.lower()
145+
assert (
146+
refined_prompt
147+
== 'powershell and powershell and powershell should all be replaced'
148+
)
149+
150+
# Test execute_bash tool name replacement
151+
test_prompt = 'Use the execute_bash tool to run commands'
152+
refined_prompt = refine_prompt(test_prompt)
153+
154+
assert 'execute_bash' not in refined_prompt.lower()
155+
assert 'execute_powershell' in refined_prompt.lower()
156+
assert refined_prompt == 'Use the execute_powershell tool to run commands'
157+
158+
# Test that words containing 'bash' but not equal to 'bash' are preserved
159+
test_prompt = 'The bashful person likes bash-like syntax'
160+
refined_prompt = refine_prompt(test_prompt)
161+
162+
# 'bashful' should be preserved, 'bash-like' should become 'powershell-like'
163+
assert 'bashful' in refined_prompt
164+
assert 'powershell-like' in refined_prompt
165+
assert refined_prompt == 'The bashful person likes powershell-like syntax'
166+
167+
168+
def test_refine_prompt_function_on_non_windows():
169+
"""Test that the refine_prompt function doesn't change anything on non-Windows platforms."""
170+
from openhands.agenthub.codeact_agent.tools.bash import refine_prompt
171+
172+
# Mock sys.platform to simulate non-Windows
173+
with patch('openhands.agenthub.codeact_agent.tools.bash.sys.platform', 'linux'):
174+
test_prompt = 'Execute a bash command to list files'
175+
refined_prompt = refine_prompt(test_prompt)
176+
177+
# On non-Windows, the prompt should remain unchanged
178+
assert refined_prompt == test_prompt
179+
assert 'bash' in refined_prompt.lower()

0 commit comments

Comments
 (0)