← Back to Blog

Keyword boosting: teaching Speech-To-Text your vocabulary

7 min read
Keyword boosting for Speech-To-Text. A Gradium transcription card reads

Speech-To-Text models are trained on enormous amounts of everyday speech, so they transcribe ordinary conversation at close to human accuracy. What they have rarely seen is the vocabulary specific to your product: a drug approved last month, a player who joined last season, or the name of your own company. Faced with a word it does not know well, the model falls back on a common word that sounds similar.

Keyword boosting is how you close that gap. You give the model a short dictionary of the terms you care about, and it raises their probability while decoding, in real time, with no retraining. This post explains why you need it, how the boost parameter behaves, what a sweep across hard clips says about the value to pick, and how to add it to your integration, with a sample prompt for your coding agent.

Why keyword boosting

The failure is often the same shape: rare, specific proper nouns. The model has seen the everyday name "Jamal" thousands of times and the footballer "Yamal" almost never, so when the two sound alike, the familiar spelling wins. In one of our own launch videos, the baseline transcribed the company name Gradium as "Gratium". A general model has no reason to prefer a word it has never seen over one it has, and we were pretty new at the time of the company launch.

Keyword boosting is the lever for exactly this class of word: brand and product names, drugs, athletes, places, and jargon. It does not change how the model handles ordinary speech. It makes sure the handful of words that actually carry your meaning come through.

Examples: what boosting recovers

Each of the clips below is the same audio, transcribed twice: once with no dictionary, and once with the rare term added to one. How hard the model leans toward a dictionary term is set by a single number, the boost, and every example here uses a boost of 3. Both transcripts are verified against an independent high-accuracy reference. We leaned into live sports commentary, the Tour de France and the World Cup, because it is a firehose of rare names, riders, players, and referees, that a general model has barely encountered, with medical and developer terms alongside to show the same effect outside sport.

Our launch video▶ YouTube
0:00 / 0:00

Introducing GratiumGradium Phonon, our first on-device Text-To-Speech model.

Football▶ YouTube
0:00 / 0:00

Great ball by DrexlerDraxler, wasn't it?

Football▶ YouTube
0:00 / 0:00

Real Madrid team is JosiasCasillas, Michel Salgado, Roberto Carlos, Sergio Ramos.

Football▶ YouTube
0:00 / 0:00

Aimed in towards Olmo and now JamalYamal!

0:00 / 0:00

Leadership at that team will rest on the shoulders of Michał KvjetkowskiKwiatkowski.

0:00 / 0:00

Tim WellandsWellens is there, Sivakov on his wheel.

0:00 / 0:00

I guess BooneBoonen, you'd say

Cycling · place name▶ YouTube
0:00 / 0:00

We are midway up the Col de la MedonMadone in southern France.

Medical · drug name▶ YouTube
0:00 / 0:00

And then manipurMenopur is one of the medications.

Tech · framework▶ YouTube
0:00 / 0:00

But SpeltSvelte is the most loved framework.

Every one follows the same pattern: without a dictionary the model reaches for a common, similar-sounding word; with the term boosted, it commits to the name that was actually said.

Choosing the right boost parameters

We ran keyword boosting on 20 hard YouTube clips in four domains with jargon (football, cycling, medical, and tech), each transcribed in streaming mode at delay_in_frames 12 with a dictionary built from the clip's proper nouns (rare terms).

Boost is exponential. The model scores candidate words by log-probability, and the boost is added in that log domain: a boost of 3 already multiplies a keyword's odds about 20 times. Two things follow. Small numbers go a long way, and past a point the bias overwhelms the audio entirely.

Recommended: boost 3. It recovers rare vocabulary with little side effects. Treat boost 4 as the ceiling for unusually dense or foreign vocabulary, and stay away from 5 and 6 unless you are watching the output. Negative values do the reverse and are only useful to suppress a specific word you never want.

How to use it

Reach for keyword boosting whenever you can anticipate the rare, specific terms likely to come up in a session, your product and brand names, or the people and places that recur in your domain, and list them ahead of time so the model expects them.

Boosting is a single field in the setup message you send to the STT WebSocket:

json
{
  "language": "en",
  "delay_in_frames": 12,
  "keywords": {
    "words": ["Gradium", "Phonon", "Gradbot"],
    "boost": 3
  }
}

words is your dictionary and boost is how hard to bias toward it. Everything else stays the same. If you would rather not touch code, the same dictionary can be set in the Gradium studio under Vocabulary, in the Keyword boost tab.

Once you save a dictionary in the studio, use it in a session by passing its id as keyword_boost_id in json_config, instead of sending an inline words list.

Two rules matter when building the dictionary, both because matching happens token by token:

  1. Keywords are single tokens, with no spaces. Split multi-word names, so "Ferran Torres" becomes Ferran and Torres.
  2. Case and accents are matched exactly. Mbappé and Mbappe are different keys, so include the variants you expect.

A dictionary holds up to 500 words, and every variant counts against that limit, so a name expanded into four case and accent forms uses four of the 500.

