南北阁 Nanbeige 4.1-3B 实战教程:添加WebRTC音视频通话接口实现多模态交互
南北阁 Nanbeige 4.1-3B 实战教程:添加WebRTC音视频通话接口实现多模态交互
今天我们来玩点不一样的。你已经体验过南北阁 Nanbeige 4.1-3B 这个轻量级模型的文本对话能力了,它流式输出很丝滑,思考过程也能看得一清二楚。但你想过没有,如果能让这个模型不仅能“听”文字,还能“看”画面、“听”声音,甚至和你实时视频通话,那会是什么体验?
这就是我们今天要做的:给这个纯文本对话工具,加上一双“眼睛”和一对“耳朵”。我们将通过 WebRTC 技术,为它集成音视频通话接口,让它从一个单纯的聊天机器人,升级成一个能看、能听、能说的多模态交互助手。想象一下,你可以打开摄像头,让它实时分析你手头的物品、帮你读一段文档、甚至根据你的手势给出反馈。
听起来很酷?别担心,整个过程并不复杂。我们不需要重写整个项目,而是在现有流畅的 Streamlit 界面上,巧妙地“嫁接”上音视频能力。我会手把手带你,从环境准备、代码集成,到最终实现一个能跑起来的原型。你会发现,让 AI 模型“睁开眼”,其实比你想象的要简单。
1. 项目回顾与升级思路
在开始动手之前,我们先快速回顾一下手头的“地基”——南北阁 Nanbeige 4.1-3B 对话工具,并理清我们要在上面盖的“新楼”是什么样子。
1.1 现有工具的核心能力
你手上的这个工具,核心优势在于“轻快准”:
- 轻:30亿参数的模型,对硬件极其友好,普通显卡甚至纯CPU都能跑起来,部署门槛低。
- 快:采用了
TextIteratorStreamer,实现了逐字输出的“打字机”效果,交互感很强,没有卡顿感。 - 准:严格遵循了官方推荐的加载和推理参数(比如那个关键的
eos_token_id=166101),保证了模型输出的效果和稳定性。 - 直观:最大的亮点是把模型的“思考过程”(CoT)用折叠面板展示了出来。你可以选择看它一步步的推理,也可以直接看最终答案,设计得很人性化。
它的架构很清晰:一个用 Streamlit 构建的现代化网页界面,后端是加载好的 Nanbeige 模型。用户输入文字,模型思考并流式输出文字。这是一个高效、专注的文本对话管道。
1.2 引入WebRTC的目标
我们现在要做的,就是给这条文本管道,并联上音视频管道。WebRTC 正是实现这个目标的绝佳技术。
WebRTC 是什么? 你可以把它理解成浏览器内置的“实时通信引擎”。它允许网页应用在用户之间(Peer-to-Peer)直接传输音频、视频流和数据,延迟极低,无需安装插件。我们正是要利用它的两个核心能力:
- 获取媒体流:调用用户的摄像头和麦克风。
- 处理与传输:将获取到的音视频流,实时地交给我们的 Python 后端进行处理。
我们的升级目标:
- 增加音视频输入:在现有聊天界面旁,增加一个视频预览窗口和麦克风开关。
- 实现多模态输入:用户可以选择用文字提问,也可以直接“说话”或者“展示图片/视频帧”给模型。
- 扩展模型交互:后端需要能接收、处理这些音视频数据(例如,将音频转为文字,将视频帧转为图像描述),再交给 Nanbeige 模型进行理解和回复。
- 保持原有体验:不破坏已有的流式文本输出、CoT折叠展示等优秀特性。
简单说,就是从 “文本输入 -> 文本输出” 的单车道,升级为 “(文本/音频/视频)输入 -> 智能处理 -> 文本输出” 的多车道立交桥。
2. 环境准备与依赖安装
工欲善其事,必先利其器。我们需要在原有环境的基础上,新增几个处理音视频和 Web 实时通信的库。
2.1 检查与安装Python库
假设你的项目已经能正常运行原有的 Nanbeige 对话工具。现在,打开你的终端,激活项目所用的 Python 环境,安装以下新依赖:
# 核心WebRTC支持库,用于在Streamlit中集成音视频组件
pip install streamlit-webrtc
# 音频处理库,用于录制、播放和转换音频格式
pip install pyaudio
# 这是pyaudio在Windows上可能需要的底层依赖,如果在其他系统安装pyaudio失败,可以尝试先安装这个
# pip install pipwin
# pipwin install pyaudio
# 强大的音视频处理库,我们将用它来解码、处理视频帧
pip install opencv-python-headless # 使用headless版本,无需GUI支持,更适合服务器环境
# 语音识别库,用于将用户的语音输入转换成文字(这是实现语音交互的关键)
pip install SpeechRecognition
# 语音转文字引擎的后端,我们选择离线的Vosk,它轻量且无需网络API
pip install vosk
# 可选但推荐:用于播放提示音或合成语音回复(本期先聚焦输入,输出可后续扩展)
# pip install playsound
# pip install gTTS
安装注意事项:
pyaudio在某些系统上可能安装稍麻烦,如果遇到问题,请搜索“pip install pyaudio[你的操作系统]”寻找解决方案,通常需要一些系统级的音频开发包。opencv-python-headless比完整的opencv-python更轻量,因为我们不需要在服务器端弹出任何视频窗口。vosk需要下载对应的语音识别模型文件(稍后会讲到)。
2.2 下载语音识别模型
我们使用 Vosk 进行离线语音识别。它需要一个小型的模型文件。这里我们下载一个适用于中文、体积较小的模型。
- 访问 Vosk 的模型仓库:
https://alphacephei.com/vosk/models - 找到一个适合的模型,例如
vosk-model-small-cn-0.22(约40MB),这个模型对于中文语音识别有不错的效果。 - 下载并解压。在你的项目目录下,创建一个名为
models的文件夹,将解压后的模型文件放进去。你的项目目录/ ├── app.py # 你的Streamlit主程序 ├── models/ │ └── vosk-model-small-cn-0.22/ # 解压后的模型文件 └── ...
环境准备好后,我们就可以开始动手改造代码了。
3. 代码集成与实现步骤
我们将对原有的 Streamlit 应用进行模块化改造。核心思路是:在侧边栏增加音视频控制面板,在主界面复用原有的聊天区域,并创建新的后台处理逻辑。
3.1 改造应用界面与状态管理
首先,我们修改 app.py 的头部,导入新的库并初始化一些全局状态。为了清晰,我们将较大的功能块(如视频处理、语音识别)拆分成函数。
# app.py
import streamlit as st
from streamlit_webrtc import webrtc_streamer, WebRtcMode, ClientSettings
import av
import cv2
import numpy as np
import queue
import threading
import json
import time
from pathlib import Path
# 原有的模型加载和推理相关导入
# from transformers import ...
# import torch
# 语音识别相关
import speech_recognition as sr
from vosk import Model, KaldiRecognizer
import pyaudio
# --- 状态初始化 ---
# 初始化session state,用于在页面重载间保持数据
if 'messages' not in st.session_state:
st.session_state.messages = [] # 保存对话历史
if 'audio_queue' not in st.session_state:
st.session_state.audio_queue = queue.Queue() # 用于传递音频数据
if 'last_video_frame' not in st.session_state:
st.session_state.last_video_frame = None # 保存最后一帧视频图像
if 'recognized_text' not in st.session_state:
st.session_state.recognized_text = "" # 保存语音识别结果
if 'webrtc_ctx' not in st.session_state:
st.session_state.webrtc_ctx = None # 保存WebRTC上下文,便于控制
# --- 语音识别初始化(Vosk)---
@st.cache_resource
def load_vosk_model():
model_path = Path("models/vosk-model-small-cn-0.22")
if not model_path.exists():
st.error(f"未找到Vosk模型,请下载并放置在 {model_path} 目录下")
return None
return Model(str(model_path))
vosk_model = load_vosk_model()
recognizer = None
if vosk_model:
recognizer = KaldiRecognizer(vosk_model, 16000) # 采样率16kHz
3.2 创建音视频处理回调函数
这是 WebRTC 的核心。我们需要定义当收到音频帧和视频帧时,应该做什么。
# --- 音视频处理回调函数 ---
class VideoProcessor:
def __init__(self):
self.frames_received = 0
def recv(self, frame):
"""处理接收到的视频帧"""
img = frame.to_ndarray(format="bgr24") # 将帧转换为OpenCV格式
self.frames_received += 1
# 每隔一定帧数处理一次,避免过于频繁(例如每秒处理5次)
if self.frames_received % 6 == 0: # 假设30fps,每6帧处理一次即5fps处理频率
# 将当前帧存入session state,供其他部分(如截图按钮)使用
st.session_state.last_video_frame = img.copy()
# 这里可以添加图像分析逻辑,例如:
# 1. 使用图像描述模型(如BLIP)生成描述
# 2. 进行物体检测
# 3. 简单显示在界面上
# 示例:在侧边栏显示一个缩略图(需要转换为RGB)
# st.sidebar.image(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), caption=f"最新画面", use_column_width=True)
# 必须返回一个视频帧
return av.VideoFrame.from_ndarray(img, format="bgr24")
class AudioProcessor:
def __init__(self):
self.audio_buffer = []
self.sample_rate = 16000
self.channels = 1
def recv(self, frame):
"""处理接收到的音频帧"""
# 将音频帧转换为numpy数组
audio_data = frame.to_ndarray()
# 如果是多声道,取平均值转为单声道
if len(audio_data.shape) > 1:
audio_data = audio_data.mean(axis=1)
# 存入队列,供独立的语音识别线程消费
st.session_state.audio_queue.put(audio_data.tobytes())
# 必须返回一个音频帧(通常原样返回)
return frame
# --- 独立的语音识别线程函数 ---
def speech_recognition_thread():
"""从音频队列中取出数据,进行持续语音识别"""
p = pyaudio.PyAudio()
# 注意:这里的recognizer是之前初始化的Vosk识别器
global recognizer
if not recognizer:
return
while True:
try:
# 从队列获取音频数据块
audio_chunk = st.session_state.audio_queue.get(timeout=1.0)
if recognizer.AcceptWaveform(audio_chunk):
# 识别出一句完整的话
result = json.loads(recognizer.Result())
text = result.get("text", "")
if text:
st.session_state.recognized_text = text
# 这里可以触发一个事件,例如自动将识别文本填入输入框
# st.session_state.user_input = text
else:
# 部分识别结果
partial = json.loads(recognizer.PartialResult())
partial_text = partial.get("partial", "")
# 可以实时更新一个“正在听...”的显示区域
# st.sidebar.caption(f"正在识别: {partial_text}")
except queue.Empty:
continue
except Exception as e:
print(f"语音识别线程错误: {e}")
break
# 启动语音识别线程(确保只启动一次)
if 'recognition_thread_started' not in st.session_state:
st.session_state.recognition_thread_started = True
threading.Thread(target=speech_recognition_thread, daemon=True).start()
3.3 构建增强版Streamlit界面
现在,我们来重新布局页面,将音视频控制、原有聊天界面和新的状态显示结合起来。
# --- 页面布局 ---
st.set_page_config(page_title="Nanbeige 4.1-3B 多模态助手", layout="wide")
st.title("🎤👁️ Nanbeige 4.1-3B 多模态交互助手")
# 侧边栏 - 控制面板
with st.sidebar:
st.header("⚙️ 控制面板")
# 原有的模型参数设置可以保留或精简
# ...
st.markdown("---")
st.header("🎥 音视频输入")
# WebRTC 音视频流组件
webrtc_ctx = webrtc_streamer(
key="nanbeige-webrtc",
mode=WebRtcMode.SENDRECV, # 既发送也接收,我们主要用发送(获取用户媒体)
client_settings=ClientSettings(
rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]},
media_stream_constraints={
"video": True,
"audio": True,
},
),
video_processor_factory=VideoProcessor,
audio_processor_factory=AudioProcessor,
async_processing=True,
)
st.session_state.webrtc_ctx = webrtc_ctx
if webrtc_ctx.state.playing:
st.success("摄像头和麦克风已激活")
# 显示视频截图按钮
if st.button("📸 截图并分析当前画面"):
if st.session_state.last_video_frame is not None:
# 保存或处理截图
img_rgb = cv2.cvtColor(st.session_state.last_video_frame, cv2.COLOR_BGR2RGB)
# 这里可以调用一个图像描述函数,将img_rgb传给模型
# description = describe_image(img_rgb)
# st.write(f"画面描述: {description}")
# 暂时先显示图片
st.image(img_rgb, caption="截取的画面", use_column_width=True)
st.info("图像分析功能需接入视觉模型(如BLIP)后生效。")
else:
st.warning("尚未接收到视频帧,请稍候。")
else:
st.info("点击上方「START」按钮开启摄像头和麦克风")
st.markdown("---")
st.header("🗣️ 语音识别状态")
if st.session_state.recognized_text:
st.write(f"**最新识别结果:** {st.session_state.recognized_text}")
if st.button("💬 将识别文本发送给模型"):
# 将识别到的文本填入主输入框并触发发送(这里需要一些状态联动)
# 一种简单实现:将文本存入一个特殊状态,在主界面检查并处理
st.session_state.pending_audio_text = st.session_state.recognized_text
st.session_state.recognized_text = "" # 清空当前识别结果
st.rerun() # 触发重载以处理新文本
else:
st.caption("语音识别待命中...")
# 主区域 - 聊天界面 (基本保持原样,但增加对语音输入的处理)
st.header("💬 与 Nanbeige 对话")
# 显示历史消息
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 检查是否有来自语音识别的待处理文本
user_input = ""
if 'pending_audio_text' in st.session_state and st.session_state.pending_audio_text:
user_input = st.session_state.pending_audio_text
del st.session_state.pending_audio_text
# 文本输入框
if prompt := st.chat_input("输入您的问题,或使用语音...", value=user_input):
# 将用户输入添加到历史并显示
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# 调用原有的模型生成函数(假设这个函数叫 `generate_response`)
# 它应该接收prompt,并返回一个生成器用于流式输出
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# 这里调用你的模型响应生成函数
# 例如:response_generator = generate_response(prompt, st.session_state.messages)
# 为了示例,我们模拟一个流式响应
def mock_generator(text):
for chunk in text.split():
yield chunk + " "
time.sleep(0.05)
# 模拟调用
for chunk in mock_generator(f"这是对「{prompt}」的模拟回复。实际应接入Nanbeige模型。"):
full_response += chunk
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
# 将助手回复加入历史
st.session_state.messages.append({"role": "assistant", "content": full_response})
# 清空历史按钮(保留原有功能)
if st.sidebar.button("🗑️ 清空对话历史"):
st.session_state.messages = []
st.rerun()
4. 功能演示与效果展示
代码整合完毕后,让我们来看看这个升级版工具能做什么。
4.1 启动与界面概览
在终端运行 streamlit run app.py。浏览器打开后,你会看到一个分为两栏的界面:
- 左侧侧边栏:这是我们的“控制中心”。上半部分保留了原有的模型设置(如果有),下半部分是全新的“音视频输入”和“语音识别状态”面板。
- 右侧主区域:和以前一样的聊天窗口,显示对话历史。
4.2 核心功能操作流程
1. 激活音视频输入: 点击侧边栏上方的 “START” 按钮。浏览器会请求摄像头和麦克风权限,请点击“允许”。成功后,你会看到“摄像头和麦克风已激活”的提示。此时,你的视频画面应该已经能传输到后端(虽然界面上没有直接显示预览,但数据已在处理)。
2. 进行语音对话:
- 保持麦克风开启,直接对着麦克风说话,比如:“你好,介绍一下你自己。”
- 观察侧边栏的“语音识别状态”区域。稍等片刻,你说话的文本内容就会显示在“最新识别结果”后面。
- 点击下方的 “将识别文本发送给模型” 按钮。神奇的事情发生了:你刚刚说的话,自动填充到了主聊天窗口的输入框,并像你手动输入一样发送给了 Nanbeige 模型。
- 模型开始流式思考(显示“思考中...”),并最终给出文字回复。整个过程,你只用动嘴,不用动手打字。
3. 使用视觉输入:
- 确保摄像头已开启。将你想让模型“看”的东西放在摄像头前,比如一本书的封面。
- 点击侧边栏的 “截图并分析当前画面” 按钮。当前摄像头画面会被捕获并显示在侧边栏下方。
- (进阶) 目前我们只是展示了截图功能。要真正让模型“理解”图片,你需要在此处集成一个视觉语言模型(如 BLIP、Qwen-VL)。集成后,点击按钮就能自动生成对画面的描述,并将描述文本作为问题发送给 Nanbeige。例如,自动生成问题:“图片里是一本蓝色的书,书名是《人工智能导论》,请根据封面设计风格谈谈它的受众。”
4.3 多模态交互的想象空间
通过这个基础框架,你已经搭建了一个多模态交互的管道。你可以在此基础上继续扩展:
- 实时视频分析:不点击截图,而是让模型持续分析视频流,描述场景变化。
- 音频直接驱动:去掉“点击发送”的步骤,实现语音指令的自动触发(例如,检测到“小阁小阁”唤醒词后,自动将后续语音转为文字并查询)。
- 音视频输出:不仅输入是多模态,输出也可以。将模型的文字回复,通过 TTS 合成语音播放出来,实现真正的语音对话。
- 结合具体场景:比如,做一个“实时翻译助手”,你说中文,它显示英文文本;或者做一个“产品讲解员”,你展示商品,它自动介绍特性。
5. 总结与展望
回顾一下我们完成的工作:我们成功地将 WebRTC 音视频流接入到了原本纯文本的 Nanbeige 对话工具中,构建了一个支持语音和视觉输入的多模态交互原型。
5.1 本教程核心要点
- 轻量级集成:我们没有替换 Streamlit,而是在其生态内,利用
streamlit-webrtc组件无缝添加了音视频能力。原有优秀的文本对话体验得以完整保留。 - 模块化设计:将视频处理 (
VideoProcessor)、音频处理 (AudioProcessor) 和语音识别(独立线程)分离,代码结构清晰,便于后续维护和扩展。 - 离线优先:语音识别选用了离线的 Vosk 模型,保证了所有交互的隐私性和低延迟,符合原工具“纯本地运行”的哲学。
- 渐进式增强:我们首先实现了“语音转文字”和“视频截图”这两个最实用、最易实现的功能,为后续集成更复杂的视觉模型打下了坚实基础。
5.2 可能遇到的问题与解决思路
- WebRTC 连接失败:这通常是由于网络环境限制(如某些企业防火墙)或 STUN 服务器不可用。可以尝试更换
iceServers中的 STUN 服务器地址,或者在内网环境下使用。 - 语音识别不准:Vosk 小模型对发音、环境噪音有一定要求。可以尝试:
- 使用更大的 Vosk 模型(如
vosk-model-cn-0.22,约1.4G)。 - 在调用识别前,增加简单的音频降噪预处理(
pydub库)。 - 如果对实时性要求不高,可以换用识别率更高的云端 API(如百度、阿里云的短语音识别),但会引入网络延迟。
- 使用更大的 Vosk 模型(如
- 性能开销:同时运行大语言模型、语音识别和视频流处理,对 CPU 有一定压力。确保你的设备有足够的内存。可以考虑将视频帧处理频率降低(调整
VideoProcessor中的帧采样逻辑)。
5.3 下一步的探索方向
你现在拥有的是一个功能强大的“脚手架”。接下来,可以沿着这些方向深化:
- 接入视觉模型:这是最直接的升级。将 BLIP2、Qwen-VL 等开源视觉语言模型集成进来,让工具真正能“看懂”图片和视频,实现图文对话。
- 优化用户体验:
- 在界面上实时显示摄像头预览。
- 增加语音输入的 VAD(语音活动检测),自动开始/结束录音。
- 为模型的文字回复加上语音合成(TTS),实现全双工语音对话。
- 探索具体应用:基于这个多模态框架,可以开发出许多有趣的应用,如智能直播助手、远程协作指导、互动教育工具等。
让 AI 模型从“文本大脑”进化成“多感官智能体”,WebRTC 是你手中那把关键的钥匙。希望这篇教程能帮你打开这扇门,开始构建更自然、更强大的人机交互应用。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)