前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9654
  • 给多Agent加上预算限制与人工审批
  • AgentSec 用对抗测试排查智能体安全漏洞
  • 美团如何用统一模型承接外卖多业务精排
  • Safari 空白页如何卡住 MCP 权限校验
  • 会议录音应用的说话人识别与检索踩坑
  • 给 AI 产品文档加一道事实校验关
  • 防止 Agent 靠修改测试制造假通过
  • 故意破坏代码,揭穿回归检查的假绿灯
  • 模型版本变了,评测分差就不能直接比较
  • JetBrains 版 Copilot 增强模型与 MCP 管理
  • 点选界面元素,把修改需求送到对应 Agent
  • 跨公司 Agent 协商,用代码落实数据契约
  • 代码送入远程模型前先做数据分级
  • 把 Agent 权限约束落实到执行层
  • 模型评测暴露环境破坏与联网绕限制行为
  • 微软Decision-1主攻低延迟分类与路由
  • 腾讯元器停服,智能体业务需限期迁移
  • Anthropic暂停全部内部评测联网
  • 模型单价相同,任务成本仍可差近一倍
  • 测 LLM 接口延迟,别只报平均数
  • 用 TypeScript 拦住格式正确的错误数据
  • 微软在 Foundry 推出 Qwen 决策模型
  • 大模型故障切换也要守住能力边界
  • 弱网下如何保住大模型流式响应
  • 用 CI 硬约束控制 AI 代码膨胀
  • Agent 上线前,先验证工具权限边界
  • steadysdk 降低接口客户端再生成风险
  • 生产 Agent 的权限不能照搬人类账号
  • 递归生成可验证的终端 Agent 难题
  • 用缺陷风险排序分配代码审查预算
  • 用真实几何验证模型修复代码的能力
  • 用 Hooks 强制执行 AI 编码质量检查
  • TeamAI 用 Git 同步团队 Agent 配置
  • 为编码助手补充 SwiftUI 开发规范
  • LiteLLM 统一多家模型接口与调用治理
  • 已加载 35 / 9654
8.0
热点
AI SCORE
技术实践2026-10-11 03:25

会议录音应用的说话人识别与检索踩坑

dev.to · AI#语音转录#说话人识别#向量检索
Editor brief · 编辑速览

作者构建桌面会议助手,将麦克风和系统音频接入实时转录、说话人识别及网页端摘要与语义检索。文章梳理转录停滞、视频声音误认、双音源冲突、人名识别错误和向量未存储等故障。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

我不是在做又一个录音工具。

我在做一个会议助手。

一个放在桌面上的小应用,听着通话,把内容记录下来:

然后把对话保存到一个地方,方便团队以后找到。

录下一场三人通话。

你应该能看到清晰的转录文本、摘要,以及标在自己发言旁边的名字。

然后,我播放了一段 YouTube 视频。

应用却认定,YouTube 视频里的声音就是我。

Bug #1 —— 转录文本卡住了

Bug #2 —— YouTube 视频成了我

Bug #3 —— 两个麦克风,一个真相

Bug #4 —— 电脑音频让我成了陌生人

Bug #5 —— “记住这个声音”什么也没记住

Bug #6 —— “Okafor”变成了“Aquifer”

Bug #7 —— 从未存进去的向量

最终架构

Your Microphone + Your Computer's Audio
              ↓
      Live Transcription
              ↓
      Who Is Speaking?
              ↓
      Save to the Web App
              ↓
      Summary + Search

最终效果应该是:

每一行都标着正确的名字。

以后可以按语义找到内容,而不只是靠关键词。

听起来很简单。实际并不是。

这个系统由四部分组成。

桌面应用用 Electron 构建。它会采集麦克风音频,也可以把电脑声音作为第二个声道一起采集,这样通话另一端的声音就能与你的声音分开录制。

你说话时,音频会持续流向 Deepgram 的语音转文本模型。中间经过一个小型本地中继服务,由它保管 API key,因此密钥不会随应用一起分发。

返回的文字已经按不同声音分好了。Deepgram 把这叫作 diarization,也就是区分出 Speaker 0、Speaker 1、Speaker 2。

但 diarization 只知道这些声音来自不同的人。

识别具体是谁,是说话人识别引擎 VoiceBio 的工作。

你只需要读一遍简短的段落。它就会建立 voiceprint,也就是你声音的数学指纹。voiceprint 经过加密,只保存在你自己的电脑上。

录音过程中,每个声音都会取几秒钟,与它进行比对。引擎返回一个相似度分数:分数高,表示“这是你”;分数低,表示“这是别人”。

Web 应用永远接触不到你的声音。它只接收姓名标签。

数据库负责保存记录。向量数据库 Pinecone 负责建立索引。

每份转录文本都会被切成片段、转换为 embeddings 并存储起来,这样你以后就能按表达的意思搜索,而不只是按说过的字词搜索。