A small helper that expands every term into its case and accent variants:

python
import unicodedata

def expand(terms):
    strip = lambda s: "".join(c for c in unicodedata.normalize("NFKD", s)
                              if not unicodedata.combining(c))
    out = []
    for term in terms:
        for token in term.split():                 # split phrases into tokens
            for v in (token, token.lower(), strip(token), strip(token).lower()):
                if v not in out:
                    out.append(v)
    return out

expand(["Ferran Torres", "Mbappé"])
# -> ['Ferran','ferran','Torres','torres','Mbappé','mbappé','Mbappe','mbappe']

And a minimal streaming client: connect, send the setup with your dictionary, stream 24 kHz PCM, and read the text back.

python
import asyncio, base64, json, os, websockets

URL = "wss://api.gradium.ai/api/speech/asr"
KEY = os.environ["GRADIUM_API_KEY"]
DICT = expand(["Gradium", "Phonon", "Gradbot"])

async def transcribe(pcm):                          # pcm: int16 mono @ 24 kHz
    async with websockets.connect(URL, additional_headers=[("x-api-key", KEY)]) as ws:
        await ws.send(json.dumps({
            "type": "setup", "model_name": "default", "input_format": "pcm",
            "json_config": json.dumps({
                "language": "en",
                "delay_in_frames": 12,               # streaming, low-latency
                "keywords": {"words": DICT, "boost": 3},
            }),
        }))
        async def send():
            for i in range(0, len(pcm), 1920):       # 80 ms chunks
                await ws.send(json.dumps({"type": "audio",
                    "audio": base64.b64encode(pcm[i:i+1920].tobytes()).decode()}))
            await ws.send(json.dumps({"type": "end_of_stream"}))
        out = []
        async def recv():
            while True:
                try:
                    m = json.loads(await ws.recv())
                    if m.get("type") == "text":
                        out.append(m["text"])
                except websockets.ConnectionClosedOK:
                    break
        await asyncio.gather(send(), recv())
        return " ".join(out)

Boosting composes with everything else (language, streaming, latency tuning), and it earns its keep most in low-latency settings, where the model has less look-ahead to disambiguate a rare word on its own.

Get started

Point the STT WebSocket at a clip your current model gets wrong, add the terms it misses, and set boost 3. Add a keywords block to your setup message, or set the dictionary in the Gradium studio under Vocabulary. If you are tuning a full voice pipeline, our writeups on semantic VAD and turn-taking and optimizing quality versus latency in real-time TTS cover the neighbouring settings. Create a free account to try it on your own audio, or read the STT WebSocket reference for the full API.

Set it up with a coding agent

Would rather not write the dictionary or the client by hand? Paste the prompt below into Claude, Codex, or any coding agent, fill in your topic, and it will produce a ready-to-run Python script.

text
You are a senior Python engineer. Configure Gradium Speech-To-Text with keyword
boosting for the topic below and produce one runnable script.

Inputs
- Topic: <TOPIC, for example: professional cycling, its riders, teams, and Grand Tour climbs>
- API key: do NOT ask me to paste it here. The script must read it at runtime from
  the GRADIUM_API_KEY environment variable. Never hardcode it, never print or log it.

Steps
1. Build a keyword dictionary of 40 to 100 terms likely to appear in this topic that a
   general speech-to-text model rarely sees: people, brands and products, teams, drugs,
   jargon, and place names. Rules: keywords are single tokens with no spaces, so split
   multi-word names ("Ferran Torres" becomes "Ferran" and "Torres"); include the case and
   accent variants of each ("Mbappé", "Mbappe", "mbappe"); de-duplicate. Keep the raw
   terms in a TERMS list and expand them programmatically into the variant list.
2. Write one self-contained Python file (dependencies: websockets, numpy, sphn) that:
   - reads GRADIUM_API_KEY from the environment and exits with a clear message if unset;
   - takes a WAV path as the first argument and checks it exists;
   - loads it as 24 kHz mono 16-bit PCM (resample and downmix to mono if needed);
   - connects to wss://api.gradium.ai/api/speech/asr with the header x-api-key set to the
     env value;
   - sends a setup message with json_config =
     {"language": "en", "delay_in_frames": 12,
      "keywords": {"words": [ ...the expanded dictionary... ], "boost": 3}};
   - streams the audio in 1920-sample (80 ms) chunks, sends {"type": "end_of_stream"},
     reads the "text" messages, and prints the assembled transcript to stdout;
   - handles connection errors and a closed socket gracefully, with one reconnect retry.
3. Add a top-of-file docstring covering: installing the dependencies, setting
   GRADIUM_API_KEY (export in the shell, or a .env file that is gitignored), and the run
   command "python stt.py audio.wav". Do not put the key in the code or in any example.
4. If any API detail is unclear, consult the Gradium docs at docs.gradium.ai. Output only
   the finished Python file.

Constraints: boost 3; delay_in_frames 12; the API key comes only from the environment,
never from source or logs.

Related posts

Frequently Asked Questions