Think-Aloud 模式
标注者一边干活一边说话,Potato 把逐字转写稿存下来当作理由。语音转文字完全在本地跑,用的是 faster-whisper,音频不出本机,流程里没有云端 API,也没有 LLM。
v2.7.0 新增
在 LLM 出现之前,理解判断过程的黄金方法是出声思考法,而它从来没能真正进到标注工具里。Think-Aloud 模式让标注者干活时直接说出来就行。
逐字转写稿会被原样存成理由,刻意不做摘要,因为对出声思考的记录做转述,等于污染了你本来想采集的那份材料。标签可以用语音提交,靠的是一套基于规则的解析器识别固定说法,整条流程里没有任何 LLM。
语音转文字完全在本地通过 faster-whisper 运行,39 MB 的 tiny.en 模型在 CPU 上就能实时跑。音频不会离开本机,没有云端 API 要调,也没有按 token 计费的账单要付。
Note: Think-Aloud 转写的是你的标注者。如果你想标注的是已有的转写稿,来自 Whisper、云端 ASR API 或下载来的字幕,那是另一个功能:见转写格式。这两者常被混淆,因为都用到了 Whisper。

记录人的思维链
重点不只是收集理由。Think-Aloud 捕捉的是一个人究竟怎么推到那个标签上,于是你可以把这条人的思维链,和模型在同一条目上的思维链摆在一起看。
Potato 的过程奖励标注逐步拆解模型怎么推理,Think-Aloud 捕捉的是人怎么推理。两者都是采集界面,把它们在同一个条目上并排放着,有意思的分歧就是在那里冒出来的。
工作方式
- 标注者点一下 🎤 出声思考,然后随便说。
- 音频按完整的六秒分块采集,并在本地转写。
- 要用语音提交标签,就用下面这些被接受的说法之一:
- "I label this Polite" / "I'd call it neutral"
- "My answer is impolite"
- "Final answer: polite" / "I go with neutral"
- 识别到之后会自动在界面里选中对应选项,常规的保存流程随之触发,小标签给出确认:Heard: Impolite ✓。之后再说一句新的,标签就改了,以最后一次提交为准。
- 设了
require_spoken_label: true时,还没提交标签就按下一条,会触发一次提示,告诉他们期望的说法。再按一次就放行,而点击标签始终有效。
思考过程中提到的一切都会被忽略。"This seems polite, but…" 什么也不会提交。只有固定说法才会提交,这正是基于规则的解析够用的原因。听错了会由标签的模糊匹配兜住,所以 "in polite" 会解析成 Impolite。
安装
pip install faster-whisper # local STT; first recording downloads the model (~39 MB)浏览器需要麦克风权限。localhost 算作安全上下文。
配置
thinkaloud:
enabled: true
schema: politeness # scheme whose labels can be spoken (default: first radio)
stt: auto # faster_whisper | mock | auto
model: tiny.en # tiny.en is CPU real-time; base.en is sturdier
chunk_seconds: 6 # recording chunk length
require_spoken_label: true # nudge on Next without a committed label
# stems: # override accepted phrasing regexes (advanced)
# fillers: [um, uh, hmm, i guess, maybe]
# language: en| 选项 | 默认值 | 说明 |
|---|---|---|
stt | auto | faster_whisper(本地)、mock(测试和开发用),或 auto,它会选 faster-whisper,缺失时给出有用的报错。 |
model | tiny.en | 任意 faster-whisper 模型 id。 |
chunk_seconds | 6 | 每个分块都是一个完整的音频文件。 |
stems | 内置 | 被接受说法的正则词干;每条都会捕获其后跟着的词。 |
fillers | um, uh, hmm, … | 犹豫计数器所用的词表。 |
require_spoken_label | true | 什么都没提交时,在下一条按钮上给一次性提示。 |
你能得到什么
- 逐字的理由文本流,与每个 (标注者, 实例) 对齐,标签那句话被单独拆出来。转写稿减去提交那句,就是理由。
- 确定性的犹豫信号:静音分块计数,以及基于可配置词表的填充词计数,都用算术算出来,不靠模型。
- 一个审阅页面
/thinkaloud/review(管理员),列出每个会话的转写稿、语音提交的标签、置信度和犹豫统计。 - 免手操作的标注,算是顺带的好处,其中包括实打实的无障碍支持和对重复性劳损的缓解。
数据与 API
转写稿持久化到 {output_annotation_dir}/thinkaloud/transcripts.jsonl(只追加,每个分块一条记录)。
| 端点 | 方法 | 鉴权 | 用途 |
|---|---|---|---|
/thinkaloud/api/chunk | POST | session | Multipart 音频分块 → 转写 + 识别 |
/thinkaloud/api/text | POST | session | 文本分块(无音频路径) |
/thinkaloud/api/state | GET | session | 某个实例的会话聚合数据 |
/thinkaloud/review | GET | admin | 转写稿审阅页面 |
/thinkaloud/api/export | GET | admin | 所有会话的 JSON |
设计说明
- 解析器跑在最近两个分块的滑动窗口上,因此跨分块边界的说法依然能识别到。
- 标签匹配先精确匹配,再前缀匹配,最后用
difflib在 0.8 阈值上匹配,并优先取精确匹配。"polite" 绝不会模糊撞上 "Impolite"。 - 要接入新的 STT 后端,就在
potato/thinkaloud/stt.py里继承STTBackend,并在create_stt中注册。