Skip to main content
Lesson 13 of 16 · Voxtral Audio: Transcription and Speech-to-Text

Real-Time Transcription Use Cases

Lesson 13 of 16 Updated November 3, 2026 Developer
On this page
  1. Four concrete streaming applications
  2. Live captioning
  3. Interactive voice assistant
  4. Voice dictation with autosave
  5. Conference transcription with metadata
  6. Key takeaways

Course updated November 3, 2026 for Voxtral 3, based on Mistral’s Migrate from Voxtral 2 guide: Voxtral 2 models (voxtral-mini-2602, voxtral-mini-transcribe-realtime-2602, voxtral-mini-tts-2603), deprecated October 31, 2026 and retired April 30, 2027, are replaced in examples by voxtral-mini-transcribe-3, voxtral-mini-transcribe-realtime-3, and voxtral-mini-tts-3-0. Key changes: timestamps are segment-only (word returns a 400 error), real-time diarization is supported, real-time session settings are locked after the first audio chunk, and context_bias inputs are free-form (phrases with spaces). Sources: Migrate from Voxtral 2 · Speech to Text.

Four concrete streaming applications

You now have all the building blocks: the stream, the microphone, and latency tuning. This lesson assembles them into four complete applications—live captioning, a voice assistant, dictation, and conference transcription—that you can use as-is as a starting point. Each illustrates a problem that offline mode cannot solve, and each uses a different latency setting, giving you a concrete feel for the tuning discussed earlier.

Live captioning

Captioning is the most visible real-time use case, and also the most demanding in terms of presentation. Transcribing isn’t enough: you must break the text into short lines, display only two at a time, and scroll the whole thing without ever splitting a word in half. That’s exactly what the following class does. When the current line reaches the maximum length, it finds the last space before that limit and breaks there; if no space is found—a very long word, a dictated URL—it cuts at the maximum length rather than letting the line overflow. The 800-millisecond setting reflects this use case’s priority: readable text is better than instant but broken text, and the audience won’t notice this slight delay.

import asyncio
import os
from mistralai.client import Mistral
from mistralai.client.models import (
    AudioFormat,
    TranscriptionStreamTextDelta,
    TranscriptionStreamDone
)

class LiveCaptioning:
    """Live captioning system with line management."""

    def __init__(self, max_chars_per_line=50, max_lines=2):
        self.max_chars = max_chars_per_line
        self.max_lines = max_lines
        self.current_line = ""
        self.lines = []

    async def run(self, audio_source):
        client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
        audio_format = AudioFormat(encoding="pcm_s16le", sample_rate=16000)

        async for event in client.audio.realtime.transcribe_stream(
            audio_stream=audio_source,
            model="voxtral-mini-transcribe-realtime-3",
            audio_format=audio_format,
            target_streaming_delay_ms=800,
        ):
            if isinstance(event, TranscriptionStreamTextDelta):
                self._add_text(event.text)
            elif isinstance(event, TranscriptionStreamDone):
                self._afficher_final()

    def _add_text(self, texte):
        self.current_line += texte

        if len(self.current_line) >= self.max_chars:
            pos = self.current_line.rfind(" ", 0, self.max_chars)
            if pos > 0:
                self.lines.append(self.current_line[:pos])
                self.current_line = self.current_line[pos + 1:]
            else:
                self.lines.append(self.current_line[:self.max_chars])
                self.current_line = self.current_line[self.max_chars:]

            if len(self.lines) > self.max_lines:
                self.lines = self.lines[-self.max_lines:]

        self._afficher()

    def _afficher(self):
        for ligne in self.lines:
            print(f"  {ligne}")
        print(f"  {self.current_line}", end="", flush=True)

    def _afficher_final(self):
        print("\n\n[Captioning finished]")

Interactive voice assistant

A voice assistant can’t afford silence. It must understand the user while they speak, or else leave a gap after every question that immediately gives away the machine. Real-time transcription is the first link in a three-step chain: Speech-to-Text, then LLM, then Text-to-Speech. Here, latency drops to 300 milliseconds, the most aggressive value in the entire lesson, and the text displays by overwriting the current line using a carriage return, creating the impression of a sentence completing itself before your eyes.

The second half of the function switches to the LLM. The response is itself requested as a stream, so the first words arrive before the model has finished its sentence: the responsiveness gained at the transcription stage isn’t wasted in the next step. The system prompt enforces conciseness, a decisive detail in spoken interaction where a ten-line response becomes unlistenable.

