mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-04 20:14:43 -04:00
feat(silero-vad): allow threshold/silence/pad via model options (#12430)
* feat(silero-vad): allow threshold/silence/pad via model options Signed-off-by: anton ziderer <Antonziderer@mail.ru> Co-authored-by: Cursor <cursoragent@cursor.com> * fix(silero-vad): ignore NaN thresholds NaN passes the detector validation but prevents speech comparisons from succeeding. Ignore it like malformed input and document the option validation. Convert the option tests to Ginkgo and cover invalid overrides. Assisted-by: Codex:gpt-6 Signed-off-by: anton ziderer <Antonziderer@mail.ru> --------- Signed-off-by: anton ziderer <Antonziderer@mail.ru> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
This commit is contained in:
4 files changed
+145
-10
No files matched your search
@@ -4,32 +4,70 @@ package main
|
||||
// It is meant to be used by the main executable that is the server for the specific backend type (falcon, gpt3, etc)
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mudler/LocalAI/pkg/grpc/base"
|
||||
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||
"github.com/streamer45/silero-vad-go/speech"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultThreshold = 0.5
|
||||
defaultMinSilenceDurationMs = 100
|
||||
defaultSpeechPadMs = 30
|
||||
)
|
||||
|
||||
type VAD struct {
|
||||
base.SingleThread
|
||||
detector *speech.Detector
|
||||
}
|
||||
|
||||
func (vad *VAD) Load(opts *pb.ModelOptions) error {
|
||||
v, err := speech.NewDetector(speech.DetectorConfig{
|
||||
ModelPath: opts.ModelFile,
|
||||
SampleRate: 16000,
|
||||
//WindowSize: 1024,
|
||||
Threshold: 0.5,
|
||||
MinSilenceDurationMs: 100,
|
||||
SpeechPadMs: 30,
|
||||
})
|
||||
cfg := detectorConfigFromOptions(opts)
|
||||
|
||||
v, err := speech.NewDetector(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create silero detector: %w", err)
|
||||
}
|
||||
|
||||
vad.detector = v
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectorConfigFromOptions(opts *pb.ModelOptions) speech.DetectorConfig {
|
||||
cfg := speech.DetectorConfig{
|
||||
ModelPath: opts.ModelFile,
|
||||
SampleRate: 16000,
|
||||
Threshold: defaultThreshold,
|
||||
MinSilenceDurationMs: defaultMinSilenceDurationMs,
|
||||
SpeechPadMs: defaultSpeechPadMs,
|
||||
}
|
||||
|
||||
for _, opt := range opts.Options {
|
||||
key, value, ok := strings.Cut(opt, ":")
|
||||
if !ok || value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(key)) {
|
||||
case "threshold":
|
||||
if v, err := strconv.ParseFloat(strings.TrimSpace(value), 32); err == nil && !math.IsNaN(v) {
|
||||
cfg.Threshold = float32(v)
|
||||
}
|
||||
case "min_silence_duration_ms":
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(value)); err == nil && v >= 0 {
|
||||
cfg.MinSilenceDurationMs = v
|
||||
}
|
||||
case "speech_pad_ms":
|
||||
if v, err := strconv.Atoi(strings.TrimSpace(value)); err == nil && v >= 0 {
|
||||
cfg.SpeechPadMs = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (vad *VAD) VAD(req *pb.VADRequest) (pb.VADResponse, error) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestVADOptions(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Silero VAD Options")
|
||||
}
|
||||
|
||||
var _ = Describe("Detector configuration", func() {
|
||||
It("preserves the model path and defaults", func() {
|
||||
cfg := detectorConfigFromOptions(&pb.ModelOptions{ModelFile: "silero_vad.onnx"})
|
||||
Expect(cfg.ModelPath).To(Equal("silero_vad.onnx"))
|
||||
Expect(cfg.SampleRate).To(Equal(16000))
|
||||
Expect(cfg.Threshold).To(Equal(float32(0.5)))
|
||||
Expect(cfg.MinSilenceDurationMs).To(Equal(100))
|
||||
Expect(cfg.SpeechPadMs).To(Equal(30))
|
||||
})
|
||||
|
||||
It("applies model options", func() {
|
||||
cfg := detectorConfigFromOptions(&pb.ModelOptions{
|
||||
ModelFile: "silero_vad.onnx",
|
||||
Options: []string{
|
||||
"threshold:0.55",
|
||||
"min_silence_duration_ms:50",
|
||||
"speech_pad_ms:450",
|
||||
"ignored",
|
||||
"bad_threshold:abc",
|
||||
},
|
||||
})
|
||||
Expect(cfg.Threshold).To(Equal(float32(0.55)))
|
||||
Expect(cfg.MinSilenceDurationMs).To(Equal(50))
|
||||
Expect(cfg.SpeechPadMs).To(Equal(450))
|
||||
})
|
||||
|
||||
It("ignores NaN and malformed values without replacing valid options", func() {
|
||||
cfg := detectorConfigFromOptions(&pb.ModelOptions{
|
||||
ModelFile: "silero_vad.onnx",
|
||||
Options: []string{
|
||||
"threshold:0.55",
|
||||
"threshold:NaN",
|
||||
"threshold:abc",
|
||||
"min_silence_duration_ms:-1",
|
||||
"speech_pad_ms:abc",
|
||||
},
|
||||
})
|
||||
Expect(cfg.Threshold).To(Equal(float32(0.55)))
|
||||
Expect(cfg.MinSilenceDurationMs).To(Equal(100))
|
||||
Expect(cfg.SpeechPadMs).To(Equal(30))
|
||||
Expect(cfg.IsValid()).To(Succeed())
|
||||
})
|
||||
|
||||
It("uses the default threshold when the only override is NaN", func() {
|
||||
cfg := detectorConfigFromOptions(&pb.ModelOptions{
|
||||
ModelFile: "silero_vad.onnx",
|
||||
Options: []string{"threshold:NaN"},
|
||||
})
|
||||
Expect(cfg.Threshold).To(Equal(float32(0.5)))
|
||||
Expect(cfg.IsValid()).To(Succeed())
|
||||
})
|
||||
})
|
||||
@@ -93,9 +93,33 @@ name: silero-vad
|
||||
backend: silero-vad
|
||||
```
|
||||
|
||||
Detection parameters can be overridden via model `options` (`key:value` entries):
|
||||
|
||||
```yaml
|
||||
name: silero-vad
|
||||
backend: silero-vad
|
||||
options:
|
||||
- threshold:0.55
|
||||
- min_silence_duration_ms:50
|
||||
- speech_pad_ms:450
|
||||
```
|
||||
|
||||
Supported options:
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `threshold` | float | `0.5` | Speech probability threshold |
|
||||
| `min_silence_duration_ms` | int | `100` | Minimum silence before ending a speech segment |
|
||||
| `speech_pad_ms` | int | `30` | Padding added around each speech segment |
|
||||
|
||||
Thresholds must be greater than 0 and less than 1. Durations must be nonnegative integers.
|
||||
Malformed values, negative durations, and NaN thresholds are ignored; the default or last valid value remains in use.
|
||||
|
||||
Reload the model (or restart LocalAI) after changing these options.
|
||||
|
||||
## Detection Parameters
|
||||
|
||||
The Silero VAD backend uses the following internal defaults:
|
||||
The Silero VAD backend uses the following internal defaults (overridable via `options` above):
|
||||
|
||||
- **Sample rate:** 16kHz
|
||||
- **Threshold:** 0.5
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
name: silero-vad
|
||||
backend: silero-vad
|
||||
options:
|
||||
- threshold:0.55
|
||||
- min_silence_duration_ms:50
|
||||
- speech_pad_ms:450
|
||||
Reference in new issue
Block a user