一、项目背景:为什么要搭建离线语音识别服务?
在开发企业 AI 应用时,语音识别是一个比较重要的基础能力。
例如,在企业会议场景中,用户上传一段会议录音,系统需要完成:
- 将音频转换为文字。
- 自动识别普通话、粤语等不同语言。
- 自动添加标点符号。
- 区分不同发言人。
- 识别发言人身份。
- 将转写结果交给大模型,生成会议纪要、讨论结论和待办事项。
最初接触语音识别时,我也尝试过一些在线 ASR 服务。但在企业项目中,尤其是政务、国企和内网部署场景,音频数据往往不适合直接上传到第三方平台。
因此,我决定搭建一套本地离线语音识别服务。
这次实践主要采用:
- SenseVoice:负责多语种语音识别。
- sherpa-onnx:负责模型推理和语音服务运行。
- FastAPI:封装 REST API。
- WebSocket:提供语音识别服务接口。
- Pyannote:扩展说话人分离能力。
- 3D-Speaker:扩展声纹识别能力。
最终希望形成一套可以独立部署、供其他 AI 应用调用的语音服务。
本文记录实际部署过程、环境配置、服务启动和后续集成思路。
二、整体技术架构
先看一下整体结构。
TaskWeave AI 应用平台
|
会议助手 / 语音应用
|
TaskWeave ASR API
|
+-------------+-------------+
| |
WebSocket REST API
:6006 :8088
| |
+-------------+-------------+
|
ASR 服务层
|
+------------+------------+
| | |
SenseVoice 标点模型 音频处理
| | |
+------------+------------+
|
识别结果输出
|
+------------+------------+
| | |
语音转写 自动标点 时间戳处理
|
+------+------+
| |
说话人分离 声纹识别
| |
+------+------+
|
会议助手业务
|
摘要 / 结论 / 待办
这里有一个需要注意的地方:
SenseVoice 主要负责语音识别,不应该把说话人分离、声纹识别等能力全部理解成同一个模型的功能。
实际项目中,我将这些能力拆分为不同的处理模块,方便独立部署、测试和后续替换。
三、服务器环境准备
本次使用的服务器环境如下:
| 项目 | 配置 |
|---|---|
| 操作系统 | Anolis OS 8.10 |
| 系统架构 | x86_64 |
| Python | 3.11 |
| Python 环境 | Conda |
| 环境名称 | taskweave-asr |
| 推理框架 | sherpa-onnx 1.13.8 |
| 推理方式 | CPU |
| WebSocket | 6006 |
| REST API | 8088 |
本次先以 CPU 推理为例,不依赖 NVIDIA GPU。
1. 检查系统环境
cat /etc/os-release
uname -m
python3 --version
检查系统架构:
uname -m
正常情况下应返回:
x86_64
2. 创建 Python 环境
如果服务器已经安装 Anaconda 或 Miniconda,可以创建独立环境:
conda create -n taskweave-asr python=3.11 -y
激活环境:
conda activate taskweave-asr
检查 Python 版本:
python --version
3. 安装 sherpa-onnx
安装指定版本:
pip install sherpa-onnx==1.13.8
安装 FastAPI 和 Uvicorn:
pip install fastapi uvicorn python-multipart
安装其他常用依赖:
pip install numpy soundfile librosa
检查安装结果:
python -c "import sherpa_onnx; print('sherpa-onnx installed')"
需要注意:不同系统架构、Python 版本和 CPU 指令集可能影响预编译包的兼容性。如果 pip 无法安装对应版本,需要先确认 sherpa-onnx 是否提供当前环境可用的发行包,再考虑源码编译。
四、部署 SenseVoice 多语种识别模型
这是整个服务最核心的部分。
我选择的是 SenseVoice 多语种模型:
SenseVoice zh-en-ja-ko-yue int8
模型支持的主要语言包括:
| 语言 | 说明 |
|---|---|
| zh | 中文普通话 |
| en | 英语 |
| ja | 日语 |
| ko | 韩语 |
| yue | 粤语 |
选择这个模型,主要是考虑到实际会议场景中,普通话、粤语和英语可能混合出现。
尤其是粤语识别,对于广东地区的企业应用来说,是一个比较有实际价值的能力。
1. 模型目录规划
我将模型文件统一放在独立目录中,方便后续管理:
TaskWeave-ASR/
│
├── models/
│ │
│ ├── sensevoice/
│ │ ├── model.int8.onnx
│ │ └── tokens.txt
│ │
│ ├── punctuation/
│ │ └── punct-ct-transformer/
│ │
│ ├── speaker/
│ │ └── pyannote-segmentation-3.0/
│ │
│ └── speaker-embedding/
│ └── 3dspeaker-eres2net/
│
├── app/
│ ├── main.py
│ ├── asr.py
│ ├── punctuation.py
│ └── speakers.py
│
├── logs/
│
├── start.sh
└── taskweave-asr.service
注意:以上是整理后的目录规划,具体模型文件名应以下载的模型包为准,不要直接将示例文件名当成所有模型包的固定名称。
2. 下载模型
建议从 sherpa-onnx 官方模型仓库选择对应的 SenseVoice 模型。
官方项目:
模型仓库:
下载模型后,将模型文件解压到:
models/sensevoice/
检查文件:
ls -lh models/sensevoice/
重点确认:
- ONNX 模型文件完整。
- tokens.txt 文件存在。
- 模型文件与当前 sherpa-onnx 版本兼容。
- 文件权限允许服务进程读取。
3. CPU 推理配置
本次使用 CPU 推理。
主要配置:
provider = cpu
num_threads = 4
language = auto
其中:
provider:指定 CPU 推理。num_threads:设置推理线程数量。language:使用自动语言识别模式。
线程数不一定越大越好,需要结合服务器 CPU 核数、并发量和音频时长进行测试。
五、启动 WebSocket 语音识别服务
模型准备完成后,可以使用 sherpa-onnx 提供的离线 WebSocket 服务程序。
它的作用是将语音识别能力封装为网络服务,供其他系统调用。
本次部署使用的服务程序:
sherpa-onnx-offline-websocket-server
先检查可执行程序是否存在:
which sherpa-onnx-offline-websocket-server
如果找不到,可以检查 Python 环境:
conda activate taskweave-asr
pip show sherpa-onnx
python -c "import sherpa_onnx; print(sherpa_onnx.__file__)"
查看服务程序支持的参数:
sherpa-onnx-offline-websocket-server --help
不同版本的命令行参数可能存在差异,因此应以当前安装版本的帮助信息为准。
启动时需要配置:
- SenseVoice ONNX 模型路径。
- tokens 文件路径。
- CPU 推理方式。
- 推理线程数。
- WebSocket 监听端口。
- 语言识别模式。
本次实际服务端口:
6006
服务启动后检查端口:
ss -lntp | grep 6006
如果出现监听记录,说明服务已经启动并开始监听。
注意:端口监听成功不代表识别功能已经正常,需要进一步通过实际音频进行识别测试。
六、增加自动标点能力
语音识别模型输出的文字,不一定符合正常阅读习惯。
例如:
原始识别结果:
大家下午好我们开始今天的项目周会
今天主要看一下语音模块目前的开发进度
重点验证语音识别自动标点说话人分离
这样的内容虽然能看懂,但是直接用于会议纪要并不理想。
因此,我额外部署了标点模型:
sherpa-onnx-punct-ct-transformer-zh-en-2024-04-12
主要用于中文和英文文本的自动标点处理。
处理流程:
音频输入
|
SenseVoice 语音识别
|
原始识别文本
|
标点模型
|
添加标点
|
最终文本
例如:
处理前:
大家下午好我们开始今天的项目周会
处理后:
大家下午好,我们开始今天的项目周会。
在实际项目中,我将标点处理独立封装为模块,避免与 ASR 主识别逻辑耦合。
这样做有几个好处:
- 可以独立替换标点模型。
- 可以单独测试标点效果。
- 可以根据业务需要决定是否启用。
- 方便后续对会议文本进行二次处理。
七、进一步扩展:说话人分离与声纹识别
如果只是普通语音转文字,通常不需要区分说话人。
但会议场景不一样。
例如,一场五人会议,需要知道:
- 谁在发言?
- 每个人说了什么?
- 发言顺序是什么?
- 哪些观点属于同一个人?
因此,我进一步增加了两个模块。
1. 说话人分离
使用:
Pyannote Segmentation 3.0(ONNX)
说话人分离主要解决:
音频
|
说话人分离
|
Speaker 0
Speaker 1
Speaker 2
Speaker 3
它可以帮助识别不同时间段内的说话人。
需要注意,说话人分离得到的是不同的说话人编号,不会自动知道这个人真实叫什么名字。
2. 声纹识别
使用:
3D-Speaker ERes2Net Base SV zh-cn 16k(ONNX)
声纹识别主要用于建立和比对说话人的声音特征。
实际应用中,可以设计这样的流程:
第一次会议
|
识别说话人
|
Speaker 0
|
管理员绑定员工档案
|
张三
|
保存声纹特征
|
后续会议
|
声纹比对
|
辅助识别张三
这样,会议记录就有机会从:
说话人1:今天我们讨论项目进度。
说话人2:目前已经完成第一阶段。
逐步变成:
张三:今天我们讨论项目进度。
李四:目前已经完成第一阶段。
这里需要强调:声纹匹配是概率性识别,不能仅凭一次匹配结果就认定发言人身份。还需要考虑录音质量、重叠发言、噪声和相似声音,并提供人工纠正入口。
声纹数据也属于需要严格保护的个人信息,实际部署应获得适当授权并设置访问权限和保存期限。
八、使用 FastAPI 封装 REST API
只有 WebSocket 服务还不够。
在实际企业应用中,不同系统可能使用不同的接入方式。
例如:
- Web 前端实时传递音频。
- 后端上传录音文件。
- 会议助手调用转写接口。
- 其他业务系统通过 HTTP 接口接入。
因此,我另外使用 FastAPI 封装 REST API。
本次服务端口:
8088
接口规划如下:
| 接口 | 功能 |
|---|---|
/api/v1/asr/transcriptions | 音频转写 |
/api/v1/asr/punctuation | 自动标点 |
/api/v1/asr/speakers | 说话人管理 |
/api/v1/asr/speakers/enroll | 声纹注册 |
/api/v1/asr/speakers/identify | 声纹识别 |
/api/v1/asr/speakers/list | 声纹列表 |
/api/v1/asr/speakers/delete | 声纹删除 |
这些是业务层接口路径规划,实际请求参数、响应字段和鉴权方式应以项目中的 FastAPI 实现为准。
REST 服务可以按照下面的方式启动:
uvicorn app.main:app \
--host 0.0.0.0 \
--port 8088 \
--workers 1
这里先使用单 Worker 部署。
原因是模型可能在应用启动时加载到进程内存中,如果直接增加 Worker,可能导致多个进程分别加载模型,增加内存占用。
如果后续需要提高并发,可以通过多进程、任务队列或独立推理服务进行扩展,并根据实际资源情况测试。
启动后可以通过浏览器访问:
http://服务器IP:8088/docs
查看 FastAPI 自动生成的接口文档。
生产环境应根据实际网络环境增加 HTTPS、身份认证、请求大小限制和访问控制,不建议直接将未经保护的接口暴露到公网。
九、配置 Linux systemd 开机自启动
服务部署完成后,不能每次都手动启动。
因此,需要将服务交给 systemd 管理。
我使用的服务配置如下:
文件:
/etc/systemd/system/taskweave-asr.service
配置:
[Unit]
Description=TaskWeave ASR Service
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/TaskWeave-ASR
ExecStart=/bin/bash /opt/TaskWeave-ASR/start.sh
Restart=on-failure
RestartSec=5
Environment=PATH=/opt/asr-venv/bin:/usr/local/bin:/usr/bin:/bin
[Install]
WantedBy=multi-user.target
注意:
这里的 /opt/TaskWeave-ASR 和 /opt/asr-venv 是示例路径,部署时必须替换成服务器真实路径。
如果使用 Conda 环境,建议在启动脚本中显式加载 Conda 环境,而不是直接假定 systemd 能自动继承交互式终端的环境变量。
例如:
#!/bin/bash
set -e
source /opt/miniconda3/etc/profile.d/conda.sh
conda activate taskweave-asr
cd /opt/TaskWeave-ASR
# 在这里启动实际的 WebSocket 和 FastAPI 服务
如果两个服务需要同时运行,建议分别建立 systemd 服务单元,避免其中一个服务退出后,另一个服务的状态无法独立管理。
重新加载配置:
systemctl daemon-reload
设置开机启动:
systemctl enable taskweave-asr
启动服务:
systemctl start taskweave-asr
查看运行状态:
systemctl status taskweave-asr
查看运行日志:
journalctl -u taskweave-asr -f
如果启动失败,优先检查:
- Python 环境路径是否正确。
- 模型文件是否存在。
- 启动脚本是否有执行权限。
- 端口是否被其他进程占用。
- 服务运行用户是否有模型目录读取权限。
十、如何验证识别效果?
服务能够启动只是第一步。
真正需要验证的是:识别结果是否满足业务要求。
我专门准备了一份五人会议测试音频。
测试数据:
| 项目 | 测试数据 |
|---|---|
| 会议人数 | 5 人 |
| 音频时长 | 约 385.8 秒 |
| 发言段数 | 25 段 |
| 音频内容 | 普通话、粤语等 |
| 测试文件 | meeting_5people.wav |
| 对照文件 | ground_truth.json |
这里使用 Ground Truth 作为人工校对后的参考文本,可以帮助比较识别结果。
1. 测试普通话
重点关注:
- 普通话识别准确性。
- 专业术语识别。
- 数字和日期识别。
- 长句断句效果。
2. 测试粤语
重点关注:
- 粤语口语表达。
- 粤语与普通话混合。
- 语速较快的情况。
- 地名、人名和专业词汇。
粤语识别不能只看是否输出了文字,还需要检查口语表达是否准确。
3. 测试说话人分离
重点关注:
- 是否正确区分五位发言人。
- 同一个人的多段发言是否能够关联。
- 发言时间戳是否合理。
- 多人连续发言时是否出现错误切分。
4. 建立量化评估
后续可以增加以下指标:
| 指标 | 用途 |
|---|---|
| CER | 中文字符错误率 |
| WER | 英文词错误率 |
| DER | 说话人分离错误率 |
| RTF | 实时率,评估识别耗时 |
| 延迟 | 从音频输入到结果输出的时间 |
其中,RTF 可以按以下方式计算:
RTF = 识别耗时 / 音频时长
例如,一段 100 秒的音频,用 20 秒完成识别:
RTF = 20 / 100 = 0.2
RTF 越低,说明相对于音频时长,处理速度越快。
需要注意,这些指标需要实际运行测试后才能得出,不能直接用模型宣传数据代替自己的测试结果。
十一、接入 TaskWeave 会议助手
完成 ASR 服务后,我没有把它当成一个独立的语音转文字工具,而是进一步集成到 TaskWeave 会议助手中。
整体业务流程:
用户上传会议录音
|
v
TaskWeave 会议助手
|
v
调用本地 ASR 服务
|
v
SenseVoice 语音识别
|
v
自动标点
|
v
说话人分离
|
v
声纹匹配
|
v
生成结构化会议记录
|
v
调用大模型
|
+----+----+--------+
| | |
会议摘要 讨论结论 待办事项
| | |
+----+----+--------+
|
生成会议纪要
这里有一个重要的架构原则:
语音识别负责把音频转换成结构化文本,大模型负责理解内容并生成会议成果。
不应该让大模型承担本来应该由 ASR 处理的音频识别工作。
这样可以让两部分能力独立升级,也方便后续替换语音模型或大语言模型。
十二、离线部署需要注意什么?
在企业内网部署时,还需要考虑几个实际问题。
1. 模型文件与依赖管理
部署前应提前准备:
- Python 运行环境。
- sherpa-onnx 安装包。
- ONNX 模型文件。
- 标点模型。
- 说话人相关模型。
- 项目依赖。
如果目标服务器不能访问互联网,需要在外部环境完成下载,再将安装包和模型文件通过受控方式导入内网。
2. CPU 与内存资源
不同模型的资源消耗不同。
不能只看模型文件大小,还需要考虑:
- 模型加载后的内存占用。
- 音频解码占用。
- 并发识别数量。
- 说话人分离的额外计算。
- 多个服务同时运行的资源消耗。
建议先使用单任务测试,再逐步提高并发。
3. 网络安全
内网部署也不意味着可以忽略安全问题。
建议:
- 只开放必要的服务端口。
- API 增加身份认证。
- 对上传文件进行格式和大小校验。
- 限制音频文件存储权限。
- 设置临时文件清理机制。
- 对声纹数据进行加密和权限管理。
- 保留必要的访问日志。
4. 识别结果不能直接作为最终会议纪要
语音识别可能出现错字、漏字和说话人判断错误。
因此,正式会议纪要应保留人工纠错和确认环节。
十三、总结
这次实践让我更加明确了一点:
企业 AI 应用的开发,并不是简单地接入一个大模型 API。
很多时候,需要先把基础能力建设好,再通过业务流程将它们组合起来。
这次离线 ASR 部署,完成了多语种语音识别服务的基础建设,并进一步探索了自动标点、说话人分离、声纹识别以及会议助手集成。
后续还可以继续优化:
- 提高粤语和混合语言识别效果。
- 优化长音频分段和处理速度。
- 完善实时语音识别。
- 提高说话人分离准确率。
- 将声纹与企业员工档案关联。
- 优化会议纪要的自动生成和人工纠错流程。
这套能力也是我持续研发 TaskWeave AI Agent 平台的一部分。
后续我会继续分享 .NET、Vue、RAG、MCP、AI Agent 以及企业 AI 应用私有化部署方面的真实开发实践。
项目归属:TaskWeave 与 AgentWeave
本文介绍的离线语音识别服务,是我在持续研发 TaskWeave AI Agent 应用平台 过程中建设的一项基础技术能力。
TaskWeave 是由 AgentWeave 发起并持续研发的 AI Agent 应用平台,主要探索企业智能体、知识库 RAG、会议助手、材料写作、Skill、MCP 和 Workflow 等应用场景。
其中,TaskWeave ASR 是平台的语音能力模块,负责提供离线语音识别、多语种转写和相关语音处理服务,为会议助手等应用提供底层技术支持。
- 品牌:AgentWeave
- 产品:TaskWeave AI Agent 应用平台
- 技术模块:TaskWeave ASR
- 官方网站:www.agentweave.site/
本文记录的是其中一项真实技术实践。
关于作者与项目
我是张国军(Armys),一名长期从事 .NET、Java、Vue 企业应用开发,并持续探索 AI 应用落地的独立开发者。
AgentWeave 是我的个人 AI 应用开发品牌,专注于佛山及周边企业 AI 应用落地。
目前正在持续研发 TaskWeave AI Agent 应用平台,探索企业智能体、知识库 RAG、AI 会议助手、材料写作及企业业务系统 AI 改造。
相关项目:
- AgentWeave 官方网站:www.agentweave.site/
- TaskWeave 项目介绍:www.agentweave.site/ai-agent/
- 企业知识库:www.agentweave.site/rag/
- 私有化部署:www.agentweave.site/private-dep…
GitHub:github.com/ArmysZHANG/…
Gitee:gitee.com/Armys/agent…
后续将持续分享 TaskWeave 的实际开发过程,以及 .NET、Vue、AI Agent 和企业 AI 私有化部署实践。