Web 端语音对话 AI 示例:使用 Whisper 和 llama.cpp 构建语音聊天机器人

大语言模型(LLM)为基于文本的对话提供了强大的能力。那么,能否进一步扩展,将其转化为语音对话的形式呢?本文将展示如何使用 Whisper 语音识别和 llama.cpp 构建一个 Web 端语音聊天机器人。

系统概览

如上图所示,系统的工作流程如下:

  1. 用户通过语音输入。
  2. 语音识别,转换为文本。
  3. 文本通过大语言模型(LLM)生成文本响应。
  4. 最后,文本转语音播放结果。

系统实现

端侧的具体形态(如 web 端、桌面端、手机端)直接影响了第一步用户语言的输入,以及最后一步响应结果的语音播放。
在本文中,我们选择使用 Web 端作为示例,利用浏览器本身的语言采集和语音播放功能,来实现用户与系统的互动。

下图展示了系统架构:

用户通过 Web 端与系统交互,语音数据通过 WebSocket 传输到后端服务,后端服务使用 Whisper 将语音转换为文本,接着通过 llama.cpp 调用 LLM 生成文本响应,最后,文本响应通过 WebSocket 发送回前端,并利用浏览器的语音播放功能将其朗读出来。

Web 端

Web 端的实现主要依赖 HTML5 和 JavaScript。我们使用浏览器的 Web API 进行语音采集和语音播放。以下是简化的 Web 端代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Voice Chat AI</title>
    <style>
        #loading { display: none; font-weight: bold; color: blue }
        #response { white-space: pre-wrap; }
    </style>
</head>
<body>
    <h1>Voice Chat AI</h1>
    <button id="start">Start Recording</button>
    <button id="stop" disabled>Stop Recording</button>
    <p id="loading">Loading...</p>
    <p>AI Response: <span id="response"></span></p>

    <script>
        let audioContext, mediaRecorder;
        const startButton = document.getElementById("start");
        const stopButton = document.getElementById("stop");
        const responseElement = document.getElementById("response");
        const loadingElement = document.getElementById("loading");

        let socket = new WebSocket("ws://localhost:8765/ws");

        socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            const inputText = data.input || "No input detected";
            responseElement.textContent += `\nUser said: ${inputText}`;
            const aiResponse = data.response || "No response from AI";
            responseElement.textContent += `\nAI says: ${aiResponse}\n`;
            loadingElement.style.display = "none";

            const utterance = new SpeechSynthesisUtterance(aiResponse);
            speechSynthesis.speak(utterance);
        };

        socket.onerror = (error) => {
            console.error("WebSocket error:", error);
            loadingElement.style.display = "none";
        };

        startButton.addEventListener("click", async () => {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            mediaRecorder = new MediaRecorder(stream);

            const audioChunks = [];
            mediaRecorder.ondataavailable = (event) => {
                audioChunks.push(event.data);
            };

            mediaRecorder.onstop = () => {
                const audioBlob = new Blob(audioChunks, { type: "audio/webm" });
                loadingElement.style.display = "block";
                socket.send(audioBlob);
            };

            mediaRecorder.start();
            startButton.disabled = true;
            stopButton.disabled = false;
        });

        stopButton.addEventListener("click", () => {
            mediaRecorder.stop();
            startButton.disabled = false;
            stopButton.disabled = true;
        });
    </script>
</body>
</html>

为了简化示例代码,使用了开始和结束按钮来手动控制语音的录制。如果要实现实时对话,除了需要合理设置语音采集的时间间隔,还需要确保后端能够快速响应,避免延迟影响用户体验(这在我的笔记本电脑上无法做到)。

WebSocket 服务端

服务端实现为:

  • 使用 Pythonfastapi 框架搭建 WebSocket 服务。
  • 使用 whisper 进行语音识别,将语音转换为文本,注意系统环境需要额外安装 ffmpeg 命令行工具。
  • 通过 llama.cpp 加载 LLM(我使用的是 llama3.2-1B 模型) 并生成响应文本。

以下是服务端的代码示例:

from fastapi import FastAPI, WebSocket
import uvicorn
import whisper
import tempfile
import os
import signal

app = FastAPI()

# 加载 Whisper 模型,默认存储位置 ~/.cache/whisper,可以通过 download_root 设置
model = whisper.load_model("base", download_root="WHISPER_MODEL")

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    try:
        await websocket.accept()
        while True:
            # 接收音频数据
            audio_data = await websocket.receive_bytes()

            # 保存临时音频文件
            with tempfile.NamedTemporaryFile(delete=False, suffix=".webm") as temp_audio:
                temp_audio.write(audio_data)
                temp_audio_path = temp_audio.name

            # Whisper 语音识别
            result = model.transcribe(temp_audio_path)
            os.remove(temp_audio_path)
            text = result["text"]
            print("user input: ", text)

            # 生成 AI 回复
            response_text = LLMResponse(text)
            print("AI response: ", response_text)

            await websocket.send_json({"input": text, "response": response_text})
    except Exception as e:
        print("Error: ", e)


