大多数商业语音助手在响应前都会将您的语音数据发送到云服务器。通过使用开源工具,您可以直接在手机上运行所有组件,获得更好的隐私保护、更快的响应速度,并完全控制助手的行为。
在本教程中,我将逐步引导您完成整个过程。您不需要有机器学习模型的经验,因为我们将逐步构建系统并测试每个部分。最终,您将拥有一个完全本地的移动语音助手,由以下组件驱动:
- Whisper:用于自动语音识别(ASR)
- 机器学习编译器(MLC)LLM:用于设备端推理
- 系统文本转语音(TTS):使用内置的Android TTS
您的助手将能够:
- 离线理解您的语音命令
- 通过合成语音回应您
- 执行工具调用操作(例如控制智能设备)
- 存储个人记忆和偏好
- 使用检索增强生成(RAG)从您自己的笔记中回答问题
- 执行多步骤智能工作流程,例如生成晨间简报并可选地将摘要发送给联系人
本教程重点介绍在Android上使用Termux(Android终端环境)实现完全本地化的工作流程。
系统概述
该图表显示了您的语音在助手中的流动过程:语音输入→转录→推理→行动→语音回复。
该流程描述了核心流程:
- 您对着麦克风说话
- Whisper将音频转换为文本
- 本地LLM解读您的请求
- 助手可能调用工具(例如发送通知或创建事件)
- 使用设备的文本转语音系统大声说出响应
本教程使用的关键概念
- 自动语音识别(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])
|
运行:
步骤6:工具调用(让它行动)
此步骤的作用:通过调用设备上的实际函数,使助手能够执行操作——不仅仅是回复。
工具调用让助手执行操作,而不仅仅是回答。当模型识别到操作请求时,它会输出一个小的JSON指令,您的代码运行相应的函数。您向模型展示存在哪些工具以及如何调用它们。程序拦截调用并运行相应的代码。
示例用例:
您说:“Schedule a meeting tomorrow at 3 PM with John.”
助手:
- 转录您说的话
- 检测到这不是一个问题,而是一个操作请求
- 使用正确的参数调用add_event()函数
- 确认:“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
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"时,助手将:
- 从本地文件读取今天的日程安排
- 总结它
- 大声说出来
- 通过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、智能照明)
- 更好的记忆模式和日历或联系人适配器
您的数据永远不会离开您的设备,并且您控制堆栈的每个部分。这是一个私有的、可定制的助手,您可以根据自己的喜好进行扩展。