feat(voice): "Konversation Ende" deterministisch — nicht nur ARIAs [[ENDE]]-Marker
Beobachtung Stefan: ARIA haelt die Konversation offen (Default, korrekt), aber auf "Konversation Ende" hin schloss sie NICHT — sie hat den [[ENDE]]-Marker nicht gesetzt. Das aufs LLM allein zu verlassen ist zu unzuverlaessig fuer einen festen Trigger. Fix: _user_wants_conversation_end() erkennt explizite Beenden-Phrasen im User-Text (konversation/gespraech/befehlskette + ende/beenden/aus/stop/schluss, beide Reihenfolgen, ein Satzteil) und erzwingt converse=false an ALLEN drei Returns (fast-path/local/claude). ARIA beantwortet eine evtl. enthaltene Frage noch normal (speak bleibt), danach zurueck aufs Wake-Word. "befehls?kette" statt bare "kette", damit "Lieferkette" u.ae. nicht faelschlich matchen (per Test abgesichert). Deploy: Brain-Neustart (nicht waehrend ARIA arbeitet). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1394,6 +1394,30 @@ def _extract_flow_markers(text: str) -> tuple:
|
||||
return text.strip(), speak_ov, conv_ov
|
||||
|
||||
|
||||
# Explizite "Konversation beenden"-Phrasen vom USER — deterministisch, NICHT auf
|
||||
# ARIAs [[ENDE]]-Marker angewiesen. Stefan will "Konversation Ende" o.ae. als
|
||||
# festen Trigger: danach zurueck aufs Wake-Word, egal was ARIA sonst tut. Eine in
|
||||
# derselben Nachricht enthaltene Frage beantwortet sie normal (wird vorgelesen),
|
||||
# aber converse wird auf false gezwungen. Nomen + Ende-Wort in EINEM Satzteil
|
||||
# ([^.!?]{0,15}) in beliebiger Reihenfolge; "befehls?kette" damit "Lieferkette"
|
||||
# o.ae. nicht faelschlich matcht.
|
||||
_CONV_NOUN = r"(?:konversation|gespr[aä]ch|befehls?kette)"
|
||||
_CONV_END_VERB = r"(?:ende|beenden|beende|aus|stop|stopp|schluss)"
|
||||
_END_CONVERSATION_RE = re.compile(
|
||||
rf"\b{_CONV_NOUN}\b[^.!?]{{0,15}}\b{_CONV_END_VERB}\b"
|
||||
rf"|\b(?:beende|schlie(?:ß|ss)e?)\b[^.!?]{{0,15}}\b{_CONV_NOUN}\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _user_wants_conversation_end(text: str) -> bool:
|
||||
"""True, wenn der User in dieser Nachricht explizit die Konversation/Kette
|
||||
beenden will (deterministisch, unabhaengig vom LLM-Marker)."""
|
||||
if not text:
|
||||
return False
|
||||
return bool(_END_CONVERSATION_RE.search(_strip_leading_hint_blocks(text)))
|
||||
|
||||
|
||||
def _normalize_for_fast_match(text: str) -> str:
|
||||
norm = _strip_leading_hint_blocks(text).lower()
|
||||
norm = _fold_umlauts(norm)
|
||||
@@ -1802,6 +1826,11 @@ class Agent:
|
||||
if not user_message:
|
||||
raise ValueError("Leere Nachricht")
|
||||
|
||||
# Expliziter "Konversation/Kette beenden"-Wunsch (deterministisch). Wird an
|
||||
# JEDEM Return auf converse=false angewendet — unabhaengig davon, ob ARIA
|
||||
# den [[ENDE]]-Marker setzt. Stefans fester Trigger "Konversation Ende".
|
||||
_wants_end = _user_wants_conversation_end(user_message)
|
||||
|
||||
# Events vom letzten Turn weglassen
|
||||
self._pending_events = []
|
||||
|
||||
@@ -1829,6 +1858,8 @@ class Agent:
|
||||
speak = bool(getattr(self, "_fast_path_speak", False))
|
||||
# converse folgt dem Skill (Manifest/Output) — nicht mehr generell False.
|
||||
converse = bool(getattr(self, "_fast_path_converse", False))
|
||||
if _wants_end:
|
||||
converse = False
|
||||
# Fast-Path = reiner Steuerbefehl, nie eine Rueckfrage → awaiting=False.
|
||||
return fast_reply, "fast-path", speak, converse, False
|
||||
|
||||
@@ -1848,6 +1879,8 @@ class Agent:
|
||||
# dem Skill (bzw. Default: Info/Gespraech = vorlesen + 30s).
|
||||
speak = getattr(self, "_local_turn_speak", True)
|
||||
converse = getattr(self, "_local_turn_converse", True)
|
||||
if _wants_end:
|
||||
converse = False
|
||||
# Local ist tool-loses Reden; blockierende Rueckfragen macht Claude.
|
||||
return local_reply, "local", speak, converse, False
|
||||
|
||||
@@ -2096,6 +2129,10 @@ class Agent:
|
||||
speak = _speak_ov
|
||||
if _conv_ov is not None:
|
||||
converse = _conv_ov
|
||||
# Expliziter User-Wunsch "Konversation beenden" gewinnt IMMER — Frage wird
|
||||
# noch beantwortet (speak bleibt), aber danach zurueck aufs Wake-Word.
|
||||
if _wants_end:
|
||||
converse = False
|
||||
# awaiting_reply = ARIA stellt eine blockierende Rueckfrage (Queue pausiert).
|
||||
return (final_reply, "claude", speak, converse, awaiting_reply)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user