使用开源工具构建私有语音助手:一步步实战指南

本教程详细讲解如何使用开源工具在安卓设备上构建完全本地的私有语音助手,涵盖语音识别、本地大模型推理、文本转语音等核心技术,确保数据隐私和完全控制权。

大多数商业语音助手在响应前都会将您的语音数据发送到云服务器。通过使用开源工具,您可以直接在手机上运行所有组件,获得更好的隐私保护、更快的响应速度,并完全控制助手的行为。

在本教程中,我将逐步引导您完成整个过程。您不需要有机器学习模型的经验,因为我们将逐步构建系统并测试每个部分。最终,您将拥有一个完全本地的移动语音助手,由以下组件驱动:

  • Whisper:用于自动语音识别(ASR)
  • 机器学习编译器(MLC)LLM:用于设备端推理
  • 系统文本转语音(TTS):使用内置的Android TTS

您的助手将能够:

  • 离线理解您的语音命令
  • 通过合成语音回应您
  • 执行工具调用操作(例如控制智能设备)
  • 存储个人记忆和偏好
  • 使用检索增强生成(RAG)从您自己的笔记中回答问题
  • 执行多步骤智能工作流程,例如生成晨间简报并可选地将摘要发送给联系人

本教程重点介绍在Android上使用Termux(Android终端环境)实现完全本地化的工作流程。

系统概述

该图表显示了您的语音在助手中的流动过程:语音输入→转录→推理→行动→语音回复。

该流程描述了核心流程:

  1. 您对着麦克风说话
  2. Whisper将音频转换为文本
  3. 本地LLM解读您的请求
  4. 助手可能调用工具(例如发送通知或创建事件)
  5. 使用设备的文本转语音系统大声说出响应

本教程使用的关键概念

  • 自动语音识别(ASR):将您的语音转换为文本。我们使用Whisper或Faster-Whisper
  • 本地大语言模型(LLM):使用MLC引擎在手机上运行的推理模型
  • 文本转语音(TTS):将文本转换回语音。我们使用Android内置的系统TTS
  • 工具调用:允许助手执行操作(例如发送通知或创建事件)
  • 记忆:存储助手在对话过程中学到的个性化事实
  • 检索增强生成(RAG):让助手参考您的文档或笔记
  • 智能体工作流程:助手将多种能力结合使用的多步骤链

要求

您应该熟悉:

  • 基本命令行使用(运行命令、导航目录)
  • 非常基础的Python(调用函数、编辑.py脚本)

您不需要:

  • 机器学习经验
  • 对神经网络的深入理解
  • 之前有语音或音频模型的经验

以下是您需要跟随使用的工具和技术:

  • 推荐使用Snapdragon 8+ Gen 1或更新版本的Android手机(旧设备仍可使用,但响应可能较慢)
  • Termux
  • Termux中的Python 3.9+
  • 足够的空闲存储空间(至少4-6 GB)来存储模型和音频文件

为什么这些要求很重要: Whisper和Llama模型在设备上运行,因此手机必须处理实时计算。MLC针对您设备的GPU/NPU优化模型,因此更新的处理器运行速度更快、温度更低。系统TTS和Termux API让助手能够在本地说话并与手机交互。

如果您的手机较旧或中端,请在步骤3中将模型切换为更小更快的Phi-3.5-Mini。

我们将从设置您的Android环境开始,包括Termux、Python、媒体访问和存储权限,以便后续步骤可以录制音频、运行模型和说话。

立即运行:

1
2
3
4
# 在Termux中
pkg update && pkg upgrade -y
pkg install -y python git ffmpeg termux-api
termux-setup-storage  # 授予存储权限

步骤1:在Android上测试麦克风和音频播放

此步骤的作用:在将麦克风和扬声器连接到语音助手之前,验证它们通过Termux正常工作。

设备端助手需要可靠地访问麦克风和扬声器。在Android上,Termux提供了录制音频和播放媒体的实用程序。这避免了复杂的音频依赖,并可在更多设备上工作。

这些命令让您快速测试麦克风和音频播放,无需编写任何代码。这在引入Whisper或TTS之前验证设备权限和音频路径是否正常工作非常有用。

  • termux-microphone-record 从设备麦克风录制到.wav文件
  • termux-media-player 播放音频文件
  • termux-tts-speak 使用系统TTS语音说话(快速回退)

