Qwen ASR 1.7B 实战:把本地语音识别接入熙瑾会悟,搭建私有化 AI 会议助手
1. 引言:会议转写不难,难的是把它变成可交付系统
很多企业第一次接触 AI 会议助手,往往是从“录音转文字”开始的:上传一段会议录音,等待模型识别,得到一份文字稿。这个过程看起来并不复杂,但真正进入生产环境后,问题会迅速变多。
一场真实会议可能持续 30 分钟、1 小时甚至更久;会场里可能有多人轮流发言,也可能有领导插话、远距离拾音、背景噪声、专业术语和中英文混杂。更重要的是,企业真正需要的通常不是一段原始转写文本,而是一份可以归档、检索、复核、生成纪要、提取待办的会议资料。
这也是熙瑾会悟这类 AI 会议助手和单独 ASR Demo 的区别。
会悟确实使用了 Qwen ASR 1.7B 作为语音识别底层能力之一,但它的产品价值不止是“调用模型转文字”。更准确地说,会悟是在 Qwen ASR 1.7B 的基础上,把会议录音采集、语音识别、文本结构化、会议纪要生成、待办事项提取、知识归档和私有化部署整合成一套完整的会议智能化系统。
本文就以一个实战部署流程为例,演示如何从 Qwen ASR 1.7B 的本地推理开始,逐步封装接口、处理长会议录音,并将转写结果接入熙瑾会悟的会议流程。
2. 环境准备:先把 Qwen ASR 1.7B 跑起来
为了让文章更贴近真实部署,我们不从云端 API 开始,而是从本地模型部署开始。企业私有化会议系统的核心前提是:音频、文本、纪要尽量在内网闭环处理,避免会议数据外传。
推荐准备如下环境:
| 项目 | 建议配置 |
|---|---|
| 操作系统 | Ubuntu 20.04 / 22.04 或企业常用 Linux 发行版 |
| Python | Python 3.10+,建议独立 conda 环境 |
| GPU | 建议 16GB 显存以上,长音频或并发场景建议更高 |
| 音频工具 | ffmpeg |
| 服务框架 | FastAPI / Uvicorn |
| 模型能力 | Qwen ASR 1.7B |
| 上层系统 | 熙瑾会悟私有化会议助手 |
先创建独立环境:
conda create -n huiwu-asr python=3.12 -y
conda activate huiwu-asr
pip install -U qwen-asr
pip install fastapi uvicorn python-multipart requests pydub
如果部署机可以访问模型仓库,可以直接通过模型名加载;如果是政企内网或离线环境,建议提前在外网机器下载模型文件,再拷贝到内网服务器。
# 方式一:通过 ModelScope 下载,适合国内网络环境
pip install -U modelscope
modelscope download --model Qwen/Qwen3-ASR-1.7B --local_dir ./models/Qwen3-ASR-1.7B
# 方式二:通过 Hugging Face 下载
pip install -U "huggingface_hub[cli]"
huggingface-cli download Qwen/Qwen3-ASR-1.7B --local-dir ./models/Qwen3-ASR-1.7B
部署建议:如果是客户现场交付,不建议运行时再自动下载模型。更稳妥的方式是把模型权重、代码、镜像、配置文件全部打包成离线交付物,现场只做加载和启动验证。
3. 第一次推理:验证模型是否能正常识别会议录音
模型部署完成后,先不要急着接入会悟。第一步应该先用最小代码验证 ASR 是否能正常推理。
新建 test_asr.py:
import torch
from qwen_asr import Qwen3ASRModel
MODEL_PATH = "./models/Qwen3-ASR-1.7B"
AUDIO_PATH = "./samples/meeting_10s.wav"
model = Qwen3ASRModel.from_pretrained(
MODEL_PATH,
dtype=torch.bfloat16,
device_map="cuda:0",
max_inference_batch_size=8,
max_new_tokens=512,
)
results = model.transcribe(
audio=AUDIO_PATH,
language=None, # None 表示自动识别语言;也可以指定 "Chinese" / "English"
)
for r in results:
print("识别语言:", r.language)
print("识别文本:", r.text)
执行:
python test_asr.py
如果输出类似下面的结果,说明模型链路已经打通:
识别语言:Chinese
识别文本:本次项目评审会主要讨论三个问题,第一是私有化部署环境,第二是会议转写准确率,第三是会后纪要自动生成。
这一步主要验证三件事:
第一,模型权重是否完整。
第二,GPU 环境是否可用。
第三,音频格式是否能被正常读取。
如果这里都不能跑通,就不要继续封装 API。企业交付里很多问题不是上层系统问题,而是模型文件、CUDA、显存、音频格式在最底层就没有稳定。
4. 音频格式处理:把会议录音统一转成 16k 单声道 WAV
实际业务里的会议录音格式非常混乱,可能是 mp3、m4a、aac、wav,也可能是线上会议软件导出的混合格式。为了减少识别波动,建议进入 ASR 前统一转成 16kHz、单声道 WAV。
安装 ffmpeg:
sudo apt-get update
sudo apt-get install -y ffmpeg
单个文件转换:
ffmpeg -y -i input.mp3 -ar 16000 -ac 1 output.wav
批量转换会议目录:
mkdir -p ./wav_meetings
for file in ./raw_meetings/*; do
name=$(basename "$file")
base="${name%.*}"
ffmpeg -y -i "$file" -ar 16000 -ac 1 "./wav_meetings/${base}.wav"
done
在会悟的实际会议链路中,这一步通常会被放到音频预处理模块里自动完成。用户不需要关心原始文件是什么格式,系统会先做采样率、声道、格式和音频时长检查,再送入 ASR 模型。
可以写一个简单的 Python 工具函数:
from pathlib import Path
from pydub import AudioSegment
def normalize_audio(input_path: str, output_dir: str = "./normalized") -> str:
"""
将任意常见音频格式转为 16kHz、单声道 WAV。
适合在会议录音进入 ASR 前统一预处理。
"""
input_path = Path(input_path)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / f"{input_path.stem}.wav"
audio = AudioSegment.from_file(input_path)
audio = audio.set_frame_rate(16000).set_channels(1)
audio.export(output_path, format="wav")
return str(output_path)
这类代码看起来简单,但在真实部署中非常关键。很多“识别不准”的问题,本质上并不是模型不行,而是音频输入质量不稳定。
5. 服务化封装:把 Qwen ASR 变成内网 FastAPI 接口
单机脚本只能验证模型能力,不能直接服务企业会议系统。会悟需要的是稳定的后端接口:会议录音上传后,系统自动调用 ASR,返回语言、文本、耗时、RTF 等结构化结果。
新建 asr_server.py:
import time
import tempfile
from pathlib import Path
from typing import Optional
import torch
from fastapi import FastAPI, UploadFile, File, Form
from pydantic import BaseModel
from qwen_asr import Qwen3ASRModel
MODEL_PATH = "./models/Qwen3-ASR-1.7B"
app = FastAPI(title="Huiwu Qwen ASR Service", version="1.0.0")
model = Qwen3ASRModel.from_pretrained(
MODEL_PATH,
dtype=torch.bfloat16,
device_map="cuda:0",
max_inference_batch_size=8,
max_new_tokens=1024,
)
class ASRResponse(BaseModel):
language: str
text: str
duration_sec: float
process_sec: float
rtf: float
@app.get("/health")
def health_check():
return {"status": "ok", "model": "Qwen3-ASR-1.7B"}
@app.post("/asr", response_model=ASRResponse)
async def transcribe(
audio_file: UploadFile = File(...),
language: Optional[str] = Form(default=None),
):
"""
会议录音转写接口。
language 为空时自动识别,也可以传 Chinese / English 等。
"""
suffix = Path(audio_file.filename).suffix or ".wav"
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
tmp.write(await audio_file.read())
tmp_path = tmp.name
start = time.time()
results = model.transcribe(
audio=tmp_path,
language=language,
)
process_sec = time.time() - start
result = results[0]
# 如果需要精确音频时长,可在预处理阶段用 ffprobe 读取
# 这里为了示例,duration_sec 先返回 0
duration_sec = 0.0
return {
"language": result.language,
"text": result.text,
"duration_sec": duration_sec,
"process_sec": round(process_sec, 3),
"rtf": 0.0 if duration_sec == 0 else round(duration_sec / process_sec, 3),
}
启动服务:
uvicorn asr_server:app --host 0.0.0.0 --port 7861
测试健康检查:
curl http://127.0.0.1:7861/health
返回:
{
"status": "ok",
"model": "Qwen3-ASR-1.7B"
}
上传会议录音测试:
curl -X POST "http://127.0.0.1:7861/asr" \
-F "audio_file=@./samples/meeting_10s.wav" \
-F "language=Chinese"
返回示例:
{
"language": "Chinese",
"text": "本次会议主要确认私有化部署方案,并讨论会悟会议纪要自动生成的上线计划。",
"duration_sec": 0.0,
"process_sec": 1.842,
"rtf": 0.0
}
到这里,一个最小可用的本地 ASR 服务就完成了。它可以被会悟后端调用,也可以被其他业务系统调用。
6. 更接近生产:增加音频时长、RTF 和异常处理
企业部署不能只看“能不能返回文本”,还要看服务是否可观测。至少要记录每次请求的文件名、音频时长、处理耗时、RTF、错误信息。
可以用 ffprobe 获取音频时长:
import json
import subprocess
def get_audio_duration(audio_path: str) -> float:
"""
使用 ffprobe 获取音频时长,单位:秒。
"""
cmd = [
"ffprobe",
"-v", "quiet",
"-print_format", "json",
"-show_format",
audio_path,
]
output = subprocess.check_output(cmd).decode("utf-8")
info = json.loads(output)
return float(info["format"]["duration"])
把它加入接口:
@app.post("/asr", response_model=ASRResponse)
async def transcribe(
audio_file: UploadFile = File(...),
language: Optional[str] = Form(default=None),
):
suffix = Path(audio_file.filename).suffix or ".wav"
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
tmp.write(await audio_file.read())
tmp_path = tmp.name
duration_sec = get_audio_duration(tmp_path)
start = time.time()
results = model.transcribe(audio=tmp_path, language=language)
process_sec = time.time() - start
rtf = duration_sec / process_sec if process_sec > 0 else 0
result = results[0]
print(
f"[ASR] file={audio_file.filename}, "
f"duration={duration_sec:.2f}s, "
f"process={process_sec:.2f}s, "
f"rtf={rtf:.2f}x, "
f"lang={result.language}"
)
return {
"language": result.language,
"text": result.text,
"duration_sec": round(duration_sec, 3),
"process_sec": round(process_sec, 3),
"rtf": round(rtf, 3),
}
日志示例:
[ASR] file=meeting_20260706.wav, duration=312.54s, process=48.63s, rtf=6.43x, lang=Chinese
这个指标对运维很有价值。RTF 越高,说明处理速度越快于实时播放。如果一段 300 秒的会议音频处理了 50 秒,那么系统完全可以支撑会后快速生成转写初稿。
7. 长会议处理:不要把 2 小时录音直接丢给模型
真实会议经常超过 1 小时。虽然模型和推理框架可能支持长音频,但工程上不建议简单粗暴地把超长文件一次性丢进去。更稳妥的做法是分段、转写、合并。
会议场景常用三种分段方式:
第一,固定时长切分,例如每 120 秒切一段,实现简单。
第二,静音检测切分,在停顿处断开,语义更完整。
第三,重叠切分,每段保留 2 到 5 秒重叠,避免句子被切断。
下面是一个偏实用的固定时长切分脚本,适合快速落地:
from pydub import AudioSegment
from pathlib import Path
def split_audio_fixed(
wav_path: str,
output_dir: str = "./segments",
chunk_ms: int = 120_000,
overlap_ms: int = 3_000,
):
"""
将长会议录音切成多个短片段。
chunk_ms: 每段长度,默认 120 秒
overlap_ms: 相邻片段重叠,默认 3 秒
"""
wav_path = Path(wav_path)
output_dir = Path(output_dir) / wav_path.stem
output_dir.mkdir(parents=True, exist_ok=True)
audio = AudioSegment.from_wav(wav_path)
segments = []
start = 0
index = 1
while start < len(audio):
end = min(start + chunk_ms, len(audio))
segment = audio[start:end]
segment_path = output_dir / f"{wav_path.stem}_{index:04d}.wav"
segment.export(segment_path, format="wav")
segments.append(str(segment_path))
if end >= len(audio):
break
start = end - overlap_ms
index += 1
return segments
批量调用 ASR 接口并合并结果:
import requests
ASR_API = "http://127.0.0.1:7861/asr"
def transcribe_segments(segment_paths):
texts = []
for i, path in enumerate(segment_paths, start=1):
with open(path, "rb") as f:
resp = requests.post(
ASR_API,
files={"audio_file": f},
data={"language": "Chinese"},
timeout=300,
)
resp.raise_for_status()
data = resp.json()
print(f"片段 {i}/{len(segment_paths)} 完成:{data['text'][:40]}...")
texts.append(data["text"])
return "\n".join(texts)
if __name__ == "__main__":
segments = split_audio_fixed("./wav_meetings/project_review.wav")
full_text = transcribe_segments(segments)
with open("./project_review_transcript.txt", "w", encoding="utf-8") as f:
f.write(full_text)
这一步完成后,我们已经有了一份完整的会议转写文本。但这仍然只是“听写稿”,还不是用户真正想要的会议纪要。
8. 接入熙瑾会悟:从 ASR 文本到会议纪要
会悟的关键价值,是把 ASR 输出继续往后处理。
一个典型流程可以设计成这样:
会议录音
↓
音频预处理:格式转换、采样率统一、音频切分
↓
Qwen ASR 1.7B:会议语音转写
↓
会悟文本处理:段落整理、口语清洗、术语修正
↓
会悟智能纪要:摘要、议题、决策、待办、风险点
↓
会议归档:全文检索、权限控制、知识库沉淀
为了演示,可以把前面生成的转写文本提交给会悟的会议处理接口。下面的接口路径是示例,实际部署时以会悟现场配置为准。
import requests
from datetime import datetime
HUIWU_API = "http://127.0.0.1:18080/api/meeting/minutes/generate"
def submit_to_huiwu(transcript_text: str):
payload = {
"meeting_title": "私有化 AI 会议助手部署评审会",
"meeting_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"participants": ["项目负责人", "运维工程师", "产品经理", "客户代表"],
"asr_model": "Qwen-ASR-1.7B",
"source": "local_asr",
"transcript": transcript_text,
"output_format": {
"summary": True,
"topics": True,
"decisions": True,
"todos": True,
"risks": True
}
}
resp = requests.post(HUIWU_API, json=payload, timeout=300)
resp.raise_for_status()
return resp.json()
返回结果可以设计成结构化 JSON:
{
"summary": "本次会议主要讨论了 Qwen ASR 1.7B 在会悟私有化部署中的接入方案,确认了音频预处理、ASR 服务封装、长音频切分和纪要生成流程。",
"topics": [
"Qwen ASR 1.7B 本地化部署",
"会议录音格式统一与长音频切分",
"ASR 服务接口封装",
"会悟会议纪要生成与归档"
],
"decisions": [
"ASR 服务以内网 FastAPI 方式提供给会悟后端调用",
"会议录音统一转为 16kHz 单声道 WAV 后再进入识别流程",
"超过 30 分钟的会议录音采用分段识别与结果合并机制"
],
"todos": [
{
"owner": "运维工程师",
"task": "完成 Qwen ASR 1.7B 模型离线部署和服务化启动脚本",
"deadline": "本周五"
},
{
"owner": "产品经理",
"task": "确认会悟纪要模板和客户侧输出格式",
"deadline": "下周二"
}
],
"risks": [
"长会议场景下需要关注显存占用和并发队列",
"嘈杂会议室需要优化麦克风采集和音频预处理"
]
}
到这里,系统才真正从“ASR 模型”变成“AI 会议助手”。
因为用户最终看到的不是模型日志,也不是连续转写文本,而是可以直接使用的会议成果:摘要、议题、决策、待办、风险和归档记录。
9. 批量处理历史会议:把存量录音变成可检索资料
很多单位上 AI 会议助手时,不仅希望处理新会议,也希望把过去积累的大量会议录音转成资料库。这个时候可以写一个批处理脚本,把目录里的录音统一处理。
from pathlib import Path
import json
def process_meeting_folder(raw_dir: str):
raw_dir = Path(raw_dir)
results = []
for file in raw_dir.iterdir():
if file.suffix.lower() not in [".mp3", ".wav", ".m4a", ".aac"]:
continue
print(f"\n开始处理:{file.name}")
wav_path = normalize_audio(str(file))
segments = split_audio_fixed(wav_path)
transcript = transcribe_segments(segments)
huiwu_result = submit_to_huiwu(transcript)
item = {
"filename": file.name,
"wav_path": wav_path,
"transcript": transcript,
"minutes": huiwu_result,
}
results.append(item)
output_path = Path("./outputs") / f"{file.stem}.json"
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
json.dump(item, f, ensure_ascii=False, indent=2)
print(f"处理完成:{output_path}")
return results
if __name__ == "__main__":
process_meeting_folder("./raw_meetings")
这个脚本对应到真实业务,就是会悟的“历史会议资料入库”能力:
把过去散落在文件夹里的录音,统一转成文本;再通过智能纪要模块提炼内容;最后进入会议知识库,支持后续按项目、部门、人员、关键词检索。
这一步对政企、科研院所、医院、高校和大型集团很有价值。因为很多会议内容并不是当场用完就结束,而是会在未来反复被查阅、审计、复盘和引用。
10. 私有化部署建议:不要只部署模型,要部署完整链路
在客户现场交付时,建议不要把 Qwen ASR 1.7B 当成一个孤立服务交付,而应该纳入会悟的完整部署架构。
一个相对稳妥的私有化部署结构如下:
Nginx / 网关
├── 会悟前端
├── 会悟业务后端
├── 会议文件服务
├── ASR 服务:Qwen ASR 1.7B
├── 大模型总结服务
├── Redis / MySQL / Elasticsearch
└── 日志与监控
ASR 服务建议单独容器化,便于独立扩容和重启:
version: "3.8"
services:
huiwu-qwen-asr:
image: huiwu/qwen-asr:1.7b
container_name: huiwu-qwen-asr
restart: always
ports:
- "7861:7861"
volumes:
- ./models/Qwen3-ASR-1.7B:/app/models/Qwen3-ASR-1.7B
- ./logs:/app/logs
- ./tmp:/app/tmp
environment:
- MODEL_PATH=/app/models/Qwen3-ASR-1.7B
- CUDA_VISIBLE_DEVICES=0
- MAX_BATCH_SIZE=8
- MAX_NEW_TOKENS=1024
command: >
uvicorn asr_server:app
--host 0.0.0.0
--port 7861
会悟业务后端只需要在配置文件里指定 ASR 服务地址:
ASR_PROVIDER=qwen
ASR_MODEL=Qwen-ASR-1.7B
ASR_BASE_URL=http://huiwu-qwen-asr:7861
ASR_TRANSCRIBE_PATH=/asr
ASR_LANGUAGE=Chinese
这样做的好处是,ASR 模型升级、显卡迁移、接口调优,都不会影响会悟主业务系统。对运维来说,也更容易定位问题:到底是音频上传失败、ASR 推理失败,还是纪要生成失败。
11. 常见问题:部署时最容易踩的几个坑
11.1 显存不足
如果启动时出现 CUDA OOM,可以先降低 batch size:
max_inference_batch_size=1
max_new_tokens=512
也可以在服务侧限制并发,让会议录音排队处理,而不是同时把多个长音频送进 GPU。
11.2 识别慢
先看三个指标:
音频时长 duration_sec
处理耗时 process_sec
实时因子 rtf
如果 RTF 很低,说明 GPU 推理速度不理想。可以检查是否启用了合适的后端、是否有其他进程占用显存、音频是否过长、是否需要切分。
11.3 长会议文本重复或断句不自然
这通常和分段策略有关。固定切分容易切断句子,可以改成静音检测切分,或增加 2 到 5 秒重叠,再在合并阶段做去重。
11.4 专业术语识别不稳定
会议系统最好增加术语后处理模块。例如企业名、人名、项目代号、产品名、缩写词,可以在 ASR 后做替换和规范化。
def normalize_terms(text: str) -> str:
term_map = {
"西晋会务": "熙瑾会悟",
"会雾": "会悟",
"Q 文 ASR": "Qwen ASR",
"GPU 利用": "GPU 利用率",
}
for wrong, right in term_map.items():
text = text.replace(wrong, right)
return text
这类工程细节虽然不如模型参数吸引眼球,但在企业真实使用中非常重要。用户判断一个 AI 会议助手是否好用,往往不是看宣传页,而是看它能不能准确识别本单位常用词。
12. 总结:Qwen ASR 1.7B 是底座,会悟解决的是会议闭环
从这次实战可以看到,Qwen ASR 1.7B 能够为企业会议系统提供本地语音识别能力;通过 FastAPI 封装后,它可以变成一个可被会悟调用的内网 ASR 服务;再配合音频预处理、长音频切分、批量转写、术语修正和结构化纪要生成,就能形成一条完整的 AI 会议处理链路。
单独部署 ASR,解决的是“把声音变成文字”。
接入熙瑾会悟后,解决的是“把会议变成资料、把资料变成知识、把知识变成可追踪的行动”。
对于政企、军工、纪检、医疗、高校和大型集团来说,AI 会议助手的核心价值并不只是节省人工记录时间,而是让会议内容在内网中安全流转、规范沉淀、快速检索、持续复用。
这也是会悟选择 Qwen ASR 1.7B 这类本地化语音识别能力的原因:底层模型负责听清楚会议,上层系统负责理解会议、整理会议、管理会议。只有两者结合,AI 会议助手才真正从技术 Demo 走向企业级生产系统。
更多推荐



所有评论(0)