Hermes Agent 本地语音识别实战:从 whisper.cpp 到 SenseVoice
Hermes Agent 本地语音识别实战:从 whisper.cpp 到 SenseVoice
Hermes Agent 的语言模型和语音识别是两条独立链路。MiMo 可以继续负责理解、推理和回复,录音则先送到局域网内的 ASR 服务,转成文字后再交给 MiMo。
这套方案最初使用 whisper.cpp + CT-Punc,随后经过 Paraformer 和 SenseVoice A/B 测试,最终把 SenseVoice 作为 Hermes 当前主后端,Paraformer 保留为带显式术语纠正的回退方案。
最终架构
内网地址和容器编号已泛化,示例中的 ASR 主机为 192.168.100.50:
Hermes Agent
stt.provider = openai
base_url = http://192.168.100.50:8080/v1/sensevoice
│
▼
Nginx :8080
├─ /v1/sensevoice/ → SenseVoice :8085(当前主链路)
└─ /v1/paraformer/ → Paraformer :8084(回退与术语对照)两个服务只监听容器内的 127.0.0.1,由 Nginx 提供局域网入口。它们都实现 OpenAI 风格的:
POST /v1/audio/transcriptions
Content-Type: multipart/form-data
Response: {"text":"识别结果"}因此 Hermes 仍使用内置 openai STT provider,不需要为了本地模型修改 Agent 的转写调用代码。
为什么没有停在 whisper.cpp
第一版链路是:
Hermes → Nginx → whisper.cpp(Vulkan)→ CT-Punc 标点代理whisper.cpp 的优点是部署直接、可量化、可利用 AMD 核显 Vulkan;但中文同音词、中文夹 IT 英文术语和标点仍需要额外处理。为了获得更自然的中文输出,后续部署了两个 FunASR 后端:
| 后端 | 特点 | 定位 |
|---|---|---|
| SenseVoiceSmall | 中文速度快,自带标点和 ITN | Hermes 当前主后端 |
| Paraformer-large | VAD、CT-Punc 和显式错词纠正 | 回退、术语测试与对照 |
切换后,旧 whisper-server 和独立 whisper-punctuation 服务已停用,但模型和配置备份仍暂时保留,便于回滚和后续微调对比。
systemd 与 Nginx
后端分别由 systemd 管理:
sensevoice-server.service → 127.0.0.1:8085
paraformer-server.service → 127.0.0.1:8084Nginx 路由示例:
server {
listen 8080;
client_max_body_size 64m;
proxy_read_timeout 180s;
location /v1/sensevoice/ {
proxy_pass http://127.0.0.1:8085/v1/;
}
location /v1/paraformer/ {
proxy_pass http://127.0.0.1:8084/v1/;
}
}这里要注意 location 和 proxy_pass 末尾的 /。Hermes 在 Base URL 后追加 audio/transcriptions,最终应得到:
/v1/sensevoice/audio/transcriptions不要把 Base URL 直接写成完整的 /audio/transcriptions,否则客户端可能再次追加路径。
先单独验证后端
在修改 Hermes 前,用一段真实录音确认两条路由:
curl http://192.168.100.50:8080/v1/sensevoice/audio/transcriptions \
-H 'Authorization: Bearer local-token' \
-F 'file=@/path/to/test.m4a' \
-F 'model=whisper-1' \
-F 'language=zh'
curl http://192.168.100.50:8080/v1/paraformer/audio/transcriptions \
-H 'Authorization: Bearer local-token' \
-F 'file=@/path/to/test.m4a' \
-F 'model=whisper-1' \
-F 'language=zh'正常响应:
{"text":"这是识别出来的中文。"}这里的 whisper-1 只是为了兼容 OpenAI provider 的模型字段。实际运行哪一个 ASR 模型,由 Nginx 路由后的后端决定。
Hermes 配置
修改前备份,不要覆盖整个配置文件:
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak.$(date +%Y%m%d-%H%M%S)
cp ~/.hermes/.env ~/.hermes/.env.bak.$(date +%Y%m%d-%H%M%S)在 ~/.hermes/config.yaml 中合并 stt:
stt:
enabled: true
provider: openai
language: zh
openai:
model: whisper-1
base_url: http://192.168.100.50:8080/v1/sensevoice部分 Hermes 版本还会先检查 OpenAI 风格的语音 Key。可以在 ~/.hermes/.env 中提供占位值:
VOICE_TOOLS_OPENAI_KEY=local-token本地服务当前不验证这个值,它只用于满足 provider 初始化条件。配置完成后先检查 YAML,再重启 Hermes Gateway:
python3 -c 'import pathlib,yaml; yaml.safe_load(pathlib.Path.home().joinpath(".hermes/config.yaml").read_text())'
hermes gateway restart如果不是由 Gateway 管理,则按实际启动方式完全退出并重新启动 Hermes。
最容易忽略的配置优先级
这次排查中,config.yaml 和 .env 一度同时存在不同的 Base URL:
config.yaml → /v1/sensevoice
.env → /v1当前版本优先使用 config.yaml,所以实际请求正常进入 SenseVoice。但如果日后删除 YAML 中的 base_url,Hermes 可能回退到 .env 的裸 /v1。
而旧 Nginx 配置中的裸路径可能仍转发到已经停用的 whisper.cpp 或 CT-Punc,最终表现为 502 Bad Gateway。因此应确保两个位置一致,或只保留一个明确的配置来源。
确认 Hermes 实际访问路径时,不要只看配置文件;直接查看 Nginx access log:
sudo tail -f /var/log/nginx/access.log发送一次语音后,应看到 /v1/sensevoice/audio/transcriptions。
Paraformer 显式术语纠正
A/B 测试发现,替换通用模型并不能自动解决 Kubernetes、LoRA、DevOps 等中文夹英文术语。两个模型都会生成相似的中文音译或近音词。
Paraformer 后端增加了一个显式纠正文件:
/etc/paraformer-hotwords.txt每行只记录一个实际错误到正确写法的映射:
cube ned tis=>Kubernetes
laura=>LoRA
develops=>DevOps服务每次请求重新读取文件,修改后无需重启。实践中必须遵守以下约束:
- 只使用明确的
错词=>对词,不要批量加入数百个裸热词做模糊匹配; - 关闭 fuzzy 匹配,否则普通中文近音词会被大面积误替换;
- 按错误串长度从长到短替换,避免短词先吃掉长词的一部分;
- 同一录音可能产生多个变体,应记录真实出现过的完整错误串;
- 如果模型直接吞掉某个词,后处理无法补回,只能通过训练或更好的声学模型解决。
热词表把测试集中的 IT 术语准确率从约 32% 提升到 65%,但它本质上仍是解码后的文本补丁,不等于模型真正学会了术语。
模型选择结论
本次最终选择 SenseVoice 作为 Hermes 主后端,原因是中文标点、资源占用和响应速度的综合表现更好。Paraformer 保留用于显式纠正、回归测试和快速切换。
切换只需要修改一个 Base URL:
# SenseVoice
base_url: http://192.168.100.50:8080/v1/sensevoice
# Paraformer
base_url: http://192.168.100.50:8080/v1/paraformer修改后重启 Gateway,并用同一段录音做 A/B。不要只比较“看起来是否正确”,还要记录响应时间、标点、数字 ITN、术语准确率和重复请求稳定性。
常见故障
STT provider: MISSING
检查 VOICE_TOOLS_OPENAI_KEY 是否存在、Hermes 实际启动用户能否读取 .env,然后完全重启进程。不要因此切回云端 STT。
404
检查 Base URL 前缀和 Nginx 路由,尤其不要把 /audio/transcriptions 写入 Base URL 两次。
502
请求命中了已停用的端口。检查 Nginx access/error log,并确认使用 /v1/sensevoice 或 /v1/paraformer,而不是裸 /v1。
局域网无法连接
ping 192.168.100.50
curl http://192.168.100.50:8080/v1/sensevoice/health如果 Hermes 位于 Docker、不同 VLAN 或远程服务器,还需要检查路由、防火墙和容器网络,而不仅是 ASR 服务状态。
服务代码已更新但结果没变化
覆盖 Python 文件不会让旧进程自动加载新代码。部署后必须:
sudo systemctl restart sensevoice-server.service
sudo journalctl -u sensevoice-server.service -n 50 --no-pager回滚与清理
快速回滚到 Paraformer,只需恢复 Base URL 并重启 Hermes。完全取消本地 STT 时,恢复 config.yaml 和 .env 的备份,或删除 stt 配置与占位 Key。
旧 whisper.cpp/CT-Punc 链路只建议用于历史对比。如果确实需要恢复,应同时启用两个 systemd 服务,并恢复配套 Nginx 配置,不能只启动其中一个。
迁移稳定后还应清理指向已停用端口的 Nginx 路由,避免以后使用裸 /v1 时得到误导性的 502。清理前先备份配置并运行:
sudo nginx -t
sudo systemctl reload nginx安全边界
示例中的 local-token 不是有效鉴权。ASR 服务只能监听可信局域网,不应把 8080 端口映射到公网。更严格的环境应增加真实认证、TLS、防火墙来源限制和音频保留策略。
总结
本地 STT 接入真正困难的部分不是“启动一个模型”,而是稳定的完整链路:
Hermes 配置
→ OpenAI 兼容请求
→ Nginx 路由
→ ASR + VAD/标点
→ 专有词纠正
→ 转写文本交给 MiMo最终方案让 Hermes 保留原有 MiMo LLM,只替换语音前处理。SenseVoice 负责日常中文转写,Paraformer承担术语纠正和回退;通过明确配置优先级、保留 A/B 路径和结构化回滚,可以继续升级模型而不改动 Hermes 的上层对话能力。
