Open source · MIT licensed · reference implementation
Audio in, grounded insight out.
A pipeline you can read and fork.
A working, end-to-end example of speech-to-text followed by LLM inference — with every model claim checked against the transcript before it is shown. The worked example is call-centre quality review; the pattern carries over to any recorded conversation.
- 3
- stages: transcribe, infer, verify
- 0
- databases or storage buckets
- ~50s
- per 10-minute call, measured
- ~$0.05
- API cost per 10-minute call
The pipeline
Four stages, each in its own file
Three short API routes drive it from the browser: upload, poll for the transcript, then analyse. No queue, no webhook, no database.
- 01
Upload and transcribe
Audio is relayed to the speech-to-text provider. The language is detected, the two speakers are separated, and every word gets a timestamp and a confidence score.
- 02
Measure without a model
Talk share, silence and interruptions are plain arithmetic over the word timings. Keeping these out of the LLM makes them repeatable.
- 03
Infer with structured output
The transcript goes to the LLM in its original language with a rubric and a JSON schema. Every observation must cite a verbatim quote, a timestamp and a speaker.
- 04
Verify before showing
Each quote is located in the real transcript. Anything that cannot be found, is at the wrong time, or is attributed to the wrong speaker is dropped and counted.
Browser ├─ POST /api/transcribe audio bytes → ASR provider, returns a job id ~2s ├─ GET /api/status?id= poll every 3s 30-60s └─ POST /api/insight transcript → LLM → verify quotes ~20s
Concepts
Techniques worth borrowing
Each of these is a design decision you will face in any voice-to-insight system. Each one links to where it is implemented.
Language detection, not declaration
Nobody tags the recording. The ASR provider detects the language per file, so mixed-language datasets need no preprocessing.
Speaker diarization
Two speakers are split automatically. Diarization gives labels (A/B), not roles — the template guesses the agent and says that it is a guess.
Evidence-carrying output
The JSON schema makes a quote, timestamp and speaker mandatory on every observation. In the UI, clicking a quote seeks the audio to that moment.
Measured kept apart from judged
Deterministic metrics and model opinions are computed separately and labelled separately, so a reader always knows which is which.
Both become “you” in English. One is a politeness violation.
No pre-translation
Translating before inference destroys what is being assessed — आप versus तू, usted versus tú, a switch out of English. The model reads the original.
Ask permission before a wait, rather than only telling the customer to bear with you.
A rubric as data
One list of categories drives the prompt, the output schema and the chart. Change the list and you have a different analyser.
Verification
Treat model output as untrusted input
In quality review, an invented quote becomes a false claim about a named person. So the pipeline does not trust the LLM: every quote is checked against the transcript before it reaches the page.
Three checks on every claim
- 1
Is the quote really in the transcript?
Fuzzy-matched against the word stream, tolerant of punctuation and reformatting.
- 2
Was it said when the model says it was?
Checked against the nearest real occurrence — which is what makes short, repeated phrases verifiable.
- 3
Did the right person say it?
Compared against the diarized speaker at that moment.
Anything that fails is dropped and counted. The count is shown with every result, and if nothing survives, no score is shown. See src/lib/verify.ts and its tests.
KEPT
Owned the failure by stating the prior tickets were never assigned.
DISCARDED — QUOTE NOT FOUND
“I have issued a full refund to your card.”
Plausible, well-formed, and never said on the call. This is what the verification step exists to catch — and the reader is told it happened.
Build your own
Where to make it your own
The call-centre framing lives in a rubric and a prompt. The pipeline around them is generic, and each provider sits behind a small seam.
Swap the speech-to-text provider
Implement the four-method AsrProvider interface — upload, submit, status, fetchResult — for Whisper, Deepgram or anything else.
Swap the LLM
The client speaks the OpenAI-compatible protocol, so another hosted model or a self-hosted vLLM endpoint is a base-URL and model-name change.
Change the use case
Replace the rubric and the system prompt to review sales calls, interviews, clinic visits or lectures. The verifier does not care what the domain is.
git clone https://github.com/nitingoswami-io/voice-insight-template.git cd voice-insight-template npm install cp .env.example .env.local # add two API keys npm run dev
You need an AssemblyAI key for transcription and an xAI key for analysis; both offer free credit. Tests run with no keys and no network.