fix(local-llm): Anti-Halluzination — behauptete Skill-Aktion ohne Tool → Claude
Local hat "Spotify: überspringe 3 Titel" / "Playlist X abspielen" geantwortet
OHNE run_spotify je zu rufen (Skill-Logs: kein Aufruf) — reine Fabrikation,
real passierte nichts. Zwei Ursachen behoben:
- Prompt: run_spotify-Beschreibung von "next/pause/weiter" auf JEDE
Musiksteuerung erweitert (Playlist, Gerät, überspringen mehrerer, Lautstärke)
+ harte Anti-Halluzinations-Regel (nie eine Aktion behaupten ohne Tool-Call).
- Guard: sagt local eine Steuerbefehl-Quittung ('Spotify: …', 'Playlist …
abspielen', 'überspringe … Titel') aber hat KEINEN run_*-Skill gerufen
(_local_ran_skill=False) → eskalieren, Claude ruft das Tool zuverlässig.
_looks_like_skill_action bewusst eng (Doppelpunkt-Praefix / spezifische Verben),
trifft normale Musik-Konversation nicht.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1064,6 +1064,23 @@ def _fold_umlauts(s: str) -> str:
|
||||
.replace("ß", "ss"))
|
||||
|
||||
|
||||
def _looks_like_skill_action(text: str) -> bool:
|
||||
"""Erkennt eine BEHAUPTETE Steuerbefehl-Quittung (v.a. Spotify) im Reply.
|
||||
Genutzt fuer den Anti-Halluzinations-Guard: sagt local sowas, hat aber KEIN
|
||||
Werkzeug gerufen, ist real nichts passiert → eskalieren. Bewusst eng, um
|
||||
normale Konversation ueber Musik nicht zu treffen."""
|
||||
t = (text or "").strip().lower()
|
||||
if not t:
|
||||
return False
|
||||
if t.startswith("spotify:") or t.startswith("spotify -") or t.startswith("musik:"):
|
||||
return True
|
||||
if "playlist" in t and ("abspiel" in t or "spiele" in t or "starte" in t):
|
||||
return True
|
||||
if ("ueberspring" in t or "überspring" in t) and ("titel" in t or "lied" in t or "song" in t):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_for_fast_match(text: str) -> str:
|
||||
norm = _strip_leading_hint_blocks(text).lower()
|
||||
norm = _fold_umlauts(norm)
|
||||
@@ -1273,6 +1290,11 @@ class Agent:
|
||||
# dem Manifest (Steuerbefehl-Skill=False → stumm, Antwort-Skill=True).
|
||||
# Info-Tools (web_search/memory_search/trigger_timer) lassen es bei True.
|
||||
self._local_turn_speak = True
|
||||
# Hat local in diesem Turn ueberhaupt einen echten Skill (run_*) gerufen?
|
||||
# Fuer den Anti-Halluzinations-Guard: behauptet local eine Spotify-/Skill-
|
||||
# Aktion OHNE das Tool zu rufen → eskalieren statt eine erfundene
|
||||
# Bestaetigung durchzulassen.
|
||||
self._local_ran_skill = False
|
||||
local_only = bool(cfg.get("localOnly"))
|
||||
local_model = cfg.get("localLlmModel") or "qwen3-8b" # B0.5: llama-swap-Key
|
||||
tools = self._build_local_tools() # B1b: kuratierte Tools
|
||||
@@ -1325,6 +1347,7 @@ class Agent:
|
||||
# Antwort-Skill=vorlesen). Letzter Skill gewinnt.
|
||||
if tname.startswith("run_") and not self._local_tool_failed(tname, tresult):
|
||||
self._local_turn_speak = self._skill_speak_flag(tname)
|
||||
self._local_ran_skill = True
|
||||
if self._local_tool_failed(tname, tresult):
|
||||
had_error = True
|
||||
messages.append({"role": "tool",
|
||||
@@ -1370,6 +1393,14 @@ class Agent:
|
||||
logger.info("[router] lokal eskaliert → Claude")
|
||||
return None
|
||||
|
||||
# Anti-Halluzination: local behauptet manchmal eine Musik-/Skill-Aktion
|
||||
# ('Spotify: …', 'Playlist … abspielen'), OHNE run_spotify gerufen zu
|
||||
# haben → es ist real nichts passiert. Dann eskalieren: Claude ruft das
|
||||
# Tool zuverlaessig. (Echte Skill-Ausfuehrung → _local_ran_skill=True.)
|
||||
if not self._local_ran_skill and _looks_like_skill_action(final):
|
||||
logger.info("[router] lokal: Skill-Aktion behauptet ohne Tool-Call → Claude")
|
||||
return None
|
||||
|
||||
logger.info("[router] lokal beantwortet (%d Zeichen)", len(final))
|
||||
self.conversation.add("assistant", final, project_id=active_project_id)
|
||||
return final
|
||||
|
||||
@@ -153,10 +153,18 @@ def build_local_system_prompt(identity_anchor: str, has_tools: bool = False,
|
||||
"GPS-Hinweis in der Nachricht.",
|
||||
"- `memory_search`: in ARIAs Gedaechtnis nachsehen (lesen).",
|
||||
"- `trigger_timer`: Timer/Erinnerung setzen ('in 10 Minuten…').",
|
||||
"- `run_spotify`: Musik steuern (naechster Titel, Pause, weiter).",
|
||||
"- `run_spotify`: JEDE Musiksteuerung — Titel wechseln/ueberspringen "
|
||||
"(auch mehrere), Pause/Weiter, eine PLAYLIST abspielen, auf einem "
|
||||
"GERAET abspielen, Lautstaerke, 'was laeuft gerade'. Fuer ALLES rund "
|
||||
"um Musik/Spotify IMMER dieses Werkzeug rufen — nie selbst antworten.",
|
||||
"WICHTIG: Bei reinem Smalltalk ('wie gehts', Begruessung, Meinung) "
|
||||
"KEIN Werkzeug — einfach direkt antworten. Werkzeuge nur bei echtem "
|
||||
"Bedarf; erfinde keine.",
|
||||
"ANTI-HALLUZINATION (kritisch): Behaupte NIEMALS eine Aktion (Musik, "
|
||||
"Timer, Suche) als erledigt, ohne das Werkzeug WIRKLICH aufgerufen zu "
|
||||
"haben. Kein 'Spotify: …' / 'Playlist abspielen' o.ae. ohne echten "
|
||||
"run_spotify-Aufruf. Kannst/willst du es nicht per Werkzeug tun, sag "
|
||||
"es ehrlich oder eskaliere — erfinde keine Bestaetigung.",
|
||||
]
|
||||
parts += [
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user