Refactor wake phrase handling: introduce REQUIRE_WAKE_WORD configuration, update documentation, and adjust assistant behavior for improved user experience.

This commit is contained in:
TheMajesticMagician
2026-10-25 22:48:37 -06:00
parent ca65670638
commit a8c7db9520
7 changed files with 84 additions and 54 deletions
+4 -2
View File
@@ -1,4 +1,5 @@
"""Bolt Assistant: say "Hey Bolt", ask, hear the answer.
"""Bolt Assistant: talk while the mic is open, hear the answer. (Setting
REQUIRE_WAKE_WORD=true puts "Hey Bolt" back in front of each request.)
python -m bolt_assistant listen on the default mic
python -m bolt_assistant --verbose also show what was heard but ignored
@@ -89,7 +90,8 @@ def main(argv=None) -> int:
return 0
if "--type" in argv: # the whole pipeline minus the microphone
print("Type what you'd say (including the wake phrase). Ctrl+C to quit.")
hint = " (including the wake phrase)" if assistant.wake_required else ""
print(f"Type what you'd say{hint}. Ctrl+C to quit.")
try:
while True:
assistant.heard(input("say> "))
+15 -2
View File
@@ -1,7 +1,10 @@
"""The assistant: hear the wake phrase, take the request, answer out loud.
"""The assistant: hear a request, answer out loud.
idle every utterance the local detector passes on is transcribed by
the server; only ones that open with a wake phrase count
the server and, by default, is itself the request — the mic
being open is the activation. With REQUIRE_WAKE_WORD on, only
ones that open with a wake phrase count instead
"what's the time" -> ask "what's the time"
"Hey Bolt, what's the time" -> ask "what's the time"
"Hey Bolt" -> chime, go to `command`
command the next thing said (within COMMAND_WAIT_SEC) is the request
@@ -88,6 +91,7 @@ class Assistant:
clock: Callable[[], float] = time.monotonic,
wake_words=None,
anywhere_words=None,
wake_required: Optional[bool] = None,
verbose: bool = False,
):
self._transcription = transcription
@@ -98,6 +102,7 @@ class Assistant:
self.tools = tools or Tools(log=log)
self.wake_words = wake_words or config.WAKE_WORDS
self.anywhere_words = config.ANYWHERE_WAKE_WORDS if anywhere_words is None else anywhere_words
self.wake_required = config.REQUIRE_WAKE_WORD if wake_required is None else wake_required
self.segmenter = Segmenter(config.VAD_RMS_THRESHOLD, config.VAD_START_FRAMES,
config.VAD_SILENCE_END_SEC, config.MIN_UTTERANCE_SEC,
config.MAX_UTTERANCE_SEC)
@@ -132,6 +137,8 @@ class Assistant:
self.handle_transcripts()
def listening_banner(self) -> str:
if not self.wake_required:
return "Listening - anything you say is a request. Ctrl+C to quit."
return 'Listening for "Hey Bolt" (or "Thunderbolt", "Hey Assistant"). Ctrl+C to quit.'
def process(self, frame: bytes) -> None:
@@ -210,6 +217,12 @@ class Assistant:
self._check_deadline()
woke, rest = find_wake(text, self.wake_words, self.anywhere_words)
if self.state == "idle":
if not self.wake_required and not woke:
# Always listening: the mic being open is the activation, so
# whatever it managed to hear is the request.
self._follow_ups = 0
self.ask(text)
return
if not woke:
if self.verbose:
self.log(f" (not for me) {text}")
+8 -1
View File
@@ -56,11 +56,18 @@ MIC_DEVICE = _device(os.environ.get("MIC_DEVICE", ""))
SPEAKER_DEVICE = _device(os.environ.get("SPEAKER_DEVICE", ""))
# ── wake phrases ────────────────────────────────────────────────────────────
# With REQUIRE_WAKE_WORD off (the default) the mic itself is the activation:
# every utterance the local detector passes on is treated as a request, and
# whatever is said near an open mic gets answered. Set it to true to go back
# to needing a phrase before each request.
REQUIRE_WAKE_WORD = _flag("REQUIRE_WAKE_WORD", "false")
# Heard through the server's speech-to-text, so these are matched against
# *text*. A phrase counts only near the start of what was said ("hey bolt,
# what's the time"), not buried in a sentence ("tighten that bolt"). Speech
# recognisers often write "Bolt" as "bolts"/"volt"/"bold"; those spellings
# only count after "hey"/"ok", where they can't be ordinary words.
# only count after "hey"/"ok", where they can't be ordinary words. Only used
# when REQUIRE_WAKE_WORD is on (a phrase said anyway is still stripped).
WAKE_WORDS = [w.strip().lower() for w in os.environ.get(
"WAKE_WORDS",