Skip to content

Commit ffe1408

Browse files
Filter Whisper no-speech hallucinations
1 parent a79d683 commit ffe1408

1 file changed

Lines changed: 33 additions & 2 deletions

File tree

crates/wisp-audiokit/src/whisper_backend.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ use crate::{
3030
pub const WHISPER_BACKEND_ID: &str = "whisper-cpp";
3131
const WHISPER_SAMPLE_RATE: u32 = 16_000;
3232
const CHUNK_SAMPLES: usize = 16_000 * 12;
33+
/// Whisper's default threshold for treating a decoded segment as silence.
34+
///
35+
/// whisper.cpp exposes this probability on every segment but does not apply
36+
/// the threshold itself. Filtering here prevents text hallucinated from
37+
/// silence (for example common outro phrases) from reaching transcript
38+
/// consumers.
39+
const NO_SPEECH_PROBABILITY_THRESHOLD: f32 = 0.6;
3340

3441
/// Factory registration for the built-in whisper.cpp provider.
3542
#[derive(Debug, Default, Clone, Copy)]
@@ -705,6 +712,9 @@ fn transcribe_chunk(
705712
.map_err(|error| format!("Whisper inference failed: {error}"))?;
706713
let offset_seconds = *offset_samples as f64 / f64::from(WHISPER_SAMPLE_RATE);
707714
for segment in state.as_iter() {
715+
if is_probable_no_speech(segment.no_speech_probability()) {
716+
continue;
717+
}
708718
let text = segment
709719
.to_str_lossy()
710720
.map_err(|error| format!("Whisper returned invalid text: {error}"))?
@@ -734,6 +744,10 @@ fn transcribe_chunk(
734744
Ok(())
735745
}
736746

747+
fn is_probable_no_speech(probability: f32) -> bool {
748+
probability.is_finite() && probability > NO_SPEECH_PROBABILITY_THRESHOLD
749+
}
750+
737751
#[cfg(test)]
738752
mod tests {
739753
use std::time::Duration;
@@ -749,10 +763,27 @@ mod tests {
749763
};
750764

751765
use super::{
752-
StreamingResampler, WHISPER_BACKEND_ID, WhisperTranscriberBackend,
753-
WhisperTranscriberFactory, frame_to_mono_samples, model_file_ready, whisper_language,
766+
NO_SPEECH_PROBABILITY_THRESHOLD, StreamingResampler, WHISPER_BACKEND_ID,
767+
WhisperTranscriberBackend, WhisperTranscriberFactory, frame_to_mono_samples,
768+
is_probable_no_speech, model_file_ready, whisper_language,
754769
};
755770

771+
#[test]
772+
fn high_no_speech_probability_is_filtered_as_hallucination() {
773+
assert!(is_probable_no_speech(
774+
NO_SPEECH_PROBABILITY_THRESHOLD + f32::EPSILON
775+
));
776+
assert!(is_probable_no_speech(1.0));
777+
}
778+
779+
#[test]
780+
fn speech_and_invalid_probabilities_are_not_filtered() {
781+
assert!(!is_probable_no_speech(NO_SPEECH_PROBABILITY_THRESHOLD));
782+
assert!(!is_probable_no_speech(0.0));
783+
assert!(!is_probable_no_speech(f32::NAN));
784+
assert!(!is_probable_no_speech(f32::INFINITY));
785+
}
786+
756787
#[test]
757788
fn locale_maps_to_whisper_language() {
758789
assert_eq!(whisper_language("ja-JP").as_deref(), Some("ja"));

0 commit comments

Comments
 (0)