立即运行:

1
2
3
4
5
6
7
8
# 开始4秒录制
termux-microphone-record -f in.wav -l 4 && termux-microphone-record -q

# 播放捕获的音频
termux-media-player play in.wav

# 通过系统TTS说话(如果您未安装Python TTS,则作为回退)
termux-tts-speak "Hello, this is your on-device assistant running locally."

步骤2:安装并运行Whisper进行ASR

此步骤的作用:将录制的语音转换为文本,以便语言模型理解您说的话。

Whisper监听您的音频录制并将其转换为文本。较小的版本(如tiny或base)在大多数手机上运行更快,对于日常命令来说足够好。

安装Whisper:

1
pip install openai-whisper

如果遇到安装问题,可以使用Faster-Whisper:

1
pip install faster-whisper

以下是一个小型Python脚本,它接收录制的音频文件并将其转换为文本。它首先尝试Whisper,如果不可用,将自动回退到Faster-Whisper。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 将录制的语音转换为文本 (asr_transcribe.py)
import sys

# 尝试Whisper,如果需要则回退到Faster-Whisper
try:
    import whisper
    use_faster = False
except Exception:
    use_faster = True

if use_faster:
    from faster_whisper import WhisperModel
    model = WhisperModel("tiny.en")
    segments, info = model.transcribe(sys.argv[1])
    text = " ".join(s.text for s in segments)
    print(text.strip())
else:
    model = whisper.load_model("tiny.en")
    result = model.transcribe(sys.argv[1], fp16=False)
    print(result["text"].strip())

立即运行:

1
2
3
# 录制4秒并转录
termux-microphone-record -f in.wav -l 4 && termux-microphone-record -q
python asr_transcribe.py in.wav

步骤3:使用MLC安装本地LLM

此步骤的作用:安装并测试设备端推理模型,该模型将生成对转录语音的响应。

MLC将transformer模型编译到移动GPU和神经处理单元,实现设备端推理。您将运行具有4位或8位权重的指令调优模型以提高速度。

像这样安装命令行界面:

1
2
3
4
5
# 克隆并安装Python绑定(用于脚本)和CLI
git clone https://github.com/mlc-ai/mlc-llm.git
cd mlc-llm
pip install -r requirements.txt
pip install -e python

我们将使用Llama 3 8B Instruct q4,因为它提供了强大的推理能力,同时仍可在许多最近的Android设备上运行。如果您的手机内存较少或想要更快的响应,可以换用Phi-3.5 Mini(约3.8B)而无需更改任何代码。

下载移动优化模型:

1
mlc_llm download Llama-3-8B-Instruct-q4f16_1

我们将使用一个简短的Python脚本将文本发送到模型并打印响应。这让我们在将模型连接到音频之前验证模型是否正确安装。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 本地LLM文本生成 (local_llm.py)
from mlc_llm import MLCEngine
import sys

engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1")
prompt = sys.argv[1] if len(sys.argv) > 1 else "Hello"
resp = engine.chat([{"role": "user", "content": prompt}])
# 引擎在不同版本中可能返回不同的结构
reply_text = resp.get("message", resp) if isinstance(resp, dict) else str(resp)
print(reply_text)

立即运行:

1
python local_llm.py "Summarize this in one sentence: building a local voice assistant on Android"

步骤4:本地文本转语音(TTS)

此步骤的作用:将模型的文本响应转换为语音音频,以便助手可以回话。

此步骤将模型返回的文本转换为语音音频,以便助手可以回话。它使用内置的Android文本转语音语音,不需要额外的Python包。

1
termux-tts-speak "Hello, I am running entirely on your device."

这是我们将在整个教程中使用的语音输出方法。

步骤5:核心语音循环

此步骤的作用:将语音识别、语言模型推理和语音合成连接到一个交互式对话循环中。

此循环将录制、转录、响应生成和播放绑定在一起。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 连接ASR + LLM + TTS的核心语音循环 (voice_loop.py)
import subprocess, os

def run(cmd): return subprocess.check_output(cmd).decode().strip()

