From a9e1a46a2fbb1efb30ab4fe44912681a5cae4700 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 21:33:17 +0200 Subject: [PATCH] =?UTF-8?q?feat(local-llm):=20Skill-Ausf=C3=BChrung=20via?= =?UTF-8?q?=20local=20=3D=20kein=20TTS=20(speak=3DFalse)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wenn das lokale LLM einen echten Skill (run_*, z.B. Spotify) ausführt, ist es ein Steuerbefehl — genau wie Fast-Path. Jetzt speak=False: kein Vorlesen, und die App beendet direkt (STOP) statt ins 30s-Gespräch zu gehen. Deckt den Fall ab, wo Whisper sich verhört ("Nächster Slick") und local statt Fast-Path den Skill auffängt. Info-Tools (web_search/memory_search/trigger_timer) zählen NICHT — deren Antwort/Bestätigung bleibt gesprochen. Reiner Brain-Fix: die App liest speak bereits (2b48e5c), kein neuer APK nötig. Co-Authored-By: Claude Opus 4.8 --- aria-brain/agent.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/aria-brain/agent.py b/aria-brain/agent.py index c851190..e0e2fce 100644 --- a/aria-brain/agent.py +++ b/aria-brain/agent.py @@ -1226,6 +1226,11 @@ class Agent: cfg = router_mod.load_config() if not router_mod.should_try_local(user_message, cfg): return None + # Merker: hat dieser lokale Turn einen echten Skill (run_*) ausgefuehrt? + # Dann ist es ein Steuerbefehl (z.B. Spotify) → NICHT vorlesen (speak= + # False), wie beim Fast-Path. Info-Tools (web_search/memory_search/ + # trigger_timer) zaehlen NICHT — deren Antwort/Bestaetigung wird gesprochen. + self._local_turn_executed_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 @@ -1273,6 +1278,10 @@ class Agent: logger.info("[router] lokal Tool-Call: %s(%s)", tname, ", ".join(targs.keys())) tresult = self._dispatch_tool(tname, targs) + # Echter Skill (run_*) ausgefuehrt = Steuerbefehl → nachher + # nicht vorlesen. Nur bei Erfolg (Fehler → eskaliert eh). + if tname.startswith("run_") and not self._local_tool_failed(tname, tresult): + self._local_turn_executed_skill = True if self._local_tool_failed(tname, tresult): had_error = True messages.append({"role": "tool", @@ -1383,7 +1392,11 @@ class Agent: # teure Claude-Aufbau + Tool-Loop wird uebersprungen. Sonst None → Claude. local_reply = self._try_local_fast_lane(user_message, active_project_id) if local_reply is not None: - return local_reply, "local", True # ARIA-Antwort → vorlesen ok + # Hat local einen echten Skill (run_*) ausgefuehrt → Steuerbefehl, + # NICHT vorlesen (kein TTS, App beendet direkt statt 30s-Gespraech) — + # genau wie Fast-Path. Sonst normale gesprochene Antwort. + speak = not getattr(self, "_local_turn_executed_skill", False) + return local_reply, "local", speak # 2. Hot Memory (alle pinned Punkte) hot = self.store.list_pinned()