mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 04:02:22 +02:00
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""
|
|
Loop detection for the browser sub-agent.
|
|
|
|
Tracks recent state-mutating tool calls in a sliding window. If the model
|
|
repeats the same (tool, input) with the same result several times, we inject
|
|
an is_error message in the next tool_result to force a strategy change. This
|
|
prevents the model from burning the entire turn budget on a failing approach.
|
|
"""
|
|
|
|
import json
|
|
|
|
# Tools that are read-only / idempotent and should NOT count toward loop
|
|
# detection. Repeating these is normal (scrolling through a feed, taking
|
|
# successive screenshots, polling for an element to appear).
|
|
_LOOP_DETECTION_EXCLUDED_TOOLS = {
|
|
"BrowserScreenshot",
|
|
"BrowserGetText",
|
|
"BrowserGetElements",
|
|
"BrowserListInteractives", # Phase 3
|
|
"BrowserWait",
|
|
"ReportProgress", # Phase 2
|
|
"RequestHumanIntervention",
|
|
}
|
|
|
|
_LOOP_WINDOW_SIZE = 5
|
|
_LOOP_REPEAT_THRESHOLD = 3
|
|
_LOOP_HARD_CAP = 5
|
|
|
|
|
|
def _hash_tool_call(tool_name: str, tool_input: dict, result: dict) -> tuple[str, str, str]:
|
|
"""Build a stable hash key for a tool call, including its result.
|
|
|
|
Including the result hash means that legitimate progress (same input,
|
|
different output; e.g. BrowserScroll on a long feed) does NOT count
|
|
as a loop. Only same-input + same-output is treated as stuck.
|
|
"""
|
|
try:
|
|
input_key = json.dumps(tool_input, sort_keys=True, default=str)
|
|
except Exception:
|
|
input_key = repr(tool_input)
|
|
try:
|
|
# Truncate the result hash to avoid huge image blobs in the key
|
|
result_key = json.dumps(result, sort_keys=True, default=str)[:300]
|
|
except Exception:
|
|
result_key = repr(result)[:300]
|
|
return (tool_name, input_key, result_key)
|
|
|
|
|
|
def _detect_loop(
|
|
recent_calls: list[tuple[str, str, str]],
|
|
new_call: tuple[str, str, str],
|
|
) -> bool:
|
|
"""Return True if `new_call` constitutes a loop given recent history.
|
|
|
|
A loop is when the same (tool, input, result) has appeared at least
|
|
`_LOOP_REPEAT_THRESHOLD` times within the last `_LOOP_WINDOW_SIZE`
|
|
state-mutating calls (the new call counts as one of those occurrences).
|
|
"""
|
|
if new_call[0] in _LOOP_DETECTION_EXCLUDED_TOOLS:
|
|
return False
|
|
window = recent_calls[-(_LOOP_WINDOW_SIZE - 1):] + [new_call]
|
|
matches = sum(1 for c in window if c == new_call)
|
|
return matches >= _LOOP_REPEAT_THRESHOLD
|
|
|
|
|
|
_LOOP_WARNING_TEXT = (
|
|
"LOOP DETECTED: You have called this tool with these exact parameters and "
|
|
"gotten the same result {count} times in a row. STOP retrying this approach "
|
|
", it is not working. Try a fundamentally different strategy: "
|
|
"(1) check the page state with BrowserScreenshot or BrowserGetText, "
|
|
"(2) try a different selector or a different tool, "
|
|
"(3) use BrowserPressKey for keyboard shortcuts if the site supports them, "
|
|
"or (4) call RequestHumanIntervention if you genuinely cannot proceed."
|
|
)
|