print("Listening...")
subprocess.run(["termux-microphone-record", "-f", "in.wav", "-l", "4"]) ; subprocess.run(["termux-microphone-record", "-q"])
text = run(["python", "asr_transcribe.py", "in.wav"])
reply = run(["python", "local_llm.py", text])
try:
    subprocess.run(["python", "speak_xtts.py", reply]); subprocess.run(["termux-media-player", "play", "out.wav"])
except:
    subprocess.run(["termux-tts-speak", reply])

运行:

1
python voice_loop.py

步骤6:工具调用(让它行动)

此步骤的作用:通过调用设备上的实际函数,使助手能够执行操作——不仅仅是回复。

工具调用让助手执行操作,而不仅仅是回答。当模型识别到操作请求时,它会输出一个小的JSON指令,您的代码运行相应的函数。您向模型展示存在哪些工具以及如何调用它们。程序拦截调用并运行相应的代码。

示例用例: 您说:“Schedule a meeting tomorrow at 3 PM with John.” 助手:

  1. 转录您说的话
  2. 检测到这不是一个问题,而是一个操作请求
  3. 使用正确的参数调用add_event()函数
  4. 确认:“Okay, I scheduled that.”

以下是工具调用的工作结构:

  • 定义Python函数,如add_event、control_light
  • 提供模式供模型在想要调用工具时输出
  • 在LLM输出中检测该模式并执行函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 工具调用函数 (tools.py)
import json

def add_event(title: str, date: str) -> dict:
    # 替换为实际的日历集成
    return {"status": "ok", "title": title, "date": date}

TOOLS = {
    "add_event": add_event,
}

def run_tool(call_json: str) -> str:
    """call_json: '{"tool":"add_event","args":{"title":"Dentist","date":"2025-11-10 10:00"}}'"""
    data = json.loads(call_json)
    name = data["tool"]
    args = data.get("args", {})
    if name in TOOLS:
        result = TOOLS[name](**args)
        return json.dumps({"tool_result": result})
    return json.dumps({"error": "unknown tool"})

提示模型使用工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 启用工具使用的LLM包装器 (llm_with_tools.py)
from mlc_llm import MLCEngine
import json, sys

SYSTEM = (
    "You can call tools by emitting a single JSON object with keys 'tool' and 'args'. "
    "Available tools: add_event(title:str, date:str). "
    "If no tool is needed, answer directly."
)

engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1")
user = sys.argv[1]
resp = engine.chat([
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": user},
])
print(resp.get("message", resp) if isinstance(resp, dict) else str(resp))

然后将其粘合在一起:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 运行带有工具调用检测的LLM (run_with_tools.py)
import subprocess, json
from tools import run_tool

user = "Add a dentist appointment next Thursday at 10"
raw = subprocess.check_output(["python", "llm_with_tools.py", user]).decode().strip()

# 如果模型返回了JSON工具调用,运行它
try:
    data = json.loads(raw)
    if isinstance(data, dict) and "tool" in data:
        print("Tool call:", data)
        print(run_tool(raw))
    else:
        print("Assistant:", raw)
except Exception:
    print("Assistant:", raw)

立即运行:

1
python run_with_tools.py

步骤7:记忆和个性化

此步骤的作用:允许助手记住您分享的个人信息,使对话感觉连续和自适应。

一个有用的助手应该感觉它在与您一起学习。记忆允许系统跟踪您在对话中自然提到的小细节。

没有记忆,每次对话都从头开始。有了记忆,您的助手可以记住个人事实(例如生日、最喜欢的音乐)、您的日常安排、设备设置或您在对话中提到的笔记。这解锁了更自然的互动,并随着时间的推移实现个性化。

您可以从一个简单的键值存储开始,并随时间扩展。您的程序在推理前读取记忆,并在之后写回新事实。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 简单的键值记忆存储 (memory.py)
import json
from pathlib import Path

MEM_PATH = Path("memory.json")

def mem_load():
    return json.loads(MEM_PATH.read_text()) if MEM_PATH.exists() else {}

def mem_save(mem):
    MEM_PATH.write_text(json.dumps(mem, indent=2))

def remember(key: str, value: str):
    mem = mem_load()
    mem[key] = value
    mem_save(mem)

在循环中使用记忆:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 带有记忆加载和更新的语音循环 (voice_loop_with_memory.py)
import subprocess, json
from memory import mem_load, remember

