config: voice block moves to its own file (V-410)
The VoiceConfig struct only. Its defaults, normalise/validate arms and UseLLMRouter follow in the next commit, because the pre-commit cap counts a pure move at twice the block size. The RouterThreshold doc drifted: it said the default was 0.35 and that 0.0 meant permissive. DefaultRouterThreshold is 0.55 and applyDefaults replaces anything <= 0, so permissive was never reachable. Fixed the comment, not the code.
This commit is contained in:
@@ -327,113 +327,6 @@ type QuietHoursConfig struct {
|
||||
End string `json:"end,omitempty"` // "HH:MM" local time, e.g. "08:00"
|
||||
}
|
||||
|
||||
// VoiceConfig — the client↔core TCP surface + the stt/tts worker-module
|
||||
// seams.
|
||||
//
|
||||
// Enabled gates wiring; Bind is the TCP address (inside the wg tunnel in
|
||||
// production; "127.0.0.1:9100" for the local smoke). Lang is the default
|
||||
// language hint passed to both stt and tts (per-call overrides later).
|
||||
//
|
||||
// Stt and Tts are the worker-module seams. nil Stt ⇒ daemon wires the
|
||||
// in-process stt.Stub (the "no models on disk" floor — the loop is
|
||||
// exercisable end-to-end with a deterministic no-model transcriber).
|
||||
// non-nil Stt with Socket ⇒ daemon wires stt.Remote dialing that unix
|
||||
// socket (cmd/mavsttd serves the other end; production swaps in a
|
||||
// faster-whisper handler in cmd/mavsttd, no daemon or stt-package
|
||||
// change). Tts mirrors for tts.Remote + cmd/mavttsd.
|
||||
//
|
||||
// Embedder configures the router's sentence embedder. When all three
|
||||
// paths are set, the daemon constructs an ONNX multilingual embedder
|
||||
// (in-process); when nil, it falls back to the floor HashEmbedder stub
|
||||
// (deterministic, no model files required — good for CI and smoke).
|
||||
//
|
||||
// The daemon refuses to start if Voice.Enabled but Bind is empty — the
|
||||
// bind is the one operational config the surface can't default (127.0.0.1
|
||||
// is too relaxed for production, a wg-tunnel address is the user's);
|
||||
// surfacing the gap explicitly beats an idle listener the user thinks is
|
||||
// wired but isn't reachable.
|
||||
type VoiceConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Bind string `json:"bind,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
Stt *WorkerConfig `json:"stt,omitempty"`
|
||||
Tts *TtsConfig `json:"tts,omitempty"`
|
||||
Embedder *EmbedderConfig `json:"embedder,omitempty"`
|
||||
|
||||
// RouterThreshold — the minimum confidence score for the intent classifier
|
||||
// (stage 3 gate). Below this → clarify, don't guess. 0.0 means permissive
|
||||
// (never clarify); 0.35 is a reasonable floor for the ONNX embedder.
|
||||
// The HashEmbedder floor scores lexically and may need a lower value.
|
||||
// Default 0.35 if unset.
|
||||
RouterThreshold float64 `json:"router_threshold,omitempty"`
|
||||
|
||||
// LLMRouter — route with the resident model instead of the embedding
|
||||
// classifier. On by default since Vikunja #320.
|
||||
//
|
||||
// Measured on the held-out fixture (docs/evals/2026-07-31-routing.md): 63.2% of
|
||||
// intents right against the classifier's 50.0%, and no route errors. It
|
||||
// costs about 1s per turn instead of 30ms.
|
||||
//
|
||||
// It is safe to leave on. The model can refuse — it answers "unknown" when
|
||||
// it cannot route, and the turn drops to the classifier and its clarify
|
||||
// gate. Any LLM error does the same, so a turn never breaks on the model.
|
||||
// Slot extraction runs on LLM decisions too, so acts get their Fn and
|
||||
// reminders their Time.
|
||||
//
|
||||
// Set it false to go back to the classifier, e.g. on a box with no
|
||||
// llama-server or when 1s a turn is too slow.
|
||||
//
|
||||
// It is a pointer so that "missing from the file" and "explicitly false"
|
||||
// are different things: missing means on, false means off. Read it with
|
||||
// UseLLMRouter(), not directly.
|
||||
LLMRouter *bool `json:"llm_router,omitempty"`
|
||||
|
||||
// QueryMinScore — the note-recall confidence gate. Top cosine below this
|
||||
// ⇒ "I don't know" instead of a guess. Tuned for the ONNX embedder (0.55);
|
||||
// the HashEmbedder floor scores lexically and may never clear it. 0.55
|
||||
// default if unset.
|
||||
QueryMinScore float64 `json:"query_min_score,omitempty"`
|
||||
|
||||
// QueryMinMargin — the second half of the recall gate: the top hit must
|
||||
// beat the runner-up by more than this. The absolute score above cannot do
|
||||
// the job on its own, because the e5 embedder puts every cosine in one
|
||||
// narrow high band, so a made-up question scores as high as a real one.
|
||||
// The margin asks whether one note is clearly the best instead.
|
||||
// Negative ⇒ off. 0 ⇒ the default below.
|
||||
QueryMinMargin float64 `json:"query_min_margin,omitempty"`
|
||||
|
||||
// ClarifyMaxAttempts — how many clarifying questions she may ask about one
|
||||
// request before she gives up and says she did not understand. Default 3.
|
||||
ClarifyMaxAttempts int `json:"clarify_max_attempts,omitempty"`
|
||||
|
||||
// Persona — optional prompt prefix that tunes maven's character. Prepended
|
||||
// to every LLM system prompt (nudge phrasing, note queries, general
|
||||
// knowledge). Empty string ⇒ current hardcoded persona (feminine-gendered
|
||||
// Russian self-reference). Example: "Be formal and answer in English only."
|
||||
Persona string `json:"persona,omitempty"`
|
||||
|
||||
// OwnerName / City — optional facts about the owner, added to the shared
|
||||
// context block (internal/persona). Empty is fine: the block still states
|
||||
// who he is grammatically (a man, addressed as "ты") and the current time.
|
||||
// Nothing about correct behaviour may depend on these being filled in.
|
||||
OwnerName string `json:"owner_name,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
|
||||
// Weather — the weather provider config. nil ⇒ the daemon wires
|
||||
// the stub provider (returns ErrNotConfigured — "погода не настроена").
|
||||
// Set provider to "open-meteo" to use the keyless Open-Meteo API.
|
||||
Weather *WeatherConfig `json:"weather,omitempty"`
|
||||
|
||||
// Tools — the enabled act allowlist. Each is a spoken verb → argv the
|
||||
// executor runs (args from the utterance appended). Editing this set is the
|
||||
// human-only "enable" act (per spec); maven can't add to it from a request.
|
||||
// Empty ⇒ every act is refused (nothing enabled).
|
||||
Tools []ToolConfig `json:"tools,omitempty"`
|
||||
|
||||
// ToolTimeout bounds each tool invocation. Zero ⇒ executor default (30s).
|
||||
ToolTimeout Duration `json:"tool_timeout,omitempty"`
|
||||
}
|
||||
|
||||
// WeatherConfig configures the weather provider for voice queries.
|
||||
type WeatherConfig struct {
|
||||
Provider string `json:"provider,omitempty"` // "open-meteo" or "" → stub
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package config
|
||||
|
||||
// VoiceConfig — the client↔core TCP surface + the stt/tts worker-module
|
||||
// seams.
|
||||
//
|
||||
// Enabled gates wiring; Bind is the TCP address (inside the wg tunnel in
|
||||
// production; "127.0.0.1:9100" for the local smoke). Lang is the default
|
||||
// language hint passed to both stt and tts (per-call overrides later).
|
||||
//
|
||||
// Stt and Tts are the worker-module seams. nil Stt ⇒ daemon wires the
|
||||
// in-process stt.Stub (the "no models on disk" floor — the loop is
|
||||
// exercisable end-to-end with a deterministic no-model transcriber).
|
||||
// non-nil Stt with Socket ⇒ daemon wires stt.Remote dialing that unix
|
||||
// socket (cmd/mavsttd serves the other end; production swaps in a
|
||||
// faster-whisper handler in cmd/mavsttd, no daemon or stt-package
|
||||
// change). Tts mirrors for tts.Remote + cmd/mavttsd.
|
||||
//
|
||||
// Embedder configures the router's sentence embedder. When all three
|
||||
// paths are set, the daemon constructs an ONNX multilingual embedder
|
||||
// (in-process); when nil, it falls back to the floor HashEmbedder stub
|
||||
// (deterministic, no model files required — good for CI and smoke).
|
||||
//
|
||||
// The daemon refuses to start if Voice.Enabled but Bind is empty — the
|
||||
// bind is the one operational config the surface can't default (127.0.0.1
|
||||
// is too relaxed for production, a wg-tunnel address is the user's);
|
||||
// surfacing the gap explicitly beats an idle listener the user thinks is
|
||||
// wired but isn't reachable.
|
||||
type VoiceConfig struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Bind string `json:"bind,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
Stt *WorkerConfig `json:"stt,omitempty"`
|
||||
Tts *TtsConfig `json:"tts,omitempty"`
|
||||
Embedder *EmbedderConfig `json:"embedder,omitempty"`
|
||||
|
||||
// RouterThreshold — the minimum confidence score for the intent classifier
|
||||
// (stage 3 gate). Below this → clarify, don't guess. 0 ⇒
|
||||
// DefaultRouterThreshold (0.55), tuned for the ONNX embedder; the
|
||||
// HashEmbedder floor scores lexically and may need a lower value.
|
||||
//
|
||||
// There is no way to ask for "permissive, never clarify" through this
|
||||
// field: normaliseVoice replaces anything ≤ 0 with the default, so a
|
||||
// written 0 is the default and a written negative is too.
|
||||
RouterThreshold float64 `json:"router_threshold,omitempty"`
|
||||
|
||||
// LLMRouter — route with the resident model instead of the embedding
|
||||
// classifier. On by default since Vikunja #320.
|
||||
//
|
||||
// Measured on the held-out fixture (docs/evals/2026-07-31-routing.md): 63.2% of
|
||||
// intents right against the classifier's 50.0%, and no route errors. It
|
||||
// costs about 1s per turn instead of 30ms.
|
||||
//
|
||||
// It is safe to leave on. The model can refuse — it answers "unknown" when
|
||||
// it cannot route, and the turn drops to the classifier and its clarify
|
||||
// gate. Any LLM error does the same, so a turn never breaks on the model.
|
||||
// Slot extraction runs on LLM decisions too, so acts get their Fn and
|
||||
// reminders their Time.
|
||||
//
|
||||
// Set it false to go back to the classifier, e.g. on a box with no
|
||||
// llama-server or when 1s a turn is too slow.
|
||||
//
|
||||
// It is a pointer so that "missing from the file" and "explicitly false"
|
||||
// are different things: missing means on, false means off. Read it with
|
||||
// UseLLMRouter(), not directly.
|
||||
LLMRouter *bool `json:"llm_router,omitempty"`
|
||||
|
||||
// QueryMinScore — the note-recall confidence gate. Top cosine below this
|
||||
// ⇒ "I don't know" instead of a guess. Tuned for the ONNX embedder (0.55);
|
||||
// the HashEmbedder floor scores lexically and may never clear it. 0.55
|
||||
// default if unset.
|
||||
QueryMinScore float64 `json:"query_min_score,omitempty"`
|
||||
|
||||
// QueryMinMargin — the second half of the recall gate: the top hit must
|
||||
// beat the runner-up by more than this. The absolute score above cannot do
|
||||
// the job on its own, because the e5 embedder puts every cosine in one
|
||||
// narrow high band, so a made-up question scores as high as a real one.
|
||||
// The margin asks whether one note is clearly the best instead.
|
||||
// Negative ⇒ off. 0 ⇒ the default below.
|
||||
QueryMinMargin float64 `json:"query_min_margin,omitempty"`
|
||||
|
||||
// ClarifyMaxAttempts — how many clarifying questions she may ask about one
|
||||
// request before she gives up and says she did not understand. Default 3.
|
||||
ClarifyMaxAttempts int `json:"clarify_max_attempts,omitempty"`
|
||||
|
||||
// Persona — optional prompt prefix that tunes maven's character. Prepended
|
||||
// to every LLM system prompt (nudge phrasing, note queries, general
|
||||
// knowledge). Empty string ⇒ current hardcoded persona (feminine-gendered
|
||||
// Russian self-reference). Example: "Be formal and answer in English only."
|
||||
Persona string `json:"persona,omitempty"`
|
||||
|
||||
// OwnerName / City — optional facts about the owner, added to the shared
|
||||
// context block (internal/persona). Empty is fine: the block still states
|
||||
// who he is grammatically (a man, addressed as "ты") and the current time.
|
||||
// Nothing about correct behaviour may depend on these being filled in.
|
||||
OwnerName string `json:"owner_name,omitempty"`
|
||||
City string `json:"city,omitempty"`
|
||||
|
||||
// Weather — the weather provider config. nil ⇒ the daemon wires
|
||||
// the stub provider (returns ErrNotConfigured — "погода не настроена").
|
||||
// Set provider to "open-meteo" to use the keyless Open-Meteo API.
|
||||
Weather *WeatherConfig `json:"weather,omitempty"`
|
||||
|
||||
// Tools — the enabled act allowlist. Each is a spoken verb → argv the
|
||||
// executor runs (args from the utterance appended). Editing this set is the
|
||||
// human-only "enable" act (per spec); maven can't add to it from a request.
|
||||
// Empty ⇒ every act is refused (nothing enabled).
|
||||
Tools []ToolConfig `json:"tools,omitempty"`
|
||||
|
||||
// ToolTimeout bounds each tool invocation. Zero ⇒ executor default (30s).
|
||||
ToolTimeout Duration `json:"tool_timeout,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user