)
用 Moonshine Voice 与 AgentFlow 在树莓派上打造语音控制机器人My Dalek 实战指南【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshine本指南基于 Moonshine Voice 开源仓库中的 My Dalek 示例完整讲解如何用 Python 绑定库moonshine_voice在 Raspberry Pi 上搭建一个听到自然语言命令 → 语义匹配 → 触发对应动作的语音控制界面。读完本文你将掌握AgentFlow的安装、运行、命令注册与阈值调优全流程并能将其扩展到多轮对话、机器控制、FAQ 应答等真实场景。一、示例概览一句Exterminate!触发一个动作My Dalek 是一个以科幻剧《神秘博士》中的 Dalek 机器人命名的演示程序树莓派接上 USB 麦克风后程序持续监听语音把move forwardturn leftexterminate这类自然语言短语语义化地匹配到对应的处理函数上。示例本身只打印动作名Moving forwardEXTERMINATE!真正的机器人硬件轮子、吸盘式死光炮留给读者自行实现——但它完整展示了 Moonshine Voice 语音交互链路的最小闭环识别STT→ 语义匹配Embedding→ 动作执行。核心入口是AgentFlow它把语音识别、语音合成、短语语义匹配、麦克风管理全部封装起来调用方只需注册短语与回调。二、环境准备安装 moonshine-voice先进入示例目录并安装 Moonshine Voice 的 Python 包cd examples/raspberry-pi/my-dalek pip install moonshine-voice如果系统提示关于系统包system packages的警告常见于较新的 Debian/Ubuntu 系树莓派 OS有两种处理方式方式一显式覆盖警告不推荐在共享环境使用pip install --break-system-packages moonshine-voice方式二使用 uv 创建虚拟环境推荐# 安装 uv curl -LsSf https://astral.sh/uv/install.sh | sh uv venv source .venv/bin/activate uv pip install moonshine-voice注意使用uv后每次重新登录终端都需要先执行source .venv/bin/activate再运行脚本否则moonshine_voice不会被解释器找到。装好后把 USB 麦克风插到树莓派上程序依赖麦克风捕捉你的语音。运行python my-dalek.py首次运行时AgentFlow.load()会自动下载语音识别与短语匹配所需的模型随后进入监听状态屏幕输出类似 Listening for voice commands... Try saying phrases with the same meaning as these actions: - move forward - move backward - turn left - turn right - kill all humans - exterminate Were doing fuzzy matching of natural language, so phrases like Go forward or Move ahead or Advance will trigger the move forward action, for example. Press CtrlC to stop.正如提示所说试着用不同说法下达同一指令例如 Go ahead 或 Murder everyone——它们会分别命中 move forward 和 kill all humans。CtrlC退出。三、逐行拆解 my-dalek.py命令注册与链式配置示例脚本本身只有 90 行位于 examples/raspberry-pi/my-dalek/my-dalek.py。核心结构如下。3.1 定义动作处理函数def on_move_forward(d): print(Moving forward) def on_move_backward(d): print(Moving backward) def on_turn_left(d): print(Turning left) def on_turn_right(d): print(Turning right) def on_exterminate(d): print(EXTERMINATE!)每个处理函数接收一个d参数即AgentFlow注入的Dialog上下文对象在 agent_flow.py 中定义为Dialog类见 agent_flow.py#L427-L521。即使你暂时不用它参数也必须保留。在真实项目中这里就是控制机器人轮子、启动吸盘死光炮的位置。3.2 短语 → 处理函数的映射commands { move forward: on_move_forward, move backward: on_move_backward, turn left: on_turn_left, turn right: on_turn_right, kill all humans: on_exterminate, exterminate: on_exterminate, }注意kill all humans和exterminate都映射到on_exterminate——一个动作可以有多个触发短语。AgentFlow对这些短语做的是语义匹配semantic matching而非字符串精确匹配所以 Go forwardMove aheadAdvance 都能触发 move forward这正是示例输出中 fuzzy matching 的含义。3.3 链式配置与注册dalek ( AgentFlow() .language(en) .trigger_threshold(args.threshold) .speech(False) .beeps(False) .on_heard(lambda text: print(text)) .on_progress( lambda fraction, name: print( fLoading {name}... {fraction:.0%}, filesys.stderr ) ) ) if args.model_arch is not None: dalek.model_arch(args.model_arch) for phrase, handler in commands.items(): dalek.always(phrase, handler) dalek.load() dalek.start_listening()这里出现的链式配置项都有实际意义对应 agent_flow.py 中的同名 setter配置项作用默认值源码位置.language(en)设置识别与合成的语言enagent_flow.py#L728-L731.trigger_threshold(x)短语匹配所需达到的相似度阈值越接近 1.0 要求越严格0.7agent_flow.py#L782-L791.speech(False)关闭语音合成器本例只用文字反馈Trueagent_flow.py#L763-L770.beeps(False)关闭识别成功/失败提示音Trueagent_flow.py#L830-L839.on_heard(cb)每听到一句语音就回调这里直接打印原文无agent_flow.py#L798-L801.on_progress(cb)汇报模型下载/加载进度(fraction, name)无agent_flow.py#L793-L796.model_arch(arch)指定识别模型架构大小按语言自动选择agent_flow.py#L733-L736示例代码注释解释了.beeps(False)的原因提示音是 runner 对匹配成功/失败的音频反馈而本例已经用文字汇报听到的内容因此关掉避免噪音。3.4 命令行参数脚本通过argparse暴露两个实用参数parser.add_argument( --model-arch, typeint, defaultNone, helpModel architecture to use for transcription, ) parser.add_argument( --threshold, typefloat, default0.7, helpSimilarity threshold for command matching (default: 0.7), )--threshold语义匹配阈值默认0.7。阈值过高会导致本该命中的指令不触发过低会导致无关语音误触发需要根据麦克风环境和说话习惯实测调整。--model-arch指定语音识别模型架构。可选值定义在 moonshine_api.py#L197-L205 的ModelArch枚举中TINY0、BASE1、TINY_STREAMING2、BASE_STREAMING3、SMALL_STREAMING4、MEDIUM_STREAMING5。在内存有限的树莓派上选择 TINY 级别可以在精度与资源占用之间取得平衡。3.5 主循环与退出dalek.start_listening() try: while True: time.sleep(0.1) except KeyboardInterrupt: print(\n\nStopping..., filesys.stderr) finally: dalek.close()start_listening()是非阻塞的转录事件在音频线程上到达并驱动你的回调因此主线程只需要sleep保持存活。CtrlC后close()会释放 runner 自己创建的所有资源麦克风、模型等。四、AgentFlow 的工作原理globals 与 flows原文档指出AgentFlowis the entry point for voice interfaces。这里只注册了globals随时生效的单次命令但同一个 runner 也能通过listen_for处理多轮对话。理解两者的区别是扩展这个示例的关键它们定义在 agent_flow.py 中always(phrase, handler)agent_flow.py#L1165-L1182注册一个任何时刻都活跃的全局短语。handler 接收当前Dialog可以返回一个Prompt如Say让 runner 说出来或返回None。listen_for(trigger_phrase, flow)agent_flow.py#L1144-L1156注册一个多轮对话流程。flow是一个生成器函数通过yield d.ask(...)、yield d.confirm(...)与用户一来一回。load()agent_flow.py#L975-L1042负责下载并打开语音识别、语音合成、短语匹配三类模型start_listening()agent_flow.py#L1082-L1102打开麦克风并立刻返回——除此之外没有任何需要手动接线的部分。从源码结构看两条内置全局短语值得一提cancel和start over在 runner 初始化时就被注册见 agent_flow.py#L710-L711但默认只在多轮对话进行中生效flow-scoped。My Dalek 示例是纯单次命令场景这两个短语不影响行为。五、语义匹配的底层机制Go ahead 能触发 move forward 并非魔法而是由嵌入模型embedding model与余弦相似度实现的。核心类PhraseMatcheragent_flow.py#L267-L360的工作方式构造时用嵌入后端为每个注册短语计算一次 embedding 并缓存匹配时把用户说出的整句话也嵌入一次与所有短语的 embedding 逐一计算余弦相似度相似度计算封装在distance()中取分返回相似度最高且超过trigger_threshold默认 0.7的短语对应的 key低于阈值则返回None此时 runner 播放没听懂提示音。值得注意的工程细节若嵌入模型不可用或use_embeddings(False)AgentFlow会回退到SubstringMatcheragent_flow.py#L363-L414即大小写不敏感的子串匹配。它只认用户逐字说出的内容适合离线测试与冒烟检查不适合真实语音场景。触发匹配器按候选短语集合缓存见_get_trigger_matcheragent_flow.py#L1460-L1485进入/退出流程时只是切换缓存不会对短语重复做 embedding。库内置的 yes/no 短语等固定文本的 embedding 通过assets/cached_embeddings.tsv预置见CachedEmbeddings缓存未命中通常是用户 utterance才回落到嵌入模型从而减少下载与计算开销。5.1 无音频环境的验证方式若你暂时没有树莓派或麦克风官方测试给出了纯文本驱动 runner 的方法。测试文件 test_agent_flow_api.py 中的 fixture 展示了关键组合moonshine_voice.AgentFlow() .microphone(False) .speech(False) .use_embeddings(False)关闭麦克风、合成器与嵌入模型后即可通过runner.handle_utterance(...)以文本方式喂入语句观察触发行为。同文件中的test_the_built_in_cancel_stops_the_active_flow、test_the_built_in_start_over_restarts_the_active_flow等用例验证了流程生命周期My Dalek 这类单命令场景同样可以用handle_utterance(go ahead)做离线验证。六、从单命令到多轮对话listen_for 与 Dialog原文档提到 the same runner also handles multi-turn conversations throughlisten_for, where it can ask a question, wait for the answer, and confirm it。仓库中的 examples/python/agent_flow.py 提供了一个完整的 Wi-Fi 配置多轮对话示例展示了Dialog的三种核心 Promptdef setup_wifi(d): ssid yield d.ask(Whats the name of your wifi network?) if not (yield d.confirm(fI heard, {ssid}. Is that right?)): yield d.say(No problem, lets start over.) return password yield d.ask( Please spell the wifi password, one letter at a time, and say done when finished., modeSPELLED, ) if (yield d.confirm(Would you like to hear it read back?)): yield d.say(fI heard: {spell_out(password)}) if (yield d.confirm(Apply these changes?)): _apply_wifi_config(ssid, password) yield d.say(Done. Your wifi is set up.) else: yield d.say(Okay, nothing changed.)然后注册触发短语并启动runner ( AgentFlow() .listen_for(set up wifi, setup_wifi) .listen_for(configure wifi, setup_wifi) ) runner.load() runner.start_listening()关键点d.ask(prompt)说话提问下一句用户语音以字符串形式返回支持modeSPELLED/DIGITS拼写模式d.confirm(prompt)提问并返回布尔值内建 yes/no 短语表可覆盖yes_phrases/no_phrasesd.say(text)纯播报播完继续流程d.cancel()/d.restart()抛出异常以放弃或重启当前流程与内置的 cancel/start over 全局短语对应子流程可用yield from组合流程像脚本一样从上往下读分支是if/else重试是while。从源码看Dialog本身不做任何 I/Oagent_flow.py#L427-L435 的类注释明确说明所有 Prompt 由 runner 执行后把结果send回生成器因此流程函数可以在无音频、无 TTS、无事件循环的条件下做单元测试。七、扩展到你的应用自定义短语与调优建议原文档强调You can also change the phrases to whatever you need for your application, and the same kind of semantic matching will work for them too。仓库 READMElanguage-bindings/python/README.md#L159-L205给出了同样的模式例如灯光控制from moonshine_voice import AgentFlow def lights_on(d): print(\n LIGHTS ON!) def lights_off(d): print(\n LIGHTS OFF!) runner ( AgentFlow() .always(turn on the lights, lights_on) .always(turn off the lights, lights_off) ) runner.load() runner.start_listening()实战调优建议依据源码行为与参数默认值短语用一句话的语义核心而非字面词AgentFlow按意义匹配因此 kill all humans 既能被 Murder everyone 命中也可能被语义相近但你不希望触发的说法命中。触发过于敏感时调高trigger_threshold如0.8触发不灵敏时调低如0.6。一个动作注册多个变体短语像 kill all humans 与 exterminate 共用一个 handler是最直接的误触发控制手段。树莓派资源受限时选小模型通过--model-arch或代码里的.model_arch()选择TINY0/TINY_STREAMING2等架构减小内存与延迟开销。监听反馈用on_heard打印用户原话、on_progress汇报模型下载进度便于排查没听到还是没匹配上。多轮场景注册为 flow需要确认、追问或拼写输入时使用listen_for并利用内置的 cancel/start over 让用户随时退出或重来。八、小结与进一步阅读My Dalek 示例展示了 Moonshine Voice 构建语音界面的完整闭环安装 → 配置 → 注册语义短语 →load()加载模型 →start_listening()开始监听。AgentFlow把识别、合成、语义匹配和麦克风管理封装在一个对象里单次命令用always多轮对话用listen_for无论控制机器人、工业机械还是应答 FAQ注册短语的模式完全一致。想继续深入可参考仓库中的这些资源示例源码examples/raspberry-pi/my-dalek/my-dalek.py多轮对话完整示例examples/python/agent_flow.pyAgentFlow核心实现language-bindings/python/src/moonshine_voice/agent_flow.pyPython 绑定使用说明language-bindings/python/README.md行为验证测试language-bindings/python/tests/test_agent_flow_api.py【免费下载链接】moonshineVery low latency speech to text, intent recognition, and text to speech, for building voice agents and interfaces项目地址: https://gitcode.com/GitHub_Trending/moonshine3/moonshine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考