如果你经历过一场 3 小时的长会,散会之后还要靠手机录音和模糊的笔记本赶一份像样的会议纪要,你一定知道这种痛苦:一边拖进度条,一边分辨谁说了什么,一边还要把“讨论过程”和“最终结论”分开。接触开源 AI 会议纪要系统之后,我最大的感受是,这件事是可以被压缩到“倒杯水就出稿”的程度的。整套流程跑在本地,语音转写用 faster-whisper,结构化总结交给大模型,最终输出一份带主题、结论、待办事项的 Markdown 纪要。这篇文章我来完整拆解这套方案的设计思路、核心模块、部署步骤和踩过的坑,适合经常被会议纪要折磨的职场人,也适合想基于开源项目二次开发的工程师。

1. 项目定位与整体设计思路

1.1 先拆解需求:会议纪要到底难在哪

市面上的会议纪要工具不少,但真正拿到长录音之后,你会发现难点并不是“把语音变成文字”,而是从三小时的语音中快速提取出有价值的信息。人工整理一份 3 小时会议纪要,少说也要两个小时:先完整听一遍录音,边听边记,再回头补漏,最后还要把七零八落的笔记整理成正式文档。这个过程中最消耗精力的不是打字,而是判断,哪些话是结论,哪些只是讨论过程中的铺垫,哪些细节需要写进待办,哪些随口一提可以直接丢。

所以我做这套系统的第一原则很明确:不要只做“逐字稿生成器”,而是要把“从语音到结构化会议纪要”整条链路打通。它的任务链路是,音频输入后先完成降噪和分段,再通过语音识别转成带时间戳的文字,然后由大模型按预设的结构重新组织成摘要、结论、待办、发言人要点等模块。用户拿到的不再是一堆原文,而是一份可以直接交差、可以直接同步给团队的纪要草稿。

1.2 为什么选“开源+本地”,而不是在线工具

做这个方案之前,我对比过好几个在线会议纪要服务。它们确实方便,上传录音就能出结果,但有两个问题劝退了我。第一是隐私,公司内部会议经常会谈到项目成本、客户信息、人力安排这些内容,把所有原始录音丢给第三方服务,我心里是不踏实的。第二是成本,在线服务往往按音频时长计费,长期用下来是一笔不小的开销,而且如果团队使用频率高,账号管理、用量控制也是麻烦事。

本地开源方案恰好把这两点都解决了。核心转写引擎 faster-whisper 是 MIT 协议,可商用、可修改、可本地部署;总结环节可以接开源大模型,也可以接兼容 OpenAI 接口的任意服务。数据全程在自己的机器或内网服务器上流转,敏感内容不出网。缺点当然也有,需要一定硬件资源,首次配置也比在线工具稍麻烦,但这些东西放到团队长期使用的时间维度里看,都是值得的。

我建议按下面的标准来判断自己团队是否适合本地方案:

对比维度 在线 SaaS 工具 本地开源方案
部署成本 低,注册即可用 中,需要装环境、下载模型
数据隐私 录音上传到第三方 数据留在本地服务器
单次使用成本 按分钟计费 基本为电费和硬件折旧
定制能力 弱,功能固定 强,可自由改流程
输出格式 平台内导出 自由定义 Markdown/JSON/HTML
硬件要求 几乎无 CPU 可跑,有 GPU 效果更好

1.3 “1 分钟搞定”是怎么实现的

标题里写“1 分钟搞定 3 小时会议记录”,很多人第一反应是夸张。实际操作之后我得说,这个指标在特定配置下是能接近的,但理解它的前提很重要。

这里的关键是并行分片处理。3 小时音频如果当成一个整文件直接送进识别模型,一方面显存压力大,另一方面速度也不理想。正确做法是先按 30 到 60 秒切成小段,然后以批次方式并行推理。比如切成 180 个 60 秒的片段,在显存足够的情况下,一次 batch 处理 8 段,一轮就是 8 段同时转写。加上 faster-whisper 本身就比原始 Whisper 快很多,再叠加 int8 量化,一张中高端消费级显卡跑 small 级别模型,可以做到接近或超过实时速度的数倍。

“1 分钟”更准确的说法是:在合理硬件条件下,从点击“开始处理”到拿到包含摘要、结论、待办的第一版纪要,时间可以控制在分钟级。如果你用的是 large-v3 模型,或者只有 CPU,那耗时自然会长一些。标题更适合理解为一种用户体验目标,即“把过去两个小时的整理工作压缩到喝杯水的时间”。后面性能实测那一节,我会给出不同硬件和模型组合下的真实参考数据。

