STT glossary drift: consumers.yaml stt_prompt is hand-maintained and diverges from mVoice lexicon.yaml (missing mBrian → 'mBryan')
#10
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
m's proper noun mBrian transcribes as mBryan in production PWA voice. mVoice's own STT renders it correctly; the divergence is in the production path.
Root cause
The STT bias vocabulary exists in two places and they have drifted:
mVoice
~/.config/mvoice/lexicon.yaml— the intended source of truth, 14stt_biasterms, includesmBrian. Editable by hand and via the #12 lexicon feedback API.mGPUmanager
config/consumers.yamlstt_prompt— 11 hand-typed terms, forwarded to whisper.cpp as the per-requestprompt. Verified value:mBrianis absent. So the production STT is not biased toward it, and whisper hears the more common-lookingmBryan.The consequence is larger than one word: the production STT path never sees mVoice's lexicon. Every term added through the #12 feedback API — the whole point of which was to let Otto fix a misrecognition without an ssh + yaml-edit + reload — lands in lexicon.yaml and never reaches
consumers.yaml, so it has no effect on what m actually experiences.Fix direction
Generate
consumers.yaml'sstt_prompt(or whatever mGPUmanager forwards as the whisper prompt) from mVoice's lexicon.yamlstt_biasterms, rather than maintaining a second hand-typed list. Then lexicon.yaml is genuinely the single source and the #12 API works end to end. Decide the mechanism (build-time generation, a shared file, or mGPUmanager reading lexicon.yaml directly) on the mGPUmanager side.Note there is also a static
--prompt "Dania, Matthias, Otto, Jule, Hogan Lovells"baked intowhisper-server.serviceitself; consumers.yaml notes the per-request prompt overrides it. Whatever the fix, collapse to one source rather than three.Acceptance
mBriantranscribes asmBrianin the production PWA path, verified live.stt_prompt.Provenance
Surfaced by apollo during
m/mVoice#22; both config values independently re-verified by mvoice/head before filing (lexicon.yaml has 14 stt_bias terms incl. mBrian; consumers.yaml stt_prompt has 11 without it). Filed in mGPUmanager because that is where the drifted config and the forwarding logic live. Left unassigned — mGPUmanager owns the priority call.Related:
m/mVoice#22, #12; and the-l delanguage-forcing defect filed alongside.Collapsed to one source: the broker reads mVoice's lexicon
Branch
mai/curie/mgpumanager-stt-truth-9.Commits: 730fe90 · cbebade
Mechanism, and what it costs
A new consumer field
stt_prompt_filenames~/.config/mvoice/lexicon.yaml. The broker stats it per STT request and re-reads on an mtime or size change, so a term m adds through mVoice's feedback API applies to the next transcription with no restart on either side. mVoice writes the file atomically (tmp + rename), so a partial read is not possible.Rejected alternatives:
stt_promptat build timeThe cost. mGPUmanager takes a read-only dependency on a file mVoice owns — three fields of it:
word,aliases,stt_bias. The two services must share a host. They do, both on mRock, but that is now load-bearing where it was incidental, and a schema change in mVoice is a silent break here. Two things bound the damage: the broker reads only those three fields, and an unreadable or empty lexicon falls back tostt_promptinconsumers.yamlrather than leaving STT unbiased.Aliases too, not only terms
The adapter also applies the lexicon's
aliases, the same rewrite mVoice'scanonicalize_sttperforms. Aliases are the repair m reaches for when a name comes back wrong — that is what the #12 API is for — so honouring the prompt while ignoring the aliases would have left the feedback API half a no-op on the production path. Whole-word, case-insensitive, canonical spelling wins.This is a behaviour change worth objecting to if it is unwanted: an alias now rewrites the transcript on the PWA path exactly as it already does on mVoice's own path. If m says "Daniel" and the lexicon lists
Danielas an alias ofDania, the transcript saysDania.Lexicon changes, all made through the #12 API
Backup on mRock:
~/.config/mvoice/lexicon.yaml.bak-curie.consumers.yamlhad and the lexicon did not:Matthias,Jule,mRiver,mBreeze,mDock,mLake,mGPUmanager. Without these, switching the source would have regressed them.mBryanas an alias ofmBrian— the misrecognition this issue reports.ottotoOtto. The prompt influences casing, and the production list carriedOtto. One API call reverts it if the lowercase spelling was deliberate.glossary.txt:mAi,goldi,RaceTrack,Juliane Rein,Juliane Brandes.Result: 26
stt_biasterms, a strict superset of the 11 that were instt_prompt. No regression on any of them.The prompt format
A bare comma-joined term list, terminated by a full stop. Not mVoice's
"Glossar: … ."— a German label is German text in the initial prompt and would bias the language auto-detection that #9 just unblocked.The full stop is not decoration. Live on the German reference clip, same audio, same terms:
Detail on #9.
What did NOT collapse — two things, stated plainly
1.
whisper-server.service --promptstays. The per-request prompt overrides it, so it does not govern PWA voice. But it is the only biasing left for a caller that sends no prompt of its own, and there is one:mAi/chats/transcribe-audio.shtranscribes WhatsApp voice notes straight against:8178, sendinglanguage=deand no prompt. Removing the launch prompt would silently degrade WhatsApp voice notes. Moving that caller onto the broker is mAi's change, not this one — worth a follow-up issue there.2. There is a fourth copy:
~/.config/mvoice/glossary.txt. mVoice merges it at load and its own docs call it transitional. Its 5 unique terms are now inlexicon.yamltoo, so the broker sees them, but retiring the file is mVoice's call.Acceptance
TestLexiconCacheRereadsOnChange) and by the 12 entries above, all written through that API.mBriantranscribing asmBrianlive is not verified.mBrianis now in the production prompt withmBryanas an alias, which is the fix, but espeak-ng cannot pronounce it — the synthetic clip came out as "im Bayern", which no prompt can repair. That last criterion needs m's own voice.Tests
TestSTTPromptComesFromLexicon,TestSTTPromptFallsBackToStatic,TestSTTCanonicalizesAliases,TestParseLexiconTakesOnlyBiasedWords,TestParseLexiconNilWithoutBiasedWords,TestCanonicalizeRewritesWholeWordsOnly,TestLexiconCacheRereadsOnChange,TestLexiconCacheNilForMissingFile,TestLoadSTTPromptFile. Each fix was re-run as a negative control with the fix disabled, and each test failed.Design note:
docs/designs/issue-9-10-stt-language-and-lexicon.md.Deployed, and the acceptance criterion is demonstrated live
On main 7b2a0ba. Broker deployed to mRock 12:05.
"A term added via the mVoice #12 API takes effect in production without hand-editing mGPUmanager config"
Run end to end against production, nothing else touched — no config edit, no restart of either service:
1. Added a term through mVoice's API alone:
2. Same audio, production broker
mrock:8770/v1/stt:3. Deleted the term again, transcribed again — the rewrite is gone, back to
Test Satz.The test term was removed; m's lexicon is left as it was, at 28 entries.
That closes the loop the issue described: the API edit reached the model that actually transcribes m's voice, in the next request, with no ssh and no yaml edit on this side.
The other two criteria
stt_biasterms, a strict superset. Verified after the deploy.mBriantranscribes asmBrianlive — not verified, and it cannot be from here.mBrianis in the production prompt now withmBryanas an alias, which is the fix. But espeak-ng cannot say it: the synthetic clip came out as "im Bayern", an acoustic miss no prompt repairs. This one needs m's own voice.Deploy notes
stt_prompt_file: ~/.config/mvoice/lexicon.yamlis live in mRock'sconsumers.yaml; the previous file is kept asconsumers.yaml.bak-curie. The host-sidecomfyui vram_resident_mib: 6000hand-edit was preserved (repo says 13000).whisper-server.servicelaunch--promptnow ends in a full stop, unit backed up as.bak-curie2.After the restart:
/healthzok,/v1/statusall four consumers healthy,POST /v1/gamemode200 and released cleanly. mDock failover untouched and answering.