| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- """Configuration management for STT daemon."""
- from dataclasses import dataclass, field
- import logging
- import tomllib
- from pathlib import Path
- from typing import Any
- logger = logging.getLogger(__name__)
- KNOWN_SECTIONS = {
- "audio",
- "vad",
- "whisper",
- "output",
- "sink",
- "daemon",
- "events",
- "assistant",
- "audit",
- "llm",
- "triage",
- "speaker",
- "brief",
- "debug",
- }
- @dataclass
- class AudioConfig:
- """Audio capture configuration."""
- backend: str = "parec"
- device: str = "VirtualMicSink.monitor"
- sample_rate: int = 16000
- channels: int = 1
- frame_ms: int = 30
- gain: float = 1.0
- @dataclass
- class VadConfig:
- """Voice Activity Detection configuration."""
- aggressiveness: int = 2
- pre_roll_ms: int = 400
- speech_start_ms: int = 90
- silence_ms: int = 700
- min_utterance_ms: int = 300
- max_utterance_s: float = 30.0
- chunk_pause_ms: int = 350
- soft_max_s: float = 10.0
- rms_floor: float = 0.008
- trailing_trim_ms: int = 200
- @dataclass
- class WhisperConfig:
- """Whisper transcription configuration."""
- backend: str = "faster-whisper"
- model: str = "small"
- device: str = "cpu"
- compute_type: str = "int8"
- language: str | None = None
- beam_size: int = 1
- vad_filter: bool = False
- cpu_threads: int = 0
- no_speech_prob_max: float = 0.6
- avg_logprob_min: float = -1.0
- language_strategy: str = "static"
- sticky_redetect_logprob: float = -0.8
- @dataclass
- class OutputConfig:
- """Output configuration."""
- stdout: bool = True
- jsonl: bool = False
- session_dir: str = "transcripts"
- save_audio: bool = False
- session_gap_min: float = 5.0
- @dataclass
- class DaemonConfig:
- """Daemon runtime configuration."""
- queue_size: int = 50
- backlog_warn_at: int = 5
- @dataclass
- class EventsConfig:
- """Publish-only event bus configuration."""
- socket: str = ""
- heartbeat_ms: int = 1000
- ring_size: int = 200
- @dataclass
- class AssistantConfig:
- """Assistant (controller) configuration."""
- control_socket: str = ""
- stall_timeout_ms: int = 3000
- worker_stall_ms: int = 15000
- command_arm_timeout_s: float = 10.0
- chat_idle_timeout_s: float = 300.0
- chat_exit_phrases: list[str] = field(
- default_factory=lambda: ["end conversation", "that's all", "stop listening"]
- )
- require_viewer: bool = True
- turn_gap_s: float = 1.5
- @dataclass
- class AuditConfig:
- """Action audit configuration (daily JSONL, like transcripts)."""
- enabled: bool = True
- dir: str = "troubleshoot/audit"
- @dataclass
- class LlmConfig:
- """llama.cpp (OpenAI-compatible) configuration."""
- enabled: bool = True
- endpoint: str = "http://localhost:9931/v1/chat/completions"
- model: str = ""
- api_key_env: str = ""
- session_id: str = ""
- session_header: str = ""
- user_agent: str = ""
- reasoning_effort: str = ""
- extra_body: dict[str, Any] = field(default_factory=dict)
- temperature: float = 0.7
- max_tokens: int = 1024
- timeout_s: float = 120.0
- cache_prompt: bool = True
- chat_slot_id: int | None = None
- command_slot_id: int | None = None
- max_context_tokens: int = 131072
- compaction_safety_margin: int = 256
- compaction_enabled: bool = True
- compaction_trigger_ratio: float = 0.8
- compaction_keep_turns: int = 6
- compaction_max_tokens: int = 512
- compaction_timeout_s: float = 30.0
- compaction_input_tokens: int = 8192
- compaction_system_prompt: str = (
- "You compress a conversation. Preserve names, numbers, decisions, user "
- "preferences, and unresolved questions. Omit pleasantries. Output only the summary."
- )
- brief_compaction_system_prompt: str = (
- "You compress a triage briefing conversation. Preserve every opportunity, blocker, "
- "owner, responsibility, decision, deadline, and open question; drop pleasantries. "
- "Output only the summary."
- )
- triage_system_prompt: str = (
- "You maintain a living brief of a business conversation. The caller sends "
- "CURRENT LEDGER plus CURRENT BRIEF plus NEW UTTERANCES. Reply with exactly "
- "two sections in this order: NEW FACTS: first, then BRIEF:. Facts go first "
- "so a truncated reply still preserves extraction (truncation safety). In "
- "NEW FACTS:, emit one fact per line as '- <fact>', or the single line "
- "'none' when the new utterances add no new facts. Never repeat facts "
- "already listed in CURRENT LEDGER. In BRIEF:, return the UPDATED brief as "
- "plain text only, with no markdown fences and no JSON. Open with a Summary "
- "section: a rolling overview of the conversation so far, one to a few "
- "sentences for a short session and growing to a short paragraph or two as "
- "the session extends. Preserve prior content unless the new utterances "
- "contradict it; merge duplicates; prune stale items; keep owners, "
- "responsibilities, and deadlines explicit. Use short sections: Summary, "
- "Opportunities, Blockers/Risks, Responsibilities, Decisions, Action Items, "
- "Deadlines, Open Questions. Never silently delete Action Items, Open "
- "Questions, or Decisions lines. When an action completes or a question "
- "is answered, do NOT drop the line — move it to Decisions as "
- "'Done: <original> — resolved: <what happened>' (or keep it in place "
- "with a 'Done (...)' suffix) AND list the resolution as a NEW FACTS "
- "bullet. Prune only exact duplicates, never resolved history. "
- "Never emit a 'Prior Details:' section. Output "
- "the two sections directly; never emit tool calls, function-call syntax, "
- "or special wrapper tokens."
- )
- inference_queue_size: int = 8
- trim_block_turns: int = 4
- max_tool_iterations: int = 3
- system_prompt_chat: str = (
- "You are a concise voice assistant. Reply in 1-3 sentences."
- )
- system_prompt_command: str = (
- "You translate a spoken request into a single tool call when one applies."
- )
- @property
- def prompt_budget(self) -> int:
- """Prompt tokens available: the context window minus generation and slack."""
- return self.max_context_tokens - self.max_tokens - self.compaction_safety_margin
- @dataclass
- class TriageConfig:
- """Laya triage-panel configuration (in-process lane, multilingual pinned)."""
- enabled: bool = False
- questions_file: str = "questions.json"
- window: int = 500
- state_char_budget: int = 3000
- periodic_s: float = 30.0
- predict_max_tokens: int = 1024
- confidence_field: str = "answer_confidence"
- escalate_threshold: float = 0.6
- choice_min_confidence: float = 0.85
- extract_enabled: bool = True
- extract_max_tokens: int = 3072
- extract_timeout_s: float = 30.0
- extract_temperature: float = 0.2
- brief_slot_id: int | None = None
- brief_gate_enabled: bool = True
- brief_gate_threshold: float = 0.5
- brief_max_chars: int = 4000
- brief_pending_char_budget: int = 3000
- ledger_max_facts: int = 200
- fact_max_chars: int = 240
- device: str = ""
- preload: list[str] = field(default_factory=lambda: ["multilingual"])
- default_checkpoint: str = "multilingual"
- dump_io: bool = False
- @dataclass
- class SpeakerConfig:
- """Speaker-identification configuration (sherpa-onnx embedding lane)."""
- enabled: bool = False
- model_path: str | None = None
- provider: str = "cpu"
- threshold_id: float = 0.60
- threshold_new: float = 0.45
- min_enroll_s: float = 2.0
- rms_floor: float = 0.0
- margin: float = 0.04
- mint_cooldown_s: float = 60.0
- confirm_s: float = 12.0
- max_prototypes: int = 8
- provisional_ttl_h: float = 24.0
- store_dir: str = "speakers"
- join_timeout_s: float = 2.0
- @dataclass
- class BriefConfig:
- """Durable brief document configuration (markdown snapshot written to disk)."""
- write: bool = False
- @dataclass
- class DebugConfig:
- """Debug signal-log configuration (JSONL diagnostics, gitignored)."""
- enabled: bool = False
- dir: str = "troubleshoot/debug"
- heartbeat: bool = True
- max_bytes_per_day: int = 50000000
- @dataclass
- class SinkConfig:
- """Virtual audio sink configuration."""
- sink_name: str = "VirtualMicSink"
- remap_source_name: str = "VirtualMic"
- loopback_latency_msec: int = 1
- monitor_output: bool = False
- @dataclass
- class Config:
- """Complete daemon configuration."""
- audio: AudioConfig = field(default_factory=AudioConfig)
- vad: VadConfig = field(default_factory=VadConfig)
- whisper: WhisperConfig = field(default_factory=WhisperConfig)
- output: OutputConfig = field(default_factory=OutputConfig)
- sink: SinkConfig = field(default_factory=SinkConfig)
- daemon: DaemonConfig = field(default_factory=DaemonConfig)
- events: EventsConfig = field(default_factory=EventsConfig)
- assistant: AssistantConfig = field(default_factory=AssistantConfig)
- audit: AuditConfig = field(default_factory=AuditConfig)
- llm: LlmConfig = field(default_factory=LlmConfig)
- triage: TriageConfig = field(default_factory=TriageConfig)
- speaker: SpeakerConfig = field(default_factory=SpeakerConfig)
- brief: BriefConfig = field(default_factory=BriefConfig)
- debug: DebugConfig = field(default_factory=DebugConfig)
- def _optional_int(value: Any) -> int | None:
- """Coerce an optional TOML value to int, degrading to None on bad input.
- TOML has no null, so an absent key is "unset" and reaches this helper as
- ``None``. ``0`` is a valid slot id and must never be mistaken for unset.
- """
- if value is None:
- return None
- try:
- return int(value)
- except (TypeError, ValueError):
- return None
- def load_config(path: str | None = None) -> Config:
- """Load configuration from TOML file, with defaults.
- Args:
- path: Path to config.toml. If None, looks for config.toml in cwd,
- then uses all defaults.
- Returns:
- Config with loaded values overlayed on defaults.
- """
- cfg = Config()
- # Try to find and load config file
- config_path = None
- if path:
- config_path = Path(path)
- else:
- default_path = Path("config.toml")
- if default_path.exists():
- config_path = default_path
- if config_path and config_path.exists():
- with open(config_path, "rb") as f:
- data = tomllib.load(f)
- unknown = set(data) - KNOWN_SECTIONS
- if unknown:
- logger.warning(
- "Ignoring unknown config sections: %s", ", ".join(sorted(unknown))
- )
- # Audio section
- if "audio" in data:
- audio_data = data["audio"]
- cfg.audio = AudioConfig(
- backend=audio_data.get("backend", cfg.audio.backend),
- device=audio_data.get("device", cfg.audio.device),
- sample_rate=audio_data.get("sample_rate", cfg.audio.sample_rate),
- channels=audio_data.get("channels", cfg.audio.channels),
- frame_ms=audio_data.get("frame_ms", cfg.audio.frame_ms),
- gain=audio_data.get("gain", cfg.audio.gain),
- )
- # VAD section
- if "vad" in data:
- vad_data = data["vad"]
- cfg.vad = VadConfig(
- aggressiveness=vad_data.get("aggressiveness", cfg.vad.aggressiveness),
- pre_roll_ms=vad_data.get("pre_roll_ms", cfg.vad.pre_roll_ms),
- speech_start_ms=vad_data.get("speech_start_ms", cfg.vad.speech_start_ms),
- silence_ms=vad_data.get("silence_ms", cfg.vad.silence_ms),
- min_utterance_ms=vad_data.get("min_utterance_ms", cfg.vad.min_utterance_ms),
- max_utterance_s=vad_data.get("max_utterance_s", cfg.vad.max_utterance_s),
- chunk_pause_ms=vad_data.get("chunk_pause_ms", cfg.vad.chunk_pause_ms),
- soft_max_s=vad_data.get("soft_max_s", cfg.vad.soft_max_s),
- rms_floor=vad_data.get("rms_floor", cfg.vad.rms_floor),
- trailing_trim_ms=vad_data.get("trailing_trim_ms", cfg.vad.trailing_trim_ms),
- )
- # Whisper section
- if "whisper" in data:
- whisper_data = data["whisper"]
- backend = whisper_data.get("backend", cfg.whisper.backend)
- backend = backend.strip().lower().replace("_", "-")
- if backend not in ("faster-whisper", "pywhispercpp"):
- logger.warning(
- "Unknown whisper backend %r; falling back to 'faster-whisper'", backend
- )
- backend = "faster-whisper"
- language = whisper_data.get("language", cfg.whisper.language)
- # Normalize language: "", "auto", "none" -> None
- if language in ("", "auto", "none"):
- language = None
- strategy = whisper_data.get("language_strategy", cfg.whisper.language_strategy)
- if strategy not in ("static", "sticky"):
- logger.warning(
- "Unknown language_strategy %r; falling back to 'static'", strategy
- )
- strategy = "static"
- cfg.whisper = WhisperConfig(
- backend=backend,
- model=whisper_data.get("model", cfg.whisper.model),
- device=whisper_data.get("device", cfg.whisper.device),
- compute_type=whisper_data.get("compute_type", cfg.whisper.compute_type),
- language=language,
- beam_size=whisper_data.get("beam_size", cfg.whisper.beam_size),
- vad_filter=whisper_data.get("vad_filter", cfg.whisper.vad_filter),
- cpu_threads=whisper_data.get("cpu_threads", cfg.whisper.cpu_threads),
- no_speech_prob_max=whisper_data.get(
- "no_speech_prob_max", cfg.whisper.no_speech_prob_max
- ),
- avg_logprob_min=whisper_data.get(
- "avg_logprob_min", cfg.whisper.avg_logprob_min
- ),
- language_strategy=strategy,
- sticky_redetect_logprob=whisper_data.get(
- "sticky_redetect_logprob", cfg.whisper.sticky_redetect_logprob
- ),
- )
- # Output section
- if "output" in data:
- output_data = data["output"]
- cfg.output = OutputConfig(
- stdout=output_data.get("stdout", cfg.output.stdout),
- jsonl=output_data.get("jsonl", cfg.output.jsonl),
- session_dir=output_data.get("session_dir", cfg.output.session_dir),
- save_audio=output_data.get("save_audio", cfg.output.save_audio),
- session_gap_min=output_data.get(
- "session_gap_min", cfg.output.session_gap_min
- ),
- )
- # Sink section
- if "sink" in data:
- sink_data = data["sink"]
- cfg.sink = SinkConfig(
- sink_name=sink_data.get("sink_name", cfg.sink.sink_name),
- remap_source_name=sink_data.get(
- "remap_source_name", cfg.sink.remap_source_name
- ),
- loopback_latency_msec=sink_data.get(
- "loopback_latency_msec", cfg.sink.loopback_latency_msec
- ),
- monitor_output=sink_data.get("monitor_output", cfg.sink.monitor_output),
- )
- # Daemon section
- if "daemon" in data:
- daemon_data = data["daemon"]
- cfg.daemon = DaemonConfig(
- queue_size=daemon_data.get("queue_size", cfg.daemon.queue_size),
- backlog_warn_at=daemon_data.get(
- "backlog_warn_at", cfg.daemon.backlog_warn_at
- ),
- )
- # Events section
- if "events" in data:
- events_data = data["events"]
- cfg.events = EventsConfig(
- socket=events_data.get("socket", cfg.events.socket),
- heartbeat_ms=events_data.get("heartbeat_ms", cfg.events.heartbeat_ms),
- ring_size=events_data.get("ring_size", cfg.events.ring_size),
- )
- # Assistant section
- if "assistant" in data:
- assistant_data = data["assistant"]
- cfg.assistant = AssistantConfig(
- control_socket=assistant_data.get(
- "control_socket", cfg.assistant.control_socket
- ),
- stall_timeout_ms=assistant_data.get(
- "stall_timeout_ms", cfg.assistant.stall_timeout_ms
- ),
- worker_stall_ms=assistant_data.get(
- "worker_stall_ms", cfg.assistant.worker_stall_ms
- ),
- command_arm_timeout_s=assistant_data.get(
- "command_arm_timeout_s", cfg.assistant.command_arm_timeout_s
- ),
- chat_idle_timeout_s=assistant_data.get(
- "chat_idle_timeout_s", cfg.assistant.chat_idle_timeout_s
- ),
- chat_exit_phrases=list(
- assistant_data.get(
- "chat_exit_phrases", cfg.assistant.chat_exit_phrases
- )
- ),
- require_viewer=assistant_data.get(
- "require_viewer", cfg.assistant.require_viewer
- ),
- turn_gap_s=assistant_data.get("turn_gap_s", cfg.assistant.turn_gap_s),
- )
- # Audit section
- if "audit" in data:
- audit_data = data["audit"]
- cfg.audit = AuditConfig(
- enabled=audit_data.get("enabled", cfg.audit.enabled),
- dir=audit_data.get("dir", cfg.audit.dir),
- )
- # LLM section
- if "llm" in data:
- llm_data = data["llm"]
- if "max_prompt_tokens" in llm_data:
- logger.warning(
- "llm.max_prompt_tokens is deprecated and ignored; the prompt budget is "
- "derived as max_context_tokens - max_tokens - compaction_safety_margin"
- )
- cfg.llm = LlmConfig(
- enabled=llm_data.get("enabled", cfg.llm.enabled),
- endpoint=llm_data.get("endpoint", cfg.llm.endpoint),
- model=llm_data.get("model", cfg.llm.model),
- api_key_env=llm_data.get("api_key_env", cfg.llm.api_key_env),
- session_id=llm_data.get("session_id", cfg.llm.session_id),
- session_header=llm_data.get("session_header", cfg.llm.session_header),
- user_agent=llm_data.get("user_agent", cfg.llm.user_agent),
- reasoning_effort=llm_data.get(
- "reasoning_effort", cfg.llm.reasoning_effort
- ),
- extra_body=dict(llm_data.get("extra_body", cfg.llm.extra_body)),
- temperature=llm_data.get("temperature", cfg.llm.temperature),
- max_tokens=llm_data.get("max_tokens", cfg.llm.max_tokens),
- timeout_s=llm_data.get("timeout_s", cfg.llm.timeout_s),
- cache_prompt=llm_data.get("cache_prompt", cfg.llm.cache_prompt),
- chat_slot_id=_optional_int(llm_data.get("chat_slot_id")),
- command_slot_id=_optional_int(llm_data.get("command_slot_id")),
- max_context_tokens=llm_data.get(
- "max_context_tokens", cfg.llm.max_context_tokens
- ),
- compaction_safety_margin=llm_data.get(
- "compaction_safety_margin", cfg.llm.compaction_safety_margin
- ),
- compaction_enabled=llm_data.get(
- "compaction_enabled", cfg.llm.compaction_enabled
- ),
- compaction_trigger_ratio=llm_data.get(
- "compaction_trigger_ratio", cfg.llm.compaction_trigger_ratio
- ),
- compaction_keep_turns=llm_data.get(
- "compaction_keep_turns", cfg.llm.compaction_keep_turns
- ),
- compaction_max_tokens=llm_data.get(
- "compaction_max_tokens", cfg.llm.compaction_max_tokens
- ),
- compaction_timeout_s=llm_data.get(
- "compaction_timeout_s", cfg.llm.compaction_timeout_s
- ),
- compaction_input_tokens=llm_data.get(
- "compaction_input_tokens", cfg.llm.compaction_input_tokens
- ),
- compaction_system_prompt=llm_data.get(
- "compaction_system_prompt", cfg.llm.compaction_system_prompt
- ),
- brief_compaction_system_prompt=llm_data.get(
- "brief_compaction_system_prompt", cfg.llm.brief_compaction_system_prompt
- ),
- triage_system_prompt=llm_data.get(
- "triage_system_prompt", cfg.llm.triage_system_prompt
- ),
- inference_queue_size=llm_data.get(
- "inference_queue_size", cfg.llm.inference_queue_size
- ),
- trim_block_turns=llm_data.get(
- "trim_block_turns", cfg.llm.trim_block_turns
- ),
- max_tool_iterations=llm_data.get(
- "max_tool_iterations", cfg.llm.max_tool_iterations
- ),
- system_prompt_chat=llm_data.get(
- "system_prompt_chat", cfg.llm.system_prompt_chat
- ),
- system_prompt_command=llm_data.get(
- "system_prompt_command", cfg.llm.system_prompt_command
- ),
- )
- # Triage section
- if "triage" in data:
- triage_data = data["triage"]
- cfg.triage = TriageConfig(
- enabled=triage_data.get("enabled", cfg.triage.enabled),
- questions_file=triage_data.get("questions_file", cfg.triage.questions_file),
- window=triage_data.get("window", cfg.triage.window),
- state_char_budget=triage_data.get(
- "state_char_budget", cfg.triage.state_char_budget
- ),
- periodic_s=triage_data.get("periodic_s", cfg.triage.periodic_s),
- predict_max_tokens=triage_data.get(
- "predict_max_tokens", cfg.triage.predict_max_tokens
- ),
- confidence_field=triage_data.get(
- "confidence_field", cfg.triage.confidence_field
- ),
- escalate_threshold=triage_data.get(
- "escalate_threshold", cfg.triage.escalate_threshold
- ),
- choice_min_confidence=triage_data.get(
- "choice_min_confidence", cfg.triage.choice_min_confidence
- ),
- extract_enabled=triage_data.get(
- "extract_enabled", cfg.triage.extract_enabled
- ),
- extract_max_tokens=triage_data.get(
- "extract_max_tokens", cfg.triage.extract_max_tokens
- ),
- extract_timeout_s=triage_data.get(
- "extract_timeout_s", cfg.triage.extract_timeout_s
- ),
- extract_temperature=triage_data.get(
- "extract_temperature", cfg.triage.extract_temperature
- ),
- brief_slot_id=_optional_int(triage_data.get("brief_slot_id")),
- brief_gate_enabled=triage_data.get(
- "brief_gate_enabled", cfg.triage.brief_gate_enabled
- ),
- brief_gate_threshold=triage_data.get(
- "brief_gate_threshold", cfg.triage.brief_gate_threshold
- ),
- brief_max_chars=triage_data.get(
- "brief_max_chars", cfg.triage.brief_max_chars
- ),
- brief_pending_char_budget=triage_data.get(
- "brief_pending_char_budget", cfg.triage.brief_pending_char_budget
- ),
- ledger_max_facts=triage_data.get(
- "ledger_max_facts", cfg.triage.ledger_max_facts
- ),
- fact_max_chars=triage_data.get(
- "fact_max_chars", cfg.triage.fact_max_chars
- ),
- device=triage_data.get("device", cfg.triage.device),
- preload=list(triage_data.get("preload", cfg.triage.preload)),
- default_checkpoint=triage_data.get(
- "default_checkpoint", cfg.triage.default_checkpoint
- ),
- dump_io=triage_data.get("dump_io", cfg.triage.dump_io),
- )
- # Speaker section
- if "speaker" in data:
- speaker_data = data["speaker"]
- provider = speaker_data.get("provider", cfg.speaker.provider)
- provider = provider.strip().lower()
- if provider not in ("cpu", "cuda"):
- logger.warning(
- "Unknown speaker provider %r; falling back to 'cpu'", provider
- )
- provider = "cpu"
- cfg.speaker = SpeakerConfig(
- enabled=speaker_data.get("enabled", cfg.speaker.enabled),
- model_path=speaker_data.get("model_path", cfg.speaker.model_path),
- provider=provider,
- threshold_id=speaker_data.get("threshold_id", cfg.speaker.threshold_id),
- threshold_new=speaker_data.get("threshold_new", cfg.speaker.threshold_new),
- min_enroll_s=speaker_data.get("min_enroll_s", cfg.speaker.min_enroll_s),
- rms_floor=speaker_data.get("rms_floor", cfg.speaker.rms_floor),
- margin=speaker_data.get("margin", cfg.speaker.margin),
- mint_cooldown_s=speaker_data.get(
- "mint_cooldown_s", cfg.speaker.mint_cooldown_s
- ),
- confirm_s=speaker_data.get("confirm_s", cfg.speaker.confirm_s),
- max_prototypes=speaker_data.get("max_prototypes", cfg.speaker.max_prototypes),
- provisional_ttl_h=speaker_data.get(
- "provisional_ttl_h", cfg.speaker.provisional_ttl_h
- ),
- store_dir=speaker_data.get("store_dir", cfg.speaker.store_dir),
- join_timeout_s=speaker_data.get("join_timeout_s", cfg.speaker.join_timeout_s),
- )
- # Brief section
- if "brief" in data:
- brief_data = data["brief"]
- cfg.brief = BriefConfig(
- write=brief_data.get("write", cfg.brief.write),
- )
- # Debug section
- if "debug" in data:
- debug_data = data["debug"]
- cfg.debug = DebugConfig(
- enabled=debug_data.get("enabled", cfg.debug.enabled),
- dir=debug_data.get("dir", cfg.debug.dir),
- heartbeat=debug_data.get("heartbeat", cfg.debug.heartbeat),
- max_bytes_per_day=debug_data.get(
- "max_bytes_per_day", cfg.debug.max_bytes_per_day
- ),
- )
- return cfg
|