steep-balloon-41261
05/01/2026, 2:00 PMtall-airline-16756
05/01/2026, 2:00 PMtall-airline-16756
05/01/2026, 2:00 PMtall-airline-16756
05/01/2026, 2:05 PMtall-airline-16756
05/01/2026, 2:07 PMtranscription.done if needed.
• vLLM realtime auth via API key; chat completions via HTTP(S) streaming.
• Existing TTS node accepts incremental text chunks and supports cancelling playback.
• chat_ctx persisted in LiveKit agent memory and exposed to admin APIs; persistence across restarts is out of scope (can be added externally).
• Goal: single in‑process RealtimeModel + RealtimeSession adapter bridging audio→vLLM ASR → /chat/completions → LiveKit TTS, with canonical chat_ctx commits only for final user/assistant turns.
Design (concise, ordered):
1) Extension point
• Use custom RealtimeModel + RealtimeSession in‑process. Rationale: need direct access to rtc.AudioFrame stream, session lifecycle hooks, emission of transcription and generation events, and tight synchronous control for commit/turn semantics.
2) Turn-detection ownership
• Primary owner: LiveKit VAD for user turn boundary detection.
• Secondary confirmation: send vLLM input_audio_buffer.commit when LiveKit VAD signals end-of-turn.
• Treat vLLM transcription.done as authoritative for ASR finalization only if it arrives after commit; do not rely solely on it for boundary detection because vLLM has no turn IDs.
3) Audio ingestion
• RealtimeSession should receive pushed `rtc.AudioFrame`s from LiveKit (adapter registers a frame handler on session/track subscription). Implement per-participant audio streams keyed by participant ID.
4) Audio conversion pipeline
• Convert per-frame:
◦ Deinterleave stereo → pick dominant speaker channel or mix to mono.
◦ Resample from 48 kHz float32 → 16 kHz.
◦ Convert float → PCM16 little-endian.
◦ Base64-encode PCM16 blocks sized to vLLM buffer targets (e.g., 100–200 ms).
• Provide unit for conversions in adapter (do not depend on LiveKit utilities: assume none provided).
5) When to send input_audio_buffer.append / commit
• Append continuously as audio frames arrive (batched into 100–200 ms chunks).
• On LiveKit VAD end-of-turn: call input_audio_buffer.commit immediately.
• If VAD absent or unreliable: use trailing silence timeout (e.g., 600 ms) then commit.
6) Exposing transcription.delta to UI without polluting chat_ctx
• Emit LiveKit transcription events with a flag interim=true (or use transcription.delta event type).
• Map these to UI-only channels (transcription UI stream) and do NOT append to chat_ctx.
• Include metadata: asr_session_id, asr_chunk_id, confidence, timestamp.
• Keep interim transcripts ephemeral in adapter state for UI replay; drop on final commit.
7) Converting transcription.done → final user turn
• On transcription.done (matching latest asr_commit_id) and after VAD commit:
◦ Create a canonical user message object: { role: "user", text: final_transcript, asr_commit_id, turn_id }.
◦ Mark it final and emit an event transcription.final for UI.
◦ Do NOT auto-add to chat_ctx yet (see next).
8) Who commits final user message into chat_ctx
• Adapter should explicitly append the final user message into chat_ctx via LiveKit API (call it chat_ctx.append_message() or equivalent). Rationale: LiveKit will not infer/merge from transcription events because vLLM lacks IDs; explicit append avoids ambiguity and gives the adapter control to dedupe.
9) Where/how to call /v1/chat/completions
• After appending final user message to chat_ctx (ensures chat_ctx canonical ordering), adapter should call chat/completions:
◦ Build request with selected chat history (chat_ctx windowing policy) + new final user message ID reference.
◦ Open HTTP streaming connection; record generation_id.
◦ Emit generation start event into LiveKit with generation_id and linked turn_id.
10) Streaming assistant deltas into TTS pipeline
• As streaming HTTP yields deltas:
◦ Publish each delta to LiveKit llm/generation stream as llm.ChatChunk or MessageGeneration.text_stream equivalent (incremental text, generation_id).
◦ Forward the same deltas to TTS node via its incremental API (e.g., tts.append_text_chunk(generation_id, chunk_text)).
• If TTS requires final text only, buffer deltas and flush final on completion.
11) Who commits final assistant message into chat_ctx
• Adapter should append the final assistant message into chat_ctx only after the chat/completions stream completes successfully.
• On completion, create assistant message object { role: "assistant", text: full_text, generation_id, turn_id } and append to chat_ctx.
12) Avoiding duplicate messages
• Use immutable IDs:
◦ turn_id (per user turn)
◦ asr_commit_id (per vLLM commit)
◦ generation_id (per chat completion)
• chat_ctx append operations must include the ID; append endpoint should be idempotent (adapter should check whether message with same ID exists before appending).
• Maintain local index mapping IDs → appended flag; skip appends if already present.
13) Capability flags
• audio_output=False (adapter does not synthesize audio itself).
• user_transcription=True (adapter provides ASR transcripts).
• turn_detection=False? No — set turn_detection=False only if LiveKit requires model to do turn detection. Since LiveKit VAD owns endpointing in our design, set turn_detection=False to indicate model does not perform turn detection. But the adapter still emits final user turns (it appends final transcripts) — this is fine: turn_detection=False means model won't claim to own live endpointing, not that it can't emit final messages.
• mutable_chat_context=True (adapter will read and append to chat_ctx).
14) Internal IDs/state
• Maintain:
◦ session_id (LiveKit AgentSession id)
◦ participant_id
◦ turn_id (UUID per VAD-detected user turn)
◦ asr_commit_id (UUID per vLLM commit)
◦ generation_id (UUID per chat/completions call)
◦ appended_user_message_ids, appended_assistant_message_ids (sets for idempotency)
◦ pending_generations map: generation_id → cancellation token / HTTP stream handle
◦ last_interim_sequence per participant for ordering
15) Interruption behavior
• On user interrupt during assistant generation/TTS:
◦ Cancel TTS playback immediately via TTS node cancel API.
◦ Cancel the /v1/chat/completions stream (if supported). vLLM chat does support cancellation via closing connection; adapter should close HTTP stream and mark generation cancelled.
◦ Mark generation as cancelled and avoid appending partial assistant text to chat_ctx.
◦ Ignore late assistant deltas by checking generation_id and cancelled flag—do not forward them to TTS or append.
• If chat completion cannot be cancelled server-side, close client-side stream and discard incoming bytes; TTS must be cancelled locally.
16) Reconnect to vLLM / resiliency
• Maintain short-lived vLLM realtime connections per participant session.
• On ASR WS reconnect:
◦ Recreate a fresh vLLM session with new asr_session_id.
◦ Resume streaming new audio; do NOT resend old audio; vLLM has no memory anyway.
◦ If there was an in-progress asr_commit that never returned transcription.done, mark it failed and notify UI.
• Keep retry/backoff and surface reconnect status to UI.
17) Conversation history selection
• Adapter controls windowing policy:
◦ Keep last N messages by tokens or time, prefer assistant + final user messages only.
◦ When calling chat/completions, fetch canonical chat_ctx (adapter-read) and select messages per policy.
◦ Include message IDs so chat/completions responses can be linked back to chat_ctx entries on append.
18) Tests to write
• Unit:
◦ Audio conversion: stereo→mono, resample, float→PCM16, base64 correctness.
◦ ID handling: idempotent append behavior.
◦ Interim vs final event routing.
• Integration:
◦ Full flow: audio -> vLLM WS (mock) emits deltas/done -> adapter appends user -> calls chat/completions (mock) streams assistant deltas -> TTS receives chunks -> final append.
◦ Interrupt test: while streaming assistant, send user audio -> adapter cancels generation and TTS.
◦ Reconnect test: drop ASR WS mid-turn -> reconnect -> new asr_session.
• E2E:
◦ UI sees interim transcripts, final commits in chat history, no duplicates.
19) LiveKit SDK limitations / APIs to avoid
• Avoid relying on undocumented/private methods or internal state mutation APIs.
• Do not assume LiveKit will auto-commit transcription events to chat_ctx.
• Do not rely on LiveKit exposing low-level audio format conversion utilities — implement adapter-side conversions.
• Avoid assuming server-side cancellation behavior for external chat endpoints.
Operational notes
, with interactive elementstall-airline-16756
05/01/2026, 2:09 PM