Spaces:
Sleeping
Sleeping
| from pydantic_settings import BaseSettings | |
| class Settings(BaseSettings): | |
| groq_api_key: str = "" | |
| groq_model: str = "llama-3.1-8b-instant" | |
| groq_base_url: str = "https://api.groq.com/openai/v1" | |
| # Speech-to-text for the voice chat panel — same Groq account/credential, | |
| # different endpoint (audio/transcriptions instead of chat/completions). | |
| groq_whisper_model: str = "whisper-large-v3-turbo" | |
| # Comma-separated fallback models tried in order when primary hits rate | |
| # limit (decommissioned entries are skipped automatically at runtime) | |
| groq_fallback_models: str = ( | |
| "llama-3.3-70b-versatile," | |
| "openai/gpt-oss-20b," | |
| "openai/gpt-oss-120b," | |
| "qwen/qwen3-32b" | |
| ) | |
| # ── Optional extra LLM providers (OpenAI-compatible) ───────────────────── | |
| # Tried in order after Groq when its models are rate-limited/unavailable. | |
| # A provider is skipped entirely when its API key is empty. | |
| cerebras_api_key: str = "" | |
| cerebras_base_url: str = "https://api.cerebras.ai/v1" | |
| cerebras_models: str = "llama-3.3-70b,llama3.1-8b" | |
| gemini_api_key: str = "" | |
| gemini_base_url: str = "https://generativelanguage.googleapis.com/v1beta/openai" | |
| gemini_models: str = "gemini-2.0-flash" | |
| openrouter_api_key: str = "" | |
| openrouter_base_url: str = "https://openrouter.ai/api/v1" | |
| # All current free models on OpenRouter (ordered best → smallest). | |
| # Audio/image/safety/vision-only and sub-2B models are excluded. | |
| # Decommissioned entries are skipped automatically at runtime. | |
| openrouter_models: str = ( | |
| "openai/gpt-oss-120b:free," | |
| "nvidia/nemotron-3-ultra-550b-a55b:free," | |
| "nousresearch/hermes-3-llama-3.1-405b:free," | |
| "nvidia/nemotron-3-super-120b-a12b:free," | |
| "meta-llama/llama-3.3-70b-instruct:free," | |
| "qwen/qwen3-next-80b-a3b-instruct:free," | |
| "qwen/qwen3-coder:free," | |
| "moonshotai/kimi-k2.6:free," | |
| "google/gemma-4-31b-it:free," | |
| "google/gemma-4-26b-a4b-it:free," | |
| "nvidia/nemotron-3-nano-30b-a3b:free," | |
| "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free," | |
| "openai/gpt-oss-20b:free," | |
| "cognitivecomputations/dolphin-mistral-24b-venice-edition:free," | |
| "nvidia/nemotron-nano-9b-v2:free," | |
| "meta-llama/llama-3.2-3b-instruct:free," | |
| "poolside/laguna-m.1:free," | |
| "poolside/laguna-xs.2:free," | |
| "nex-agi/nex-n2-pro:free" | |
| ) | |
| # Comma-separated list of allowed browser origins (frontend URLs). | |
| cors_origins: str = "http://localhost:3000,http://127.0.0.1:3000" | |
| # ── Abuse protection / cost control ────────────────────────────────────── | |
| # Required to call POST /ingest over HTTP; empty = endpoint disabled | |
| # (local ingestion via scripts/ingest.py is unaffected). | |
| admin_token: str = "" | |
| rate_limit_chat_per_minute: int = 20 # per client IP | |
| rate_limit_voice_per_minute: int = 15 # per client IP, voice transcription | |
| voice_max_upload_mb: int = 8 # ~60s of webm/opus, generous ceiling | |
| email_max_per_session: int = 5 | |
| email_max_per_day: int = 50 # global, resets at UTC midnight | |
| response_cache_ttl_seconds: int = 3600 | |
| response_cache_max_entries: int = 200 | |
| session_idle_ttl_seconds: int = 6 * 3600 | |
| max_sessions: int = 1000 | |
| chroma_persist_directory: str = "./chroma_db" | |
| chroma_collection_name: str = "negoptim_knowledge" | |
| embedding_model: str = "intfloat/multilingual-e5-small" | |
| retrieval_top_k: int = 5 | |
| chunk_size: int = 700 | |
| chunk_overlap: int = 100 | |
| max_history_turns: int = 10 | |
| knowledge_base_path: str = "../knowledge" | |
| # ── Email ───────────────────────────────────────────────────────────────── | |
| # Priority: Resend (HTTP, works on any host) → SMTP (blocked on HF free tier) | |
| # Either absent → simulation mode (logs + reports success, no real delivery). | |
| resend_api_key: str = "" | |
| resend_from: str = "Negoptim AI <onboarding@resend.dev>" | |
| smtp_host: str = "smtp.gmail.com" | |
| smtp_port: int = 587 | |
| smtp_user: str = "" | |
| smtp_password: str = "" | |
| smtp_from: str = "" # defaults to smtp_user when empty | |
| smtp_from_name: str = "Negoptim AI" | |
| # All outgoing mail is force-routed here while testing (per spec). | |
| # Set to "" to deliver to the real recipient. | |
| email_override_to: str = "samirmajzoub9@gmail.com" | |
| def fallback_model_list(self) -> list[str]: | |
| return [m.strip() for m in self.groq_fallback_models.split(",") if m.strip()] | |
| def cerebras_model_list(self) -> list[str]: | |
| return [m.strip() for m in self.cerebras_models.split(",") if m.strip()] | |
| def gemini_model_list(self) -> list[str]: | |
| return [m.strip() for m in self.gemini_models.split(",") if m.strip()] | |
| def openrouter_model_list(self) -> list[str]: | |
| return [m.strip() for m in self.openrouter_models.split(",") if m.strip()] | |
| def cors_origin_list(self) -> list[str]: | |
| return [o.strip() for o in self.cors_origins.split(",") if o.strip()] | |
| def resend_configured(self) -> bool: | |
| return bool(self.resend_api_key) | |
| def smtp_configured(self) -> bool: | |
| return bool(self.smtp_host and self.smtp_user and self.smtp_password) | |
| def smtp_sender(self) -> str: | |
| return self.smtp_from or self.smtp_user | |
| class Config: | |
| env_file = ".env" | |
| settings = Settings() | |