diff --git a/aria-brain/agent.py b/aria-brain/agent.py index ddfcd1b..c851190 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1018,10 +1018,36 @@ META_TOOLS = [ # Diese Logik ist generisch — ARIA deklariert die Patterns selbst beim # skill_create / skill_update, das Brain orchestriert nur. +def _strip_leading_hint_blocks(text: str) -> str: + """Entfernt fuehrende Hint-Bloecke `[ ... ]`, die die Bridge an + Voice-Nachrichten haengt (GPS-Position, Barge-In-Hinweis). Ohne das matcht + KEIN Voice-Befehl je den Fast-Path (Regex ist ^...$-verankert) — selbst + 'nächstes lied' landete unnoetig bei Claude statt beim 0-Token-Fast-Path.""" + s = (text or "").strip() + prev = None + while prev != s: + prev = s + s = re.sub(r"^\s*\[[^\]]*\]\s*", "", s) + return s + + +def _fold_umlauts(s: str) -> str: + """ä→ae, ö→oe, ü→ue, ß→ss (lowercase erwartet). Whisper liefert echte + Umlaute ('Nächstes Lied'), viele Skill-fast_patterns sind aber in ae/oe/ue + geschrieben — ohne Faltung matcht das nie.""" + return (s.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue") + .replace("ß", "ss")) + + def _normalize_for_fast_match(text: str) -> str: - norm = (text or "").strip().lower() - norm = re.sub(r"[.!?]+$", "", norm) - norm = re.sub(r"\s+", " ", norm) + norm = _strip_leading_hint_blocks(text).lower() + norm = _fold_umlauts(norm) + # Interne Satzzeichen raus, die Whisper einstreut ('Nächstes Lied, bitte.'). + # Kommas/Semikola/Doppelpunkte → Space, Punkt/!/? ganz weg. Sonst matcht das + # ^...$-verankerte fast_pattern nie, weil das Komma dazwischenfunkt. + norm = re.sub(r"[,;:]", " ", norm) + norm = re.sub(r"[.!?]+", "", norm) + norm = re.sub(r"\s+", " ", norm).strip() return norm @@ -1117,6 +1143,9 @@ class Agent: rx = pat.get("match") or "" if not rx: continue + # Pattern GLEICH falten wie den Text — egal ob der Skill-Autor + # 'naechstes' oder 'nächstes' geschrieben hat. + rx = _fold_umlauts(rx) try: if not re.match(rx, norm, re.IGNORECASE): continue diff --git a/aria-brain/router.py b/aria-brain/router.py index 49ac9fd..840a22b 100644 --- a/aria-brain/router.py +++ b/aria-brain/router.py @@ -87,6 +87,17 @@ _HARD_HINTS = re.compile( _MAX_LEN_FOR_LOCAL = 220 # laengere Nachrichten = eher komplexe Aufgaben → Claude +def _strip_leading_hint_blocks(text: str) -> str: + """Fuehrende `[ ... ]`-Hint-Bloecke (GPS, Barge-In von der Bridge) weg — + sonst blaeht der Praefix die Laenge auf und verfaelscht die Heuristik.""" + s = (text or "").strip() + prev = None + while prev != s: + prev = s + s = re.sub(r"^\s*\[[^\]]*\]\s*", "", s) + return s + + def should_try_local(user_message: str, cfg: dict) -> bool: """True, wenn der Router diesen Turn (B1a, reden-only) lokal versuchen soll. localOnly überschreibt die Heuristik (dann IMMER lokal).""" @@ -94,7 +105,7 @@ def should_try_local(user_message: str, cfg: dict) -> bool: return False if cfg.get("localOnly"): return True - msg = (user_message or "").strip() + msg = _strip_leading_hint_blocks(user_message) if not msg or len(msg) > _MAX_LEN_FOR_LOCAL: return False if _TOOL_HINTS.search(msg):