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_WINDOW_TURNS = 8 # nur die letzten N Turns ans lokale Modell (Speed)
|
||||||
_LOCAL_TOOL_ITERATIONS = 3 # max Tool-Runden lokal, sonst → Claude
|
_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,
|
# Kuratierte Tool-Auswahl fuers lokale Tier (B1b): web_search (local-only,
|
||||||
# SearXNG) + memory_search/trigger_timer (aus META_TOOLS) + Spotify-Skill.
|
# SearXNG) + memory_search/trigger_timer (aus META_TOOLS) + Spotify-Skill.
|
||||||
# Bewusst klein (Speed + Sicherheit); alles andere → Claude.
|
# Bewusst klein (Speed + Sicherheit); alles andere → Claude.
|
||||||
@@ -1210,7 +1236,7 @@ class Agent:
|
|||||||
logger.info("[router] lokal Tool-Call: %s(%s)", tname,
|
logger.info("[router] lokal Tool-Call: %s(%s)", tname,
|
||||||
", ".join(targs.keys()))
|
", ".join(targs.keys()))
|
||||||
tresult = self._dispatch_tool(tname, targs)
|
tresult = self._dispatch_tool(tname, targs)
|
||||||
if (tresult or "").strip().startswith("FEHLER"):
|
if self._local_tool_failed(tname, tresult):
|
||||||
had_error = True
|
had_error = True
|
||||||
messages.append({"role": "tool",
|
messages.append({"role": "tool",
|
||||||
"tool_call_id": tc.get("id") or "",
|
"tool_call_id": tc.get("id") or "",
|
||||||
|
|||||||
Reference in New Issue
Block a user