Add timeout parameter to bash tool for hard timeout control (#8106)

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
This commit is contained in:
Graham Neubig 2025-05-15 01:24:42 -04:00 committed by GitHub
parent 3ca585b79f
commit e4c284f96d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 32 additions and 3 deletions

View File

@ -93,6 +93,15 @@ def response_to_actions(
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
# ================================================
# IPythonTool (Jupyter)
# ================================================

View File

@ -7,10 +7,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 120 seconds, once that's reached, you have the option to continue or interrupt the command (see section below for details)
* 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)
### 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 &`.
* 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.
* 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
@ -25,7 +25,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 &`.
* 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.
* 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."""
@ -63,6 +63,10 @@ def create_cmd_run_tool(
),
'enum': ['true', 'false'],
},
'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.',
},
},
'required': ['command'],
},

View File

@ -1,6 +1,7 @@
"""Test function calling module."""
import json
from unittest.mock import patch
import pytest
from litellm import ModelResponse
@ -56,6 +57,21 @@ def test_execute_bash_valid():
assert actions[0].command == 'ls'
assert actions[0].is_input is False
# Test with 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}
)
actions_with_timeout = response_to_actions(response_with_timeout)
# Verify set_hard_timeout was called with the correct value
mock_set_hard_timeout.assert_called_once_with(30.0)
assert len(actions_with_timeout) == 1
assert isinstance(actions_with_timeout[0], CmdRunAction)
assert actions_with_timeout[0].command == 'ls'
assert actions_with_timeout[0].is_input is False
def test_execute_bash_missing_command():
"""Test execute_bash with missing command argument."""