def handle_shutdown(signal_num, frame):
    print(f"Received shutdown signal: {signal_num}")

def setup_signal_handlers():
    signal.signal(signal.SIGTERM, handle_shutdown)
    signal.signal(signal.SIGINT, handle_shutdown)

if __name__ == "__main__":
    setup_signal_handlers()

    config = uvicorn.Config("main:app", port=8765, log_level="info")
    server = uvicorn.Server(config)
    server.run()

此外,llama.cpp 使用 Docker 容器运行,作为 HTTP 服务来提供 LLM 的能力。启动命令如下:

docker run -p 8080:8080 -v ~/ai-models:/models \
    ghcr.io/ggerganov/llama.cpp:server \
    -m /models/llama3.2-1B.gguf -c 512 \
    --host 0.0.0.0 --port 8080

WebSocket serverllama.cpp 之间则可以直接使用 HTTP 的方式通信,示例代码如下:

import requests
import json

class LlamaCppClient:
    def __init__(self, host="http://localhost", port=8080):
        self.base_url = f"{host}:{port}"

    def completion(self, prompt):
        url = f"{self.base_url}/v1/chat/completions"
        headers = {"Content-Type": "application/json"}
        payload = {
            "messages": [
                {
                    "role": "system",
                    "content": """
                        You are a friendly conversation partner. Be natural, engaging, and helpful in our discussions. Respond to questions clearly and follow the conversation flow naturally.
                    """
                },
                {
                    "role": "user",
                    "content": prompt
                }
            ]
        }
        
        try:
            response = requests.post(url, headers=headers, data=json.dumps(payload))
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            return {"error": str(e)}

最后,用户与 AI 的聊天结果类似下图:

总结

通过结合 Web 端的语音识别和语音合成功能、Whisper 的语音转文本能力、以及 llama.cpp 提供的 LLM 服务,我们成功构建了一个语音对话系统。语音对话的场景非常丰富,例如口语外教、语音问答等等。希望本文的示例能够为你在构建语音交互式 AI 系统时提供启发。


(我是凌虚,关注我,无广告,专注技术,不煽动情绪,欢迎与我交流)


参考资料:

  • https://github.com/openai/whisper
  • https://github.com/ggerganov/llama.cpp/blob/master/examples/server/README.md
  • https://github.com/fastapi/fastapi
  • https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925059.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Excel把其中一张工作表导出成一个新的文件

excel导出一张工作表 一个Excel表里有多个工作表&#xff0c;怎么才能导出一个工作表&#xff0c;让其生成新的Excel文件呢&#xff1f; 第一步&#xff1a;首先打开Excel表格&#xff0c;然后选择要导出的工作表的名字&#xff0c;比如“Sheet1”&#xff0c;把鼠标放到“She…

Django+Nginx+uwsgi网站Channels+redis+daphne多人在线聊天实现粘贴上传图片

在DjangoNginxuwsgi网站Channelsredisdaphne多人在线的基础上&#xff08;详见DjangoNginxuwsgi网站使用Channelsredisdaphne实现简单的多人在线聊天及消息存储功能-CSDN博客&#xff09;&#xff0c;实现在输入框粘贴或打开本地图片&#xff0c;上传到网站后返回图片路径&…

【Git】Git 命令参考手册

目录 Git 命令参考手册1. 创建仓库1.1 创建一个新的本地仓库1.2 克隆一个仓库1.3 克隆仓库到指定目录 2. 提交更改2.1 显示工作目录中已修改的文件&#xff0c;准备提交2.2 将文件添加到暂存区&#xff0c;准备提交2.3 将所有已修改的文件添加到暂存区&#xff0c;准备提交2.4 …

TDengine在debian安装

参考官网文档&#xff1a; 官网安装文档链接 从列表中下载获得 Deb 安装包&#xff1b; TDengine-server-3.3.4.3-Linux-x64.deb (61 M) 进入到安装包所在目录&#xff0c;执行如下的安装命令&#xff1a; sudo dpkg -i TDengine-server-<version>-Linux-x64.debNOTE 当…

linux安装mysql8.0.40

一、下载MySQL安装包 1.查看glibc版本 rpm -qa | grep glibc 2.到mysql官网下载安装包 ​ 二、解压安装 1.上传压缩包纸/usr/local 目录下&#xff0c;解压&#xff1a; tar -xvf mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz 2.重命名&#xff1a; mv mysql-8.0.40-linux-…

用Pycharm安装manim

由于版本和工具的差异&#xff0c;manim的安装方式不尽相同。本文用Pycharm来安装manim. 一、准备工作&#xff1a;安装相应版本的python、pycharm和ffmpeg. 此处提供一种安装ffmpeg的方式 下载地址&#xff1a;FFmpeg 下载后&#xff0c;解压到指定目录。 配置环境变量&am…

运维面试整理总结

面试题可以参考:面试题总结 查看系统相关信息 查看系统登陆成功与失败记录 成功&#xff1a;last失败&#xff1a;lastb 查看二进制文件 hexdump查看进程端口或连接 netstat -nltp ss -nltp补充&#xff1a;pidof与lsof命令 pidof [进程名] #根据 进程名 查询进程id ls…

Kubernetes 之 Ingress 和 Service 的异同点

1. 概念与作用 1.1 Ingress Ingress 是什么&#xff1f; Ingress主要负责七层负载&#xff0c;将外部 HTTP/HTTPS 请求路由到集群内部的服务。它可以基于域名和路径定义规则&#xff0c;从而将外部请求分配到不同的服务。 ingress作用 提供 基于 HTTP/HTTPS 的路由。 支持 …

初识 Django

声明 适用于想要快速入门的开发者&#xff0c;有前后端开发以及语言基础&#xff0c;想要学习语法或者特性。 想要学会快速开发&#xff0c;快速入门&#xff0c;请看博客【实用向】Django 框架入门并结合本篇文章。 命令 命令描述startproject创建一个 Django 项目startapp…

python简单算法

冒泡 def boll(lis):i 0while i<len(lis)-1:j 0while j<len(lis)-1-i:if lis[j] > lis[j1]:lis[j],lis[j 1] lis[j1],lis[j]j1i1选择排序 def selct1(lit):i 0while i<len(lit)-1:j i1min1 iwhile j < len(lit):if lit[j] < lit[min1]:min1 jj 1li…

【大模型】基于LLaMA-Factory的模型高效微调

LLaMA-Factory项目介绍 LLaMA Factory 是一个简单易用且高效的大型语言模型&#xff08;Large Language Model&#xff09;训练与微调平台。通过 LLaMA Factory&#xff0c;可以在无需编写任何代码的前提下&#xff0c;在本地完成上百种预训练模型的微调&#xff0c;框架特性包…

电脑中的vcruntime140_1.dll文件有问题要怎么解决?一键修复vcruntime140_1.dll

遇到“vcruntime140_1.dll无法继续执行代码”的错误通常表明电脑中的vcruntime140_1.dll文件有问题。这个文件属于Visual C Redistributable&#xff0c;对很多程序的运行至关重要。本文将提供几个步骤&#xff0c;帮助你迅速修复这一错误&#xff0c;使电脑恢复正常工作状态。…

鼠标前进后退键改双击,键盘映射(AutoHotkey)

初衷&#xff1a; 1.大部分鼠标为不可自定义按键&#xff0c;可以自定义的又很贵。 鼠标左键是双击是很频类很高的操作&#xff0c;鼠标前进/后退按键个人感觉使用频率很低&#xff0c;因此把鼠标前进/后退改为双击还是很合适的。 2.有些短款的键盘没有Home或End键&#xff0c;…

VSCode Terminal无法运行node以及node-gyp等指令

无法使用node指令&#xff0c;使用管理员权限启动VSCode即可&#xff0c;或者右键VSCode属性&#xff0c;修改兼容性中使用管理员权限打开。 运行node-gyp等指令出现因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID1351…

npm install -g@vue/cli报错解决:npm error code ENOENT npm error syscall open

这里写目录标题 报错信息1解决方案 报错信息2解决方案 报错信息1 使用npm install -gvue/cli时&#xff0c;发生报错&#xff0c;报错图片如下&#xff1a; 根据报错信息可以知道&#xff0c;缺少package.json文件。 解决方案 缺什么补什么&#xff0c;这里我们使用命令npm…

Elasticsearch:Retrievers 介绍

检索器&#xff08;retrievers&#xff09;是 Elasticsearch 中搜索 API 中添加的新抽象层。它们提供了在单个 _search API 调用中配置多阶段检索管道的便利。此架构通过消除对复杂搜索查询的多个 Elasticsearch API 调用的需求&#xff0c;简化了应用程序中的搜索逻辑。它还减…

nvidia-container-toolkit安装问题(OpenPGP)

1.正常情况下 apt-get install -y nvidia-container-toolkit2.使用nvidia源 nvidia-container-toolkit官网有安装教程 2.1 配置生产存储库 curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-containe…

open-instruct - 训练开放式指令跟随语言模型

文章目录 关于 open-instruct设置训练微调偏好调整RLVR 污染检查开发中仓库结构 致谢 关于 open-instruct github : https://github.com/allenai/open-instruct 这个仓库是我们对在公共数据集上对流行的预训练语言模型进行指令微调的开放努力。我们发布这个仓库&#xff0c;并…

38 基于单片机的宠物喂食(ESP8266、红外、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用L298N驱动连接P2.3和P2.4口进行电机驱动&#xff0c; 然后串口连接P3.0和P3.1模拟ESP8266&#xff0c; 红外传感器连接ADC0832数模转换器连接单片机的P1.0~P1.…

Mac安装及合规无限使用Beyond Compare

文章目录 Beyond CompareBeyond Compare简介Beyond Compare安装Beyond Compare到期后继续免费使用 Beyond Compare Beyond Compare简介 Beyond Compare 是一款由 Scooter Software 开发的文件和文件夹比较工具。它主要用于对比两个文件或文件夹之间的差异&#xff0c;并支持文…