Home → Blog → How to improve speech-to-text accuracy: eight practical fixes
How to improve speech-to-text accuracy: eight practical fixes
“Recognition is bad” is not a diagnosis. In almost every real case the quality problem is one of a handful of concrete causes — and most of them are fixed on your side, before the audio ever leaves your server. Here they are, in the order in which they are worth trying.
First: measure, do not guess
Without a number, every change is a matter of taste. Take 20–30 recordings that look like your production traffic, write down what was actually said, and count the word error rate: substitutions plus deletions plus insertions, divided by the number of words spoken.
pip install jiwer
from jiwer import wer
score = wer(reference_text, recognised_text)
print(f"WER: {score:.1%}")
Two things matter more than the absolute value. First, measure on your audio, not on a public benchmark. Second, weight by what you care about: if the business needs order numbers, a 5% WER with every number wrong is a failure, and a 15% WER with clean numbers is a success.
Fix 1. Give the model clean audio
Recognition models work internally with mono at 16 kHz. Anything above that is discarded, and anything below that is lost forever:
- Do not re-compress twice. A file that went mp3 to wav to mp3 keeps the artefacts of both passes.
- Do not upsample. Converting 8 kHz phone audio to 44 kHz adds bytes, not information.
- Mix down to mono when both channels carry the same conversation; keep the channels separate when they carry different speakers.
ffmpeg -i input.wav -ac 1 -ar 16000 -b:a 64k clean.mp3
Heavy noise reduction is the exception that usually backfires: aggressive filters chew up consonants and recognition gets worse, not better. Fix noise at the microphone if you can.
Fix 2. Say what language it is
Automatic detection is right almost always on long recordings and noticeably less often on three-second clips. If the stream is single-language, say so:
r = client.audio.transcriptions.create(
model="whisper-1", file=f, language="en",
)
This removes a whole class of failures where a short phrase is recognised as another language and comes back as nonsense. It also speeds processing up slightly, since the detection pass is skipped.
Fix 3. Feed in the vocabulary through prompt
The single highest-value parameter, and the most often ignored. The prompt does not appear in the response — it biases recognition towards the words you list:
r = client.audio.transcriptions.create(
model="whisper-1", file=f,
prompt="Products: Kestrel, Kestrel Pro, Hawknest. Agents: Duarte, Ivanova, Ng.",
)
Keep it short and specific. A list of 20–40 terms that actually occur works; a paragraph of general description does nothing. Rotate the list per context: a support queue about billing needs different terms than one about hardware.
The prompt also carries style. If your transcripts should keep punctuation and capitalisation, write the hint as a properly punctuated sentence — the model tends to continue in the style it was given.
Fix 4. Cut the silence
Long silent stretches are where hallucinations come from: with nothing to recognise, a model can produce filler text. They also cost money, because billing is per minute of audio.
ffmpeg -i input.mp3 -af silenceremove=start_periods=1:stop_periods=-1:stop_duration=2:stop_threshold=-45dB out.mp3
Be careful with the threshold: cutting too aggressively removes quiet speech along with the silence. Check the result on a few files before putting it in a pipeline.
Fix 5. Split on pauses, not on the clock
Long recordings have to be split anyway to fit the 25 MB limit. Where you cut matters: a chunk boundary in the middle of a sentence loses the words on both sides of it.
# split on silence rather than at a fixed minute
ffmpeg -i long.mp3 -af silencedetect=noise=-40dB:d=0.7 -f null - 2> pauses.txt
Use the detected pauses as candidate cut points, and keep chunks in the 5–20 minute range. Shorter chunks lose context between them; much longer ones bring no extra benefit.
Fix 6. Ask for the format that carries confidence
verbose_json returns not only the text but also the segments and the detected language with its probability. That is the raw material for automatic quality control:
r = client.audio.transcriptions.create(
model="whisper-1", file=f, response_format="verbose_json",
)
if r.language_probability < 0.6:
queue_for_review(r) # suspicious recording, look at it by hand
Routing the doubtful few per cent to a human review queue improves the quality of the whole pipeline far more cheaply than trying to squeeze the last percent out of recognition itself.
Fix 7. Post-process instead of re-recognising
Some errors are systematic: the same product name comes back wrong the same way every time. A dictionary of replacements fixes it in one line and costs nothing:
FIXES = {"cast rail": "Kestrel", "hawk nest": "Hawknest"}
for wrong, right in FIXES.items():
text = text.replace(wrong, right)
Build the dictionary from real mistakes you have seen, not from imagination. Ten entries collected over a week of production traffic typically remove more visible errors than any parameter tuning.
Fix 8. Catch empty and invented output
Two failure modes are worth handling explicitly in code: an empty result, and a result that is suspiciously generic. Hallucination filters cut the classic cases such as closing credits and “thanks for watching”, so an empty string genuinely means there was no speech.
text = r.text.strip()
if not text:
return None # silence, not an error — do not retry
Retrying an empty recording only spends minutes. Log it, skip it, and move on.
What will not help
- Raising the sample rate of audio that was recorded at 8 kHz. The information is gone.
- Sending the video file instead of the audio track. The picture is not used.
- Very long prompts. Only the beginning is taken into account, and a wall of text dilutes the terms that mattered.
- Retrying the same file hoping for a better roll. The result is stable; change the input or the parameters instead.
Try it on your own recordings. Sign-up takes a minute, and the free minutes are enough to judge the quality.
Get a free API keyFrequently asked questions
What word error rate should I expect?
On clean speech in a well-supported language, single-digit percentages are normal. Phone audio, background noise, crosstalk and strong accents raise it. Measure on your own recordings — a public benchmark says nothing about your traffic.
Does the prompt parameter really change anything?
Yes, and it is the cheapest improvement available. A short list of the names, products and codes that occur in your audio noticeably reduces errors on exactly the words that matter most.
Should I specify the language or let it be detected?
Specify it when the stream is single-language, especially for short clips. Leave detection on when messages genuinely arrive in different languages.
Is it worth removing noise before sending?
Light normalisation helps; aggressive noise reduction usually hurts, because it damages consonants. If the noise is severe, fixing the recording setup beats any filter.
How large should the chunks of a long recording be?
Five to twenty minutes, cut at pauses rather than at fixed intervals. That keeps sentences intact and stays inside the 25 MB request limit.
Related reading
- How to transcribe a call recording to text — A step-by-step guide to turning phone call recordings into text through an API in minutes: Python and C# samples, response formats and the errors you will meet.
- Voice message transcription: Telegram, WhatsApp and other messengers — How to turn voice messages into text automatically: working with ogg/opus, short clips, silent recordings and ready code for chat bots that answer in text.
- How to transcribe meeting recordings and get usable notes — Turn Zoom, Teams and Google Meet recordings into searchable text and structured notes: where the file lives, how to transcribe it and how to extract decisions.