From 37759f2b2c16c530874a8ff0b65dfe1e58477904 Mon Sep 17 00:00:00 2001 From: javis-bot Date: Sun, 14 Jun 2026 03:21:52 +0900 Subject: [PATCH] perf: conversational fast-path (skip enrichment) + shorter silence wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greetings/small-talk routed no data tool yet still ran the episodic memory enrichment (LLM keyword extract + diary/graph search, ~1s) every turn. Skip it when the router picked no external-data tool — the always-injected warm profile still personalises the reply. Also drop the voice silence-detection wait 800ms -> 600ms for snappier turn-taking. Warm "안녕" now lands well under the 3-4s target. Co-Authored-By: Claude Opus 4.7 --- docker-compose.yml | 1 + src/jarvis/reply/engine.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 4be1b75..4cc7605 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,6 +77,7 @@ services: PLANNER_ENABLED: ${PLANNER_ENABLED:-0} # Lock STT to Korean (skip Whisper auto-detect). STT_LANGUAGE: ${STT_LANGUAGE:-ko} + VOICE_SILENCE_MS: ${VOICE_SILENCE_MS:-600} BRIDGE_URL: http://127.0.0.1:8765 depends_on: - ollama diff --git a/src/jarvis/reply/engine.py b/src/jarvis/reply/engine.py index 4960846..9cc3db3 100644 --- a/src/jarvis/reply/engine.py +++ b/src/jarvis/reply/engine.py @@ -1027,6 +1027,19 @@ def run_reply_engine(db: "Database", cfg, tts: Optional[Any], "planning", ) + # Conversational fast-path signal: did the router pick any tool that needs + # EXTERNAL DATA? Captured BEFORE the screen-share unions below add browser + # tools to every turn. When nothing data-bearing was routed (greetings, + # small talk, behavioural instructions), the episodic memory enrichment + # (LLM keyword extract + diary/graph search) is pure latency — the warm + # profile already carries the user's identity/interests in the prompt. Used + # at the needs_memory gate to skip enrichment for those turns. + _DATA_TOOLS = { + "webSearch", "getWeather", "fetchWebPage", "fetchMeals", "logMeal", + "deleteMeal", "localFiles", "controlBrowser", "browseAndPlay", "screenshot", + } + _router_wants_data = any(t in routed_tools for t in _DATA_TOOLS) + # In screen-share mode, always offer setBroadcast. "Turn the broadcast # on/off" is language-agnostic intent the embedding/keyword router won't # reliably surface for a non-English utterance (e.g. "방송 꺼줘"), so the @@ -1118,6 +1131,15 @@ def run_reply_engine(db: "Database", cfg, tts: Optional[Any], needs_memory = False except Exception as exc: # noqa: BLE001 debug_log(f"recall gate failed (fail-open): {exc}", "memory") + + # Conversational fast-path: when the router routed NO external-data tool, + # this is a greeting / small-talk / behavioural-instruction turn. Skip the + # episodic enrichment (LLM keyword extract + diary/graph vector search) — + # the always-injected warm profile still personalises the reply, and this + # shaves ~1s off the most common (and latency-sensitive) voice turns. + if needs_memory and not plan_demands_memory and not _router_wants_data: + debug_log("fast-path: no data tool routed — skipping episodic enrichment", "memory") + needs_memory = False # Topic hint from the directive (if any) — passed to the memory # extractor so keyword selection is anchored on what the planner # actually wanted to look up, instead of re-deriving from the raw