Pip的红围巾不见了。我想让Dash飞到Mo那里,开口问一句。
Dash:“Mo,你见过Pip的红围巾吗?”
Mo:“见过,就在池塘边的芦苇丛里。”
这段对话我用本地模型跑通了。目前它运行在Llama Village之外的一个小型命令行程序里。提问是一段合成录音,Mo的提示词里直接写明了围巾的位置。游戏本身还没有语音控制。
Mo听懂人话的两条路
ASR是语音识别,TTS是文本转语音。两条路线最后都要用TTS把回答念出来。
第一条走文本:Qwen3-ASR把录音转成文字,对话模型给出回答,再由Qwen3-TTS朗读。有转写文本的好处是,“Pip”被听错时还能纠正。
第二条直接吃音频:Qwen2.5-Omni接收录音,直接写出回答。这个例子里没有单独的转写文本。GGUF转换能接收音频但不生成音频,所以Mo的回复仍然要靠TTS。
对比时两条路线用的是同一个3B对话模型。第一条路线里它收到的是文本,第二条里它还要加载自己的音频投影层。这是一条经过测试的多模态路线,不代表所有多模态模型都需要单独的TTS。
文本路线我还试了一组更小的搭配:Qwen3.5 0.8B负责对话,Qwen3-TTS 0.6B Base负责语音,Qwen3-ASR 0.6B继续做识别。小模型组合是下面的默认项,staged模式保留原来的3B/1.7B组合用于对比。这里的Qwen3.5只用于文本,不接收音频输入。
在Mac上跑起来
完整示例需要macOS、Dart 3.10.7或更新版本,五个小模型文件约占2.31 GB,另外还要留出Dart包和原生运行时的空间。测试环境是M4 Max,Dart 3.13.5,llamadart 0.11.0。命令用到macOS的say、afconvert和afplay,这不是手机或浏览器演示。
第一步,创建项目。在终端里运行下面的命令。Dart程序使用固定版本的ModelSource.huggingFace引用,每个引擎的load()会通过llamadart的下载管理器拉取缺失文件并复用缓存,不需要单独的curl脚本。首次运行需要联网,耗时更长;之后的生成都使用本地已下载的模型。
第二步,添加完整程序。留在llama-voice-demo目录里粘贴代码块,它会创建bin/voice.dart。程序一次只加载一个引擎,加载下一个之前先释放上一个。
程序里定义了五个模型来源,分别对应asr、omni、dialogue、tts和smallTts,每个都带固定的仓库ID和版本号。运行时通过命令行参数选择模式:不带第二个参数或传small,走小模型文本路线;传staged,用原来的3B/1.7B组合;传audio,走Qwen2.5-Omni直接吃音频的路线。还可以传入第三个参数作为语音参考文件。
程序会先检查音频文件是否存在。如果不是audio模式,就先加载ASR引擎做转写,上下文提示里带上“Mo, Pip, Dash, Llama Village”这几个词,转写结果为空会直接报错。接着加载对话引擎,audio模式下会检查模型是否支持音频输入,不支持就抛错。系统提示词把Mo设定成一个友好的面包师羊驼,告诉它Dash来拜访,并明确写出“Pip的红围巾在池塘边的芦苇丛里”,要求用一句简短的英文回答,不要舞台提示,不要编造不知道的事实。生成参数是maxTokens 100、温度0、随机种子42。
回答生成后,如果文本为空同样会报错。整个流程走完,Mo就能用本地模型开口说话。
热门跟贴