"""Configuration du STT-server — via variables d'environnement (12-factor / k8s).""" from __future__ import annotations import os class Settings: # LiteLLM (s01) — joint en IP directe depuis le cluster (cf. open-webui). litellm_url: str = os.getenv( "STT_LITELLM_URL", "http://192.168.10.1:4000/v1/chat/completions" ) litellm_key: str = os.getenv("STT_LITELLM_KEY", "lm-studio") # Modèle par défaut + alias LiteLLM autorisés (le client peut en demander un par requête) model: str = os.getenv("STT_MODEL", "hermes-default") allowed_models: list[str] = [ m.strip() for m in os.getenv( "STT_ALLOWED_MODELS", "hermes-default,qwen3-8b,claude-sonnet-4-6,claude-opus-4-7", ).split(",") if m.strip() ] system_prompt: str = os.getenv( "STT_SYSTEM_PROMPT", "Tu es Hermes, l'assistant vocal du homelab Funk. " "Réponds toujours en français, de façon concise (2-3 phrases maximum), " "sans markdown ni listes. " "Pour toute question sur Funk (cluster, machines, services, matériel, configuration), " "appuie-toi EXCLUSIVEMENT sur la documentation fournie ci-dessous. " "Règle stricte : si la doc ne mentionne pas explicitement l'élément demandé, réponds " "« la doc Funk ne le précise pas » — n'affirme JAMAIS un oui ni un non par déduction, " "et n'invente aucun détail (modèle, marque, nombre, nom).", ) max_tokens: int = int(os.getenv("STT_MAX_TOKENS", "200")) temperature: float = float(os.getenv("STT_TEMPERATURE", "0.7")) # Timeout PAR tentative vers LiteLLM (brain fait jusqu'à 2 tentatives). Une réponse vocale # avec /no_think tient en quelques secondes → 30s suffit, et borne l'attente d'un blip backend. request_timeout: float = float(os.getenv("STT_REQUEST_TIMEOUT", "30")) # Qwen3 est un modèle « thinking » → ajoute `/no_think` pour désactiver le raisonnement # (sinon content vide / timeout). Inoffensif pour les modèles non-Qwen. disable_thinking: bool = os.getenv("STT_DISABLE_THINKING", "true").lower() == "true" # Mémoire long-terme (Qdrant) — dégrade proprement si Qdrant/embeddings injoignables memory_longterm: bool = os.getenv("STT_MEMORY_LONGTERM", "true").lower() == "true" qdrant_url: str = os.getenv("STT_QDRANT_URL", "http://192.168.10.1:6333") qdrant_collection: str = os.getenv("STT_QDRANT_COLLECTION", "stt-memory") # Embeddings : instance dédiée nomic-embed-text sur llama-server gpu-01 (:1238, dim 768). embed_url: str = os.getenv("STT_EMBED_URL", "http://192.168.10.20:1238/v1/embeddings") embed_model: str = os.getenv("STT_EMBED_MODEL", "nomic-embed-text") memory_top_k: int = int(os.getenv("STT_MEMORY_TOPK", "3")) # Le recall (embed + recherche) est sur le chemin de réponse : timeout SERRÉ pour qu'un # embed lent/mort dégrade vite (souvenirs vides) au lieu d'ajouter des secondes au client. # Le store tourne en tâche de fond (après la réponse) → timeout plus large toléré. memory_recall_timeout: float = float(os.getenv("STT_MEMORY_RECALL_TIMEOUT", "4")) memory_store_timeout: float = float(os.getenv("STT_MEMORY_STORE_TIMEOUT", "20")) # RAG documentaire : ancre les réponses dans la doc Funk (collection `funk-docs`, # même embedder nomic :1238 que la mémoire → le vecteur de requête est réutilisé). # Sans ça, le modèle répond de connaissances générales (hallucine sur le homelab). docs_rag: bool = os.getenv("STT_DOCS_RAG", "true").lower() == "true" docs_collection: str = os.getenv("STT_DOCS_COLLECTION", "funk-docs") docs_top_k: int = int(os.getenv("STT_DOCS_TOPK", "6")) # nomic étale les scores plus bas → seuil permissif (aligné sur rag-query). docs_min_score: float = float(os.getenv("STT_DOCS_MIN_SCORE", "0.45")) # Sur le chemin de réponse (1 recherche Qdrant, vecteur déjà calculé) → timeout serré. docs_timeout: float = float(os.getenv("STT_DOCS_TIMEOUT", "4")) settings = Settings()