第10章(2)------项目四:基于ONNX Runtime、VAD-WEB和Groq的卡路里助手
-
- [10.3 基于ONNX-Runtime、VAD-WEB和Groq的卡路里助手](#10.3 基于ONNX-Runtime、VAD-WEB和Groq的卡路里助手)
-
- [10.3.1 核心组件:vad-web与Groq](#10.3.1 核心组件:vad-web与Groq)
-
- [1. 基于ONNX Runtime的语音活动检测器:vad-web](#1. 基于ONNX Runtime的语音活动检测器:vad-web)
- [2. Groq:高速大模型调用库](#2. Groq:高速大模型调用库)
- [10.3.2 LiteLLM与AI-SDK](#10.3.2 LiteLLM与AI-SDK)
- [10.3.3 项目四:基于VAD和Groq的卡路里助手](#10.3.3 项目四:基于VAD和Groq的卡路里助手)
-
- [1. 环境配置与状态类](#1. 环境配置与状态类)
- [2. 使用Groq客户端处理对话](#2. 使用Groq客户端处理对话)
- [3. 使用ONNX Runtime Web实现免提交互](#3. 使用ONNX Runtime Web实现免提交互)
- [4. 使用Gradio构建用户界面](#4. 使用Gradio构建用户界面)
- [5. 运行卡路里助手](#5. 运行卡路里助手)
10.3 基于ONNX-Runtime、VAD-WEB和Groq的卡路里助手
目前多数语音应用程序的工作方式是单击录制后说话,虽然很有效,但与语音交互最自然的方式是应用程序动态检测用户何时在说话,以便用户无需单击录制即可双向对话。现代语音应用应当突破传统的"单击录音"模式,Gradio通过在ONNX环境运行VAD(Voice Activity Detection:语音活动检测),实现自动检测语音,再结合Whisper语音转录和Groq模型库的高速推理,可以获取动态且低延迟的响应,从而构建语音与文本更直观的交互模式------用户只需随时说话,即可与AI进行交互。
10.3.1 核心组件:vad-web与Groq
本小节讲解需要用到的核心组件,卡路里助手应用的核心组件有:
(1)@ricky0123/vad-web:实现语音活动检测功能。
(2)Whisper:完成语音到文本的转换。
(3)Groq:为自然对话提供高速的大语言模型推理支持。
(4)Gradio:提供Web界面与音频录取和播放能力。
Gradio和Whisper无需多言,下面介绍@ricky0123/vad-web和Groq,以及Groq的两种调用库:Python语言的LiteLLM和JavaScript语言的AI-SDK。
1. 基于ONNX Runtime的语音活动检测器:vad-web
@ricky0123/vad-web是基于JavaScript的语音活动检测器,它仅需几行代码即可实现对包含人类语音的音频片段执行回调检测,旨在提供可在浏览器中运行的精准、用户友好的语音活动检测器。通过该软件包,可以请求用户授予麦克风权限、开始音频录制、将带有语音的音频片段发送至后端处理、在用户说话时显示特定动画或指示器。
@ricky0123/vad-web在后台通过ONNX Runtime Web/ONNX Runtime Node.js运行Silero VAD。ONNX (Open Neural Network Exchange🖇️链接10-5)即开放神经网络交换,是用于表示机器学习模型的开放标准。详细介绍如下:
- 开放神经网络交换(Open Neural Network Exchange🖇️链接10-5)是一个开放生态系统,旨在实现机器学习模型的标准化表示。ONNX为深度学习与传统机器学习模型提供了开源格式标准,定义了可扩展的计算图模型、内置运算符体系及标准数据类型,使AI开发者能够在多种框架、工具、运行时环境及编译器中无缝使用模型,打通从研究到生产的路径。目前该项目主要聚焦于推理(评分)场景的核心能力需求。ONNX Runtime(🖇️链接10-6)是一个跨平台的机器学习模型加速引擎,它提供灵活接口以集成硬件专用库,该运行时支持运行来自PyTorch、Tensorflow/Keras、TFLite、scikit-learn及其他框架的模型。
- ONNX Runtime Web/Node.js:是一款用于在浏览器或Node.js环境中运行ONNX模型的JavaScript库。该库采用WebAssembly与WebGL技术,可以为CPU及GPU提供了性能优化的ONNX模型推理运行时环境引擎,因此网页开发者可直接在浏览器中运行模型推理。详情请参考🖇️链接10-7。
- Silero VAD:是经过预先训练的企业级语音活动检测器,详细信息请参考🖇️链接10-8。类似应用还有性能更优的TEN VAD🖇️链接10-9。
本节基于浏览器使用@ricky0123/vad-web链接10-10。要通过浏览器中的脚本标记使用VAD,请包含以下脚本标记,如代码10-11所示:
代码10-11
html
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/ort.wasm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/bundle.min.js"></script>
<script>
async function main() {
const myvad = await vad.MicVAD.new({
onSpeechStart: () => {
console.log("Speech start detected")
},
onSpeechEnd: (audio) => {
// do sth with `audio` (Float32Array of audio samples at sample rate 16000)...
},
onnxWASMBasePath:
"https://cdn.jsdelivr.net/npm/onnxruntime-web@1.22.0/dist/",
baseAssetPath:
"https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.29/dist/",
})
myvad.start()
}
main()
</script>
这段代码实现了一个基于Web的语音活动检测(VAD)系统,可作为语音识别流程的前端预处理组件,主要功能包括:
- 依赖库引入:①ort.wasm.min.js,加载ONNX Runtime的WebAssembly版本,提供全局对象ort(ONNX Runtime)AI推理引擎,包含.wasm二进制文件,CPU计算接近原生速度,模型推理在本地运行;②bundle.min.js:包含VAD模型和逻辑,提供全局对象vad,用于语音检测。
- 核心VAD逻辑:异步操作(如加载模型、访问麦克风)创建VAD实例。检测到语音开始时开始录音、显示UI提示等;语音结束时,实时发送到服务器或本地识别处理录音。通过 myvad.start() 启动VAD,开始监听麦克风。
- 路径配置:onnxWASMBasePath告诉VAD库在哪里找到ONNX Runtime的WASM文件,用于构建AI推理环境;baseAssetPath是预训练的ONNX模型库路径(如silero_vad.onnx),提供具体的语音检测逻辑和模型。
2. Groq:高速大模型调用库
Groq公司创立于2016年,提供云端和本地AI计算中心的高速AI推理服务,其核心产品是LPU(Language Processing Unit),这是一款专为大型语言模型推理设计的专用ASIC芯片。Groq的LPU以其高性能和成本效益著称,能够在AI推理领域挑战传统GPU的统治地位。Groq的众多模型广泛应用于自然语言处理任务,包括文本生成、翻译和情感分析等,其API支持多种语言转录和翻译,特别适合需要高速响应的实时应用场景。此外,Groq还提供了免费的Whisper Large-V3模型,支持语音转文本和翻译功能,用户可以通过其线上仿真或本地项目中使用API进行体验。
关于Groq的使用及申请GROQ_API_KEY地址:Groq - Text Chat - Chat Completion Models🖇️链接10-11。使用Groq完成聊天的流程如代码10-12所示:
代码10-12
py
import os
from groq import Groq
client = Groq(
api_key=os.environ.get("GROQ_API_KEY"),
)
chat_completion = client.chat.completions.create(
messages=[
{
"role": "user",
"content": "Explain the importance of fast language models",
}
],
model="llama-3.3-70b-versatile",
)
print(chat_completion.choices[0].message.content)
这段代码使用Groq API函数completions调用Llama 3.3 70B大语言模型,它是统一大模型调用接口,发送API请求并获取模型生成的回复内容,存储在返回结果的第一个选项的消息文本中。此外,Groq还有另外两种用法:LiteLLM和AI SDK。
10.3.2 LiteLLM与AI-SDK
Groq有两种调用方法:Python语言的LiteLLM和JavaScript语言的AI-SDK。
LiteLLM (🖇️链接10-12)既是一个基于Python的开源库,也是一个代理/网关服务器,支持快速切换不同的大语言模型提供商和模型版本,可简化LLM应用开发。首先,安装litellm包并设置API密钥GROQ_API_KEY,然后使用Groq中任意模型,只需设置前缀model=groq/即可。如代码10-13所示:
代码10-13
py
from litellm import completion, responses
import os
os.environ['GROQ_API_KEY'] = "your-api-key"
response = completion(
model="groq/llama-3.3-70b-versatile",
messages=[{"role": "user", "content": "hello from litellm"}]
)
print(response)
os.environ["OPENAI_API_KEY"] = "your-api-key"
response_2 = responses(
model="gpt-5-mini",
input="What is the capital of France?",
reasoning_effort="medium"
)
print(response_2)
print(response_2.choices[0].message.content) # response
print(response_2.choices[0].message.reasoning_content) # reasoning
代码中函数completion()是基础用法,而函数responses()适用于支持推理内容的高级模型,例如GPT-5、o3等。使用LiteLLM提供的统一抽象层接口,支持多平台模型接入。在成功获取聊天补全结果和回复功能后,还可以尝试其他端点功能。
AI SDK (链接10-13)是一个基于JavaScript的开源大模型调用库,可简化模型调用流程,加快LLM应用程序的开发。那么如何在AI SDK中使用Groq呢?
首先,安装JavaScript版的ai包和Groq模型程序@ai-sdk/groq:npm add ai @ai-sdk/groq。
然后,使用Groq提供程序生成文本,该程序默认查找GROQ_API_KEY作为API密钥,如代码10-14所示:
代码10-14
json
import { groq } from '@ai-sdk/groq';
import { generateText } from 'ai';
const { text } = await generateText({
model: groq('llama-3.3-70b-versatile'),
prompt: 'Write a vegetarian lasagna recipe for 2 people.',
});
console.log(text)
这段代码使用AI SDK调用Groq平台上的Llama 3.3 70B大语言模型来生成文本。从@ai-sdk/groq中导入groq模型客户端,从ai库导入 generateText 函数用于文本生成,然后异步调用模型生成相应内容。将代码保存为groq_ai.js,然后使用命令node groq_ai.js运行,运行结果如下所示:
bash
D:\gradio_example\groq>node groq_ai.js
Here is a delicious vegetarian lasagna recipe for 2 people:
Ingredients:
* 8-10 lasagna noodles
* 2 cups marinara sauce (homemade or store-bought)
...
在成功获取聊天补全结果后,还可以尝试使用API中的其他端点功能。
10.3.3 项目四:基于VAD和Groq的卡路里助手
本项目结合这些技术构建一个卡路里助手应用。用户可以随时与助手对话,助手会自动检测用户开始和停止对话的时间,并提供文本响应;还可以通过提问引导用户,以便用户对上次用餐进行卡路里估算。本项目的VAD功能和Gradio代码参考了WillHeld/diva-audio-chat🖇️链接10-14。
1. 环境配置与状态类
首先安装核心库,requirements.txt内容:gradio groq numpy soundfile librosa spaces xxhash datasets。
导入库并配置GROQ_API_KEY,并创建Groq客户端,如代码10-15所示:
代码10-15
py
import groq
import gradio as gr
import soundfile as sf
from dataclasses import dataclass, field
import os
# Initialize Groq client securely
os.environ["GROQ_API_KEY"] = "your groq api key"
api_key = os.environ.get("GROQ_API_KEY")
if not api_key:
raise ValueError("Please set the GROQ_API_KEY environment variable.")
client = groq.Client(api_key=api_key)
通过环境变量访问密钥,是避免密钥泄露的最佳安全实践。
定义无缝对话的状态类。为保持对话历史记录并管理录音状态,创建AppState状态类,如代码10-16所示:
代码10-16
py
@dataclass
class AppState:
conversation: list = field(default_factory=list)
stopped: bool = False
该AppState类是管理对话历史和跟踪录音状态的有效工具,每个用例将维护独立的会话列表,确保聊天历史隔离在各自会话中。
2. 使用Groq客户端处理对话
Groq客户端处理逻辑包括Whisper转录音频、Llama-4产生对话和语音回复函数。
使用Whisper转录音频。录制音频后,使用Groq上托管的转录模型Whisper将音频转录为文本。此转录还将判断输入中是否包含有意义的语音,如代码10-17所示:
代码10-17
py
def transcribe_audio(client, file_name):
if file_name is None:
return None
try:
with open(file_name, "rb") as audio_file:
response = client.audio.transcriptions.with_raw_response.create(
model="whisper-large-v3-turbo",
file=("audio.wav", audio_file),
response_format="verbose_json",
)
completion = process_whisper_response(response.parse())
return completion
except Exception as e:
print(f"Error in transcription: {e}")
return f"Error in transcription: {str(e)}"
此函数打开音频文件并将其发送到客户端Groq的Whisper模型进行转录,同时请求详细的JSON输出。这里需要verbose_json格式来获取信息,还会处理任何潜在的错误,以确保如果API请求出现问题,应用程序不会完全崩溃。对于返回的JSON信息,使用函数process_whisper_response()进一步处理,如代码10-18所示:
代码10-18
py
def process_whisper_response(completion):
"""
Process Whisper transcription response and return text or null based on no_speech_prob
Args:
completion: Whisper transcription response object
Returns:
str or None: Transcribed text if no_speech_prob <= 0.7, otherwise None
"""
if completion.segments and len(completion.segments) > 0:
no_speech_prob = completion.segments[0].get('no_speech_prob', 0)
print("No speech prob:", no_speech_prob)
if no_speech_prob > 0.7:
return None
return completion.text.strip()
return None
函数process_whisper_response接收Whisper模型返回的识别结果,检查音频是仅包含背景噪音还是包含有意义的文本。本例使用0.7的阈值做判断,如果没有语音,将返回None;否则返回人类语音的转录文本。
使用Llama-4生成文本。对于转录文本,聊天机器人需要提供自然、智能、友好的响应。可以通过使用Groq托管的Llama-4来实现这一点,如代码10-19所示:
代码10-19
py
def generate_chat_completion(client, history):
messages = []
messages.append(
{
"role": "system",
"content": "In conversation with the user, ask questions to estimate and provide (1) total calories, (2) protein, carbs, and fat in grams, (3) fiber and sugar content. Only ask *one question at a time*. Be conversational and natural.",
}
)
for message in history:
messages.append(message)
try:
completion = client.chat.completions.create(
model="meta-llama/llama-4-scout-17b-16e-instruct",
messages=messages,
)
return completion.choices[0].message.content
except Exception as e:
return f"Error in generating chat completion: {str(e)}"
生成聊天补全函数首先定义消息内容,它包含一个系统提示来指导聊天机器人的行为,确保它一次只问一个问题,并保持对话的自然性;然后填入转录文本;最后调用Groq客户端的Llama-4模型来补全聊天内容。此处也包括错误处理,以确保应用程序能够优雅地处理任何问题。
定义语音响应函数。最后,结合转录文本和生成回复函数,定义语音响应函数。检测到开始录音时,对于流式处理,只需返回当前音频和历史数据即可;处理完上次对话并重新开始录音时,只需保存聊天历史记录并清空输入框;检测到结束录音时,需要使用Groq客户端的大模型转录语音和生成回复。如代码10-20所示:
代码10-20
py
def process_audio(audio: tuple, state: AppState):
return [audio, state]
def restart_recording(state: AppState):
return None
def response(state: AppState, audio: tuple):
if not audio:
return AppState()
file_name = f"/tmp/{xxhash.xxh32(bytes(audio[1])).hexdigest()}.wav"
# 示例:audio = (16000, [0.1, -0.05, 0.2, ...]) # (采样率, PCM数据数组)
sf.write(file_name, audio[1], audio[0], format="wav")
# Transcribe the audio file
transcription = transcribe_audio(client, file_name)
if transcription:
if transcription.startswith("Error"):
transcription += "Error in audio transcription."
# Append the user's message in the proper format
state.conversation.append({"role": "user", "content": transcription})
# Generate assistant response
assistant_message = generate_chat_completion(client, state.conversation)
# Append the assistant's message in the proper format
state.conversation.append({"role": "assistant", "content": assistant_message})
print(state.conversation)
# Optionally, remove the temporary file
os.remove(file_name)
return state, state.conversation
注意,此处的函数process_audio不做任何处理。回复函数讲解如下:
- 生成唯一文件名并写入音频数据:其中audio[1]是音频数据数组,bytes(audio[1])转换为字节序列,xxhash.xxh32(...)使用xxHash算法计算32位哈希,hexdigest()获取16进制哈希字符串,示例:/tmp/a1b2c3d4.wav。最后调用soundfile.write()将音频数据以适合语音识别的PCM WAV格式写入文件。
- 由音频转录文本:调用transcribe_audio将音频转录为文本,对文本进行错误检查,将正确文本以用户消息加入状态的对话记录。
- 生成聊天回复:调用generate_chat_completion生成助理回复消息并加入对话记录,最后删除临时文件。
3. 使用ONNX Runtime Web实现免提交互
为了使聊天机器人实现免提操作,添加语音活动监测VAD,以自动检测用户何时开始或停止说话。如代码10-21所示:
代码10-21
py
js = """
async function main() {
const script1 = document.createElement("script");
script1.src = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.14.0/dist/ort.js";
document.head.appendChild(script1)
const script2 = document.createElement("script");
script2.src = "https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.7/dist/bundle.min.js";
script2.onload = async () => {console.log("vad loaded") ;
var record = document.querySelector('.record-button');
record.textContent = "Just Start Talking!"
record.style = "width: fit-content; padding-right: 0.5vw;"
const myvad = await vad.MicVAD.new({
onSpeechStart: () => {var record = document.querySelector('.record-button');
var player = document.querySelector('#streaming-out')
if (record != null && (player == null || player.paused)) {
console.log(record);
record.click();}},
onSpeechEnd: (audio) => {
var stop = document.querySelector('.stop-button');
if (stop != null) {console.log(stop);
stop.click();}}})
myvad.start()
}
script1.onload = () => {console.log("onnx loaded")
document.head.appendChild(script2)};
}
"""
js_reset = """
() => {
var record = document.querySelector('.record-button');
record.textContent = "Just Start Talking!"
record.style = "width: fit-content; padding-right: 0.5vw;"
}
"""
此段代码是智能语音交互系统的前端注入脚本,用于实现自动语音检测及触发录音功能。其中js是主脚本,初始化语音活动检测;js_reset 重置UI的回调函数。Js使用代码10-11的框架,下面只讲述不同点:
- 第一个脚本动态加载ONNX Runtime:首先动态创建脚本元素,它不修改HTML,只在运行时动态添加脚本;然后加载ONNX Runtime Web 1.14.0的完整版本(非WASM专用版);最后添加到DOM,让浏览器开始下载。script1.onload确保ONNX Runtime先加载,再加载第二个脚本的VAD。
- 创建第二个脚本并初始化:首先动态创建脚本;然后加载vad-web 0.0.7;最后script2.onload在VAD库加载完成后回调,执行初始化。最终加载顺序为:开始先加载ONNX Runtime (script1),ONNX加载完成后开始加载VAD (script2),VAD加载完成后初始化语音检测。
- VAD初始化核心逻辑:首先更新UI状态,找到类名为record-button的按钮,将按钮文字改为"Just Start Talking!"(暗示语音检测已就绪),设置宽度和内边距;然后创建语音活动检测器,包含onSpeechStart回调和onSpeechEnd回调。
- onSpeechStart回调:检测到用户开始说话时,查找录音按钮和音频播放器,当录音按钮存在并且播放器不存在或已暂停,模拟点击录音按钮。此时将自动开始录音,无需用户手动点击。
- onSpeechEnd回调:检测到用户停止说话时,找到停止按钮并模拟点击,将自动停止录音。录音停止后,将自动触发事件input_audio.stop_recording,调用函数response处理录音数据。参数audio包含录音音频数据的Float32Array,虽然此处未使用,但它被传递给response函数的入参audio。
- UI重置函数js_reset:箭头函数可以简洁的进行函数定义,将录音按钮重置为初始状态。可能在页面重置或重新初始化时调用。
本小节涉及JavaScript元素与Gradio组件的交互,逻辑较为复杂,为方便理解,作者制作了Mermaid代码图表,如图10-5所示:

图10-5
4. 使用Gradio构建用户界面
现在使用Gradio创建一个直观且视觉上吸引人的用户界面。如代码10-22所示:
代码10-22
py
with gr.Blocks(theme=theme, js=js, title="Calorie Tracker") as demo:
with gr.Row():
input_audio = gr.Audio(label="Input Audio",
sources=["microphone"], type="numpy", streaming=Fasle,
waveform_options=gr.WaveformOptions(waveform_color="#B83A4B"))
with gr.Row():
chatbot = gr.Chatbot(label="Conversation")
state = gr.State(value=AppState())
stream = input_audio.start_recording(process_audio,
[input_audio, state], [input_audio, state])
respond = input_audio.stop_recording(response,
[state, input_audio], [state, chatbot])
restart = respond.then(restart_recording, [state], [input_audio]).then(
lambda state: state, state, state, js=js_reset)
在这段代码中,使用Gradio的Blocks API创建界面,详解如下:
- 界面元素:包含捕捉语音的音频输入、显示响应的聊天窗口以及保存记录的状态变量,说明:①在音频输入组件中,设置streaming=Fasle,表示非流式音频(完整录音)。②波形图可视化的自定义颜色增添了美观的视觉效果。
- 处理逻辑:①开始录音,在音频组件的开始录音事件start_recording设置函数process_audio,该函数直接返回音频数据,其原有的检测功能及流式传输由VAD自动实现。②停止录音,在停止事件stop_recording调用response,是之前解析的音频处理函数。③事件链.then()调用restart_recording,返回状态本身并重置音频输入组件,之后再次调用事件链.then(),使用js_reset执行前端UI重置。
代码设置了事件监听器,用于开始和停止录音、转录音频以及生成响应。通过连接这些事件创造了一个连贯的体验,用户只需说话,聊天机器人就会自动处理其余工作。
5. 运行卡路里助手
在理解原理后,现在通过以下步骤运行整个应用:
(1)由于Gradio 6在launch()函数加载JavaScript脚本时有Bug,所以本例需运行在Gradio 5版本,可以创建单独环境或在当前环境指定Gradio版本,以5.35.0为例指定Gradio安装版本:uv pip install gradio==5.35.0。
(2)安装库tempfile、groq、soundfile、spaces、xxhash,并设置GROQ_API_KEY后启动app.py:$env:GROQ_API_KEY="gsk_..."&&python app.py。
当用户打开应用程序时,VAD系统会自动初始化并开始监听语音。一旦用户开始说话,它会自动触发录音;当用户停止说话时,录音结束,并执行以下步骤:①使用Whisper对音频进行转录;②转录的文本被发送到Llama-4;③Llama-4生成关于卡路里助手的响应;④响应显示在聊天界面中。运行界面如图10-6所示:

图10-6
通过这些内容,应用程序创造了一个自然的双向对话,用户只需谈论饮食,就能立即获得营养成分分析。它展示了如何创建一个响应迅速且直观的自然语音界面,并通过结合Groq库的快速推理和自动语音检测,取消了手动录音控制,同时保持了高质量的交互。最终的成果就是,应用程序变成了一个实用的卡路里跟踪助手,用户可以像与人类营养师交谈一样自然地与之对话。
该应用的GitHub仓库链接为:gradio-groq-basics/calorie-tracker🖇️链接10-15。源程序运行时,由于版本及语法等原因会有报错,可参考本书线上资源。