Compare commits

...

3 Commits

Author SHA1 Message Date
Graham Neubig 86fef18b35 Merge branch 'main' into fix-issue-9013 2025-06-15 08:24:34 -04:00
openhands d707c4c3b2 Fix test_cmd_run_tool to expect timeout as a required parameter 2025-06-09 20:25:52 +00:00
openhands 67c128f101 Make timeout parameter required in execute_bash tool 2025-06-09 20:14:13 +00:00
5 changed files with 32 additions and 25 deletions
+1
View File
@@ -23,6 +23,7 @@ export interface CommandAction extends OpenHandsActionEvent<"run"> {
source: "agent" | "user";
args: {
command: string;
timeout: number;
security_risk: ActionSecurityRisk;
confirmation_state: "confirmed" | "rejected" | "awaiting_confirmation";
thought: string;
@@ -87,18 +87,21 @@ def response_to_actions(
raise FunctionCallValidationError(
f'Missing required argument "command" in tool call {tool_call.function.name}'
)
if 'timeout' not in arguments:
raise FunctionCallValidationError(
f'Missing required argument "timeout" in tool call {tool_call.function.name}'
)
# convert is_input to boolean
is_input = arguments.get('is_input', 'false') == 'true'
action = CmdRunAction(command=arguments['command'], is_input=is_input)
# Set hard timeout if provided
if 'timeout' in arguments:
try:
action.set_hard_timeout(float(arguments['timeout']))
except ValueError as e:
raise FunctionCallValidationError(
f"Invalid float passed to 'timeout' argument: {arguments['timeout']}"
) from e
# Set hard timeout
try:
action.set_hard_timeout(float(arguments['timeout']))
except ValueError as e:
raise FunctionCallValidationError(
f"Invalid float passed to 'timeout' argument: {arguments['timeout']}"
) from e
# ================================================
# IPythonTool (Jupyter)
@@ -9,10 +9,10 @@ _DETAILED_BASH_DESCRIPTION = """Execute a bash command in the terminal within a
### Command Execution
* One command at a time: You can only execute one bash command at a time. If you need to run multiple commands sequentially, use `&&` or `;` to chain them together.
* Persistent session: Commands execute in a persistent shell session where environment variables, virtual environments, and working directory persist between commands.
* Timeout: Commands have a soft timeout of 10 seconds, once that's reached, you have the option to continue or interrupt the command (see section below for details)
* Timeout: You must specify a timeout in seconds for the command execution. A value of 10 seconds is recommended if you cannot estimate how long the command should take.
### Running and Interacting with Processes
* Long running commands: For commands that may run indefinitely, run them in the background and redirect output to a file, e.g. `python3 app.py > server.log 2>&1 &`. For commands that need to run for a specific duration, like "sleep", you can set the "timeout" argument to specify a hard timeout in seconds.
* Long running commands: For commands that may run indefinitely, run them in the background and redirect output to a file, e.g. `python3 app.py > server.log 2>&1 &`.
* Interact with running process: If a bash command returns exit code `-1`, this means the process is not yet finished. By setting `is_input` to `true`, you can:
- Send empty `command` to retrieve additional logs
- Send text (set `command` to the text) to STDIN of the running process
@@ -27,7 +27,7 @@ _DETAILED_BASH_DESCRIPTION = """Execute a bash command in the terminal within a
"""
_SHORT_BASH_DESCRIPTION = """Execute a bash command in the terminal.
* Long running commands: For commands that may run indefinitely, it should be run in the background and the output should be redirected to a file, e.g. command = `python3 app.py > server.log 2>&1 &`. For commands that need to run for a specific duration, you can set the "timeout" argument to specify a hard timeout in seconds.
* Long running commands: For commands that may run indefinitely, it should be run in the background and the output should be redirected to a file, e.g. command = `python3 app.py > server.log 2>&1 &`. You must specify a timeout in seconds for the command execution. A value of 10 seconds is recommended if you cannot estimate how long the command should take.
* Interact with running process: If a bash command returns exit code `-1`, this means the process is not yet finished. By setting `is_input` to `true`, the assistant can interact with the running process and send empty `command` to retrieve any additional logs, or send additional text (set `command` to the text) to STDIN of the running process, or send command like `C-c` (Ctrl+C), `C-d` (Ctrl+D), `C-z` (Ctrl+Z) to interrupt the process.
* One command at a time: You can only execute one bash command at a time. If you need to run multiple commands sequentially, you can use `&&` or `;` to chain them together."""
@@ -67,10 +67,10 @@ def create_cmd_run_tool(
},
'timeout': {
'type': 'number',
'description': 'Optional. Sets a hard timeout in seconds for the command execution. If not provided, the command will use the default soft timeout behavior.',
'description': 'Sets a hard timeout in seconds for the command execution. Select a reasonable value, and 10 is a good option if you cannot estimate a-priori how long the command should take.',
},
},
'required': ['command'],
'required': ['command', 'timeout'],
},
),
)
+1 -1
View File
@@ -132,7 +132,7 @@ def test_cmd_run_tool():
assert CmdRunTool['type'] == 'function'
assert CmdRunTool['function']['name'] == 'execute_bash'
assert 'command' in CmdRunTool['function']['parameters']['properties']
assert CmdRunTool['function']['parameters']['required'] == ['command']
assert CmdRunTool['function']['parameters']['required'] == ['command', 'timeout']
def test_ipython_tool():
+14 -11
View File
@@ -47,16 +47,7 @@ def create_mock_response(function_name: str, arguments: dict) -> ModelResponse:
def test_execute_bash_valid():
"""Test execute_bash with valid arguments."""
response = create_mock_response(
'execute_bash', {'command': 'ls', 'is_input': 'false'}
)
actions = response_to_actions(response)
assert len(actions) == 1
assert isinstance(actions[0], CmdRunAction)
assert actions[0].command == 'ls'
assert actions[0].is_input is False
# Test with timeout parameter
# Test with required timeout parameter
with patch.object(CmdRunAction, 'set_hard_timeout') as mock_set_hard_timeout:
response_with_timeout = create_mock_response(
'execute_bash', {'command': 'ls', 'is_input': 'false', 'timeout': 30}
@@ -74,12 +65,24 @@ def test_execute_bash_valid():
def test_execute_bash_missing_command():
"""Test execute_bash with missing command argument."""
response = create_mock_response('execute_bash', {'is_input': 'false'})
response = create_mock_response(
'execute_bash', {'is_input': 'false', 'timeout': 10}
)
with pytest.raises(FunctionCallValidationError) as exc_info:
response_to_actions(response)
assert 'Missing required argument "command"' in str(exc_info.value)
def test_execute_bash_missing_timeout():
"""Test execute_bash with missing timeout argument."""
response = create_mock_response(
'execute_bash', {'command': 'ls', 'is_input': 'false'}
)
with pytest.raises(FunctionCallValidationError) as exc_info:
response_to_actions(response)
assert 'Missing required argument "timeout"' in str(exc_info.value)
def test_execute_ipython_cell_valid():
"""Test execute_ipython_cell with valid arguments."""
response = create_mock_response('execute_ipython_cell', {'code': "print('hello')"})