From 53c098a9c814f6ed3ea9024f4f07a7433fd6ec95 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 13:18:26 +0200 Subject: [PATCH] refactor(local-llm): generische Tool-Fehler-Eskalation statt per-Skill-Router-Code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan-Punkt: der 'auf '-Router-Patch war ein per-Fall-Band-Aid, das nicht skaliert (naechster Skill mit komplexer API → wieder patchen). Besser: das LLM merkt es selbst. - Router-Hardcoding zurueckgenommen (kein 'auf Geraet'→Claude mehr). - GENERAL: im lokalen Tool-Loop → scheitert ein Tool-Call (Ergebnis beginnt mit 'FEHLER'), uebernimmt Claude. Gilt fuer JEDEN Skill, kein per-Fall-Wissen im Router. Lokal probiert, bei Fehler eskaliert. - skill_create-Anleitung: SEMANTISCH bauen — Skills bieten klare Operationen als args (action/device_name), NICHT rohe {path,method,body}-Durchreichung. Das args-Schema ist die 'Bedienungsanleitung', die das LLM sieht (die haben wir also schon — sie muss nur semantisch sein). Was das LLM sonst raten muesste (Endpunkte/IDs/Payload) gehoert INS Skill. Co-Authored-By: Claude Opus 4.8 --- aria-brain/agent.py | 22 ++++++++++++++++++++++ aria-brain/router.py | 6 +----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index 7e3a284..ea2690c 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -150,6 +150,17 @@ META_TOOLS = [ "Stefan sich alle 60min manuell neu einloggen.\n" " - Bei konfigurierbaren Werten (User-IDs, Endpoints, Defaults): " "ueber `config_schema` deklarieren, NICHT hardcoden.\n\n" + "SEMANTISCH BAUEN (wichtig fuer Zuverlaessigkeit!): Ein Skill soll " + "KLARE Operationen als args anbieten, NICHT die rohe API " + "durchreichen. Schlecht: args = {path, method, body} — dann muss das " + "aufrufende LLM die ganze fremde API selbst kennen und baut bei " + "komplexen Faellen (z.B. Geraete-Transfer) falsche Calls. Gut: args " + "= {action: 'play'|'pause'|'next'|'play_on_device', device_name?: str} " + "— der Skill-Code loest intern auf (Geraet-Name → ID, richtiger " + "Endpoint) und kapselt die API. Faustregel: Was das LLM sonst RATEN " + "muesste (Endpunkte, IDs, Payload-Struktur), gehoert INS Skill. Das " + "`args`-Schema ist die Bedienungsanleitung, die das LLM sieht — mach " + "sie semantisch und selbsterklaerend.\n\n" "HARTE REGEL — IMMER Skill anlegen wenn: die Loesung erfordert eine " "pip-Library. Sonst muesste der Install bei jedem Container-Restart " "neu laufen (Brain hat keinen persistenten State ausser /data/skills/).\n\n" @@ -1186,6 +1197,7 @@ class Agent: messages.append({"role": "assistant", "content": res.get("content") or "", "tool_calls": tcs}) + had_error = False for tc in tcs: fn = tc.get("function") or {} tname = fn.get("name") or "" @@ -1196,10 +1208,20 @@ 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"): + had_error = True messages.append({"role": "tool", "tool_call_id": tc.get("id") or "", "name": tname, "content": (tresult or "")[:6000]}) + # GENERAL (kein per-Skill-Code): scheitert ein Tool-Call, macht + # das grosse Modell weiter — es baut komplexe/rohe API-Calls + # zuverlaessiger und behandelt Fehler besser. So muss der Router + # NICHT wissen, welche Skill-Aufrufe "schwer" sind; das lokale + # Tier probiert, und bei Fehler uebernimmt Claude. + if had_error and not local_only: + logger.info("[router] lokaler Tool-Fehler → Claude uebernimmt") + return None continue # naechste Runde mit Tool-Ergebnissen final = (res.get("content") or "").strip() break diff --git a/aria-brain/router.py b/aria-brain/router.py index 6bf3d82..c77dca4 100644 --- a/aria-brain/router.py +++ b/aria-brain/router.py @@ -67,11 +67,7 @@ _TOOL_HINTS = re.compile( r"skill|projekt|oauth|" r"licht|lampe|steckdose|rollade|heizung|" r"kalender|termin|" - r"maild?|e-?mail|nachricht schreiben)\b|" - # Geraete-gezieltes Spotify-Abspielen = komplexer Transfer-Flow (Geraete - # abfragen -> Name->ID -> richtiger Endpoint). Das 8B fummelt das -> Claude. - r"(abspiel|spielen|play|weiter|wechsel|übertrag|transfer).{0,15}\bauf\b|" - r"\bauf\s+(android|handy|smartphone|desktop|duffy|firetv|tv|dem\s+\w+|mein)", + r"maild?|e-?mail|nachricht schreiben)\b", re.IGNORECASE, )