Stage 4: honest health, explicit error policy, event-bus integrity
Engine mode is now explicit: USE_MOCK_SIP=true is the only way to get the mock engine; an unconfigured trunk fails startup with guidance instead of silently degrading. Root-caused why the engine always ran mock: nested pydantic-settings never read .env (no env_file on the sub-settings classes) — all 8 now declare it. /health stops lying: reports engine mode (sippy|mock), a live DB SELECT 1, trunk registration state with reason, and TTS/STT availability from their last real request; "healthy" now requires ready + db + sippy + registered trunk. Error policy: leaf services (tts/transcription/llm_client) raise and track availability; call-loop callers catch, publish EventType.ERROR naming the failed service, and apply an explicit fallback. Persistence writes get one bounded 3x exponential retry, then an ERROR log — no more silent data loss. Event bus: a full subscriber queue drops its oldest event (counted) instead of silently evicting the subscription; subscribe(replay_last=N) delivers the advertised history replay, used by /ws/events (25). Receptionist correctness: a matched TAKE_MESSAGE rule beats the LLM; voicemail polls for early hangup and stops/transcribes/hangs up in finally; RecordingSession finally keeps its leg_ids so taps detach. Dead code removed: models/contact.py + Contact table, dtmf_buffer, transcribe_stream stub, SMS stub in notification.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
21
config.py
21
config.py
@@ -11,7 +11,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
class SIPTrunkSettings(BaseSettings):
|
||||
"""SIP trunk provider configuration."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="SIP_TRUNK_")
|
||||
model_config = SettingsConfigDict(env_prefix="SIP_TRUNK_", env_file=".env", extra="ignore")
|
||||
|
||||
host: str = "sip.provider.com"
|
||||
port: int = 5060
|
||||
@@ -24,7 +24,7 @@ class SIPTrunkSettings(BaseSettings):
|
||||
class GatewaySIPSettings(BaseSettings):
|
||||
"""Gateway SIP listener for device registration."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="GATEWAY_SIP_")
|
||||
model_config = SettingsConfigDict(env_prefix="GATEWAY_SIP_", env_file=".env", extra="ignore")
|
||||
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 5060
|
||||
@@ -34,7 +34,7 @@ class GatewaySIPSettings(BaseSettings):
|
||||
class SpeachesSettings(BaseSettings):
|
||||
"""Speaches STT service configuration."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="SPEACHES_")
|
||||
model_config = SettingsConfigDict(env_prefix="SPEACHES_", env_file=".env", extra="ignore")
|
||||
|
||||
url: str = "http://localhost:22070"
|
||||
model: str = "whisper-large-v3"
|
||||
@@ -43,7 +43,7 @@ class SpeachesSettings(BaseSettings):
|
||||
class ClassifierSettings(BaseSettings):
|
||||
"""Audio classifier thresholds."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="CLASSIFIER_")
|
||||
model_config = SettingsConfigDict(env_prefix="CLASSIFIER_", env_file=".env", extra="ignore")
|
||||
|
||||
music_threshold: float = 0.7
|
||||
speech_threshold: float = 0.6
|
||||
@@ -54,7 +54,7 @@ class ClassifierSettings(BaseSettings):
|
||||
class LLMSettings(BaseSettings):
|
||||
"""LLM service configuration (OpenAI-compatible API)."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="LLM_")
|
||||
model_config = SettingsConfigDict(env_prefix="LLM_", env_file=".env", extra="ignore")
|
||||
|
||||
base_url: str = "http://localhost:11434/v1"
|
||||
model: str = "llama3"
|
||||
@@ -67,7 +67,7 @@ class LLMSettings(BaseSettings):
|
||||
class HoldSlayerSettings(BaseSettings):
|
||||
"""Hold Slayer behavior settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="HOLD_SLAYER_", env_prefix_allow_empty=True)
|
||||
model_config = SettingsConfigDict(env_prefix="HOLD_SLAYER_", env_prefix_allow_empty=True, env_file=".env", extra="ignore")
|
||||
|
||||
default_transfer_device: str = Field(
|
||||
default="sip_phone", validation_alias="DEFAULT_TRANSFER_DEVICE"
|
||||
@@ -79,7 +79,7 @@ class HoldSlayerSettings(BaseSettings):
|
||||
class TTSSettings(BaseSettings):
|
||||
"""Rhema TTS service configuration (OpenAI-compatible /v1/audio/speech)."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="TTS_")
|
||||
model_config = SettingsConfigDict(env_prefix="TTS_", env_file=".env", extra="ignore")
|
||||
|
||||
base_url: str = "http://localhost:8000"
|
||||
model: str = "speaches-ai/Kokoro-82M-v1.0-ONNX"
|
||||
@@ -92,7 +92,7 @@ class TTSSettings(BaseSettings):
|
||||
class ReceptionistSettings(BaseSettings):
|
||||
"""AI Receptionist behavior settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="RECEPTIONIST_")
|
||||
model_config = SettingsConfigDict(env_prefix="RECEPTIONIST_", env_file=".env", extra="ignore")
|
||||
|
||||
enabled: bool = True
|
||||
greeting_template: str = (
|
||||
@@ -133,6 +133,11 @@ class Settings(BaseSettings):
|
||||
# Outbound-call safety cap (REST + MCP make_call)
|
||||
max_concurrent_calls: int = 4
|
||||
|
||||
# Explicit engine mode — the mock engine must be asked for. An
|
||||
# unconfigured trunk without this flag fails startup instead of
|
||||
# silently degrading to a gateway that can't place real calls.
|
||||
use_mock_sip: bool = False
|
||||
|
||||
# Notifications
|
||||
notify_sms_number: str = ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user