diff --git a/aria-brain/agent.py b/aria-brain/agent.py index 7a6ee8a..01c299c 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1150,6 +1150,32 @@ class Agent: _LOCAL_WINDOW_TURNS = 8 # nur die letzten N Turns ans lokale Modell (Speed) _LOCAL_TOOL_ITERATIONS = 3 # max Tool-Runden lokal, sonst → Claude + # Weiche Fehler-Marker im Tool-Ausgabetext. Viele Action-Skills geben einen + # menschenlesbaren Fehler auf stdout aus und beenden sich TROTZDEM mit Exit 0 + # (z.B. Spotify: "Fehler beim Uebertragen", "Geraet nicht gefunden"). Der + # harte FEHLER-Prefix (Exit != 0) faengt das nicht. Fuer run_*-Skills werten + # wir daher auch solche Marker als Fehlschlag → eskalieren an Claude, der + # mehrstufig weiterdenkt. Info-Tools (web_search/memory_search) sind bewusst + # AUSGENOMMEN: deren Inhalt darf das Wort "Fehler" tragen ohne dass der + # Tool-Call scheiterte. Eskalieren ist immer sicher (nur langsamer). + _SOFT_FAIL_MARKERS = ( + "fehler", "fehlgeschlagen", "nicht gefunden", "not found", + "konnte nicht", "keine verbindung", "nicht verfuegbar", + "exception", "traceback", + ) + + def _local_tool_failed(self, tool_name: str, tresult: str) -> bool: + """True wenn ein lokaler Tool-Call als gescheitert gilt (→ Claude).""" + s = (tresult or "").strip() + if not s: + return False + if s.startswith("FEHLER"): # harter Exit-Code-Fehler + return True + if tool_name.startswith("run_"): # Action-Skill: auch weiche Fehler + low = s.lower() + return any(mk in low for mk in self._SOFT_FAIL_MARKERS) + return False + # Kuratierte Tool-Auswahl fuers lokale Tier (B1b): web_search (local-only, # SearXNG) + memory_search/trigger_timer (aus META_TOOLS) + Spotify-Skill. # Bewusst klein (Speed + Sicherheit); alles andere → Claude. @@ -1210,7 +1236,7 @@ class Agent: logger.info("[router] lokal Tool-Call: %s(%s)", tname, ", ".join(targs.keys())) tresult = self._dispatch_tool(tname, targs) - if (tresult or "").strip().startswith("FEHLER"): + if self._local_tool_failed(tname, tresult): had_error = True messages.append({"role": "tool", "tool_call_id": tc.get("id") or "",