Files
xiaobailing-turn-detection/labels/EOT标注/EOT逐话轮标注格式.md
T

292 lines
24 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EOT 标注与离线对话转写格式
## 当前版本:人机对话中的判断点标注
本项目当前录音是**真人用户与机器人之间的人机对话**,重点处理韩泰机器人过早接话问题。`user` 是真人用户,`agent` 是机器人;标注判断真人用户在指定时刻是否已经可以把发言权交给机器人。
**本版保留判断点标注:一行代表一个判断位置,标签回答“在该位置,让机器人接话是否合适”。** 判断位置优先取 VAD 请求 EOT 判断的时刻,可在同一通人机对话中保留多个位置。一个用户话轮可以包含多个停顿和判断点;本版不要求一个话轮恰好一个判断点,也不要求覆盖话轮内全部静音区间或先建立完整话轮表。
当前使用 `asr_history.csv` 和 `eot_labels.csv` 两份文件,按整通录音的媒体时间关联音频与文本,进行判断点标注和离线分类回测。**后续再逐步补齐完整用户话轮、静音区间和策略回放,向 LiveKit EOT bench 的评估方法靠近。** 下文的“后续计划”是扩展设计,尚不是当前标注的必填要求。
### 文件组织
| 文件 | 内容 | 一行代表什么 |
| --- | --- | --- |
| `asr_history.csv` | 整通人机对话的机器人/用户转写。 | 一段有起止时间的实际语音及其转写内容。 |
| `eot_labels.csv` | EOT 判断位置、该位置的当前用户文本及人工标签。 | 一个判断位置的 EOT 标注。 |
两份文件通过 `call_id` 关联,可以包含多通人机对话。判断点用 `call_id + decision_ms` 唯一标识;本版无需填写 `turn_id` 或 `span_index`。现有工作台只读写这两份 CSV,不要直接增加列,当前解析器会拒绝未知字段。
### 对话转写 CSV
固定表头:
```csv
call_id,role,start_ms,end_ms,transcript
```
| 字段 | 填写规则 |
| --- | --- |
| `call_id` | 通话 ID,与录音和 EOT 标注对应。 |
| `role` | `agent` 表示机器人,`user` 表示用户。 |
| `start_ms` | 该段语音在整通录音中的起点,单位为毫秒。 |
| `end_ms` | 该段语音在整通录音中的终点,单位为毫秒。 |
| `transcript` | 该段语音的转写内容,可由 ASR 生成并人工核对。 |
每段语音只保存一份转写,按录音顺序排列。同一角色可以连续出现多行。用户说话中途有停顿时,可以将停顿前后分成两段;分段仅用于对齐文本与时间,不表示用户话轮一定已经结束。
机器人记录实际说出的内容。被打断时只转写已播放的部分。转写应保留原意,不补充录音中没有说出的内容。
虚构示例见 [示例/asr_history.csv](示例/asr_history.csv):
```csv
call_id,role,start_ms,end_ms,transcript
demo,agent,1000,2400,您什么时候方便接电话?
demo,user,3000,4400,我明天下午
demo,user,5600,7100,不行,后天可以
demo,agent,7900,8500,后天上午可以吗?
demo,user,9000,9250,可以
```
### EOT 标注 CSV
固定表头:
```csv
call_id,decision_ms,user_text_at_decision,can_reply_now,confidence,reason,annotator
```
| 字段 | 填写规则 |
| --- | --- |
| `call_id` | 通话 ID,与转写和录音对应。 |
| `decision_ms` | 把音频交给 EOT 判断的位置,单位为毫秒。 |
| `user_text_at_decision` | 截至 `decision_ms`,当前用户话轮已说出的文本。尚未说完时填写部分文本,已说完时填写完整文本,不包含后续内容。 |
| `can_reply_now` | `1` 表示可以接话,`0` 表示接话会过早打断,空表示无法判断。 |
| `confidence` | 可选,填写 `high`、`medium` 或 `low`,表示标注员的把握。 |
| `reason` | 简短判断原因,取值见下表。 |
| `annotator` | 标注员 ID;仲裁后的最终标签填写仲裁员 ID。 |
`user_text_at_decision` 只保存本次判断的当前用户表达,全部对话历史仍从转写表获取。按截至判断位置的音频核对,或依据词级时间对齐填写;当前文本无法辨认时可留空。
时间统一使用整通录音的媒体时钟:以回放一致的音频起点为零,填写非负整数毫秒。裁剪片段中的时间也要换算回整通录音时间。
**decision_ms 是 EOT 的判断位置。** 例如,录音播放到第 5.2 秒时 VAD 请求 EOT 判断,就填 `5200`。它与用户最后一个字的结束位置、机器人开始播放的位置分别记录。
判断位置优先取 VAD 日志;没有日志时,可用固定版本和配置重放录音生成,并在配套配置中记录其来源。
### 按判断时间获取全部对话历史
对每一条 EOT 标注:
1. 从转写表筛选相同 `call_id` 的记录。
2. 保留 `end_ms <= decision_ms` 的所有记录。
3. 按 `start_ms` 排序;起点相同则保留 CSV 中的原有顺序,形成截至判断位置的全部对话文本。
4. 将历史、EOT 表中的 `user_text_at_decision` 和截至 `decision_ms` 的音频作为输入。`user_text_at_decision` 可作为单独的当前用户文本字段;组装成消息列表时,当前表达只保留一份,避免将历史里已有的同一内容重复追加。`agent` 可映射为模型消息中的 `assistant`,`user` 保持为 `user`。
如果某一行满足 `start_ms <= decision_ms < end_ms`,它横跨判断位置,不把该行整段文本加入历史;用户在该位置之前已说出的部分直接填写到 EOT 表的 `user_text_at_decision` 列。仅凭一段完整转写无法按时间准确截出前半句,应听音频或使用词级时间对齐核对。
转写文件保留整通对话全部内容,取上下文时保留判断位置之前的全部记录。上述规则按语音结束时间筛选离线转写,不记录 ASR 返回延迟。
### 标签和原因
标签依据音频、语气和对话上下文判断。短回答如“可以”“明天下午”也可能已经完成回答;句子完整或存在句号,不自动代表可以接话。ASR 错识别或漏识别不直接决定 EOT 标签。
| `reason` | 含义 | `can_reply_now` |
| --- | --- | --- |
| `completed_turn` | 当前轮次表达完成,可以接话。 | `1` |
| `completed_answer` | 已完成对机器人当前问题的回答。 | `1` |
| `mid_turn_pause` | 只是同一话轮内的停顿。 | `0` |
| `hesitation` | 正在犹豫或组织语言,尚未结束。 | `0` |
| `unfinished_expression` | 表达尚未完成,此刻接话会打断。 | `0` |
| `uncertain` | 判断有争议或上下文不足。 | 空 |
| `audio_issue` | 音频质量或录音缺失影响判断。 | 空 |
| `interrupted_unclear` | 机器人插话后用户停止,无法判断其原本是否准备结束。 | 空 |
虚构标注示例见 [示例/eot_labels.csv](示例/eot_labels.csv):
```csv
call_id,decision_ms,user_text_at_decision,can_reply_now,confidence,reason,annotator
demo,5200,我明天下午,0,high,mid_turn_pause,A
demo,7300,我明天下午不行,后天可以,1,high,completed_turn,A
demo,9400,可以,1,high,completed_answer,A
```
第 5.2 秒的 `user_text_at_decision` 为“我明天下午”,文本上下文包含机器人问题和这段用户表达。示例设定为用户正在同一话轮内停顿,之后继续说“不行,后天可以”,所以标 `0`;后续这段文本不进入第 5.2 秒的输入。
第 7.3 秒的当前用户表达包含同一话轮中两段已说出的内容。本例设定用户在 7.1 秒完成表达,到 7.3 秒可以接话,所以标 `1`。
第 9.4 秒的 `user_text_at_decision` 为“可以”,文本上下文包含上面五条转写记录。示例设定为用户已用“可以”完成回答且语气收束,所以标 `1`。
### 本版标注步骤和数据核对
1. 核对通话、录音和机器人/用户声道,在时间轴上定位 `decision_ms`;优先依据 VAD 日志,没有日志时记录固定配置重放或人工选点的来源。
2. 查看截至该判断位置的对话历史和音频,填写当前用户已说出的文本、能否接话、原因和标注员 ID;置信度可选。
3. 必要时听后续录音辅助仲裁,但后续内容不加入该位置的模型输入。机器人插话后用户停止、原意不明时,使用 `interrupted_unclear` 并留空标签;不要强行认定用户已自然结束。
4. 核对 `0 <= start_ms < end_ms`、`decision_ms >= 0` 且时间不超出录音、角色和标签取值、原因与标签一致,以及 `call_id + decision_ms` 无重复。`0` 是有效负例,空标签不进入二分类训练和评分。
多人独立标注时,每个人各保存一份判断点标签文件,使用同一份转写;仲裁后的最终文件每个 `call_id + decision_ms` 只有一行。
CSV 使用 UTF-8 编码和标准 CSV 读写规则。转写内容含英文逗号、双引号或换行时,用双引号包裹,内部双引号写成两个双引号。
音频路径、声道映射、转写配置和判断点来源保存在配套数据中。本版无需补齐 `user_turns.csv` 或 `silence_spans.csv`,无需把每个点归入 `hold/eot` 区间,也无需逐一核验完整话轮边界。
原始表格中的“是”(发生过早打断)和“否”(未发生过早打断)可用于挑选候选样本,仍需针对本次判断位置重新核对。韩泰初稿的判断点暂取估计的用户片段终点,标签由原表抢话结论映射;`xlsx_draft` 记录经回听确认前不进入正式评分。“未发生抢话”不能直接证明估计时刻已经可以接话。
### 本版评估范围
在已核验判断点上,将模型是否允许接话与 `can_reply_now` 比较,统计分类准确率、可以接话的检出率,以及误放行和误阻止。误放行表示人工标 `0` 时模型允许接话,误阻止表示人工标 `1` 时模型要求继续等待。人工标签与模型预测分开保存;比较模型时使用相同的判断点集合和输入规则。
这些指标只代表选定判断点上的表现。**判断点误放行不等于 LiveKit 的静音区间误抢话率;判断点检出率也不等于完整话轮末尾 EOT 检出率。** 同一静音区间中的多个点在本版可以分别评价;后续区间评测才按区间汇总。仅有判断点标签,无法计算完整话轮的平均结束等待时间,也无法推算模型拒绝后何时再次接话。
本版仍采用离线转写,不要求词级时间对齐或模拟 ASR 返回延迟;这部分在后续输入回放中完善。
## 后续计划:逐步靠近 LiveKit EOT bench
后续仍使用真人用户与机器人的对话录音,在保留现有判断点及其标注的基础上,补充完整用户话轮和静音区间,逐步从判断点分类扩展到“误抢话与结束等待时间”的策略评估。这里的用户话轮是一次完成表达的发言,可以包含多个停顿;一通人机对话通常包含多个用户话轮。
规划增加两份配套表,现有示例目录中的这两份文件仅用于演示未来扩展:
| 规划文件 | 内容 | 一行代表什么 |
| --- | --- | --- |
| `user_turns.csv` | 完整用户话轮的起点、真实最后发声终点及核验状态。 | 一个完整用户话轮或待核验的话轮候选。 |
| `silence_spans.csv` | 话轮内部停顿及末尾静音的起止时间和标签。 | 一个静音区间。 |
扩展后,四份文件通过 `call_id` 关联。配套表使用 `call_id + turn_id` 标识话轮,使用 `call_id + turn_id + span_index` 标识静音区间。现有工作台尚未实现两份规划表的编辑与校验;当前标注无需创建或维护它们。下面的字段、区间校验、文本延迟及导出规则在开展对应阶段评测时使用。
### 话轮、静音区间与判断点
- **完整用户话轮**:用户从开始发声到完成本轮表达的全过程,可以包含停顿、犹豫和自我修正。转写表的一行不等于一个话轮。
- **`hold` 区间**:同一话轮内部的静音,之后用户继续本轮表达。起点是停顿前最后发声的终点,终点是恢复发声的起点。
- **`eot` 区间**:完整用户话轮结束后的静音。起点是真实最后发声的终点,终点是可供回放评测的末尾静音结束位置,例如机器人开始发声或录音结束位置。
- **判断点**:静音区间中的一个模型输入截止位置。区间标签是真值,判断点继承所在区间的真值;模型分数及实际接话动作另行记录。
先通过音频和上下文核验话轮边界,再确定区间标签。每个已确认完整话轮有且只有一个末尾 `eot`,之前的静音区间为 `hold`。不能把机器人插话的位置、转写分段终点或任意裁剪片段的末尾自动认定为真实 EOT。
机器人抢话后用户停止、原本是否准备结束无法判断时,话轮状态填 `interrupted_unclear`。可以保留已经核验的判断点标签作局部分析,但该话轮不进入要求完整末尾 EOT 的策略评估。不能为了满足“最后一个区间为 eot”而补造正例。
静音以用户声道的发声活动为依据;机器人、背景声和声道串音应核验。默认收集至少 100 ms 的候选静音区间,以便接近 LiveKit 的候选集;使用的 VAD 版本、阈值和人工修订情况须记录。所有模型和基线使用同一个核验后的区间集合。
### 完整用户话轮 CSV
固定表头:
```csv
call_id,turn_id,start_ms,speech_end_ms,status,annotator
```
| 字段 | 填写规则 |
| --- | --- |
| `call_id` | 通话 ID。 |
| `turn_id` | 通话内稳定的话轮 ID,例如 `u001`;调整时间后仍保留 ID。 |
| `start_ms` | 该用户话轮首次发声的起点,整通录音毫秒。 |
| `speech_end_ms` | 真实最后发声的终点;尚未确认或被插话截断时留空。 |
| `status` | `complete`、`uncertain`、`audio_issue` 或 `interrupted_unclear`。只有人工核验后的完整话轮填写 `complete`。 |
| `annotator` | 核验或仲裁该话轮的标注员 ID。 |
`speech_end_ms` 不是 VAD 触发时刻,也不是机器人开始播放的时刻。完整话轮满足 `0 <= start_ms < speech_end_ms`。候选状态不自动代表其全部局部判断点无法标注,但不用于完整话轮策略评分。
### 静音区间 CSV
固定表头:
```csv
call_id,turn_id,span_index,start_ms,end_ms,label,confidence,reason,annotator
```
| 字段 | 填写规则 |
| --- | --- |
| `call_id`、`turn_id` | 关联话轮表。 |
| `span_index` | 话轮内按时间排序,从 `0` 开始连续编号。 |
| `start_ms`、`end_ms` | 静音起止时间,整通录音毫秒,满足 `start_ms < end_ms`。 |
| `label` | `hold`、`eot` 或空。空表示待核验,不当作负例。 |
| `confidence` | `high`、`medium`、`low` 或空。 |
| `reason` | 沿用当前版本的标签原因表;`hold` 对应负例原因,`eot` 对应正例原因,空对应无法判断的原因。 |
| `annotator` | 标注或最终仲裁该区间的人员 ID。 |
区间使用半开范围 `[start_ms, end_ms)`。对每个判断点,在相同通话中查找满足 `start_ms <= decision_ms < end_ms` 的区间,唯一确定其话轮和区间归属。一个区间可以关联多个判断点;同一通话的已确认区间不得重叠。不能唯一关联的点保留为待核验或局部分析样本,不直接导出为 bench 样本。
对 `complete` 话轮,所有区间必须已核验且位于该话轮起点之后;`hold` 区间结束不晚于 `speech_end_ms`,唯一末尾 `eot` 的 `start_ms` 必须等于 `speech_end_ms`。不要把说话期间的拒绝接话点当成 `hold` 静音区间。
### 后续判断点与区间关联
属于已核验区间的判断点,`hold` 对应 `can_reply_now=0`,`eot` 对应 `1`;空区间标签对应空判断点标签。若判断点与区间标签冲突,应重新核验边界和真值,不通过覆盖标签消除冲突。
判断点不需要逐一重复人工标注。核验区间后,评测可以按固定间隔生成多个判断点并继承区间标签,但每个点的音频和当前文本必须重新按该时刻截断。当前 VAD 日志中的点继续用于原策略回测,固定网格用于不同策略的统一比较。
### 后续文本可用性与 ASR 延迟
开展策略回放时,记录所用文本可用性模式:
- **离线转写模式**:使用上述规则及人工核对的当前文本,用于检查音频、语义和标注质量,不能据此宣称实时 ASR 效果。
- **固定延迟模式**:有词级对齐时,仅保留 `word_end_ms <= decision_ms - transcript_lag_ms` 的词,模拟 ASR 返回延迟。LiveKit 批量输入构造默认延迟为 500 ms。例如一个词在 1800 ms 说完,设置 500 ms 延迟后,到 2300 ms 才可作为模型文本输入。
- **日志回放模式**:按 ASR 实际返回时刻重放当时已收到的文本及修订结果,优先用于评估项目实时链路。
模拟延迟只影响模型输入,不改变话轮或区间真值。词级时间和 ASR 日志保存在配套数据中;仅有片段级完整转写时,不能按字符比例推算词级时间,也不能把离线 `user_text_at_decision` 原样用于所有生成时刻。若仅延后整段文本,报告中必须说明这是片段级近似。历史和当前文本统一遵循所选可用性规则,避免从历史绕过延迟限制。
### 后续扩展的虚构示例
对应的虚构配套示例为 [示例/user_turns.csv](示例/user_turns.csv) 和 [示例/silence_spans.csv](示例/silence_spans.csv)。本例假定录音延续到 9800 ms:
```csv
call_id,turn_id,start_ms,speech_end_ms,status,annotator
demo,u001,3000,7100,complete,A
demo,u002,9000,9250,complete,A
```
```csv
call_id,turn_id,span_index,start_ms,end_ms,label,confidence,reason,annotator
demo,u001,0,4400,5600,hold,high,mid_turn_pause,A
demo,u001,1,7100,7900,eot,high,completed_turn,A
demo,u002,0,9250,9800,eot,high,completed_answer,A
```
`5200` 属于 `u001` 的 `hold`;`7300` 属于同一话轮的 `eot`;`9400` 属于 `u002` 的 `eot`。最后一个点的静音等待为 150 ms,适用于该点的局部判断;若统一在静音 200 ms 评分,第二个话轮应另外生成 `9450 ms` 的输入和预测,不能直接复用 `9400 ms` 的分数。
### 策略评测与 LiveKit 导出
后续完整话轮策略评测的目标对比为:**在相同话轮和区间集合上,比较原 VAD 策略与 VAD + EOT 策略,观察减少的误抢话及增加的结束等待时间。** 记录 EOT 拒绝后是否重新推理、重试间隔、概率阈值、最小静音等待和超时时间。只保存一次拒绝结果,不能推算后续多久才会接话。
预测结果与人工真值分开保存。至少记录话轮、区间、`decision_ms`、已静音时长、模型分数,以及所用模型和输入配置。`can_reply_now` 是人工标签,不是模型输出。已静音时长为 `decision_ms - silence_start_ms`;两个时间都使用整通录音时钟。
评测分为两个层次:
| 层次 | 数据和指标 |
| --- | --- |
| 判断点分类 | 在已核验的选定判断点上评价误放行、误阻止、检出率等;只代表这些判断点上的表现。 |
| 静音区间策略评估 | 在完整话轮的统一候选集上,评价 `hold` 误抢话率、末尾 `eot` 检出率及平均结束等待时间,并比较不同阈值、最小等待和超时策略。 |
静音区间策略评分的具体口径:
1. **`hold` 误抢话率**:如果策略决定接话时用户还未恢复说话,计一次误抢话。分母是纳入评估的 `hold` 区间数;同一区间内多个判断点不重复计数。即使模型一直拒绝,超时早于用户恢复发声也会造成误抢话。
2. **末尾 `eot` 检出率**:模型超过阈值且经最小等待约束后能在超时时间以内触发,计检出成功;否则计漏检并由超时结束。分母是纳入评估的末尾 `eot` 区间数。
3. **平均结束等待时间**:从真实 `speech_end_ms` 到策略决定接话的时间差。成功检出使用策略动作时间,漏检使用超时时间;所有末尾 `eot` 都进入平均,不能只统计成功检出的样本。
4. `hold` 不进入末尾 EOT 检出率或平均等待时间;`eot` 不进入 `hold` 误抢话率。按区间分别评估时,即使之前的 `hold` 上发生抢话,末尾 `eot` 仍参与评分。这种口径不同于在首次抢话后停止的通话级回放。
采用 LiveKit 相近口径时,候选静音最短为 100 ms,指标默认筛选 200 ms 至 5 s 的 `hold` 区间。统一比较“平均等待不超过 300/600 ms 时的最低误抢话率”和“误抢话率不超过 5%/10% 时的最低平均等待时间”。分类指标作为辅助诊断,同时记录所用版本和配置,避免不同候选集的结果混比。
这里的等待时间衡量结束策略,未包含整个 ASR–LLM–TTS 链路耗时。若要衡量用户真正听到回复的延迟,应另记机器人播放起点,计算其与 `speech_end_ms` 的时间差。
导出到 LiveKit bench 时:
1. 每个 `complete` 话轮导出一行,`id` 使用可逆编码的 `call_id + turn_id`,确保整个数据集唯一;语言在配套配置中记录,例如中文为 `zh`。
2. 从已核验的用户声道裁剪该完整话轮及末尾静音;参照公开数据转换为 16 kHz 单声道 WAV。避免把整通双声道音频直接混成单声道而混入机器人语音。
3. 本地时间保留整通毫秒,导出时按实际裁剪起点换算为片段秒:`local_seconds = (recording_ms - crop_start_ms) / 1000`。静音区间和词级时间一起换算。
4. 生成按时间排序的 `silence_spans`,每项包含 `start`、`end`。LiveKit 会把列表最后一个区间视为 `eot`、此前区间视为 `hold`,所以导出前必须校验本地标签和完整话轮边界。
5. `messages` 只包含当前话轮之前的对话;当前话轮文本通过带时间的 `words` 按判断时刻构造。它们是可选字段,是否提供取决于模型需求;未来内容不进入输入。
6. 末尾静音需覆盖所选评分点,例如 200 ms。不足时剔除样本,或在所有模型共用的导出中统一补尾静音并明确记录补充长度和规则;不能越过机器人已发声的片段当作用户纯静音输入。补尾不改变真实 `speech_end_ms`,不能为 `hold` 人工延长静音。流式评分若需要更晚的分数,也须保证对应时间有合法的音频输入,不能把未观测时间冒充模型预测。
配套配置至少保存:录音路径、声道映射、语言、VAD 版本和参数、判断点来源、输入裁剪规则、文本可用性模式、区间筛选范围、策略参数、数据划分及 LiveKit 代码/数据版本。训练、调参和测试按通话划分,避免同一通话的不同点泄漏到不同集合。
参考:[LiveKit 评估说明](https://github.com/livekit/eot-bench#evaluation-model)、[区间和因果输入构造](https://github.com/livekit/eot-bench/blob/main/eot_harness/io.py)、[指标实现](https://github.com/livekit/eot-bench/blob/main/eot_harness/metrics.py)、[公开数据格式](https://huggingface.co/datasets/livekit/eot-bench-data)。本节为后续接入规划,当前判断点标注不要求满足这里的完整话轮评测条件。项目尚未实现上述导出器和策略评分程序。
### 后续扩展的标注与核对
1. 回听并核验完整用户话轮边界,填充话轮表;被机器人插话截断且原意不明的候选不能标为 `complete`。
2. 生成并核验话轮内的静音区间,为已确认完整话轮建立内部 `hold` 和唯一末尾 `eot`。
3. 将已有判断点关联到区间,无法唯一关联的点保留为局部分析样本;开展统一策略评估时,再按固定网格生成输入和预测。
4. 记录 EOT 拒绝后的重试和超时规则,使用相同区间集合比较原 VAD 与 VAD + EOT,并按所选模式完善文本回放。
完整话轮评测还需核对:话轮及区间唯一键、区间外键、时间顺序、静音确实存在、末尾 EOT 唯一且起点等于 `speech_end_ms`、判断点唯一归属,以及所有模型使用相同区间集。含未决区间或被插话截断的话轮不进入完整话轮策略评分;记录排除数量和原因。点位评分可以使用其中已经核验的点,但须单独报告。
原有韩泰初稿不自动生成 `complete` 话轮;需先核验真实边界和内部停顿。缺少规划表不影响当前判断点标注,也不意味着当前数据格式错误。