fix(local-llm): weiche Tool-Fehler eskalieren (Exit 0 + Fehlertext)
Die Escalate-on-Tool-Error-Logik griff nur bei hartem FEHLER-Prefix (Exit != 0). Action-Skills melden Fehlschlaege aber oft im stdout-Text bei Exit 0 (Spotify: "Fehler beim Uebertragen", "Geraet nicht gefunden"). Das lokale LLM las den Fehler dann brav vor statt zu eskalieren — und wiederholte beim Nachhaken dieselbe leere Absichtserklaerung. Generisch (nicht Spotify-spezifisch): fuer run_*-Skills werten weiche Fehler-Marker im Ausgabetext ebenfalls als Fehlschlag → Claude uebernimmt das mehrstufige Mitdenken. Info-Tools (web_search/memory_search) ausgenommen, damit deren Inhalt das Wort "Fehler" tragen darf. Eskalieren ist immer sicher. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+27
-1
@@ -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 "",
|
||||
|
||||
Reference in New Issue
Block a user