refactor(local-llm): generische Tool-Fehler-Eskalation statt per-Skill-Router-Code
Stefan-Punkt: der 'auf <Geraet>'-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 <noreply@anthropic.com>
This commit is contained in:
@@ -150,6 +150,17 @@ META_TOOLS = [
|
|||||||
"Stefan sich alle 60min manuell neu einloggen.\n"
|
"Stefan sich alle 60min manuell neu einloggen.\n"
|
||||||
" - Bei konfigurierbaren Werten (User-IDs, Endpoints, Defaults): "
|
" - Bei konfigurierbaren Werten (User-IDs, Endpoints, Defaults): "
|
||||||
"ueber `config_schema` deklarieren, NICHT hardcoden.\n\n"
|
"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 "
|
"HARTE REGEL — IMMER Skill anlegen wenn: die Loesung erfordert eine "
|
||||||
"pip-Library. Sonst muesste der Install bei jedem Container-Restart "
|
"pip-Library. Sonst muesste der Install bei jedem Container-Restart "
|
||||||
"neu laufen (Brain hat keinen persistenten State ausser /data/skills/).\n\n"
|
"neu laufen (Brain hat keinen persistenten State ausser /data/skills/).\n\n"
|
||||||
@@ -1186,6 +1197,7 @@ class Agent:
|
|||||||
messages.append({"role": "assistant",
|
messages.append({"role": "assistant",
|
||||||
"content": res.get("content") or "",
|
"content": res.get("content") or "",
|
||||||
"tool_calls": tcs})
|
"tool_calls": tcs})
|
||||||
|
had_error = False
|
||||||
for tc in tcs:
|
for tc in tcs:
|
||||||
fn = tc.get("function") or {}
|
fn = tc.get("function") or {}
|
||||||
tname = fn.get("name") or ""
|
tname = fn.get("name") or ""
|
||||||
@@ -1196,10 +1208,20 @@ 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"):
|
||||||
|
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 "",
|
||||||
"name": tname,
|
"name": tname,
|
||||||
"content": (tresult or "")[:6000]})
|
"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
|
continue # naechste Runde mit Tool-Ergebnissen
|
||||||
final = (res.get("content") or "").strip()
|
final = (res.get("content") or "").strip()
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -67,11 +67,7 @@ _TOOL_HINTS = re.compile(
|
|||||||
r"skill|projekt|oauth|"
|
r"skill|projekt|oauth|"
|
||||||
r"licht|lampe|steckdose|rollade|heizung|"
|
r"licht|lampe|steckdose|rollade|heizung|"
|
||||||
r"kalender|termin|"
|
r"kalender|termin|"
|
||||||
r"maild?|e-?mail|nachricht schreiben)\b|"
|
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)",
|
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user