3. Bug #1 —— 转录文本卡住了

录音进行到一半,实时文字停住了。

不是变慢,而是彻底停住。

日志显示,有一次声音验证整整耗时 120 秒。

声音验证沿用了 voiceprint 注册时的超时设置。注册需要上传一段较长的录音并进行训练,所以给了两分钟。

验证一段五秒钟的音频,应该只需要一秒左右。

于是,我给验证操作单独设置了较短的超时时间。现在,验证太慢就会迅速失败,转录文本仍然能继续输出。

超时不是一个无关紧要的细节。它决定了用户要为什么等待。

4. Bug #2 —— YouTube 视频成了我

我开始录音,播放一段视频,然后开口说话。

视频里的声音被标成了我。

我却被标成了“Speaker 2”。

等我的名字终于出现时,已经过去了大约 15 秒。

在任何声音通过验证之前,应用先做了一个猜测:“第一个听到的声音,大概就是用户。”

在安静的房间里,这个猜测还算合理。

但只要有别的声音先响起来,它立刻就错了。

而真正的验证需要先收集几秒钟的语音,再加上处理时间,所以这个猜测会在屏幕上停留很久,看起来就像已经确认的结果。

如果你已经有 voiceprint,那么在引擎确认之前,谁都不能被认作“你”。在此之前,姓名会以灰显状态显示,并标注“Identifying…”。

但 15 秒还是让人觉得太慢。

5. Bug #3 —— 两个麦克风,一个真相

后来,我借鉴专门的会议记录工具的做法,想到了一个更好的办法。

别问这个声音听起来像谁。

先问它来自哪个麦克风。

Channel 0 → Your Microphone    → You
Channel 1 → Your Computer      → Everyone else on the call

Deepgram 在同一条连接上转录两个声道,并为每句话标上所属声道。你的话来自你的麦克风,因此你的名字可以立即显示。

如果用扬声器而不是耳机,麦克风也会听到通话另一端的声音。同一句话会出现两次:一次来自电脑,一次来自我的麦克风。

我加了一个回声过滤器:如果电脑声道在两秒内说过相同的话,就丢弃麦克风声道里的那一行。但简短回应绝不能丢,因为别人说话时插一句“yeah”,确实是人们会有的正常交流。

接着,又出现了一个更隐蔽的问题。

在说话的间隙,少量回声漏过了过滤器,别人的话上会短暂闪出我的名字。我试着先等一段“安静窗口”,再信任麦克风。先是两秒,然后是四秒。

无论窗口设多长,都无法保证可靠。

声道告诉你音频来自哪里,却不能告诉你是谁说的。

因此,只要存有 voiceprint,麦克风里的声音仍然要经过验证。

6. Bug #4 —— 电脑音频让我成了陌生人

打开电脑音频采集后,应用完全认不出我了。

similarity: -11   →   not you

以前,我的声音得分大约是 +23。其他人的得分大约是 -57。

-11 落在两者之间,一个很奇怪的位置。

引擎并没有判断错。

是我给它喂了垃圾数据。

双声道音频采用交错存储:一个采样来自麦克风,下一个来自电脑,如此交替。

但截取我声音片段的代码,仍然假定音频只有一个声道。

所以,它发出去的片段:

截取的时间位置不对。

而且把麦克风和电脑的声音逐个采样混在了一起。

修复方法是:按正确的采样速率,只从每个声音所属的声道截取音频。

Me              +9.98   → recognized, locked
Computer audio  -57     → correctly someone else

还有一个额外收获:验证我的声音时,不再连带把通话另一端的音频一起送过去。

7. Bug #5 —— “记住这个声音”什么也没记住

应用有个功能:给说话人起个名字,比如“Milana”,再勾选“记住这个声音”,以后录音时就能自动认出她。

我给她起了名字,然后又录了一次。

没有任何人被标成 Milana。

第一个问题是,名字会立即应用到转录文本上,但训练她的 voiceprint 需要大约 20 秒的语音。语音不够时,训练会悄悄失败。无论成功与否,界面都显示成功。

第二个问题更有意思。

为了凑够 20 秒,我把她说的短句拼成一段音频,去掉了中间的空隙。结果,在 40 秒的音频里,引擎始终只能找到大约 6 秒可用的语音。

同样的音频,如果保留停顿,作为一段连续音频上传,就能立即完成注册。

句子之间的生硬剪切干扰了引擎的语音检测。

用她说话最密集的一段连续音频来训练。

训练失败,就明确告知用户。

随着她的语音不断增加,自动重试。

8. Bug #6 —— “Okafor”变成了“Aquifer”

接下来,轮到文字本身的问题。

我没有继续猜,而是开始测量。我把一份内容已知的文稿通过实际生产环境使用的完整处理流程进行流式转录,再逐词对比。

