Commit cf39a812 authored by Your Name's avatar Your Name

Add circuit breaker for tool call loops to prevent repetitive failing tool calls

parent ec55fd7f
This diff is collapsed.
...@@ -26,6 +26,9 @@ from datetime import datetime ...@@ -26,6 +26,9 @@ from datetime import datetime
import requests import requests
# Import FuzzyToolBreaker for circuit breaker functionality
from codai.models.utils import FuzzyToolBreaker
# ANSI color codes # ANSI color codes
class Colors: class Colors:
...@@ -538,6 +541,8 @@ class CoderClient: ...@@ -538,6 +541,8 @@ class CoderClient:
self.session_manager = session_manager self.session_manager = session_manager
self.session_name: Optional[str] = None self.session_name: Optional[str] = None
self.input_history: List[str] = [] # Track user inputs for readline self.input_history: List[str] = [] # Track user inputs for readline
# Initialize circuit breaker for detecting repetitive tool calls
self.tool_breaker = FuzzyToolBreaker(threshold=2)
def chat(self, message: str, stream: bool = True) -> str: def chat(self, message: str, stream: bool = True) -> str:
"""Send a message to the API and get response.""" """Send a message to the API and get response."""
...@@ -995,20 +1000,26 @@ class CoderClient: ...@@ -995,20 +1000,26 @@ class CoderClient:
# Show tool call with colors: yellow "Calling tool:", red tool name, white args # Show tool call with colors: yellow "Calling tool:", red tool name, white args
print(f"\n{Colors.YELLOW}Calling tool:{Colors.RESET} {Colors.RED}{tool_name}{Colors.RESET} -> {args_str}") print(f"\n{Colors.YELLOW}Calling tool:{Colors.RESET} {Colors.RED}{tool_name}{Colors.RESET} -> {args_str}")
# Check if confirmation is needed # Check circuit breaker first
needs_confirm = self.config.confirm_all should_stop, stop_message = self.tool_breaker.check(tool_name, arguments)
if tool_name in self.config.confirm_commands: if should_stop:
needs_confirm = self.config.confirm_commands[tool_name] print(f"{Colors.RED}{stop_message}{Colors.RESET}")
result = {"error": stop_message, "stopped_by_breaker": True}
if needs_confirm: else:
confirm = input(f"{Colors.YELLOW}Execute? (y/N): {Colors.RESET}").strip().lower() # Check if confirmation is needed
if confirm not in ('y', 'yes'): needs_confirm = self.config.confirm_all
result = {"error": "User declined execution", "declined": True} if tool_name in self.config.confirm_commands:
print(f"{Colors.YELLOW}Skipped{Colors.RESET}") needs_confirm = self.config.confirm_commands[tool_name]
if needs_confirm:
confirm = input(f"{Colors.YELLOW}Execute? (y/N): {Colors.RESET}").strip().lower()
if confirm not in ('y', 'yes'):
result = {"error": "User declined execution", "declined": True}
print(f"{Colors.YELLOW}Skipped{Colors.RESET}")
else:
result = self.tool_executor.execute(tool_name, arguments)
else: else:
result = self.tool_executor.execute(tool_name, arguments) result = self.tool_executor.execute(tool_name, arguments)
else:
result = self.tool_executor.execute(tool_name, arguments)
# Show result summary # Show result summary
if "error" in result: if "error" in result:
...@@ -1098,8 +1109,15 @@ class CoderClient: ...@@ -1098,8 +1109,15 @@ class CoderClient:
except json.JSONDecodeError: except json.JSONDecodeError:
arguments = {} arguments = {}
print(f" → {tool_name}({arguments})") # Check circuit breaker first
result = self.tool_executor.execute(tool_name, arguments) should_stop, stop_message = self.tool_breaker.check(tool_name, arguments)
if should_stop:
print(f"{Colors.RED}{stop_message}{Colors.RESET}")
result = {"error": stop_message, "stopped_by_breaker": True}
else:
print(f" → {tool_name}({arguments})")
result = self.tool_executor.execute(tool_name, arguments)
tool_results.append({ tool_results.append({
"tool_call_id": tc['id'], "tool_call_id": tc['id'],
"role": "tool", "role": "tool",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment