多路电话会议 · 实时语音流纪要 — 技术架构流程

用途:研发团队方案讨论  |  前置约束:全链路开源 + 私有化部署  ·  SIP 窄带 8kHz  |  目标:对标腾讯会议 AI 小助手纪要能力
已确认前提以下四项已拍板,不再作为待决策项
① 部署约束
全链路开源 + 私有化,数据不出域
② 音频格式:SIP/PSTN 窄带 8kHz
带宽仅 300–3400Hz,4kHz 以上频段缺失。这是全案最大的物理约束,见下方专项。
③ 会中范围
只做转译 + 原子事件。不整合、不总结、不出议题。
④ 会后范围
议题切分 + 归并去重 + 成稿。触发点:全体挂机。
触发阈值:1500 token(≈10 分钟)已定,一场 2 小时会议产出 10–12 个事实段。
重点风险8kHz 窄带对全链路的影响
环节影响应对
识别准确率 带宽缺失 4kHz 以上频段,辅音(zh/ch/sh/f/x/s)辨识度下降。CER 由宽带 5–8% 升至 15–25%。这是物理限制,不是模型不够大。 ① 8k→16k 重采样后进气(用 sinc/多相滤波,不要线性插值)
② 三方实测选型(见待决策 #6)
③ 窄带专用模型或微调
实体准确率 人名、数字、金额、项目名错误率显著上升。直接冲击纪要可信度——一个错的金额就能废掉整段结论。 热词表从“优化项”升为“必需品”:通讯录 + 项目库 + 历史会议实体,在 ASR 阶段就注入;会后追加实体矫正环节
声纹 / 说话人 窄带声纹 EER 上升明显,跨会议身份匹配不可靠。 影响可控:多路独立流下 speaker 由 stream_id 直接决定,不依赖声纹。声纹仅用于跨会议身份绑定,可降级为人工确认
前端处理 无需 AEC(每路独立流,不存在回声路径);窄带底噪与编码失真需针对性处理。 重采样 → VAD → 窄带降噪;降噪模型的训练域要对准 8k,不能直接用宽带降噪模型
编码(待确认) G.711(64kbps)几乎无损;G.729(8kbps)有强压缩失真,CER 会再抬 3–5 个点。 若链路可控,优先固定 G.711——不改模型就能拿到的准确率提升,性价比最高
一句话:8k 窄带下,模型选型的天花板比算力更早卡住你。花在热词库和窄带实测上的时间,回报远高于换更大的 LLM。

0. 三条设计原则(决定下面所有细节)

原则一:双轨制
会中产出草稿,会后产出终稿。会中为保证低延迟必须牺牲上下文,精度天然低于离线。用流式结果当终稿,等于白扔 5–10 个点准确率。
原则二:文本源与结构分离
事实段只存结构化事实 + evidence 锚点,不存原文。会后用离线重转文本回填。这样文本精度可独立升级,结构不受影响。
原则三:幻觉在每一层拦,不靠最后一道 prompt 求它别编
每条结论/待办必须有 evidence(utt 编号);实体必须与原文字符串匹配;不匹配就降级为"待确认"或丢弃。缺 evidence 的条目一律不进入终稿。

1. 全景架构:两条轨道

TRACK A 会中实时轨(低延迟优先)
多路音频接入
SIP/RTP · 每路独立 track
→
前端处理
8k→16k 重采样 · VAD · 窄带降噪
→
分叉
同一路音频,双消费
① FunASR 流式
已有实现 · 字幕专用
→
实时字幕展示
延迟 300–800ms
‖
② Qwen3-ASR-1.7B
纪要文本源
⚠ 8k 需实测
→
统一转写流
全局 time-order · turn_id
→
攒够 1500 token
≈10 分钟 · 每会 10–12 段
→
Qwen3-32B
原子事件抽取
→
幻觉闸门
evidence 必填
→
事实段草稿
增量落库
关键点:FunASR 与 Qwen3-ASR 不做一致性仲裁。FunASR 只负责字幕展示(延迟优先),Qwen3-ASR 是纪要的唯一文本来源(质量优先)。两套文本各自独立,避免"谁的文本算数"这类无解争议。
TRACK B 会后批处理轨(质量优先 · 全体挂机后触发)
落盘音频
每路原始流
→
Qwen3-ASR 离线全量重转
非流式 · 上下文完整 · 精度最高
→
全局转录
turn_id 对齐会中版本
→
Qwen3-32B
全局议题切分(独立跑)
事实段草稿
会中累积
+
全局议题树
一级/二级
→
reduce-A
事实归并 · 去重 · 冲突检测
→
reduce-B
议题标题 · 结论 · 排序
→
终检闸门
evidence + 实体匹配
→
终稿纪要
可回听 · 待办可派工
必须显性失败:任一 chunk 处理失败,终稿对应时间段必须标注"该段未处理"。一份看起来完整、实际漏了 20 分钟的纪要,比明确标注缺失的纪要危险得多。

2. 会中轨详细流程(每个触发周期执行一次)

STEP 1 · 全局时间轴重建 前置必做
多路独立流各自产出 utterance,按 t_start 合并成一条全局时间轴。
speaker_id 必须全局唯一(stream_id + 声纹注册),严禁每个 chunk 重新编号——否则归并时同一个人会变成两个人。
↓
STEP 2 · 攒够触发阈值:1500 token(≈10 分钟)已定
中文约 150 token/分钟 → 1500 token ≈ 10 分钟语音。一场 2 小时会议产出 10–12 个事实段。
单次 map 延迟约 8–15 秒(32B FP8 双卡,输出约 400 token),不阻塞实时字幕,会中每 10 分钟一次完全可接受。
↓
STEP 3 · 切分 + 责任区划分
边界优先级:静默 >1.5s > 元话语标记("接下来/另外一件事/总结一下")> 句末。不按 token 硬切(会把"三百 / 五十万"劈开)。
每段带前后各 250 token 上下文尾巴(约为本体的 1/6,不宜再大,否则输入浪费且易串段),但只允许在责任区内抽取;重叠区归后一个 chunk。
注意:1500 token 的本体较小,跨段边界密度随之升高,边界识别准确率比大 chunk 方案更关键——切错一次,两段纪要都错。
↓
STEP 4 · Qwen3-32B 原子事件抽取(map)
只抽事实,不做议题判断、不做整体总结。议题需要全局视野,局部判断必然错。
六类事件:decision / action_item / question / data / dispute / risk
输出 boundary_flags 标记头尾是否半截,供会后缝合。
↓
STEP 5 · 幻觉闸门(三道)
① evidence 为空的条目直接丢弃  ② 约束解码强制 JSON Schema,禁止自由文本  ③ 金额/日期/人名做字符串匹配,原文中没有的降级为"待确认"
↓
STEP 6 · 事实段草稿落库
存结构化事实 + evidence 锚点,不存原文文本。会后离线重转后按 turn_id 回填,文本精度升级不影响结构。

3. 会后轨详细流程(全体挂机触发)

STEP 1 · 离线全量重转
Qwen3-ASR-1.7B 非流式,对落盘音频整段重转(可带完整上下文,精度高于会中流式)。
产出全局转录,按 turn_id 与会中版本对齐,用于回填 evidence 原文。
↓
STEP 2 · 全局议题切分(独立于 map 结果)
两遍法:候选边界召回(声学 + 元话语 + 句向量相似度谷值)→ Qwen3-32B 精判 → 全局重排成一级/二级层级。
用 utterance_range 做锚点,不用时间(时间模糊、turn 索引精确)。
↓
STEP 3 · reduce-A:事实归并
① 按 utterance_range 把事件对齐到议题 ② 跨 chunk 去重(embedding 召回 0.82 → LLM 判定,避免 O(n²))
③ 冲突检测:同一待办不同 owner → 不自行裁决,标 needs_confirmation 并列全部来源 ④ boundary_flags 缝合半截议题
↓
STEP 4 · reduce-B:呈现层
重新生成议题标题、写结论、按重要性排序。只能使用给定 facts,禁止引入新信息——与 reduce-A 分开跑,防止模型为行文流畅"顺手"编过渡句。
↓
STEP 5 · 终检闸门 + 输出
evidence 存在性 → NLI 校验(Qwen3-8B 判断原文能否支持该表述)→ 实体数值匹配 → 全部通过才进终稿。
未处理段显式标注。终稿支持点击回听原音。

4. 数据契约:事实段 Schema

// 会中 STEP 6 落库的结构(注意:无原文文本,只有 evidence 锚点)
{
  "segment_id": "M20250115:s03",
  "chunk_id": 3,
  "turn_range": [121, 198],        // 全局 turn 索引,非时间
  "time_range": ["00:38:12", "00:52:40"],

  "events": [
    {
      "type": "decision",          // decision|action_item|question|data|dispute|risk
      "text": "确定 Q3 追加市场预算 300 万",
      "evidence": ["M20250115:00152"],  // 必填,空则丢弃
      "speaker": "S_003",            // 全局 speaker_id
      "confidence": 0.90
    },
    {
      "type": "action_item",
      "text": "输出追加预算的审批材料",
      "owner": "S_001",
      "due": "下周三",
      "evidence": ["M20250115:00160"],
      "confidence": 0.85
    }
  ],

  "boundary_flags": {              // 会中缝合的关键信号
    "starts_mid_topic": true,       // 头是半截 → 需与前一 chunk 拼
    "ends_mid_topic": false
  },

  "status": "ok",                  // ok | degraded | failed → failed 必须显性标注
  "model_ver": "qwen3-32b@v3",
  "prompt_ver": "map-extract@v7"   // 幂等 key 的一部分
}
原子事件的四条红线(可直接写成校验规则)
  1. 单个事件关联的 utterance 不超过 3 条——超出说明已经在做归纳
  2. 必须归属到唯一说话人;没有明确说话人的直接不输出
  3. 文本中禁止出现 总体而言 / 主要讨论了 / 整体上 / 达成一致 等归纳词
  4. 禁止出现 议题 / 方面 / 几个问题 等结构词——那是会后的事
第 3、4 条用正则就能卡,实现成本几乎为零,但能挡住绝大多数“事件变议题”的漂移。
为什么不能“会中直接出议题”
议题归属需要全局视野。会中每个 chunk 只有 10 分钟信息,判出的归属在会后全局重排时必然被推翻——等于先做一遍再全部作废,白付一次算力,还会污染会中展示。

议题是会后产物,不是会中产物。会中只产出能被全局复用的事实单元。

5. 会中到底给用户看什么(产品预期先对齐)

会中:事实流,不是议题
时间轴上按顺序排列的事件卡片:结论 / 待办 / 问题 / 数据 / 分歧 / 风险。
不做议题归纳、不做标题、不做排序、不做跨段整合、不做总结。

每个事实段独立产出、互相不知道彼此存在——这是刻意的:只有彼此独立的事实单元,才能在会后被自由重组到任意议题下。任何会中的“整合”都会把全局重组的自由度锁死。
会后:按议题组织的终稿
全局议题切分 → 事件对齐 → 归并去重 → 层级化标题 → 结论。
这才是用户认知里的「会议纪要」。

会中与会后看的不是同一个东西,这点必须在需求和 UI 文案上先讲清楚,否则上线后必然收到“我会上看到的议题怎么变了”的反馈。
术语收敛:会中产出统一叫「事实段」(UI 上显示为「实时要点」),会后产出才叫「会议纪要」。两者不是同一物的两个版本,而是原料与成品。叫法混用是需求传递中最容易出错的环节,建议在接口命名、数据库表名、UI 文案上全部统一。
唯一例外——有预设议程的会议。如果会议预约/日历中已给定议程项,会中可以做「对齐」:把事件映射到已知议程项(属于分类,不是发现)。因为议题是外部给定的,不存在被推翻的风险,这时会中展示“当前围绕议题2讨论”是可靠的。
但即使是这种情形,事件本身仍然要独立抽取,不能把议题归属写进 map 的产出物里——否则议程临时变更时整段得重跑。

6. 坑 → 防法 → 落在哪个环节

坑后果防法落在哪个 STEP
每 chunk 重编 speaker_id 同一个人的发言被当成两个人,待办归属全错 全局 speaker_id = stream_id + 声纹注册,跨 chunk 不重编 会中 STEP 1
按 token 硬切 实体被劈开("三 / 五百万") 边界优先落在静默、元话语、句末 会中 STEP 3
重叠区重复抽取 同一待办出现两条 责任区 + 上下文明示"仅供理解,不要抽取" 会中 STEP 3
map 里判断议题 局部误判导致议题错乱 议题切分剥离为独立步骤,用全局转录 会后 STEP 2
话题跨多个 chunk 一个议题被拆成三段 boundary_flags + 按 turn_range 缝合 会中 STEP 4 → 会后 STEP 3
reduce 写过渡句 凭空生成"会议达成一致意见" reduce-A / reduce-B 分离,B 只能用给定 facts 会后 STEP 4
缺 evidence 也放行 幻觉进终稿,可信度崩塌 evidence 必填 + 实体字符串匹配校验 会中 STEP 5 / 会后 STEP 5
冲突自行裁决 取"最新"或"最频繁" = 在编 标 needs_confirmation,并列全部来源,交人工 会后 STEP 3
chunk 失败静默跳过 纪要缺段但看不出来 status=failed + 终稿显式标注"该段未处理" 会后 STEP 5
流式文本当终稿 白扔 5–10 个点 CER 会后必须离线重转一遍 会后 STEP 1
8k 用线性插值上采样 引入镜像噪声,反而拉低识别率 用 sinc / 多相滤波重采样 会中 STEP 1 前端
窄带无热词表 人名项目名系统性错误,纪要全篇不可用 热词库列为一等公民,ASR 阶段注入 + 会后实体矫正 会中 STEP 1 / 会后 STEP 1

7. 待研发决策(建议会上逐条拍板)

#1 触发阈值:1500 token(≈10 分钟)— 已定
三项连带影响,需同步确认:
① overlap 从 600 降到 250。600 相对 1500 占输入 40%,浪费严重且容易串段。
② 议题天然跨 2 段。一个议题常持续 10–20 分钟,而段是 10 分钟,所以 boundary_flags 缝合会从"例外"变成"常态"。这是 1500 阈值最真实的代价:reduce 阶段工作量上升,且进一步印证「会中判议题不可行」。
③ 显存压力大幅缓解。单次 map 输入约 2500 token(1500 本体 + 500 overlap + prompt),32B 单请求 KV 仅约 0.6GB——2 卡 H20 FP8 可支撑数十路会议并发,原 8K 方案的并发瓶颈不再成立。待决策 #5 的算力估算需按此重算。
#2 Qwen3-ASR 在会中实时跑,还是只落盘、会后批量跑?
会中实时跑:事实段能实时产出,但对 GPU 有并发压力(需与 FunASR 抢卡)。
只会后跑:算力省、精度高,但会中无事实段可看。
折中:会中跑 Qwen3-ASR-0.6B(轻),会后用 1.7B 离线重转。
#3 双 ASR 文本不一致时,以谁为准?
当前方案:不做仲裁。FunASR 只服务字幕,Qwen3-ASR 是纪要唯一文本源。
需确认:用户点"查看原文"时,跳转看到的是 FunASR 字幕还是 Qwen3-ASR 转写?两套文本都存,UI 需明确。
#4 会中「实时要点草稿」是否对用户可见?
第 5 节已定调:会中只展示事件流、不展示议题。本条要定的是可见性与文案。
建议可见,标题用「实时要点(草稿)」,页脚标注「会后将按议题重新整理」。
需产品确认:是否允许用户在会中对某条要点做「保留 / 忽略」标记?若允许,该标记必须带入会后 reduce,否则用户的手工编辑会被覆盖——这是最容易被忽略、但上线后投诉最多的一个点。
#5 算力分配:一台 8 卡 H20 96G 是否够?
估算:2 卡 Qwen3-32B(TP=2, FP8)+ 2 卡 ASR 集群(FunASR + Qwen3-ASR 混跑)+ 1 卡 Qwen3-8B(校验/议题精判)+ 1 卡 embedding/rerank/声纹 + 2 卡冗余。
需按"实际并发会议数 × 每路流数"重新压测确认,不要按经验拍。
#6 8k 窄带下,纪要的文本源到底用谁?最高优先级
已确认是 8k 窄带,原“Qwen3-ASR 作纪要唯一文本源”的假设需要实测验证——Qwen3-ASR 与 FunASR 均非窄带专属训练,谁在 8k 上更强目前无定论。
A. 单模型:FunASR 同时供字幕与纪要。最简单、成本最低,精度天花板低。
B. 双模型:FunASR 供字幕,Qwen3-ASR 离线供纪要。需先证明其在 8k 上确实更优。
C. 窄带专用/微调:引入 TeleSpeech-ASR 或用自有电话录音微调。效果最好,投入最大。
动作:用 20 场真实 8k 电话会做三方对比,判据是实体错误率而不是 CER。
#7 SIP 编码协商是 G.711 还是 G.729?
G.711(64kbps)几乎无损;G.729(8kbps)有强压缩失真。若链路可控,优先固定 G.711——不改模型就能拿到的准确率提升,性价比最高。