# 1) 录制和转录
subprocess.run(["termux-microphone-record", "-f", "in.wav", "-l", "4"]) 
subprocess.run(["termux-microphone-record", "-q"]) 
user_text = subprocess.check_output(["python", "asr_transcribe.py", "in.wav"]).decode().strip()

# 2) 加载记忆并添加为系统上下文
mem = mem_load()
SYSTEM = "Known facts: " + json.dumps(mem)

# 3) 询问模型
from mlc_llm import MLCEngine
engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1")
resp = engine.chat([
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": user_text},
])
reply = resp.get("message", resp) if isinstance(resp, dict) else str(resp)
print("Assistant:", reply)

# 4) 非常简单的模式:如果用户说"remember X is Y",存储它
if user_text.lower().startswith("remember ") and " is " in user_text:
    k, v = user_text[9:].split(" is ", 1)
    remember(k.strip(), v.strip())

立即运行:

1
python voice_loop_with_memory.py

步骤8:检索增强生成(RAG)

此步骤的作用:让助手在回答时搜索您的离线笔记或文档,提高个人任务的准确性。

要使用RAG,我们首先安装一个轻量级向量数据库,然后向其中添加文档,并在回答问题时查询它。

语言模型无法神奇地了解您的生活、工作或文件的细节,除非您给它一种查找的方式。

检索增强生成(RAG)弥合了这一差距。RAG允许助手在查询时搜索您自己存储的数据。这意味着助手可以回答关于您的项目、家庭细节、旅行计划、学习或您完全离线存储的任何个人文档的问题。

RAG允许助手在回答时参考您的实际笔记,而不是仅依赖模型的内部训练。

安装向量存储:

1
pip install chromadb

添加和搜索您的笔记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 本地向量数据库索引和查询 (rag.py)
from chromadb import Client

client = Client()
notes = client.create_collection("notes")

# 添加您的文档(根据需要重复)
notes.add(documents=["Contractor quote was 42000 United States Dollars for the extension."], ids=["q1"]) 

# 查询本地向量数据库
results = notes.query(query_texts=["extension quote"], n_results=1)
context = results["documents"][0][0]
print(context)

在响应中使用检索到的上下文:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 使用检索到的上下文回答的LLM (llm_with_rag.py)
from mlc_llm import MLCEngine
from chromadb import Client

engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1")
client = Client()
notes = client.get_or_create_collection("notes")

question = "What was the quoted amount for the home extension?"
res = notes.query(query_texts=[question], n_results=2)
ctx = "\n".join([d[0] for d in res["documents"]])

SYSTEM = "Use the provided context to answer accurately. If missing, say you do not know.\nContext:\n" + ctx
ans = engine.chat([
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": question},
])
print(ans.get("message", ans) if isinstance(ans, dict) else str(ans))

立即运行:

1
2
python rag.py
python llm_with_rag.py

步骤9:多步骤智能体工作流程

此步骤的作用:将监听、推理、记忆和工具使用结合到一个自动运行的多步骤例程中。

既然助手可以监听、响应、记忆事实和调用工具,我们可以将这些能力结合到一个自动执行多个步骤的小型例程中。

实际示例:手机上的"晨间简报" 目标:当您说"Give me my morning briefing and text it to my partner"时,助手将:

  1. 从本地文件读取今天的日程安排
  2. 总结它
  3. 大声说出来
  4. 通过SMS使用Termux发送摘要

准备您的日程文件 此文件存储您当天的活动。您可以手动编辑它、生成它,或者如果您愿意,以后可以同步它。

在同一文件夹中创建agenda.json:

1
2
3
4
5
6
7
{
  "2025-11-03": [
    {"time": "09:30", "title": "Standup meeting"},
    {"time": "13:00", "title": "Lunch with Priya"},
    {"time": "16:30", "title": "Gym"}
  ]
}

此工作流程的手机集成工具:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 手机集成智能体工具 (tools_phone.py)
import json, subprocess, datetime
from pathlib import Path

AGENDA_PATH = Path("agenda.json")

def load_today_agenda():
    today = datetime.date.today().isoformat()
    if not AGENDA_PATH.exists():
        return []
    data = json.loads(AGENDA_PATH.read_text())
    return data.get(today, [])

def send_sms(number: str, text: str) -> dict:
    # 需要Termux:API和SMS权限
    subprocess.run(["termux-sms-send", "-n", number, text])
    return {"status": "sent", "to": number}

def notify(title: str, content: str) -> dict:
    subprocess.run(["termux-notification", "--title", title, "--content", content])
    return {"status": "notified"}

创建智能体例程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 多步骤晨间简报智能体 (agent_morning.py)
import json, subprocess, os
from mlc_llm import MLCEngine
from tools_phone import load_today_agenda, send_sms, notify

PARTNER_PHONE = os.environ.get("PARTNER_PHONE", "+15551234567")

TOOLS = {
    "send_sms": send_sms,
    "notify": notify,
}

SYSTEM = (
  "You assist on a phone. You may emit a single-line JSON when an action is needed "
  "with keys 'tool' and 'args'. Available tools: send_sms(number:str, text:str), "
  "notify(title:str, content:str). Keep messages concise. If no tool is needed, answer in plain text."
)

engine = MLCEngine(model="Llama-3-8B-Instruct-q4f16_1")

agenda = load_today_agenda()
agenda_text = "\n".join(f"{e['time']} - {e['title']}" for e in agenda) or "No events for today."

user_request = "Give me my morning briefing and text it to my partner."

# 1) 向LLM请求2-3句摘要来说话
summary = engine.chat([
  {"role": "system", "content": "Summarize this agenda in 2-3 sentences for a morning briefing:"},
  {"role": "user", "content": agenda_text},
])
summary_text = summary.get("message", summary) if isinstance(summary, dict) else str(summary)
print("Briefing:\n", summary_text)

# 2) 本地说话(优先XTTS,回退到系统TTS)
try:
    subprocess.run(["python", "speak_xtts.py", summary_text], check=True)
    subprocess.run(["termux-media-player", "play", "out.wav"]) 
except Exception:
    subprocess.run(["termux-tts-speak", summary_text])

# 3) 询问LLM是否发送SMS以及发送什么文本,使用工具模式
resp = engine.chat([
  {"role": "system", "content": SYSTEM},
  {"role": "user", "content": f"User said: '{user_request}'. Partner phone is {PARTNER_PHONE}. Summary: {summary_text}"},
])
msg = resp.get("message", resp) if isinstance(resp, dict) else str(resp)

# 4) 如果模型请求了工具,执行它
try:
    data = json.loads(msg)
    if isinstance(data, dict) and data.get("tool") in TOOLS:
        # 如果缺少电话号码,自动填充
        if data["tool"] == "send_sms" and "number" not in data.get("args", {}):
            data.setdefault("args", {})["number"] = PARTNER_PHONE
        result = TOOLS[data["tool"]](**data.get("args", {}))
        print("Tool result:", result)
    else:
        print("Assistant:", msg)
except Exception:
    print("Assistant:", msg)

立即运行:

1
2
export PARTNER_PHONE=+15551234567
python agent_morning.py

这个示例在Android上是现实的,因为它使用了您已经安装的Termux实用程序:本地TTS用于语音输出,termux-sms-send用于消息传递,termux-notification用于快速的设备端确认。如果您有本地服务器,以后可以用Home Assistant工具扩展它(例如切换灯光或设置恒温器场景)。

结论和后续步骤

构建完全本地的语音助手是一个渐进的过程。您添加的每个步骤——语音识别、文本生成、记忆、检索和工具执行——都解锁了新功能,并使系统更接近真实助手的行为。

您在手机上构建了一个完全本地的语音助手,具有:

  • 使用Whisper的设备端自动语音识别(带有Faster-Whisper回退)
  • 使用MLC大语言模型的设备端推理
  • 使用内置系统TTS的本地文本转语音
  • 用于实际操作的工具体调用
  • 记忆和个性化
  • 用于基于文档的知识的检索增强生成
  • 用于多步骤工作的简单智能体循环

从这里您可以添加:

  • 唤醒词检测(例如Porcupine或开放唤醒词模型)
  • 设备特定集成(例如Home Assistant、智能照明)
  • 更好的记忆模式和日历或联系人适配器

您的数据永远不会离开您的设备,并且您控制堆栈的每个部分。这是一个私有的、可定制的助手,您可以根据自己的喜好进行扩展。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计