feat: 转写支持静音段幻觉抑制参数(HST)

无 VAD 的 decode_full 会在无语音段"编"出字幕(`こんにちは`/`おはようございます`/
`東京都交通局8800形電車`)。实测这类幻觉与"呻吟间隙里的真实短台词"在
`no_speech_prob`、`avg_logprob`、silero VAD 与音频能量四个维度上都不可分,
只能用 faster-whisper 自带的 `hallucination_silence_threshold`(HST)抑制:
怀疑幻觉时跳过超过阈值的静音部分(需 `word_timestamps=True`)。

- `nodes/whisper.py` 透传 `word_timestamps` / `hallucination_silence_threshold` /
  `no_speech_threshold` / `log_prob_threshold` / `compression_ratio_threshold`,
  未配置的键不传,保持 faster-whisper 默认值与改造前行为。
- `learn-translate` 默认 HST=2.0 + word_timestamps:同一片头 120 秒无对话段由 15 条
  字幕降到 4 条且无套话幻觉残留,呻吟段基本保留;代价转写约慢 1.8×。
- 实测数据与取舍记录见 docs/workflows.md#decode_full-与幻觉呻吟清洗。
This commit is contained in:
2026-09-18 23:41:32 +08:00
parent 4d2823c005
commit 2c8bbb6469
4 changed files with 86 additions and 0 deletions
@@ -565,3 +565,53 @@ def test_invoke_drops_repetition_artifact_in_decode_full(tmp_path: Path, monkeyp
content = Path(response.outputs["srt_uri"]).read_text(encoding="utf-8")
assert artifact not in content
assert "そこ、だめ" in content
def test_invoke_passes_hallucination_guard_params(tmp_path: Path, monkeypatch) -> None:
"""数据:工作流传入幻觉抑制参数(HST + word_timestamps + 两个阈值)。
过程:调用 invoke。
验证:参数原样传给 faster-whisper——静音段幻觉只能靠这些参数抑制:
`hallucination_silence_threshold` 需要 word_timestamps 才生效。
"""
# 数据:无语音段也能"编"出字幕的假模型 + 显式传入的抑制参数。
model = FakeModel([FakeSegment(0.0, 2.0, "こんにちは")])
_inject_model(monkeypatch, model)
# 测试过程
invoke(_request(
tmp_path, SPEECH_WAV, chunk_seconds=0, language="ja",
word_timestamps=True, hallucination_silence_threshold=2.0,
no_speech_threshold=0.3, log_prob_threshold=-1.2,
compression_ratio_threshold=2.4,
))
# 验证结果
call = model.calls[0]
assert call["word_timestamps"] is True
assert call["hallucination_silence_threshold"] == 2.0
assert call["no_speech_threshold"] == 0.3
assert call["log_prob_threshold"] == -1.2
assert call["compression_ratio_threshold"] == 2.4
def test_invoke_omits_guard_params_by_default(tmp_path: Path, monkeypatch) -> None:
"""数据:不传抑制参数(默认工作流)。
过程:调用 invoke。
验证:不透传这些键,保持 faster-whisper 自身默认值,行为与改造前一致。
"""
# 数据:普通假模型。
model = FakeModel([FakeSegment(0.0, 1.0, "x")])
_inject_model(monkeypatch, model)
# 测试过程
invoke(_request(tmp_path, SPEECH_WAV, chunk_seconds=0))
# 验证结果
call = model.calls[0]
assert "hallucination_silence_threshold" not in call
assert "no_speech_threshold" not in call
assert "word_timestamps" not in call