async def assistant_vocal(audio_source):
    """Transcribes then responds via an LLM."""
    client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
    audio_format = AudioFormat(encoding="pcm_s16le", sample_rate=16000)

    # Step 1: Transcribe the question
    question_parts = []

    async for event in client.audio.realtime.transcribe_stream(
        audio_stream=audio_source,
        model="voxtral-mini-transcribe-realtime-3",
        audio_format=audio_format,
        target_streaming_delay_ms=300,
    ):
        if isinstance(event, TranscriptionStreamTextDelta):
            question_parts.append(event.text)
            texte_en_cours = "".join(question_parts)
            print(f"\rYou: {texte_en_cours}", end="", flush=True)
        elif isinstance(event, TranscriptionStreamDone):
            break

    question = "".join(question_parts).strip()
    print()

    if not question:
        print("No question detected.")
        return

    # Step 2: Send to LLM to get a response
    print("Assistant: ", end="", flush=True)

    reponse_stream = client.chat.stream(
        model="mistral-large-latest",
        messages=[
            {
                "role": "system",
                "content": "You are a concise voice assistant. "
                           "Respond in 2-3 sentences maximum."
            },
            {"role": "user", "content": question}
        ]
    )

    for chunk in reponse_stream:
        texte = chunk.data.choices[0].delta.content
        if texte:
            print(texte, end="", flush=True)

    print("\n")

Voice dictation with autosave

Dictation poses a problem the two previous cases ignore: duration. A session can stretch over an hour, and losing the text due to a crash or dropout would be unacceptable. Hence automatic saving, triggered roughly every hundred words, which rewrites the output file on the fly. The nb_mots counter also serves as user feedback: it tells you how much you’ve produced, information anyone dictating a report expects. The 600-millisecond latency places the cursor on the side of precision, since someone dictating reads back their text and prefers an accurate word to an instant one.

class DicteeVocale:
    """Voice dictation with automatic saving."""

    def __init__(self, fichier_sortie="dictee.txt"):
        self.fichier_sortie = fichier_sortie
        self.texte = []
        self.nb_mots = 0

    async def run(self, audio_source):
        client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
        audio_format = AudioFormat(encoding="pcm_s16le", sample_rate=16000)

        print("Dictating... (Ctrl+C to stop)\n")

        async for event in client.audio.realtime.transcribe_stream(
            audio_stream=audio_source,
            model="voxtral-mini-transcribe-realtime-3",
            audio_format=audio_format,
            target_streaming_delay_ms=600,
        ):
            if isinstance(event, TranscriptionStreamTextDelta):
                self.texte.append(event.text)
                self.nb_mots += len(event.text.split())
                print(event.text, end="", flush=True)

                # Autosave every 100 words
                if self.nb_mots % 100 < 5:
                    self._sauvegarder()

            elif isinstance(event, TranscriptionStreamDone):
                break

        self._sauvegarder()
        print(f"\n\nDictation finished: {self.nb_mots} words")
        print(f"File: {self.fichier_sortie}")

    def _sauvegarder(self):
        contenu = "".join(self.texte)
        with open(self.fichier_sortie, "w", encoding="utf-8") as f:
            f.write(contenu)

Conference transcription with metadata

For a long conference, raw text isn’t worth much if it’s not identifiable. Six months later, no one will know which session or speaker a file named transcription.txt came from. The following function therefore wraps the transcription in a documented structure—conference name, speaker, start and end timestamps, word count—and exports it as JSON under a timestamped name, immediately usable by an internal search engine or an archiving pipeline. The 1,200-millisecond latency is perfectly comfortable here: the audience listens to the speaker, the display is just a supplement, and precision takes priority.

import json
from datetime import datetime

async def transcribe_conference(
    audio_source,
    conference_name: str,
    speaker: str = "Inconnu"
):
    """Transcribes a conference session with metadata."""
    client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
    audio_format = AudioFormat(encoding="pcm_s16le", sample_rate=16000)

    session = {
        "conference": conference_name,
        "speaker": speaker,
        "debut": datetime.now().isoformat(),
        "texte": [],
    }

    print(f"Transcription: {conference_name}")
    print(f"Speaker: {speaker}")
    print("-" * 50)

    async for event in client.audio.realtime.transcribe_stream(
        audio_stream=audio_source,
        model="voxtral-mini-transcribe-realtime-3",
        audio_format=audio_format,
        target_streaming_delay_ms=1200,
    ):
        if isinstance(event, TranscriptionStreamTextDelta):
            session["texte"].append(event.text)
            print(event.text, end="", flush=True)
        elif isinstance(event, TranscriptionStreamDone):
            break

    session["fin"] = datetime.now().isoformat()
    session["texte"] = "".join(session["texte"])
    session["nb_mots"] = len(session["texte"].split())

    filename = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    with open(filename, "w", encoding="utf-8") as f:
        json.dump(session, f, ensure_ascii=False, indent=2)

    print(f"\n\nSession saved: {filename}")
    return session

Review the four latencies in order—300, 600, 800, then 1,200 milliseconds—and note that they follow a single logic: the more the user expects a reaction from the machine, the shorter the delay must be; the more they expect a usable document, the longer it can be. Take one of these applications and plug it into the microphone generator from the previous lesson: you’ll have a truly usable tool in just a few minutes.

Key takeaways

  • Live captioning uses 800 ms latency and manages display line by line
  • The voice assistant chains real-time transcription and an LLM to answer questions
  • Voice dictation automatically saves text on the fly
  • Conference transcription adds metadata and exports to JSON
  • Each use case has its optimal latency: 300 ms (assistant) to 1,200 ms (conference), within the accepted range of 80 to 1,200 ms or 2,400 ms