转录RAG分块重叠导致时间戳错位
一个音频转录工具启用重叠分块后,块标注的起始时间与首句实际播放时间出现偏差。文章给出短音频、1200字符分块和30秒重叠的复现配置,呈现引用定位失真的问题。
AI 深度解读
RAG 检索到了正确文本,引用时间却晚了近半分钟。作者在自己的音频转写工具里发现:开启重叠分块后,文本补进了上一块的内容,时间戳和字符数却仍按新内容计算。任务显示成功,错误会一直潜伏到有人点击引用。
这篇值得读的地方,是它把一个小型分块函数里的数据契约问题拆得很具体:怎样让正文、重叠上下文和元数据保持一致,以及修好以后仍有哪些精度边界。
为什么多补了上下文,引用反而错了
作者运营一家位于巴厘岛的签证机构,也开发 Apify Actors。其中的 Audio Transcriber 接收录音或播客链接,输出转写文本、带时间的片段,以及可选的说话人标签、SRT 和检索分块。
它的用途不只是识别文字,还要支持反向定位:看到一句话,能找到它在录音中出现的位置。因此,chunkForRag 承诺每个分块都保留起始时间。
2026 年 10 月 25 日,作者开启 overlap,也就是把上一块末尾的一段内容重复放到下一块开头。这样,横跨分块边界的问题仍有机会检索到完整上下文。
测试使用一段四分钟的公共领域录音,参数为 quality: "fast"、includeSegments: false、chunkForRag: true、chunkSize: 1200、chunkOverlapSeconds: 30,最终得到三个块。
问题出在后两个块。Chunk 1 标注从 00:01:25 开始,第一句话实际出现在 56.98 秒,即 00:00:56,晚标了约 29 秒。Chunk 2 也晚了 23.68 秒,而且文本直接从「out」开始,切在半句话中间。
检索文本正确,不代表引用位置正确。
这类故障不会让任务失败。运行状态仍是 SUCCEEDED,数据集有结果,文字顺序也正常,嵌入流水线照样会把错误时间戳存进去。
修复的关键,是先拼好文本再计算标签
作者把问题定位到约四十行的分块器,以及负责输出分块的 flush() 闭包。
这里有三个错误。第一,start 取自 current[0]['start'],代表新片段的开始;随后才添加上一块的前缀,时间没有跟着前移。第二,charCount 使用 len(text),漏算了前缀。第三,重叠窗口只筛选起始时间落在窗口内的识别片段,没有照顾句子边界。
前两个错误来自同一种顺序:先描述结果,再修改结果。
作者调整后的代码先确定前缀,再生成最终文本:
body_start = current[0]['start']
start = body_start
prefix = ''
if overlap_seconds and chunks:
tail = _overlap_tail(chunks[-1], overlap_seconds)
prefix = plain_text(tail)
if prefix and tail:
start = tail[0]['start']
full = f'{prefix} {text}'.strip() if prefix else text
要看的是 start = tail[0]['start']:只要前面实际加了内容,分块起点就跟随那段内容。随后,charCount 改为 len(full)。
输出还增加了两个字段:overlapChars 表示开头有多少字符属于重复内容,bodyStart 表示本块新增正文开始的时间。作者给出的使用规则是:按 bodyStart 去重,按 startTimecode 引用。两者回答的是不同问题,不能再共用一个起点。
句子接缝则交给 _overlap_tail()。它仍从 end - overlap_seconds 找候选片段,再向前寻找句末边界;识别规则为 r'[.!?]["\')\]]?$',最多回退到 previous['end'] - 2 * overlap_seconds。
为什么必须限制回退?测试录音的 46 个片段里,有 10 个没有句末标点。其中一个片段长达 226 字符、持续 16.94 秒。作者观察到,fast 模型在说话者不停顿时会漏标点。没有上限,寻找完整句子可能把前缀越拉越长。
这个修复是在限制内改善接缝,不能保证每块都从完整句子开始。没有标点的长段落,仍可能从半句开头。
实测证明了什么,又没有证明什么
作者没有只做本地函数回放,还用同一个测试账号、同一份 JSON,对旧版和新版执行了相隔四十分钟的运行。
原文报告,旧版引用偏移大致随请求的重叠时长增长;把 chunkOverlapSeconds 设为 0 后,两版分块逐字节一致。因此,这次缺陷集中在重叠路径,未启用重叠的默认路径不受影响。不过,输入中没有展示完整的逐项偏移数据,不能进一步推断不同音频上的误差分布。
两版费用相同。分块只处理已有数据,没有额外请求或计费事件。测试按 fast 模型的四个起算分钟,加每文件 $0.002 收费。
其中还有一个容易混淆的细节:durationSeconds 返回 240.01,但仍按四分钟计费。作者的计费器先扣除一秒宽限,再向上取整,避免编码器返回 600.01 秒时,把十分钟文件算成十一分钟。这是该工具的计费处理,不能推广成其他转写服务的规则。
原文提供的同步调用如下,它把结果直接返回给调用方:
curl -X POST "https://api.apify.com/v2/acts/lergassy~audio-transcriber/run-sync-get-dataset-items?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"urls":["https://example.com/episode-12.mp3"],
"chunkForRag":true,"chunkSize":1200,"chunkOverlapSeconds":30,"includeSegments":false}'
要注意最后一行的 chunkOverlapSeconds:它决定是否重复上下文。includeSegments: false 控制输出中不附带片段,不能据此认为分块引用具备逐词精度。
我的判断
我认同作者最有价值的结论:扩展正文时,描述正文的元数据也必须一起更新。 字符数、起点、正文起点看起来只是附属字段,进入引用和去重链路后,它们直接影响结果是否可信。
但这篇没有提供检索准确率的对照数据。30 秒重叠是否优于更短窗口,1200 字符是否合适,按句子切分能提升多少召回,都只能视为实现选择,不能当作经过验证的最佳参数。
精度边界也说得清楚。这里最小的可靠单位是识别片段;要在一个 12 秒片段内定位准确的四个词,分块时间和片段时间都不够。工具的字幕功能会按字符数比例插值,适合跟读,不能拿来证明某个词在某秒被说出。
与重叠分块相比,按句末切分且不重叠更省嵌入成本。作者建议单人朗读准备好的文本时跳过 overlap;访谈、通话、争论更可能需要它。这是有理由的经验判断,但原文没有给出两类音频的检索对照实验。
能拿来做什么
- 给带引用的音频 RAG 做验收时,除了检查任务成功,还应抽取重叠块,核对开头文字的实际播放时间,以及
charCount是否对应完整输出。 - 在 Node 数据处理或日常 AI 编程中,只要增加前缀、拼接上下文,就同步检查长度、起点等派生字段,并优先从最终结果计算它们。
- 接入这个 Actor 时,分别使用
bodyStart和startTimecode处理去重与引用;只需要全文就读取transcript,无需开启分块,费用相同。 - 产品需要逐词高亮引用时,先确认数据源是否提供相应时间精度。不要把按字符比例估算的位置包装成准确证据。