Skip to content

✨ Support for voxtral realtime stt#1277

Open
cameledev wants to merge 11 commits into
mainfrom
add-voxtral-realtime-stt
Open

✨ Support for voxtral realtime stt#1277
cameledev wants to merge 11 commits into
mainfrom
add-voxtral-realtime-stt

Conversation

@cameledev

@cameledev cameledev commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Add support for voxtral realtime stt (live transcription).

Why we can't use mistralai plugin

The official livekit-plugins-mistralai plugin is hardwired to Mistral's hosted cloud API both the URL path and the wire protocol. We self-host Voxtral on vLLM, which speaks a different protocol on a different path, so the plugin can't be pointed at it. Four distinct issues, in the order they occured:

1. Hardcoded WebSocket path

The Mistral SDK's RealtimeTranscription._build_url hardcodes the path /v1/audio/transcriptions/realtime. server_url only controls scheme + host there is no kwarg to change the suffix, and the LiveKit plugin calls connect() without overriding it either. vLLM serves Voxtral realtime at /v1/realtime. That route doesn't exist on vLLM, so every WebSocket connnection attemps returned HTTP 500. A monkeypatch on _build_url (/v1/audio/transcriptions/realtime/v1/realtime) fixed this issue.

2. Incompatible wire protocol (most important issue)

Even with the path fixed, the two sides speak different languages:

Mistral plugin/SDK vLLM Voxtral
Protocol Mistral proprietary OpenAI-Realtime derived
Server msgs RealtimeTranscriptionSessionCreated, TranscriptionStreamTextDelta, TranscriptionStreamDone session.created, transcription.delta, transcription.done
Client msgs send_audio / flush_audio / end_audio input_audio_buffer.append / .commit

The Mistral SDK cannot parse vLLM's messages. Monkeypatching this would mean reimplementing the SDK's entire receive path.

3. The handshake failures (how the mismatch actually surfaced)

rt.connect() does two sequential handshakes, and the plugin broke at both:

  • WS upgrade handshake (transport). Before the path fix, the upgrade itself was rejected: websockets.exceptions.InvalidStatus: server rejected WebSocket connection: HTTP 500 thrown out of ConnectionPool.prewarm.
  • Realtime session handshake (application). After the _build_url
    monkeypatch, the upgrade succeeded ([accepted] / connection open in vLLM logs), but _recv_handshake then blocked on await websocket.recv() waiting for Mistral's session.created event. vLLM never sends that event (its OpenAI-style handshake has a different shape), so the 10s LiveKit connect timeout fired → CancelledErrorTimeoutErrorAPIConnectionError: Connection error., retried, then session closed as unrecoverable.

4. Dependency resolution failure (packaging)

Independently, livekit-plugins-mistralai==1.5.4 pulls inmistralai[realtime]>=2.0.0. Under uv's universal resolution across
requires-python = ">=3.12" (which now includes 3.14), that extra is
unresolvable → requirements are unsatisfiable

@cameledev cameledev changed the title Support for voxtral realtime stt ✨ Support for voxtral realtime stt Apr 16, 2026
Comment thread src/agents/multi-user-transcriber.py Outdated
Comment thread src/agents/multi-user-transcriber.py Outdated
Comment thread src/agents/multi-user-transcriber.py Fixed
@cameledev
cameledev force-pushed the add-voxtral-realtime-stt branch from 5d4ec36 to ec7eb87 Compare May 12, 2026 16:33
@sonarqubecloud

Copy link
Copy Markdown

@cameledev
cameledev force-pushed the add-voxtral-realtime-stt branch from ec7eb87 to de37643 Compare May 19, 2026 16:35
Comment thread src/agents/voxtral_vllm_stt.py Fixed
Comment thread src/agents/voxtral_vllm_stt.py Fixed
Comment thread src/agents/voxtral_vllm_stt.py Fixed
Comment on lines +80 to +88
const existingIndex = prevSegments.findIndex(
(s: TranscriptionSegmentWithParticipant) => s.id === segment.id
)
if (existingIndex === -1) {
return [...prevSegments, { participant, ...segment }]
}
const next = prevSegments.slice()
next[existingIndex] = { ...next[existingIndex], ...segment }
return next

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow update of previously received segments

