上周开完项目周会,我盯着那一小时零八分钟的录音发了半小时呆。逐句听、记重点、整理成纪要、再挨个把待办私聊发给对应同事,整套流程下来将近四十分钟。开着屏幕都嫌事多,更别说中间还漏掉了一条“下周三前确认域名迁移方案”的待办,直到第二天对方来问我才知道自己漏派了。
所以我把这套流程自动化了:会议录音进来之后,先用 Octo-ASR 把语音转成文字,再让大模型把流水账整理成结构化会议纪要,顺手把里面的待办事项抽出来,最后通过 OCTO 工作流把待办派发给对应的 Agent 去执行。今天这篇就完整记录下这套方案的选型思路、部署步骤、核心代码和我在实际落地中踩过的坑。如果你也在做会议自动化、Agent 工作流,或者单纯想给“开会+跟进”这个场景减负,这篇应该能给你一份可以直接抄作业的参考。
1. 为什么要做这件事:会议纪要自动化的真实痛点
1.1 手动整理纪要的四个坑
先说痛点,不然你理解不了为什么非要把 Octo-ASR 接进 OCTO 工作流。
第一个坑是“转写不等于纪要”。市面上很多会议软件能输出逐字稿,但逐字稿直接拿去发群里,没人看得进去。口语里的“那个”“就是”“嗯嗯”,多个人抢话导致的上下文断裂,非线性的讨论过程,这些东西不改写根本没法当纪要。所以语音转写之后必须再接一层智能整理,这一步省不掉。
第二个坑是待办信息散落在对话里。项目会议里最值钱的往往就是那几句“我来负责”“下周给你反馈”“尽快处理一下”,但这类句子在转写文本里没有任何格式标记,人工梳理时需要回头翻上下文才能确认责任人和时间节点。漏掉一条待办,后面就得花好几个电话去补救。
第三个坑是派发动作太分散。整理好纪要只是第一步,把待办真正落到任务系统、发到 IM、通知到对应的人,这些动作分散在多个平台里。就算纪要写得再好,手动建任务仍然有延迟,而且容易建错负责人。
第四个坑是延迟导致的信息衰减。会议结束越久,整理纪要时对上下文的记忆越模糊。我试过隔天再整理,真是边听边猜当时为什么做出那个决定。会议一散场就自动化处理,才能最大程度保留信息完整性。
1.2 为什么选 Octo-ASR + OCTO 这对组合
选 Octo-ASR 有几个原因:它支持本地部署和流式识别,中英文效果都不错,转写速度可控,长音频的稳定性我也实测过,比之前用其他开源方案好不少。更重要的是它作为开源模型,接口层比较干净,接入工作流时不会遇到太多“黑盒”问题。
OCTO 工作流这层,你可以把它理解成一个以 Agent 为中心的流程编排平台。我在这条流水线里把整段业务拆成:音频接入节点、语音转写节点、纪要与待办提取节点(大模型节点)、任务派发节点。Octo-ASR 负责其中最关键也最脆弱的“语音转文字”环节,剩下的整理、提取、派发交给后续节点协同完成。
如果你已经用过 Dify、Coze、n8n 这类工作流工具,这个思路可以直接迁移。它们本质上是同一类东西:把多个 AI 能力和业务系统串成一条可编排、可调试、可重跑的流水线。区别在于我用 OCTO 平台来承担编排和状态管理,把 Octo-ASR 作为其中一个高价值节点接进去。
2. 整体架构与方案选型:把流水线拆成四段
2.1 流水线四段式设计
整个流程我拆成四段:
第一段:音频接入。会议录音文件落地到指定目录,或者实时会议音频流推送到转写服务。这里的关键是确定音频格式、采样率、声道数,避免后面转写出乱子。
第二段:语音转写。Octo-ASR 接收音频,输出带时间戳的文本流。这一步需要处理好长音频分段、静音裁剪、热词增强等细节,直接决定转写质量。
第三段:智能整理。把转写文本交给大模型,要求它输出结构化纪要:会议议题、关键结论、待办列表。待办列表必须带责任人和时间节点,方便后面派发,这一步通过设计好的提示词让输出既符合阅读习惯又能被程序解析。
第四段:待办派发。解析大模型输出的 JSON,把每条待办转换成 Agent 平台的任务,调用 OCTO 工作流的派发节点,把任务分配到对应 Agent 或责任人,同时把相关会议上下文作为附加上下文一起传过去。
2.2 三个关键选型判断
判断一:流式还是批量。批处理适合录制好的音频文件,实现简单,结果稳定,适合第一版跑通。流式则适合实时会议场景,边开边转,纪要能基本同步生成。我自己先跑通批处理,再在第二版里切换到流式。不要一上来就追求实时性,否则转写和服务稳定性问题会让你怀疑人生。
判断二:说话人分离要不要做。会议纪要如果有“谁说了什么”会更有价值,但说话人分离(声纹聚类)会显著增加链路复杂度。第一版我没做,而是让大模型通过文本段落结构来推断讨论视角,效果够用。如果你需要精确到人,可以在 Octo-ASR 后面再接一个说话人分离节点,但做好心理准备,这一步的调参成本不算低。
判断三:大模型在哪个层面跑。我建议走兼容 OpenAI 协议的统一接口层。不管背后是本地部署的开源模型,还是云上的大模型 API,工作流这边只需要改一个 base_url,节点逻辑完全不用动。对大多数团队来说,先跑通全链路比追求某一节点的极致效果更重要,协议统一能帮你省掉大量重构时间。
3. 环境准备与部署:先把 Octo-ASR 跑起来
3.1 Octo-ASR 本地部署步骤
我这边的部署环境是 Ubuntu 20.04 + NVIDIA 4090,显存 24G,跑中英文会议音频完全够用。如果你只有 CPU,也能跑,就是速度慢不少,长音频会等得比较久。
先把代码拉下来:
git clone https://github.com/ICT-OctoLab/Octo-ASR.git cd Octo-ASR conda create -n octoasr python=3.10 -y conda activate octoasr pip install -r requirements.txt然后从模型仓库拉取对应模型权重。模型标识可以在仓库 README 里找到,不同版本对应不同参数量级。我用的那一版是 SenseVoice 系列底座,单卡 fp16 推理,跑一小时会议音频大约需要 10 分钟出头,速度在中型会议室场景下可以接受。如果你的音频时长远超这个量级,建议在部署时打开 VAD 分段,避免一次把整个长音频塞给模型。
部署完先跑个最简单的文件转写验证环境:
from funasr import AutoModel model = AutoModel( model="iic/SenseVoiceSmall", device="cuda:0", disable_update=True ) res = model.generate( input="meeting_demo.wav", language="zh", use_itn=True, batch_size_s=60, ) print(res[0]["text"])能看到转写文本就说明环境通了。这里 language="zh" 指定中文,use_itn=True 会把数字、日期、金额这类内容自动规整成正式写法,对后面的纪要整理非常有用。
我在实际中格外注意了音频格式。Octo-ASR 对输入音频有格式要求,最好统一转换成 16kHz 采样率、单声道、WAV 或 PCM。我遇到过一个坑:一套视频会议系统导出的录音是 48kHz 双声道,直接丢给转写服务结果后半段全是乱码,后来才发现是声道叠加导致的。统一用 FFmpeg 转一下就好了:
ffmpeg -i input.mp4 -ar 16000 -ac 1 -f wav output.wav3.2 OCTO 工作流和外围服务配置
OCTO 工作流这一侧,我把它部署在同一台内网机器上,好处是 Octo-ASR 转写服务和工作流之间的调用不走公网,延迟低、稳定性高。
工作流里需要准备几个节点:
- Webhook 触发节点:用于接收“新录音文件已就绪”的事件
- HTTP 调用节点:请求 Octo-ASR 的转写接口
- 大模型节点:读取转写文本,输出结构化纪要
- 任务派发节点:调用 Agent 平台 API 创建待办
- 通知节点:把纪要摘要和待办清单推到 IM 群
这些节点在 OCTO 管理台里通过表单配置即可。配置时需要注意:大模型节点输出的字段名要和派发节点的输入字段严格对应,比如 todos、owner、deadline。我在第一次配置时字段名写岔了(模型输出 todo,派发节点读 todos),结果解析直接失败,排查了半天。建议在 OCTO 平台上把节点间的数据格式先在测试事件里跑一遍,确认字段映射没问题再挂正式触发。
4. 核心流程实现:从音频到待办的完整链路
4.1 音频接入与转写调用
我的第一版用了文件监听方式:录音文件通过上传工具丢到指定目录,一个监听脚本发现新文件后自动触发工作流。相对简单可靠,适合验证全链路。
如果对实时性有要求,可以用流式转写。Octo-ASR 提供了 WebSocket 接口,支持边传音频边拿转写结果。批处理跑通之后,我在第二版切成了流式,核心代码如下:
import asyncio import json import websockets async def stream_transcribe(pcm_path): async with websockets.connect("ws://localhost:10096") as ws: await ws.send(json.dumps({ "chunk_size": [0, 10, 5], "wav_format": "pcm" })) with open(pcm_path, "rb") as f: while True: chunk = f.read(3200) if not chunk: break await ws.send(chunk) result = await ws.recv() text = json.loads(result).get("text", "") if text: print(text, end="") await ws.send(json.dumps({"is_final": True})) asyncio.run(stream_transcribe("meeting_stream.pcm"))这里 chunk_size 里的 [0, 10, 5] 表示分片策略,10 是前向缓存块数,5 是右看上下文块数,针对会议对话场景比较稳。如果你发现输出的断句很碎,可以适当调大,让模型看得更多上下文再出字。这个参数没有绝对最优值,跟会议室环境、语速、背景噪音都有关系,需要实测。
4.2 会议纪要整理的提示词设计
转写文本拿到手,接下来是关键一步:让大模型把口语化流水账整理成可用的纪要,同时提取待办。这一步的质量一半靠模型,一半靠提示词。
我用的提示词经过了好几轮迭代,最终版核心结构如下:
你是项目会议纪要整理助手。请把用户的会议转写内容整理成结构化纪要。 要求: 1. 先区分“议题”“结论”“讨论过程”,结论要简洁明确。 2. 提取所有待办事项,输出为 JSON 数组,每个元素包含 task、owner、deadline 三个字段。 3. 如果原文没有明确责任人,owner 填 null,不要自己猜。 4. 如果原文没有明确截止时间,deadline 填 null。 5. 输出格式为 JSON,不要输出多余解释。 转写内容: {transcript_text}用 JSON 输出是为了让后续派发节点能直接解析。有朋友问我要不要给大模型一个“待办示例”来约束格式,我试过,确实能减少格式飘逸,但也会让模型在遇到零散内容时强行套模板,反而漏掉真正的待办。最后我选择只给字段名和约束规则,不给完整示例,实测效果更稳。
有个细节:deadline 的解析可以交给大模型做标准化。会议的原始说法可能是“下周三之前”“月底前”“这周五下班前”,让大模型直接把这些说法转换成 ISO 日期字符串,后面派发的时候能省掉一层日期解析逻辑。我是这样约束的:
deadline 字段必须是 ISO 格式的日期字符串,例如 2025-06-10。如果原文说“下周五”,按照当前日期 2025-06-09 推算。当前日期要动态拼进提示词里,不然模型没有时间参照系,容易算错。这个坑我踩过,第一次没给当前日期,模型把“下周三”理解成了并不存在的日期,派发出去之后闹了乌龙。
4.3 待办派发到 Agent 的实现
大模型输出 JSON 之后,OCTO 工作流里的派发节点负责把待办转成 Agent 平台的任务。调用方式很简单:
import requests import json import logging logger = logging.getLogger(__name__) def dispatch_todo(todo, meeting_summary, meeting_id, index): payload = { "title": todo["task"], "assignee": todo.get("owner") or "unassigned", "due_at": todo.get("deadline") or None, "source": "octo_asr_meeting_workflow", "source_meeting_id": meeting_id, "context": meeting_summary } headers = { "Content-Type": "application/json", "Idempotency-Key": f"{meeting_id}:{index}" } resp = requests.post( "https://your-agent-platform.internal/api/v1/tasks", json=payload, headers=headers, timeout=15 ) if resp.status_code not in (200, 201): logger.error("派发失败: %s %s", resp.status_code, resp.text) # 这里触发 OCTO 工作流的重试分支重点说三个细节。
第一个是“幂等键”设计。会议可能被重新处理,或者派发节点重试,如果没有幂等键,同一个待办会被创建两遍。我用的方案是把 meeting_id 和待办序号拼成一个唯一的 Idempotency-Key 传给任务平台,平台侧必须按这个键做去重。这样即使 OCTO 工作流因为超时重试,也不会产生重复任务。
第二个是上下文传递。派发待办时不光要传任务标题,还要把会议纪要中相关的上下文段落一起传过去。收到待办的 Agent 才能搞明白这个任务为什么存在。没有上下文的任务就像凭空冒出来的指令,执行效果很差。
第三个是失败分支。请求超时或者平台 5xx,不能直接丢弃,要让 OCTO 工作流进入重试或者人工兜底分支。我在工作流里加了一个规则:连续重试三次仍失败,就把这条待办送进“待人工处理”列表,并给管理员推通知。宁可让人工看见也不要静默丢失。
4.4 全链路测试效果
我用一次实际的项目例会做了验证。会议录音 47 分钟,主持人讲话节奏偏快,中间有一段多人讨论。
转写文本大致长这样(节选):
嗯那这个权限改造的事情我觉得还是得由小王那边来推进,毕竟底层数据权限之前就是他负责的。然后呢,前端这边下周三之前需要把原型定稿,不然测试排期就来不及了。另外腾讯会议录制文件导出之后记得要上传到网盘,方便后面追溯。Octo-ASR 转写时我把“权限改造”“数据权限”“原型定稿”这些词放进了热词表,识别精度明显提升。热词表配置:
{ "hotwords": { "权限改造": 10, "数据权限": 10, "原型定稿": 8 } }repeated 权重越高,模型越倾向于识别这个词,但别调太高,我之前把某个冷门字段名权重拉到 50,结果模型满嘴都是这个词,连“权限”都往这个方向靠了,矫枉过正。
整理之后得到的待办清单如下:
{ "todos": [ { "task": "推进底层数据权限改造方案落地", "owner": "小王", "deadline": "2025-06-13" }, { "task": "前端原型定稿", "owner": "前端负责人", "deadline": "2025-06-11" }, { "task": "上传会议录制文件到网盘", "owner": "会议发起人", "deadline": null } ] }像“腾讯会议录制文件导出之后记得要上传”这种原本我很容易漏掉的待办,大模型直接抓出来了,这是这套方案里最让我惊喜的地方。之前人工整理,这种“顺嘴一提”的任务大概率就丢了。
5. 常见问题与排查技巧实录
5.1 转写质量问题的排查思路
我在八小时的实际使用中积累了一份问题排查表,分享给你:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 专有名词识别错误 | 模型没有见过这个词 | 配置热词表,提升权重到 8-10 |
| 双声道音频转写乱码 | 声道未合并导致语音叠加 | FFmpeg 统一转成 16kHz 单声道 WAV |
| 输出断句严重碎片化 | chunk_size 太小,上下文不足 | 调大 chunk_size 的前向缓存块数 |
| 长音频后半段丢失 | 内存溢出或模型上下文窗口不够 | 开启 VAD 分段,分段转写后合并 |
| 数字、日期成乱字 | 未开启标点与逆文本规整 | use_itn=True |
这里要特别提醒:热词表不是越多越好。我建议只录入项目专有名词、产品名、人名、缩写这些模型大概率识别不好的词,权重控制在 8-10 之间就行了。权重太高会牺牲其他词的识别准确率,得不偿失。
5.2 纪要整理和待办提取的坑
待办提取这一环节,遇到最多的三类问题:
第一类是待办漏提取。尤其是那些以“记得”“回头”“有机会”开头的句子,模型倾向于认为它们不是强待办。我的解决办法是在提示词里加一条:“即使原文使用了不确定的语气词,只要句子表达了需要在未来完成某个动作,就必须提取为待办,并在 task 中保留原文语气词。”加了这条之后,召回率明显上升,但代价是误报也变多了。我宁可多几条误报待办也不愿意漏,因为漏掉的代价更高。
第二类是责任人不明确。原文经常是“那边的人跟进一下”“负责这个模块的同事看一下”,模型识别出 owner 为 null 就完事了。但派发节点需要 owner 否则无法创建任务,所以我在工作流里加了一道“负责人补全”逻辑:owner 为 null 时,从项目排期表或组织架构表里查一下这个模块的负责人。虽然不保证完全准确,但至少把任务派到了正确的团队。
第三类是 deadine 乱推。前面说过必须把当前日期动态放进提示词,不然模型没有时间参照。另外,如果会议中明确说“这个不急”,deadline 应该置为 null,不要给它默认一个日期。宁可没截止时间,也不要瞎编一个,否则会在 Agent 那边触发错误的优先级逻辑。
5.3 Agent 派发失败与重复派发的处理
派发失败基本逃不开这几种情况:任务平台接口超时、鉴权过期、字段校验不通过。我处理的办法是:
超时问题:设置重试,每次间隔递增(30 秒、1 分钟、3 分钟),最多三次。重试必须配合幂等键,否则大概率重复建任务。我见过同事在处理类似问题时没加幂等,结果工作流一重试,同一个待办建了六条任务,项目群里直接炸锅。
鉴权过期:在工作流里提前预检 token 有效期,避免等调用了才发现过期。这个预检节点非常简单,就是读一遍 token 的过期时间字段,如果快到期就提前刷新。
字段校验问题:把 OCTO 工作流里模型输出的 JSON 先做一层 schema 校验,缺字段就触发“修复节点”——把报错信息返回给大模型,让它补全字段重新输出。这个模式我用下来很管用,有点像人审流程,AI 自己发现格式不对,自己改,不用人来盯。
还有一个重复派发的隐蔽来源:Octo-ASR 转写节点超时后,工作流自动重跑了整个流程,但录音文件还在,于是整体又转写了一遍。这种情况下,即使待办派发做了幂等,纪要整理这步的重跑也可能产生重复消耗。我的解决办法是给整个会议录音的处理设置一个 dedup 标记:同一个文件如果已经完成转写并生成了指纹,直接跳过。这个指纹我用录音文件的大小加前 1 秒音频的 hash,简单够用。
结尾
整个方案从想法到稳定运行,前后大概花了两周。中间最有价值的体会是:不要把最开始的链路设计得太复杂,先用手头的录音文件跑通批处理,确认 Octo-ASR 转写质量、大模型纪要输出、待办派发三个关键环节都符合预期之后,再去上流式和实时会议场景。
最后再分享一个小技巧:给这套工作流加一个“每日 18:00 定时巡检”节点,把所有当天生成的待办汇总成一张清单,推送到项目管理群里。这样即使有漏派或者派错的任务,也能在当天被发现,而不是等到第二天追问时才后悔。自动化不是把人的事全部甩给机器,而是让机器把重复的脏活干完,人只需要在关键节点做确认就够了。