mcp-voice-agent / src /config.py
AdarshRajDS
feat: intial mcp voice agent
cf30ded
"""
config.py β€” Centralised, validated configuration using Pydantic Settings.
All values are read from environment variables (or .env file).
Import the singleton `settings` anywhere in the project.
"""
from functools import lru_cache
from typing import Literal
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# ── LLM ──────────────────────────────────────────────────────────────
groq_api_key: str = Field(..., description="Groq API key (free tier)")
llm_model: str = Field("llama-3.3-70b-versatile", description="Groq chat model")
stt_model: str = Field("whisper-large-v3-turbo", description="Groq Whisper STT model")
tts_voice: str = Field("en-US-JennyNeural", description="Edge-TTS voice")
# ── RAG ───────────────────────────────────────────────────────────────
chroma_persist_dir: str = Field("./data/chroma_db")
embedding_model: str = Field("all-MiniLM-L6-v2")
docs_dir: str = Field("./data/sample_files")
chunk_size: int = Field(500, ge=100, le=4000)
chunk_overlap: int = Field(50, ge=0, le=500)
# ── MCP Server ────────────────────────────────────────────────────────
mcp_server_host: str = Field("0.0.0.0")
mcp_server_port: int = Field(8000, ge=1024, le=65535)
mcp_transport: Literal["sse", "stdio"] = Field("sse")
# ── SQLite ────────────────────────────────────────────────────────────
sqlite_db_path: str = Field("./data/database.db")
# ── Audio ─────────────────────────────────────────────────────────────
audio_sample_rate: int = Field(16000)
audio_channels: int = Field(1)
audio_silence_threshold: int = Field(500)
audio_silence_duration_sec: float = Field(1.5)
audio_min_speech_duration_sec: float = Field(0.5)
# ── App ───────────────────────────────────────────────────────────────
app_env: Literal["development", "production"] = Field("development")
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field("INFO")
log_format: Literal["json", "console"] = Field("console")
@field_validator("chunk_overlap")
@classmethod
def overlap_less_than_chunk(cls, v: int, info) -> int:
chunk_size = info.data.get("chunk_size", 500)
if v >= chunk_size:
raise ValueError("chunk_overlap must be less than chunk_size")
return v
# NEW β€” works both locally and on HF Spaces
@property
def mcp_server_url(self) -> str:
if self.is_production:
return f"https://{self.mcp_server_host}/sse"
return f"http://localhost:{self.mcp_server_port}/sse"
@property
def is_production(self) -> bool:
return self.app_env == "production"
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""Return the cached Settings singleton."""
return Settings() # type: ignore[call-arg]
# Convenience singleton β€” import this everywhere
settings = get_settings()