Comment thread src/agents/voxtral_vllm_stt.py Fixed
Comment thread src/agents/voxtral_vllm_stt.py Fixed
@cameledev
cameledev marked this pull request as ready for review May 28, 2026 18:13
@cameledev
cameledev requested a review from lebaudantoine May 28, 2026 18:13

@cameledev cameledev left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Comment thread src/backend/core/api/__init__.py Outdated
"default_country": settings.ROOM_TELEPHONY_DEFAULT_COUNTRY,
},
"subtitle": {"enabled": settings.ROOM_SUBTITLE_ENABLED},
"subtitle": {"enabled": True}, # settings.ROOM_SUBTITLE_ENABLED},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REVERT BEFORE MERGE

Comment on lines 589 to 590

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REVERT BEFORE MERGE (2/2)

send_t.cancel()
try:
await send_t
except (asyncio.CancelledError, websockets.WebSocketException):
Comment thread src/backend/core/api/viewsets.py Outdated
authentication_classes=[LiveKitTokenAuthentication],
)
@FeatureFlag.require("subtitle")
) # @FeatureFlag.require("subtitle")

@lebaudantoine lebaudantoine May 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert before merge

Comment thread compose.yml
build:
context: ./src/agents
target: development
command: ["python", "multi_user_transcriber.py", "dev"]

@lebaudantoine lebaudantoine May 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary, but I understand if you want to keep more control over the compose service

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@lebaudantoine

Copy link
Copy Markdown
Collaborator

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown

Confidence Score: 3/5

The new plugin needs the empty-commit ordering question resolved before this is production-ready; everything else is safe.

The entire new STT plugin flows through _process_utterance, which sends an input_audio_buffer.commit before any audio is in the buffer. Whether vLLM silently ignores empty commits or responds with transcription.done determines whether the feature works at all. If the latter, every utterance produces an empty transcript and the audio is discarded. This warrants a targeted test or a clarifying note before merge.

src/agents/voxtral_vllm_stt.py — specifically the ordering of the initial commit vs. audio append in _process_utterance.

Important Files Changed

Filename Overview
src/agents/voxtral_vllm_stt.py New custom LiveKit STT plugin implementing OpenAI Realtime protocol over WebSocket for vLLM-hosted Voxtral; contains a potential protocol ordering issue where an empty input_audio_buffer.commit is sent before audio is appended, which could result in empty transcriptions.
src/agents/multi_user_transcriber.py Adds voxtral-vllm as a new STT provider option; change is minimal and straightforward.
src/frontend/src/features/subtitle/component/Subtitles.tsx Segment deduplication logic updated to merge/update existing segments in-place (enabling interim result updates); a leftover console.log should be removed.
compose.yml Adds explicit python multi_user_transcriber.py dev command to the multi-user-transcriber service; minor trailing whitespace on the target line.
env.d/development/multi_user_transcriber.dist Adds environment variable templates for the new voxtral-vllm provider and clarifies placeholders for existing providers.

Comments Outside Diff (1)

  1. src/frontend/src/features/subtitle/component/Subtitles.tsx, line 66-68 (link)

    P2 Leftover console.log debug call should be removed before merging.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "modify .dist" | Re-trigger Greptile

Comment on lines +366 to +372
# Start a fresh generation. Safe to send here: the previous utterance's
# transcription.done has already been received (we await it below), so
# the server-side generation_task is done and won't ignore this commit.
await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
send_t = asyncio.create_task(self._send_audio(ws, pending))
try:
await self._receive_one_transcription(ws, request_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Empty commit sent before audio is appended

An input_audio_buffer.commit is sent at line 369 before any audio chunks have been appended for this utterance. If vLLM responds to an empty-buffer commit with transcription.done (as the OpenAI Realtime protocol would), _receive_one_transcription returns immediately with final_text = "" (both data.get("text") and current_text are empty), send_t is cancelled in the finally block, and every utterance produces an empty transcript.

The standard Realtime protocol flow is: append audio → commit to trigger generation. The "final": True commit in _send_audio already ends the generation correctly. The preceding bare commit looks unintentional; removing it should be considered unless vLLM explicitly requires it as a "clear buffer" signal (which would need a comment explaining that contract).

Comment thread compose.yml
build:
context: ./src/agents
target: development
target: development

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Trailing whitespace on this line — cosmetic but can cause issues with some YAML linters and diff tools.

Suggested change
target: development
target: development

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants