from __future__ import annotations from dataclasses import dataclass, field from typing import Any, Literal, Optional Provenance = Literal['real_checkpoint', 'trained_by_us', 'rule', 'architecture_reimplemented', 'unavailable'] GateChoice = Literal['webrtcvad', 'silero_vad', 'none'] EncoderChoice = Literal['whisper_tiny', 'whisper_base', 'wav2vec2', 'smart_turn_onnx', 'none'] PoolingChoice = Literal['mean', 'cross_attention'] HeadChoice = Literal['linear', 'mlp'] SemanticChoice = Literal['off', 'qwen_local', 'qwen_local_streaming', 'livekit_eou', 'groq_api', 'openrouter_api'] FusionChoice = Literal['off', 'weighted_vote', 'easy_turn'] OutputClasses = Literal['binary', '3class', '4class'] FullDuplexChoice = Literal['off', 'moshi', 'human1'] class StageUnavailableError(RuntimeError): pass @dataclass class StageResult: stage: str timing_ms: float output: Any = None available: bool = True reason: Optional[str] = None provenance: Optional[Provenance] = None @dataclass class PipelineConfig: gate: GateChoice = 'silero_vad' encoder: EncoderChoice = 'whisper_tiny' pooling: PoolingChoice = 'mean' head: HeadChoice = 'linear' semantic: SemanticChoice = 'off' fusion: FusionChoice = 'off' output_classes: OutputClasses = 'binary' full_duplex: FullDuplexChoice = 'off' vad_aggressiveness: int = 2 silence_trigger_ms: int = 400 semantic_temperature: float = 0.2 acoustic_weight: float = 0.6 def __post_init__(self) -> None: if self.encoder == 'none': if self.semantic == 'off': raise ValueError("encoder='none' requires the semantic branch to be on (nothing would decide).") if self.fusion != 'off': raise ValueError("encoder='none' has no acoustic score to fuse with - set fusion='off'.") if self.encoder == 'smart_turn_onnx': if self.pooling != 'mean' or self.head != 'linear': raise ValueError("encoder='smart_turn_onnx' is a single opaque preset and cannot be combined with a separate pooling/head choice (see docs/decision-log.md #18).") if self.fusion != 'off' and self.semantic == 'off': raise ValueError('fusion requires the semantic branch to be on.') if self.full_duplex != 'off': pass @dataclass class PipelineResult: config: PipelineConfig decision: Optional[str] = None probability: Optional[float] = None transcript: Optional[str] = None semantic_verdict: Optional[str] = None stage_results: list[StageResult] = field(default_factory=list) total_latency_ms: float = 0.0 provenance: Optional[Provenance] = None preset_label: Optional[str] = None