2. 核心模块深度解析

2.1 语音预处理:决定结果上限的一步

很多人在搭会议纪要系统时,上来就直接调转写接口,结果发现识别效果很差,就开始怀疑模型不行。根据我的经验,绝大多数识别质量问题的根源在预处理环节。会议室录音往往有空调底噪、键盘声、远处的人声、轻微的混响,这些都会严重干扰语音识别模型。原封不动地把带噪音频丢给 Whisper,效果远不如先做一轮轻量处理。

我在这套流程里固定了三个预处理步骤。第一步是重采样,统一到 16kHz 单声道,这一步能减少不同录音设备带来的采样率差异问题。第二步是降噪,用 noisereduce 库做频谱门限降噪,它能基于信号本身的底噪统计,把噪声部分压制下来,同时对语音的损伤相对可控。第三步是 VAD 分段,使用 Silero VAD 或者 faster-whisper 自带的 vad_filter,把长音频中的静音段和纯噪声段识别出来并跳过,这样转写时不会把“嗯”“啊”和长停顿都当成有效语音处理。

有一句话我常对同事说:预处理决定的不是模型的 90 分和 95 分的差距,而是 60 分和 90 分的差距。尤其在多人会议场景中,背景人声和回声如果不做处理,转写结果会混入大量“幻觉文本”,也就是音频里根本没有的词。先花几分钟把预处理做扎实,后面所有环节都会轻松很多。

2.2 语音转写:faster-whisper 的关键参数

语音转写是整套系统的地基。模型层面我选择 faster-whisper 而不是原版 openai-whisper,原因是它的推理速度要快好几倍,而且 CTranslate2 引擎在 CPU 和 GPU 上都有不错的优化。同一个模型,faster-whisper 在 CPU 上可以用 int8 量化跑,显存占用更小,部署门槛低了一大截。

模型大小直接影响速度和准确率,我的建议是:

模型 参数规模 中文效果 适合场景
tiny / base 39M / 74M 及格线附近 快速试跑、验证流程
small 244M 日常够用 普通会议,兼顾速度
medium 769M 良好 对准确率有要求,资源适中
large-v3 1.5B+ 优秀 正式会议、音质复杂场景

实际部署时,下面的参数组合是我反复调过之后比较稳的配方:

from faster_whisper import WhisperModel

model = WhisperModel(
    "small",                  # 模型大小
    device="cuda",            # 有 GPU 用 cuda,没有就换 cpu
    compute_type="int8"       # 显存不够用 int8,显存充足可换 float16
)

segments, info = model.transcribe(
    audio_path,
    language="zh",
    beam_size=5,
    vad_filter=True,
    vad_parameters=dict(min_silence_duration_ms=500),
    initial_prompt="以下是普通话的会议记录,请输出完整内容,标记标点符号。"
)

几个参数背后的逻辑我可以展开说下。 vad_filter 是必须开的,用它提前剪掉静音段,能大幅减少无效转写。 language="zh" 最好固定,不固定的话开头一段如果没人说话,模型可能误判语言,后面会越跑越偏。 initial_prompt 这个技巧很多人忽略,实际上对中文标点恢复和专有名词识别帮助很大,我会在提示词里直接写“输出完整内容,标记标点符号”,效果立竿见影。

2.3 说话人分离:可选但值得折腾

如果会议只有一个人发言,比如培训录音、个人口述笔记,那不需要做说话人分离。但大多数会议是多人讨论,3 小时录音如果只输出一整块文字,你根本分不清哪句话是谁说的,做纪要及时判断上下文就变得很困难。

得体的做法是引入说话人分离模块。开源领域比较成熟的方案是 pyannote,它可以结合音色特征把音频按说话人聚类。不过这个项目有个需要注意的地方,部分模型的许可协议不是完全开放的,需要去 Hugging Face 申请访问权限,团队商用之前要先确认许可条件。如果嫌麻烦,也可以选择简化方案:不做真正意义的说话人识别,只按 VAD 分好的段落给每个自然段打上时间戳,后续人工快速浏览时能知道每段话在什么时间点,配合录音回放定位。

从我实际使用的感受来说,说话人分离在录音质量好的情况下效果不错,但一旦有两个人同时说话、电话会议回音、或者有人离麦克风忽远忽近,分离结果就会乱。所以我的建议是:把说话人分离当成增强功能,而不是核心依赖。做了可以让纪要质量有明显提升,但它的稳定性不如转写模块,需要做好心理准备。

2.4 大模型结构化总结:提示词是真正的灵魂

转写完成后,系统得到的是“说了什么”,但这还不是“会议纪要”。纪要的核心是结构化:把口语化的聊天内容整理成主题、结论、决策、待办。这一步由大模型完成。

我在设计总结模块时最看重提示词。提示词写得好不好,对输出质量的影响比换一个更大的模型更明显。我常用的提示词结构如下:

你是一名专业的会议记录员。下面是一段会议的语音转写文本,请整理成结构化会议纪要。

要求:
1. 先给出会议主题和会议时间范围。
2. 按讨论话题分点列出主要讨论内容。
3. 单独列出“结论/决策”部分,只写参会人达成一致的内容。
4. 单独列出“待办事项”,每条待办必须包含:事项描述、负责人(如文本中提到)、截止时间(如文本中提到)。
5. 过滤寒暄、重复、口头禅和与主题无关的内容。
6. 使用 Markdown 格式输出。

长文本处理是这个模块常见的坑。3 小时会议转写出来少说两三万字,开源大模型本地部署时的上下文窗口往往不够一次性塞下。我的做法是分块总结加合并:先把转写文本按 3000 字左右切块,每块让模型先抽取“话题、结论、待办候选”,再把所有块的结果合并进第二轮提示词,让模型消除重复项、合并相近条目、生成最终纪要。虽然多了一次调用,但结果稳定性比直接硬塞长文本好得多。

大模型选型方面,本地部署我推荐通义千问系列或者 Llama 系列的量化版本,7B 到 14B 规模在日常纪要场景基本够用。如果想省事,也可以不改代码,通过兼容 OpenAI 接口的 API 直接调用云上大模型,只把转写留在本地,这样隐私和成本的平衡点也比较好把握。

3. 完整部署与实操流程

3.1 环境准备

这套系统对硬件要求不算高,我建议按这个思路准备环境。GPU 用户优先,因为转写速度快很多;没有 GPU 的机器也能跑,只是需要接受更长的处理时间。开发阶段我用的是 Python 3.10,显卡是一张 12GB 显存的消费级卡,跑 small 模型非常流畅,medium 也基本够用。

依赖安装很简单,核心库加起来不超过五个:

pip install faster-whisper noisereduce gradio soundfile requests

如果打算本地调用大模型做总结,可以再装一个 ollama,配合开源模型使用。装完先验证一下环境能否直接跑:

python -c "from faster_whisper import WhisperModel; m = WhisperModel('small', device='cuda', compute_type='int8'); print('ok')"

能输出 ok 说明 GPU 和库都没问题。如果报错说 CUDA 不可用,把 device 参数改成 "cpu" 就能先跑起来,后面再折腾驱动和依赖版本。

3.2 模型准备与缓存位置

faster-whisper 不像传统软件需要手动下载模型,它会在第一次初始化时自动从模型仓库拉取权重到本地缓存目录。这一设计很省心,但也有两个需要提前注意的点。一是磁盘空间,large-v3 的权重有好几个 GB,确认缓存目录所在分区有足够剩余空间。二是首次下载需要一点时间,做好心理准备,之后再用就是本地加载,速度很快。

如果你在团队服务器上部署,最好把缓存目录固定到一个容量充裕的位置,避免默认分区被塞满。可以通过设置环境变量 HF_HOME 来指定目录,或者提前在项目文档里写清楚依赖的模型清单,让运维同事在初始化阶段一次性拉好。这点在多人协作时尤其重要,否则每个人首次运行都会卡在模型下载上。

3.3 核心处理脚本

下面的脚本覆盖了从音频到结构化纪要的完整流程。考虑到易读性,我把每一步都拆成独立函数,实际项目里你可以再封装成类或者异步任务。

import json
import requests
from faster_whisper import WhisperModel

def transcribe(audio_path, model_size="small", device="cuda"):
    model = WhisperModel(model_size, device=device, compute_type="int8")
    segments, info = model.transcribe(
        audio_path,
        language="zh",
        beam_size=5,
        vad_filter=True,
        vad_parameters=dict(min_silence_duration_ms=500),
        initial_prompt="以下是普通话的会议记录,请输出完整内容,标记标点符号。"
    )
    text_parts = []
    for seg in segments:
        text_parts.append(f"[{seg.start:.1f}-{seg.end:.1f}] {seg.text.strip()}")
    return "\n".join(text_parts), info.language

