本地部署语音AI智能体:从零构建隐私优先的离线语音助手
1. 项目概述:当AI助手能听懂你的话
最近在折腾一个挺有意思的东西:一个能完全在本地运行、用语音控制的AI智能体。简单来说,就是想让电脑或树莓派这类设备,变成一个能听懂你说话、帮你处理事情的“贾维斯”。它不依赖任何云端API,所有对话、理解和任务执行都在你自己的设备上完成,核心诉求就两点: 隐私安全 和 离线可用 。
这个想法的源头,其实是对当前主流AI助手模式的一些“不满足”。很多智能助手确实方便,但你的每一条指令、每一次对话都可能经过云端,对于一些涉及个人隐私或敏感信息的场景,心里总有点不踏实。比如,你想让它帮你整理本地文档里的日程,或者处理一些包含个人信息的文件,交给云端总感觉像把日记本交给了陌生人。另一方面,网络不稳定或者干脆没网的时候,这些助手就立刻“罢工”了。所以,打造一个完全本地的、语音交互的AI代理,就成了一个很有吸引力的技术探索方向。
它适合谁呢?如果你是对隐私有较高要求的极客、喜欢折腾智能家居的开发者、或者单纯想深入理解现代AI技术栈(语音、语言模型、智能体)如何协同工作的学习者,那么这个项目会是一个绝佳的实践案例。整个过程会涉及到语音识别、大语言模型本地部署、智能体框架搭建以及软硬件集成等多个环节,堪称一次小型的技术全景之旅。
2. 核心架构设计与技术选型
2.1 整体工作流拆解
一个完整的语音控制本地AI智能体,其工作流是一个清晰的闭环。首先,你的声音通过麦克风被捕捉,转化为数字音频流。接着,本地运行的语音识别模型将这段音频转换成文本指令,比如“帮我查一下明天上午的日程”。然后,这段文本被送入同样运行在本地的 大语言模型 。LLM在这里扮演“大脑”的角色,它不仅要理解你的指令,还要根据预设的“技能”或工具调用能力,规划出执行步骤。例如,它可能会判断需要调用“读取日历文件”这个函数。智能体框架会执行这个函数调用,获取结果后,再交由LLM组织成自然语言回复。最后,这个文本回复通过本地运行的 文本转语音 模型,变成一段语音播放出来,完成一次交互。
这个流程的关键在于“完全本地化”。每一个环节——从声波到文字,从理解到执行,再从文字到声波——都发生在你的设备内部,数据不出本地,从而确保了隐私和离线能力。这要求我们对每一个组件的资源消耗和性能都有精细的考量。
2.2 关键技术组件选型与考量
技术选型是项目成败的基础,核心在于在性能、精度和资源消耗之间找到最佳平衡点。
1. 语音识别:轻量化与准确性的权衡 对于本地语音识别,我们不再能依赖像Whisper API那样强大但笨重的模型。Whisper的“large”版本虽然准确,但模型体积巨大,对硬件要求高。因此, 轻量化 是首要原则。一个非常好的选择是 OpenAI Whisper的“tiny”或“base”版本 ,或者专门为边缘设备优化的版本如 whisper.cpp 。这些版本在牺牲少量准确度(尤其是对于复杂专业词汇或带口音的语音)的情况下,大幅降低了模型大小和推理所需的内存与算力,使其能在树莓派4B甚至更低的硬件上实时运行。
注意 :选择语音识别模型时,务必测试其在你常用环境下的表现。背景噪音、麦克风质量、以及你的发音习惯,都会显著影响离线模型的识别率。通常需要准备一个小的测试集进行验证。
2. 大语言模型:本地部署的核心 这是整个系统的“大脑”。选型需要考虑三个核心维度: 模型大小、推理速度、对话能力 。像Llama 3、Qwen 2.5、Gemma等系列的 7B参数 版本,是当前本地部署的“甜点”选择。它们具备足够强的逻辑推理和指令跟随能力,同时经过量化(如GGUF格式的Q4_K_M量化)后,可以在16GB甚至8GB内存的消费级设备上流畅运行。
量化技术 是本地部署LLM的关键。它将模型参数的精度从FP32降低到INT4或INT5,从而大幅减少内存占用和提升推理速度,而性能损失在可控范围内。我们通常会选择 llama.cpp 或 Ollama 作为推理引擎。llama.cpp兼容性极佳,效率突出;Ollama则提供了更便捷的模型管理和API调用方式,适合快速原型开发。
3. 智能体框架:连接大脑与手脚 LLM本身只是一个“思考者”,它需要框架来调用工具、执行动作。 LangChain 和 LlamaIndex 是两大主流选择。LangChain功能全面,组件丰富,像一个“智能体工厂”,但学习曲线稍陡。LlamaIndex则更专注于数据连接和检索,对于需要让AI读取本地知识库的场景非常友好。对于这个项目,由于我们主要需要调用一些本地系统工具(如文件操作、执行命令),使用LangChain来定义工具、构建代理链是一个直观的方案。也可以考虑更轻量的方案,如直接利用LLM的 函数调用 能力,配合简单的Python脚本来实现。
4. 文本转语音:让AI“开口说话” TTS的选择相对直接。 Coqui TTS 或 Piper 是优秀的开源选择,它们提供了多种语言的预训练模型,音质自然,且可以在CPU上高效运行。你需要根据偏好选择语音风格和语言。这一步的资源消耗通常远低于ASR和LLM。
3. 环境搭建与核心模块实现
3.1 基础开发环境配置
首先,我们需要一个清晰的Python环境。强烈建议使用 conda 或 venv 创建独立的虚拟环境,避免包依赖冲突。
# 使用conda创建环境
conda create -n local_ai_agent python=3.10
conda activate local_ai_agent
# 或使用venv
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
接下来安装核心依赖。这里我们以LangChain和Whisper为例。
pip install openai-whisper # 语音识别
pip install langchain langchain-community # 智能体框架
pip install sounddevice pyaudio # 音频采集
pip install TTS # Coqui TTS,文本转语音
# 如果需要使用llama.cpp,则安装其Python绑定
pip install llama-cpp-python
实操心得 :安装
llama-cpp-python时,如果遇到编译问题,可以尝试先安装CMake和C++编译器。在Mac上可能还需要brew install cmake。对于树莓派等ARM设备,编译过程可能较长,需要耐心。
3.2 语音识别模块实现
我们使用Whisper的
tiny
模型,它在速度和精度之间取得了很好的平衡。
import whisper
import numpy as np
import sounddevice as sd
class LocalSpeechRecognizer:
def __init__(self, model_size="tiny"):
# 加载模型,首次运行会自动下载
self.model = whisper.load_model(model_size)
print(f"Whisper模型 '{model_size}' 加载完成。")
def record_audio(self, duration=5, samplerate=16000):
"""录制音频"""
print(f"正在聆听...({duration}秒)")
audio = sd.rec(int(duration * samplerate),
samplerate=samplerate,
channels=1,
dtype='float32')
sd.wait() # 等待录制结束
return audio.flatten()
def transcribe(self, audio_array, samplerate=16000):
"""将音频数组转录为文本"""
# 确保音频为单声道,float32格式
audio = audio_array.astype(np.float32)
# 调用Whisper识别
result = self.model.transcribe(audio, fp16=False) # 非GPU环境用fp16=False
return result["text"].strip()
# 使用示例
if __name__ == "__main__":
recognizer = LocalSpeechRecognizer("tiny")
audio_data = recognizer.record_audio(duration=7)
text = recognizer.transcribe(audio_data)
print(f"识别结果:{text}")
这段代码创建了一个简单的语音识别类。
record_audio
函数使用
sounddevice
录制指定时长的音频,
transcribe
函数则调用Whisper模型进行识别。
fp16=False
参数在纯CPU环境下是必须的。
3.3 本地大语言模型集成
这里我们展示如何使用
llama-cpp-python
加载一个量化后的GGUF模型文件。假设你已经下载了
llama-3-8b-instruct.Q4_K_M.gguf
模型文件。
from llama_cpp import Llama
from langchain.llms import LlamaCpp
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
class LocalLLM:
def __init__(self, model_path):
# 回调管理器,支持流式输出
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
# 加载LlamaCpp模型,关键参数配置
self.llm = LlamaCpp(
model_path=model_path,
temperature=0.7, # 创造性,越高越随机
max_tokens=512, # 生成的最大token数
top_p=0.95, # 核采样参数,控制多样性
callback_manager=callback_manager,
verbose=False, # 是否显示详细日志
n_ctx=2048, # 上下文窗口大小
n_gpu_layers=0, # CPU运行设为0,若有GPU可指定层数
)
print("本地LLM加载完成。")
def generate(self, prompt):
"""生成回复"""
response = self.llm.invoke(prompt)
return response
# 使用示例
if __name__ == "__main__":
llm = LocalLLM("./models/llama-3-8b-instruct.Q4_K_M.gguf")
test_prompt = "用一句话解释什么是人工智能。"
answer = llm.generate(test_prompt)
print(f"\n模型回复:{answer}")
关键参数解析 :
-
n_ctx:决定了模型能“记住”多长的上下文对话。2048对于简单指令足够,若需长对话需增大,但内存消耗也会增加。 -
n_gpu_layers:如果系统有GPU,将此参数设置为一个大于0的数(如20),可以将模型的部分层卸载到GPU上,大幅提升推理速度。需要CUDA环境。 -
temperature:影响输出的随机性。0.1会让输出非常确定和重复,0.7-0.9适合创造性对话,1.0以上可能产生无意义内容。
3.4 智能体工具定义与链式构建
智能体的“能力”通过工具来定义。我们创建几个简单的本地工具示例。
from langchain.agents import Tool, AgentExecutor, create_react_agent
from langchain import hub
from datetime import datetime
import os
# 1. 定义工具函数
def get_current_time(input: str) -> str:
"""获取当前的日期和时间。输入参数忽略。"""
now = datetime.now()
return now.strftime("%Y年%m月%d日 %H:%M:%S")
def list_files_in_directory(input: str) -> str:
"""列出指定目录下的文件。输入应为目录路径。"""
if not input or not os.path.isdir(input):
return "错误:请输入一个有效的目录路径。"
files = os.listdir(input)
return f"目录 '{input}' 下的文件有:{', '.join(files[:10])}" # 只显示前10个
def calculate_sum(input: str) -> str:
"""计算一组数字的和。输入应为用逗号分隔的数字字符串,如'1,2,3'。"""
try:
numbers = [float(x.strip()) for x in input.split(",")]
total = sum(numbers)
return f"这些数字的和是:{total}"
except ValueError:
return "错误:输入格式不正确,请使用逗号分隔的数字,例如 '1,2,3'。"
# 2. 将函数包装成LangChain工具
tools = [
Tool(
name="当前时间",
func=get_current_time,
description="当用户询问当前时间、日期或现在几点时使用此工具。输入参数忽略。"
),
Tool(
name="列出文件",
func=list_files_in_directory,
description="当用户要求列出某个文件夹中的文件时使用。输入应该是一个有效的目录路径字符串。"
),
Tool(
name="计算求和",
func=calculate_sum,
description="当用户要求计算一系列数字的总和时使用。输入应是用逗号分隔的数字字符串,例如'5,10,15'。"
)
]
# 3. 创建智能体
def create_agent(llm):
# 从LangChain Hub拉取一个ReAct提示词模板
prompt = hub.pull("hwchase17/react")
# 创建ReAct智能体
agent = create_react_agent(llm, tools, prompt)
# 创建执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
return agent_executor
# 使用示例
if __name__ == "__main__":
# 假设llm是已初始化的LocalLLM实例
from local_llm import LocalLLM
llm_instance = LocalLLM("./models/llama-3-8b-instruct.Q4_K_M.gguf").llm
agent = create_agent(llm_instance)
query = "请告诉我现在的时间,然后计算1.5, 2.5和3的和。"
result = agent.invoke({"input": query})
print(f"\n最终结果:{result['output']}")
这个智能体使用了
ReAct
框架,它会让LLM以“思考 -> 行动 -> 观察”的循环来解决问题。
verbose=True
会让执行过程打印出来,方便调试。当LLM决定使用工具时,它会生成一个类似
Action: 当前时间
的指令,执行器便会调用对应的工具函数。
3.5 文本转语音模块集成
最后,让AI的回复“说”出来。我们使用Coqui TTS。
from TTS.api import TTS
import numpy as np
import sounddevice as sd
class LocalTTS:
def __init__(self, model_name="tts_models/en/ljspeech/tacotron2-DDC"):
# 初始化TTS,指定模型
self.tts = TTS(model_name=model_name, progress_bar=False, gpu=False)
print("TTS模型加载完成。")
def speak(self, text, output_wav_path=None):
"""将文本转换为语音并播放,可选保存为文件"""
if output_wav_path:
# 保存为wav文件
self.tts.tts_to_file(text=text, file_path=output_wav_path)
print(f"语音已保存至:{output_wav_path}")
# 在内存中生成音频数据并播放
wav = self.tts.tts(text=text)
wav_np = np.array(wav)
sd.play(wav_np, samplerate=22050) # 注意采样率需与模型匹配
sd.wait()
print("播放完毕。")
# 使用示例
if __name__ == "__main__":
tts = LocalTTS()
tts.speak("Hello, I am your local AI assistant. How can I help you today?")
# tts.speak("你好,我是你的本地AI助手。", output_wav_path="greeting.wav")
注意 :TTS模型的选择会影响音质和语言。
en/ljspeech/tacotron2-DDC是英文模型。如果需要中文语音,可以寻找并指定中文TTS模型,例如zh-CN/baker/tacotron2-DDC-GST(如果可用)。首次运行会下载模型,请确保网络通畅。
4. 系统集成与主循环逻辑
将以上所有模块串联起来,形成完整的交互闭环。
import time
from speech_recognizer import LocalSpeechRecognizer
from local_llm import LocalLLM
from agent_manager import create_agent
from local_tts import LocalTTS
class VoiceControlledAIAgent:
def __init__(self, llm_model_path, tts_model_name="tts_models/en/ljspeech/tacotron2-DDC"):
print("初始化语音控制AI智能体...")
self.asr = LocalSpeechRecognizer("base") # 使用base模型,精度更高一些
self.llm_wrapper = LocalLLM(llm_model_path)
self.agent = create_agent(self.llm_wrapper.llm)
self.tts = LocalTTS(tts_model_name)
print("所有模块初始化完成!")
def process_command(self, audio_duration=7):
"""处理单次语音命令的完整流程"""
# 1. 录音
print("\n" + "="*50)
print("请说话...")
audio = self.asr.record_audio(duration=audio_duration)
# 2. 语音识别
print("正在识别语音...")
user_text = self.asr.transcribe(audio)
if not user_text or len(user_text.strip()) < 2:
print("未识别到有效指令。")
return
print(f"您说:{user_text}")
# 3. 智能体处理
print("AI正在思考...")
try:
agent_response = self.agent.invoke({"input": user_text})
ai_text = agent_response['output']
except Exception as e:
ai_text = f"处理指令时出现错误:{e}"
print(f"AI回复:{ai_text}")
# 4. 语音合成与播放
print("AI正在说话...")
self.tts.speak(ai_text)
def run_interactive_loop(self):
"""运行交互循环"""
print("\n语音控制AI智能体已启动!")
print("说出你的指令(例如:'现在几点?' 或 '列出/home/pi/documents目录下的文件')")
print("说'退出'或'停止'来结束程序。")
while True:
self.process_command()
# 简单检查是否要退出(基于识别文本)
# 注意:由于识别可能不准,退出指令可能需要更鲁棒的检查
time.sleep(1) # 每次循环间隔,避免过热
if __name__ == "__main__":
# 请根据你的实际路径修改
MODEL_PATH = "./models/llama-3-8b-instruct.Q4_K_M.gguf"
agent = VoiceControlledAIAgent(MODEL_PATH)
agent.run_interactive_loop()
这个主类
VoiceControlledAIAgent
将四个核心模块整合在一起。
run_interactive_loop
方法启动了一个持续监听-处理-回复的循环。每次循环中,它录制一段音频,识别成文本,交给智能体处理,最后将智能体的文本回复用语音播放出来。
5. 性能优化与部署实践
5.1 硬件要求与资源管理
本地AI智能体对硬件有一定要求,主要瓶颈在 内存 和 CPU/GPU 。
- 树莓派4B (4GB/8GB RAM) :这是挑战的极限。可以运行Whisper tiny和量化到Q4的2B-3B参数模型(如Phi-2, Qwen1.5-1.8B)。运行7B模型会非常吃力,甚至因内存不足而失败。务必使用交换空间,并关闭所有不必要的后台服务。
- x86旧笔记本/迷你主机 (8GB RAM) :这是入门级门槛。可以较为流畅地运行Whisper base和7B模型的Q4量化版。推理速度可能在每秒2-5个token,响应会有明显延迟,但可交互。
-
配备GPU的台式机/服务器 (16GB+ RAM, NVIDIA GPU)
:这是理想环境。利用
n_gpu_layers将模型大部分层加载到GPU显存中,推理速度可提升10倍以上,达到实时对话体验。显存大小决定了能加载的模型尺寸(例如,RTX 4060 8G可流畅运行7B Q4模型)。
资源监控命令
:
在Linux/macOS下,运行程序时,另开一个终端使用
htop
或
top
命令观察CPU和内存占用。对于树莓派,
vcgencmd measure_temp
可以查看核心温度,防止过热降频。
5.2 延迟优化技巧
延迟是影响体验的关键。可以从以下几个层面优化:
-
语音识别优化 :
-
使用VAD
:集成语音活动检测,只在检测到人声时才录制和识别,避免处理静音片段,减少无效计算和等待时间。
webrtcvad库是一个不错的选择。 - 流式识别 :如果使用支持流式处理的ASR引擎(如faster-whisper),可以在说话的同时就开始识别,实现“边说边转”。
-
使用VAD
:集成语音活动检测,只在检测到人声时才录制和识别,避免处理静音片段,减少无效计算和等待时间。
-
LLM推理优化 :
-
调整
max_tokens:限制每次生成的最大长度,避免AI“长篇大论”拖慢响应。 -
使用更高效的推理后端
:除了llama.cpp,可以尝试
vLLM(如果支持你的模型和硬件),它专门为高吞吐量、低延迟的LLM服务设计。 - 缓存 :对于常见、固定的问题(如“你是谁?”),可以设置一个简单的内存缓存,直接返回预置答案,绕过LLM推理。
-
调整
-
流水线并行 :
- 当前流程是串行的:录音 -> ASR -> LLM -> TTS。一个高级优化思路是 部分并行 。例如,在LLM生成文本的前几个token后,就可以开始启动TTS的预处理,而不是等全部文本生成完毕。
5.3 部署为常驻服务
要让这个智能体像“管家”一样随时待命,需要将其部署为系统服务。
在Linux系统上使用systemd
:
创建一个服务文件,例如
/etc/systemd/system/local-ai-agent.service
。
[Unit]
Description=Local Voice-Controlled AI Agent
After=network.target sound.target
Wants=network.target
[Service]
Type=simple
User=pi # 替换为你的用户名
WorkingDirectory=/home/pi/voice_ai_agent # 替换为你的项目路径
Environment="PATH=/home/pi/venv/bin" # 虚拟环境路径
ExecStart=/home/pi/venv/bin/python /home/pi/voice_ai_agent/main.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
然后启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable local-ai-agent.service
sudo systemctl start local-ai-agent.service
# 查看状态
sudo systemctl status local-ai-agent.service
# 查看日志
sudo journalctl -u local-ai-agent.service -f
这样,设备开机后服务会自动启动,并在崩溃后尝试重启。你可以通过查看日志来监控运行状态。
6. 常见问题排查与调试心得
6.1 语音识别准确率低
这是最常见的问题,尤其是在有环境噪音或使用廉价麦克风时。
- 症状 :识别出的文本与所说内容完全不符或支离破碎。
-
排查步骤
:
-
检查音频输入
:首先确认麦克风是否被正确识别和选中。可以使用
arecord -l(Linux)或系统录音工具测试麦克风是否能正常录音。 -
优化录音参数
:尝试调整
sounddevice的采样率(samplerate)和设备ID(device)。采样率通常为16000Hz或22050Hz,需与模型训练时匹配。Whisper期望16kHz。 -
预处理音频
:在将音频送入模型前,可以进行简单的预处理,如归一化(
audio = audio / np.max(np.abs(audio)))和降噪(使用noisereduce库)。这对于提升低质量麦克风的识别率有奇效。 -
更换模型
:如果使用
tiny版不准,可以尝试base甚至small版,但要注意性能下降。也可以尝试其他轻量级ASR模型,如Vosk,它对特定场景可能有更好表现。 - 环境降噪 :物理上改善录音环境是最根本的方法。
-
检查音频输入
:首先确认麦克风是否被正确识别和选中。可以使用
6.2 大语言模型加载失败或推理极慢
- 症状 :程序在加载模型时崩溃,或加载后生成每个词都需要数秒。
-
排查与解决
:
-
内存不足
:这是崩溃的主因。使用
free -h或任务管理器检查可用内存。确保模型文件大小(GGUF文件大小)远小于你的可用内存。例如,一个7B Q4模型约4-5GB,系统至少需要8GB内存才比较安全。 - 量化等级过高 :如果你使用了Q2或Q3等更激进的量化,虽然模型更小,但精度损失可能导致输出乱码或逻辑错误。建议从Q4_K_M或Q5_K_M开始尝试。
-
未使用GPU加速
:在支持CUDA的机器上,务必设置
n_gpu_layers。你可以尝试将其设置为一个很大的数(如999),让库自动决定卸载所有可能层到GPU。使用nvidia-smi命令确认GPU是否被使用。 -
线程数设置
:对于纯CPU推理,
llama-cpp-python可以通过n_threads参数指定使用的CPU线程数。通常设置为物理核心数能获得较好性能。在初始化时传入:LlamaCpp(..., n_threads=4)。
-
内存不足
:这是崩溃的主因。使用
6.3 智能体不调用工具或调用错误
-
症状
:LLM直接回答了关于时间或计算的问题,而没有调用我们定义的
当前时间或计算求和工具。 -
排查与解决
:
-
工具描述不清
:检查Tool的
description字段。这个描述是LLM决定是否调用工具的唯一依据。描述必须清晰、准确,并包含触发条件。例如,“当用户询问当前时间、日期或现在几点时使用此工具”就比“获取时间”要好得多。 -
提示词模板问题
:ReAct代理的提示词模板引导LLM按照“Thought/Action/Action Input/Observation”的格式思考。如果LLM不按这个格式输出,执行器就无法解析。确保你使用的
prompt模板是适合工具调用场景的。hwchase17/react是LangChain官方维护的一个可靠选择。 - LLM能力不足 :较小的或指令跟随能力较弱的模型可能无法可靠地理解并使用工具。尝试换一个更强大的模型(如从7B换到8B或更大,或换用指令微调更充分的模型系列)。
-
开启详细日志
:设置
AgentExecutor(..., verbose=True),观察LLM的完整思考链。这能帮你看到它是如何思考、为什么决定不调用工具的,是调试的最重要手段。
-
工具描述不清
:检查Tool的
6.4 音频播放异常或没有声音
- 症状 :程序运行无报错,但听不到TTS播放的声音。
-
排查步骤
:
-
检查输出设备
:
sounddevice默认使用系统默认播放设备。可以通过sd.query_devices()列出所有设备,并在sd.play()中指定正确的device参数。 -
采样率匹配
:TTS模型生成的音频有一个固定的采样率(如22050Hz)。在
sd.play()时,samplerate参数必须与之匹配,否则播放速度会不对。查看TTS模型文档或打印生成音频的采样率。 -
保存文件测试
:先将TTS输出保存为WAV文件(使用
tts.tts_to_file),然后用系统播放器打开。如果能正常播放,说明问题出在sounddevice播放环节;如果不能,说明TTS生成环节有问题。 -
权限问题
(Linux):在某些系统上,用户可能没有直接播放音频的权限。可以尝试将用户加入
audio组:sudo usermod -a -G audio $USER,然后注销重新登录。
-
检查输出设备
:
这个项目从构思到实现,是一段充满挑战和成就感的旅程。最大的体会是, 平衡的艺术贯穿始终 :在模型精度与推理速度之间,在功能丰富性与系统资源之间,在开发复杂度与用户体验之间。每一次优化和调试,都让你对底层技术栈的理解更深一层。它不仅仅是一个可用的工具,更是一个绝佳的、全景式的AI应用开发学习案例。当你第一次听到它用你自己的声音(如果你训练了自定义TTS)流畅地回答出本地文件列表时,那种一切尽在掌控的感觉,是使用任何云端API都无法替代的。
更多推荐


所有评论(0)