Implement interrupt handling for long-running tool executions in AIAgent
- Added functionality to signal and terminate long-running terminal commands when a new user message is received, allowing for immediate agent response. - Introduced a global interrupt event in the terminal tool to facilitate early termination of subprocesses. - Updated the AIAgent class to handle interrupts gracefully, ensuring that remaining tool calls are skipped and appropriate messages are returned to maintain valid message sequences.
This commit is contained in:
parent
140d609e0c
commit
cfe2f3fe15
3 changed files with 94 additions and 14 deletions
23
run_agent.py
23
run_agent.py
|
|
@ -49,7 +49,7 @@ elif not os.getenv("HERMES_QUIET"):
|
|||
|
||||
# Import our tool system
|
||||
from model_tools import get_tool_definitions, handle_function_call, check_toolset_requirements
|
||||
from tools.terminal_tool import cleanup_vm
|
||||
from tools.terminal_tool import cleanup_vm, set_interrupt_event as _set_terminal_interrupt
|
||||
from tools.browser_tool import cleanup_browser
|
||||
|
||||
import requests
|
||||
|
|
@ -1549,6 +1549,9 @@ class AIAgent:
|
|||
Call this from another thread (e.g., input handler, message receiver)
|
||||
to gracefully stop the agent and process a new message.
|
||||
|
||||
Also signals long-running tool executions (e.g. terminal commands)
|
||||
to terminate early, so the agent can respond immediately.
|
||||
|
||||
Args:
|
||||
message: Optional new message that triggered the interrupt.
|
||||
If provided, the agent will include this in its response context.
|
||||
|
|
@ -1565,6 +1568,8 @@ class AIAgent:
|
|||
"""
|
||||
self._interrupt_requested = True
|
||||
self._interrupt_message = message
|
||||
# Signal the terminal tool to kill any running subprocess immediately
|
||||
_set_terminal_interrupt(True)
|
||||
if not self.quiet_mode:
|
||||
print(f"\n⚡ Interrupt requested" + (f": '{message[:40]}...'" if message and len(message) > 40 else f": '{message}'" if message else ""))
|
||||
|
||||
|
|
@ -1572,6 +1577,7 @@ class AIAgent:
|
|||
"""Clear any pending interrupt request."""
|
||||
self._interrupt_requested = False
|
||||
self._interrupt_message = None
|
||||
_set_terminal_interrupt(False)
|
||||
|
||||
@property
|
||||
def is_interrupted(self) -> bool:
|
||||
|
|
@ -2309,6 +2315,21 @@ class AIAgent:
|
|||
response_preview = function_result[:self.log_prefix_chars] + "..." if len(function_result) > self.log_prefix_chars else function_result
|
||||
print(f" ✅ Tool {i} completed in {tool_duration:.2f}s - {response_preview}")
|
||||
|
||||
# Check for interrupt between tool calls - skip remaining
|
||||
# tools so the agent can respond to the user immediately
|
||||
if self._interrupt_requested and i < len(assistant_message.tool_calls):
|
||||
remaining = len(assistant_message.tool_calls) - i
|
||||
print(f"{self.log_prefix}⚡ Interrupt: skipping {remaining} remaining tool call(s)")
|
||||
# Add placeholder results for skipped tool calls so the
|
||||
# message sequence stays valid (assistant tool_calls need matching tool results)
|
||||
for skipped_tc in assistant_message.tool_calls[i:]:
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"content": "[Tool execution skipped - user sent a new message]",
|
||||
"tool_call_id": skipped_tc.id
|
||||
})
|
||||
break
|
||||
|
||||
# Delay between tool calls
|
||||
if self.tool_delay > 0 and i < len(assistant_message.tool_calls):
|
||||
time.sleep(self.tool_delay)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue