Skip to content

feat(realtime): gate realtime pipeline voice models behind voice recognition#10319

Merged
mudler merged 13 commits into
masterfrom
feat/realtime-voice-gate
Jun 13, 2026
Merged

feat(realtime): gate realtime pipeline voice models behind voice recognition#10319
mudler merged 13 commits into
masterfrom
feat/realtime-voice-gate

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

What

Adds an optional speaker-verification gate to realtime pipeline voice models (VAD → STT → LLM → TTS). When a model's YAML declares a pipeline.voice_recognition block, every committed utterance is verified against authorized speakers, and unauthorized utterances are dropped before they reach the LLM (no LLM call, no tool execution, no TTS). The realtime session stays open.

This connects the existing realtime API (WebRTC + WebSocket) with the existing voice-recognition subsystem (/v1/voice/*, VoiceRegistry, SpeechBrain/ONNX speaker-recognition backend). No new infrastructure.

Configuration

pipeline:
  vad: silero-vad
  transcription: whisper
  llm: qwen
  tts: kokoro
  voice_recognition:
    model: speaker-recognition   # speaker-recognition backend model
    mode: identify               # "identify" (registry) or "verify" (references)
    threshold: 0.25              # cosine distance; <= passes
    when: every                  # "every" (default) or "first"
    on_reject: drop_event        # "drop_event" (default) or "drop_silent"
    anti_spoofing: false         # optional liveness check (verify mode)
    allow:                       # identify mode: authorized identities (multiple persons)
      names: ["alice", "bob"]
      labels: ["family"]
    references:                  # verify mode: reference speakers (multiple persons)
      - { name: alice, audio: /models/voices/alice.wav }
      - { name: bob,   audio: /models/voices/bob.wav }
  • identify (1:N): match against speakers registered via /v1/voice/register; allow.names/allow.labels filter who counts (empty = any registered speaker).
  • verify (1:few): match against reference audios, embedded once at model load.
  • Both modes support multiple authorized persons.

Design

  • Dedicated voiceGate component (realtime_voicegate.go) with a single Authorize entry point and testability seams (embedFn/verifyFn + the Registry interface), so all decision paths are unit-tested without a live backend.
  • Parallel-with-STT + hard barrier: verification runs concurrently with transcription as a latency optimization, but there is a hard join before generateResponse. An unauthorized utterance can never reach the LLM/tools/TTS regardless of how fast STT finishes.
  • Fail-closed everywhere: a backend/verification error denies the turn; an incomplete voice_recognition block (e.g. missing model) refuses the session rather than running unprotected.
  • when: first trusts the session after one successful match (guarded by a mutex); when: every re-checks each utterance.

Testing

  • Unit tests for config schema (Normalize/Validate/VoiceGateEnabled), cosineDistance, identify mode (allow by name/label/empty, threshold, unknown speaker, no-speech, embed-fail, identify-fail), verify mode (match/no-match/no-speech/embed-fail, anti-spoofing + its error path), the decide policy matrix, and newVoiceGate fail-fast.
  • go vet clean, go test -race clean on the realtime package, golangci-lint --new-from-merge-base reports 0 issues.

Notes

  • identify mode uses the existing in-memory VoiceRegistry (lost on restart); persistence is future work.
  • Built with subagent-driven development; each task was spec- and quality-reviewed.

Assisted-by: Claude:opus-4.8 [Claude Code]

mudler added 13 commits June 13, 2026 20:37
Add the PipelineVoiceRecognition config block that gates a realtime
pipeline behind speaker verification (identify against the voice
registry, or verify against reference audios), with Normalize defaults
and Validate enum/shape checks. Register the new fields in the config
meta registry so the UI renders them with proper labels/components
(required by the registry-coverage gate).

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Run speaker verification concurrently with transcription and join on a
hard barrier before generateResponse, so unauthorized utterances never
reach the LLM, tools, or TTS. Supports identify (registry) and verify
(reference) modes with multiple authorized speakers, per-utterance or
first-utterance checking, and drop-with-event or silent-drop on reject.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Only launch the verification goroutine on the transcription path and
drain it before the temp WAV is removed on the transcription-error
return, so an in-flight backend read never races the deferred cleanup.
Drop the write-only voiceMatched field; log the matched speaker instead.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
A present voice_recognition block with no model previously disabled the
gate silently, authorizing every speaker. Treat block presence as the
intent signal and reject an empty model in Validate, so the session is
refused instead of running unprotected.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
Drive the real commitUtterance path (gate goroutine, hard join before the
LLM, reject event, when:first session trust) with the existing
transport/model doubles: authorized speakers reach a full response,
unauthorized ones are dropped before the LLM with a speaker_not_authorized
event, backend errors fail closed, drop_silent stays quiet, and when:first
trusts the session after one match.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
@mudler
mudler merged commit 4ec6e32 into master Jun 13, 2026
22 checks passed
@mudler
mudler deleted the feat/realtime-voice-gate branch June 13, 2026 21:38
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