词错误率为 9.8%。

大多数错误出在人名上:

Okafor → "Akhafar", "Aquifer"

Priya Raghunathan → "Priyuragunathan"

解决办法是告诉模型可能会出现哪些名字。Deepgram 把这叫作 keyterm prompting。

这份名单来自你自己的名字、已保存声音对应的姓名,以及一份可以自行编辑的简短列表。

词错误率降到了 3.3%。所有名字都正确了。

我还试过延长等待时间,再判定一句话结束。结果更差,错误率到了 12%,所以我没有改动这项设置。

需要坦诚说明:我是用合成语音测量的。真实房间里的笔记本麦克风,改善幅度会更小。但人名往往是大家最先核对的文字。

9. Bug #7 —— 从未存进去的向量

录音保存之后:

Save to Database (the record)
          ↓
   Answer the app immediately
          ↓
   In the background:
   Chunk → Embed → Store in Vector Index

切分转录文本与切分文章不同。我按说话人轮次切分,每个片段大约 1,200 个字符,每行写成“姓名:发言”的形式,并把上一个片段的最后一行带入下一个片段,让问题和回答能保留在一起。

每个片段都会存储元数据:录音属于谁、属于哪个 workspace、包含哪些行,以及跳转到对应准确时刻的链接。

这里,隐私很重要。每个用户在索引中都有自己的独立空间,避免一个人的私人对话出现在另一个人的搜索结果里。而且,每条命中结果在展示前,都会再次与数据库核验。

上线了。测试了。搜索了。

后台任务的状态却显示失败:

You've reached the max namespaces allowed in this index (100).

索引满了。100 个空间。我们的是第 101 个。

受影响的还不只是这个功能。整个应用中新建的每个项目和 workspace,都会以同样的方式失败。那 100 个空间里,有 18 个对应的内容早就已经删除了。

排查过程中,我又发现了一个问题。

删除调用传入的是普通的 ID 列表,而库要求把这些 ID 包在一个对象里。所以,删除文档向量的操作一直在悄悄失败,什么也没删掉。已经删除的内容仍然能被搜到。

删除操作已经修好了。

namespace 数量上限涉及套餐和设计选择:升级、清理,或者共用一个空间并采用更严格的过滤条件。写下这些文字时,我们还在做决定。

不过,有一点让我很庆幸。

因为数据库保存的是正式记录,而向量只是索引,所以这次失败没有丢失任何内容。每份录音都已经保存好,只等索引腾出空间,就能建立索引。

10. 最终架构

Microphone ──┐
             ├──► Two-Channel Stream ──► Local Relay ──► Speech-to-Text
Computer ────┘                                         (names hinted)
                                                             │
                                                             ▼
                                                  Lines, split by voice
                                                             │
                          ┌──────────────────────────────────┤
                          ▼                                  ▼
              Voice check, own channel only            Echo filter
              vs voiceprint on this computer
                          │
                          ▼
             You / Named / "Identifying…"
                          │
                          ▼
             Upload (retry-safe, works offline)
                          │
                          ▼
             Database (the record) ──► Summary
                          │
                          ▼
             Chunk ──► Embed ──► Vector Index (per user)
                          │
                          ▼
             Search by meaning ──► check owner ──► open the exact line

灰显的“Identifying…”胜过一个看似确定、实际错误的名字。

2. 判断是谁之前,先弄清音频来自哪里

声道信息获取成本低,也是很有力的线索。但它不是证明。

3. 模型给出奇怪分数时,先检查你喂给它的数据

-11 不是声音引擎的错,而是我的音频切片出了问题。

4. 先测量,再调优

一份内容已知的文稿,加上词错误率,就让我知道问题在人名,而不是模型。

5. 把记录与索引分开

当数据库是事实来源,向量只是索引时,向量服务故障只会造成不便,不会造成数据丢失。

6. 向量数据库有些限制,只有到了生产环境才会碰到

比如 namespace 数量上限。还有批量限制:embedding 模型每次调用接受 96 个输入,而我们一次发送了 120 个。

7. 静默失败是最糟糕的失败

“记住这个声音”和删除操作,都是什么也没做,却报告成功。

做这个项目之前,我以为弄清谁说了什么,是模型的问题。

选一个好的语音模型,再选一个好的声音引擎,就完事了。

音频格式问题。

声道布局问题。

向量数据库限制。

每修好一个问题,都会暴露出另一个隐藏的薄弱环节。

现在,这个应用可以实时转录通话,把你的名字正确标在你的发言上,将通话另一端的声音单独保留,并把所有内容保存到团队能找到的地方。

最大的收获,并不是选了一个更好的模型。

而是认识到:弄清谁说了什么,是整个处理流程的问题,不只是模型的问题。

如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
Safari 空白页如何卡住 MCP 权限校验
下一篇
给 AI 产品文档加一道事实校验关