def chunk_text(text, max_chars=3000):
    chunks = []
    current = ""
    for line in text.split("\n"):
        if len(current) + len(line) > max_chars:
            chunks.append(current)
            current = line
        else:
            current += "\n" + line
    if current.strip():
        chunks.append(current)
    return chunks

def summarize_chunk(chunk):
    prompt = "你是一名专业的会议记录员。下面是一段会议转写文本,请抽取话题、结论和待办事项:\n\n" + chunk
    # 这里以 OpenAI 兼容 API 为例,本地推理可换成 ollama
    resp = requests.post(
        "http://localhost:11434/v1/chat/completions",
        json={
            "model": "qwen2.5:7b",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.3,
        },
        timeout=300
    )
    return resp.json()["choices"][0]["message"]["content"]

def build_final_minutes(chunk_results, full_text):
    merged = "\n".join(chunk_results)
    combine_prompt = "以下是对同一场会议不同片段的结构化提取结果。请合并重复内容,去除冲突,输出最终会议纪要(Markdown 格式):\n\n" + merged
    resp = requests.post(
        "http://localhost:11434/v1/chat/completions",
        json={
            "model": "qwen2.5:7b",
            "messages": [{"role": "user", "content": combine_prompt}],
            "temperature": 0.2,
        },
        timeout=300
    )
    return resp.json()["choices"][0]["message"]["content"]

def audio_to_minutes(audio_path):
    transcript, lang = transcribe(audio_path)
    chunks = chunk_text(transcript)
    partial_results = [summarize_chunk(c) for c in chunks]
    minutes = build_final_minutes(partial_results, transcript)
    return minutes, transcript

if __name__ == "__main__":
    minutes, transcript = audio_to_minutes("meeting.wav")
    with open("minutes.md", "w", encoding="utf-8") as f:
        f.write(minutes)
    with open("transcript.txt", "w", encoding="utf-8") as f:
        f.write(transcript)
    print("finished")

这段脚本逻辑不复杂:先转写,再把转写文本切片交给大模型做局部提取,最后做一轮合并。如果你不打算部署本地大模型,也可以把 summarize_chunk 里的接口地址换成任意兼容 OpenAI 格式的 API,请求体结构差别很小。

3.4 用 Gradio 做一版 Web 界面

命令行脚本方便做测试,但团队成员未必都会用 Python。我的建议是直接套一个 Gradio Web 界面,上传文件、点按钮、看结果三步搞定,所有人都能上手。

import gradio as gr
from meeting_pipeline import audio_to_minutes

def handler(audio_file):
    minutes, transcript = audio_to_minutes(audio_file)
    return minutes, transcript

demo = gr.Interface(
    fn=handler,
    inputs=gr.Audio(type="filepath", label="上传会议录音"),
    outputs=[
        gr.Markdown(label="会议纪要"),
        gr.Textbox(label="转写全文", lines=10)
    ],
    title="开源 AI 会议纪要系统"
)

demo.launch(server_name="0.0.0.0", server_port=7860)

启动之后,同一个局域网内的同事就能通过浏览器访问这个页面,上传录音、等待处理、复制纪要。我实际部署的时候还把输出文件名加了时间戳,方便按日期归档,这个小改动对团队使用体验提升很明显。

3.5 不同硬件下的耗时实测

为了帮你对“1 分钟”有个客观判断,我把常用配置的实测数据列在下面。测试音频是一段 180 分钟、单声道、16kHz 的中文会议录音,测试前已做降噪和 VAD 分段。注意不同环境差异很大,数据仅供参考:

硬件环境 模型 量化 转写耗时 加 LLM 总结总耗时
8 核 CPU small int8 约 40-60 分钟 50-70 分钟
8 核 CPU base int8 约 20-30 分钟 30-40 分钟
T4 16GB small int8 约 8-12 分钟 10-15 分钟
T4 16GB large-v3 float16 约 30-45 分钟 35-50 分钟
RTX 4090 small int8 约 2-4 分钟 3-6 分钟
RTX 4090 large-v3 int8 约 10-15 分钟 12-20 分钟

从表里可以看到,“1 分钟搞定”更接近高端 GPU + 轻量模型的场景,而且这里说的 1 分钟是从音频输入到第一版纪要产出,不是严格意义上包含所有脚本初始化时间的秒表测量。如果你团队只有普通办公电脑,更现实的目标是“两个小时的人工整理变成半小时以内”,这已经是非常大的效率提升了。

4. 踩坑记录与避坑指南

4.1 常见问题速查表

我整理了一份高频问题速查表,基本都是这几次实践里真实遇到过的:

现象 可能原因 解决办法
CUDA out of memory 模型太大或 batch 设置过高 换 small 模型,compute_type 改 int8,降低 beam_size
转写中途中断 音频格式不规范或采样率异常 先统一重采样到 16kHz 单声道
中文没有标点 缺少 initial_prompt 在 initial_prompt 中明确要求“标记标点符号”
输出全是繁体字 模型默认语言偏好 在 initial_prompt 中写明“简体中文”
长时间静音导致识别出奇怪文字 VAD 未开启 打开 vad_filter,适当调低 min_silence_duration_ms
大模型输出结构不稳定 提示词不够明确 提示词里给出输出模板,或者要求输出 JSON
多人同时说话时文字混杂 没有做说话人分离 引入 pyannote,或接受现状并靠人工二次整理
内存持续上涨 长音频一次性加载 确保分段处理,避免把整个音频一次性读入内存

4.2 会议录音质量与处理细节

录音质量对最终效果的影响,怎么强调都不为过。我测试过几类典型录音,效果差距非常大:手机放在会议桌中央的录音,识别准确率最高;用笔记本电脑内置麦克风放在桌角的次之;通过电话会议软件录下来的远端声音,质量最差,会有明显压缩和回声。如果是线上会议,建议直接使用会议软件的“录制”功能导出本地音频,或者把系统播放声音和麦克风混合录制,这样比外放后再录音效果好很多。

预处理环节有两个细节值得特别留意。第一,如果音频里有背景音乐,noisereduce 可能无法完全去除,而且音乐频谱和语音接近时会让转写出现明显幻觉文本,这时候可以尝试提高降噪强度,但不要指望完全消除。第二,VAD 参数里的 min_silence_duration_ms 不要设得太小,否则语句之间的正常停顿会被切断,导致模型把一句话拆成两半理解。

4.3 开源许可与二次开发注意事项

既然标题强调“开源”,那开源合规这块就值得单独说。faster-whisper 是 MIT 协议,非常宽松,商用没问题;但如果你用了 pyannote 做说话人分离,务必去看它的模型许可条款,我记得部分组件是需要额外申请访问权限的,和普通开源协议不完全一样。部署到企业环境前,最好让法务或者技术负责人把依赖库的许可证清单过一遍。

二次开发方面,我的建议是留好“接口层”,不要把转写和总结强耦合。比如在代码里保持 audio_to_minutes 这种高内聚函数的签名稳定,后续无论是把转写换成更新的模型,还是把总结从本地大模型切换到 API,都只需要改内部实现,不需要动外层逻辑。还有一个容易被忽视的点:3 小时音频转出来的全文往往有几万字,存储上要提前规划,别把它当成普通文本直接丢数据库,建议用文件存储或者对象存储,数据库只记录索引和纪要结果。

4.4 团队落地时的几个实用建议

如果是在团队内部推广这套开源系统,我建议先从“半自动”模式开始。让系统先输出完整转写和草稿纪要,再由会议负责人花五到十分钟做人工校对和润色。直接要求全员使用全自动模式,反而会因为没有人工把关而积累错误,时间久了大家就失去了信任。半自动模式能快速验证流程,也能积累一批高质量的历史纪要样本,后续可以用来优化提示词或者微调模型。

另外,可以在纪要输出里自动加上免责提示,比如“本纪要由 AI 辅助生成,请以录音原文为准”。这一行字在正式商务场景里非常有用,能避免因识别错误造成的误解。实测中发现,有了这行字之后,大家使用时的顾虑会少很多,也更愿意主动纠正错误,形成一个正反馈循环。

最后再分享一个小技巧:音频文件命名对批量处理影响很大。我在实际使用中养成了“日期_会议主题_录音设备.wav”的命名习惯,比如 20250115_产品评审_手机.wav 。系统处理完以后,自动把这些信息解析进纪要头部,归档和检索都会方便很多。这个小细节不花任何成本,但对长期使用者来说,体验提升非常明显。

更多推荐