free-claude-code/config/settings.py
Mauro Druwel de70700dde
feat: Use NVIDIA NIM ASR for audio transcription (#53)
## Summary
Added NVIDIA NIM as a second transcription option ( alongside local
Whisper). This lets you transcribe voice notes using NVIDIA's cloud API
instead of running Whisper locally.

## What changed

- **Transcription**: Now supports the two backends

  - Local Whisper: Free, runs on your GPU/CPU (existing)
  - NVIDIA NIM: Cloud API via Riva gRPC (new)

- **Supported models**: 8 NVIDIA NIM models added (Parakeet variants for
different languages, Whisper Large V3)

---------

Co-authored-by: Alishahryar1 <alishahryar2@gmail.com>
2026-02-28 08:48:59 -08:00

178 lines
6.3 KiB
Python

"""Centralized configuration using Pydantic Settings."""
from functools import lru_cache
from dotenv import load_dotenv
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from .nim import NimSettings
load_dotenv()
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# ==================== OpenRouter Config ====================
open_router_api_key: str = Field(default="", validation_alias="OPENROUTER_API_KEY")
# ==================== Messaging Platform Selection ====================
# Valid: "telegram" | "discord"
messaging_platform: str = Field(
default="discord", validation_alias="MESSAGING_PLATFORM"
)
# ==================== NVIDIA NIM Config ====================
nvidia_nim_api_key: str = ""
# ==================== LM Studio Config ====================
lm_studio_base_url: str = Field(
default="http://localhost:1234/v1",
validation_alias="LM_STUDIO_BASE_URL",
)
# ==================== Model ====================
# All Claude model requests are mapped to this single model
# Format: provider_type/model/name
model: str = "nvidia_nim/meta/llama3-70b-instruct"
# ==================== Provider Rate Limiting ====================
provider_rate_limit: int = Field(default=40, validation_alias="PROVIDER_RATE_LIMIT")
provider_rate_window: int = Field(
default=60, validation_alias="PROVIDER_RATE_WINDOW"
)
provider_max_concurrency: int = Field(
default=5, validation_alias="PROVIDER_MAX_CONCURRENCY"
)
# ==================== HTTP Client Timeouts ====================
http_read_timeout: float = Field(
default=300.0, validation_alias="HTTP_READ_TIMEOUT"
)
http_write_timeout: float = Field(
default=10.0, validation_alias="HTTP_WRITE_TIMEOUT"
)
http_connect_timeout: float = Field(
default=2.0, validation_alias="HTTP_CONNECT_TIMEOUT"
)
# ==================== Fast Prefix Detection ====================
fast_prefix_detection: bool = True
# ==================== Optimizations ====================
enable_network_probe_mock: bool = True
enable_title_generation_skip: bool = True
enable_suggestion_mode_skip: bool = True
enable_filepath_extraction_mock: bool = True
# ==================== NIM Settings ====================
nim: NimSettings = Field(default_factory=NimSettings)
# ==================== Voice Note Transcription ====================
voice_note_enabled: bool = Field(
default=True, validation_alias="VOICE_NOTE_ENABLED"
)
# Device: "cpu" | "cuda" | "nvidia_nim"
# - "cpu"/"cuda": local Whisper (requires voice_local extra: uv sync --extra voice_local)
# - "nvidia_nim": NVIDIA NIM Whisper API (requires voice extra: uv sync --extra voice)
whisper_device: str = Field(default="cpu", validation_alias="WHISPER_DEVICE")
# Whisper model ID or short name (for local Whisper) or NVIDIA NIM model (for nvidia_nim)
# Local Whisper: "tiny", "base", "small", "medium", "large-v2", "large-v3", "large-v3-turbo"
# NVIDIA NIM: "nvidia/parakeet-ctc-1.1b-asr", "openai/whisper-large-v3", etc.
whisper_model: str = Field(default="base", validation_alias="WHISPER_MODEL")
# Hugging Face token for faster model downloads (optional, for local Whisper)
hf_token: str = Field(default="", validation_alias="HF_TOKEN")
# ==================== Bot Wrapper Config ====================
telegram_bot_token: str | None = None
allowed_telegram_user_id: str | None = None
discord_bot_token: str | None = Field(
default=None, validation_alias="DISCORD_BOT_TOKEN"
)
allowed_discord_channels: str | None = Field(
default=None, validation_alias="ALLOWED_DISCORD_CHANNELS"
)
claude_workspace: str = "./agent_workspace"
allowed_dir: str = ""
# ==================== Server ====================
host: str = "0.0.0.0"
port: int = 8082
log_file: str = "server.log"
# Handle empty strings for optional string fields
@field_validator(
"telegram_bot_token",
"allowed_telegram_user_id",
"discord_bot_token",
"allowed_discord_channels",
mode="before",
)
@classmethod
def parse_optional_str(cls, v):
if v == "":
return None
return v
@field_validator("whisper_device")
@classmethod
def validate_whisper_device(cls, v: str) -> str:
if v not in ("cpu", "cuda", "nvidia_nim"):
raise ValueError(
f"whisper_device must be 'cpu', 'cuda', or 'nvidia_nim', got {v!r}"
)
return v
@field_validator("model")
@classmethod
def validate_model_format(cls, v: str) -> str:
valid_providers = ("nvidia_nim", "open_router", "lmstudio")
if "/" not in v:
raise ValueError(
f"Model must be prefixed with provider type. "
f"Valid providers: {', '.join(valid_providers)}. "
f"Format: provider_type/model/name"
)
provider = v.split("/", 1)[0]
if provider not in valid_providers:
raise ValueError(
f"Invalid provider: '{provider}'. "
f"Supported: 'nvidia_nim', 'open_router', 'lmstudio'"
)
return v
@model_validator(mode="after")
def check_nvidia_nim_api_key(self) -> Settings:
if (
self.voice_note_enabled
and self.whisper_device == "nvidia_nim"
and not self.nvidia_nim_api_key.strip()
):
raise ValueError(
"NVIDIA_NIM_API_KEY is required when WHISPER_DEVICE is 'nvidia_nim'. "
"Set it in your .env file."
)
return self
@property
def provider_type(self) -> str:
"""Extract provider type from the model string."""
return self.model.split("/", 1)[0]
@property
def model_name(self) -> str:
"""Extract the actual model name from the model string."""
return self.model.split("/", 1)[1]
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
@lru_cache
def get_settings() -> Settings:
"""Get cached settings instance."""
return Settings()