Funk-lab/stt/server/stt_server/config.py
ALI YESILKAYA 1faf50a9be
feat(stt-server): contextes présélectionnables + sources live + contexte assemblé (visualiseur) (#38)
Asa n'est plus bloqué sur le seul contexte « doc cluster grounding-strict ». Le client
choisit un contexte par requête ; le serveur change le system prompt ET injecte les
données live du domaine, puis renvoie le contexte assemblé pour le visualiseur du HUD.

- contexts.py : profils funk / ghostfolio / grafana / alerting / cluster (system prompt
  + sources) + assemble() (prompt final + structure de visualisation).
- sources.py : fetchers live best-effort (Ghostfolio auth+details, Alertmanager alerts
  hors Watchdog, Prometheus cluster/metrics), env-config, dégradation propre.
- brain.py : ask() reçoit le system prompt déjà assemblé (assemblage remonté).
- app.py : /v1/ask accepte `context`, renvoie context_id + le contexte assemblé ;
  nouveau GET /v1/contexts ; RAG doc conditionné au profil.
- config.py : URLs sources + STT_GHOSTFOLIO_TOKEN + STT_DEFAULT_CONTEXT.
- deployment : env in-cluster (Prometheus/Alertmanager monitoring, Ghostfolio ai),
  jeton via secret optionnel stt-server-secrets/ghostfolio-token.
- bump 0.3.1 → 0.4.0.

Validé en local : assemblage (blocs+RAG+mémoire), parsing des sources (mock),
endpoints /v1/contexts et /v1/ask (LLM mocké) — context_id, visualiseur, fallback
contexte inconnu → funk.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:49:29 +02:00

82 lines
4.8 KiB
Python

"""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"))
# Contextes présélectionnables (visualiseur + presets live). Le contexte « funk »
# garde le system_prompt grounding-strict ci-dessus ; les autres injectent des
# données live. URLs surchargeables selon la résolution réseau in-cluster.
default_context: str = os.getenv("STT_DEFAULT_CONTEXT", "funk")
sources_timeout: float = float(os.getenv("STT_SOURCES_TIMEOUT", "4"))
ghostfolio_url: str = os.getenv("STT_GHOSTFOLIO_URL", "http://ghostfolio.lab.local")
# Jeton « Security token » du compte Ghostfolio (vide → données indisponibles).
ghostfolio_token: str = os.getenv("STT_GHOSTFOLIO_TOKEN", "")
prometheus_url: str = os.getenv("STT_PROMETHEUS_URL", "http://prometheus.lab.local")
alertmanager_url: str = os.getenv("STT_ALERTMANAGER_URL", "http://alertmanager.lab.local")
settings = Settings()