feat: adiciona Silero VAD local com configuração por ambiente

This commit is contained in:
alex alves
2026-11-03 17:20:51 -03:00
parent 2d1acdac31
commit d27c26c166
11 changed files with 1154 additions and 11 deletions

View File

@@ -390,7 +390,7 @@ src/voice_assistant/
audio_gateway/app/ WebSocket endpoint and protocol
application/ VoiceSession and transport-independent events
domain/ Session state and domain rules
vad/ VAD port and speech boundaries; runtime integration pending
vad/ VAD port, speech boundaries, and local Silero adapter
audio/ Canonical audio models
stt/ STT contract, mock and NVIDIA adapter
agent/ Agent contract and mock adapter
@@ -432,5 +432,70 @@ boundary. Model padding never extends the input timeline.
`reset()` discards detection state and restarts the timeline, and `aclose()`
releases resources without emitting new speech events. Calls are serialized.
These contracts do not start turns, select providers, or carry transport IDs.
The Silero implementation, application integration, and browser VAD replacement
are subsequent tasks. This step leaves the active conversation flow unchanged.
The local Silero implementation is available independently of the running
conversation. Application integration and browser VAD replacement are subsequent
tasks; the active conversation flow still uses its existing browser VAD.
### Local Silero VAD adapter
`vad/silero.py` implements `VADAdapter` with the official Silero model and
`VADIterator`. `silero-vad[onnx-cpu]==6.2.3` bundles the model files; model loading
and inference use CPU locally, without runtime downloads or `torch.hub`.
The upstream package also requires PyTorch. The uv source configuration selects
CPU builds on Linux/Windows and the standard PyPI build on macOS.
Each adapter owns its model state, PCM buffer, and input timeline. It converts
PCM S16LE to normalized float samples, buffers 512-sample windows, and maps
Silero boundaries to the application events. Defaults are threshold 0.5,
300 ms of silence, 30 ms speech padding, and a 10-second worker wait limit.
These values are initial settings, to be tuned with real conversational audio.
`process()` may return several events for a large chunk. `flush()` pads a final
partial window internally while keeping event positions within real audio.
`reset()` discards detection state and restarts the timeline. `aclose()` discards
pending input and releases model references without emitting speech events.
Model loading and inference run outside the event loop. Cancellation or a worker
timeout requires reset before accepting more audio. Outstanding workers retain
ownership: reset/close wait for them, with bounded waits and retry on timeout.
Running native inference is not forcibly terminated by coroutine cancellation.
Tests with a controlled iterator cover framing, positions, isolation, and worker
ownership. `tests/test_silero_vad_integration.py` exercises the installed ONNX
model with network access blocked; it skips when the Silero dependency is absent.
It can be run with:
```bash
uv run pytest -q tests/test_silero_vad.py tests/test_silero_vad_integration.py
```
Upstream source and license: [Silero VAD](https://github.com/snakers4/silero-vad)
([MIT](https://github.com/snakers4/silero-vad/blob/master/LICENSE)).
### VAD configuration by environment
The active environment YAML configures VAD using the same `provider`/`options`
structure as STT, Agent, and TTS. `config/development.yaml` contains:
```yaml
vad:
provider: silero
options:
threshold: 0.5
min_silence_duration_ms: 300
speech_pad_ms: 30
worker_timeout_seconds: 10.0
```
`APP_ENV` selects the environment through the existing configuration loader.
`create_vad_adapter(config, catalog)` resolves the VAD from `config/providers.yaml`
and passes the environment options to its constructor. The Silero constructor
retains defaults for direct use and missing options. A missing `vad` section
remains compatible with existing configurations; explicitly creating VAD in
such an environment raises a configuration error. Changing YAML values affects
new adapter instances after configuration is reloaded; there is no live reload.
This factory makes the configuration usable independently. Connecting it to the
conversation lifecycle remains the next task.