# Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai """Configuration centrale de ka6 (chargée depuis .env). ka6 = bot FLAGSHIP du Groupe KA : ~3x plus robuste et performant que ka4 (concurrence élevée, robustesse retries + coupe-circuit), avec un objectif initial de découverte des INFLUENCEURS et CRÉATEURS DE CONTENU québécois. """ from __future__ import annotations import os from dataclasses import dataclass, field from dotenv import load_dotenv load_dotenv() def _env_bool(key: str, default: bool) -> bool: return os.getenv(key, str(default)).strip().lower() in ("1", "true", "yes", "on") @dataclass class Config: """Paramètres runtime. Toutes les clés proviennent de l'environnement.""" anthropic_api_key: str = field(default_factory=lambda: os.getenv("ANTHROPIC_API_KEY", "")) firecrawl_api_key: str = field(default_factory=lambda: os.getenv("FIRECRAWL_API_KEY", "")) scrapfly_api_key: str = field(default_factory=lambda: os.getenv("SCRAPFLY_API_KEY", "")) # Contrôleur agentique : modèle PLUS PUISSANT (planification/raisonnement) model: str = field(default_factory=lambda: os.getenv("KA_BOT_MODEL", "claude-sonnet-5")) # Extraction en masse : modèle rapide/économique extract_model: str = field(default_factory=lambda: os.getenv("KA_BOT_EXTRACT_MODEL", "claude-haiku-4-5-20251001")) db_path: str = field(default_factory=lambda: os.getenv("KA_BOT_DB", "ka6.db")) # Crawling CONCURRENT — flagship : ~3x plus de parallélisme concurrency: int = field(default_factory=lambda: int(os.getenv("KA_BOT_CONCURRENCY", "16"))) # Expansion agressive : liens auto-ajoutés à la file par page frontier_per_page: int = field(default_factory=lambda: int(os.getenv("KA_BOT_FRONTIER", "16"))) # Robustesse : nb max de tentatives par URL avant abandon (dead-letter) max_attempts: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_ATTEMPTS", "2"))) # Coupe-circuit : abandon d'un domaine après N échecs consécutifs dans un run domain_fail_threshold: int = field(default_factory=lambda: int(os.getenv("KA_BOT_DOMAIN_FAILS", "4"))) # Backend de scrape : auto | firecrawl | scrapfly scraper_backend: str = field(default_factory=lambda: os.getenv("KA_BOT_SCRAPER", "auto").strip().lower()) scrapfly_render_js: bool = field(default_factory=lambda: _env_bool("KA_BOT_SCRAPFLY_RENDER_JS", False)) firecrawl_base: str = "https://api.firecrawl.dev" scrapfly_base: str = "https://api.scrapfly.io" request_delay: float = field(default_factory=lambda: float(os.getenv("KA_BOT_DELAY", "0.35"))) respect_robots: bool = field(default_factory=lambda: _env_bool("KA_BOT_RESPECT_ROBOTS", True)) max_pages_per_run: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_PAGES", "320"))) max_context_tokens: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_CONTEXT_TOKENS", "180000"))) user_agent: str = "ka6-bot/1.0 (+contact@spboucher.ai)" def validate(self) -> "Config": missing = [] if not self.anthropic_api_key: missing.append("ANTHROPIC_API_KEY") if not self.firecrawl_api_key: missing.append("FIRECRAWL_API_KEY (requis pour search/map)") if self.scraper_backend == "scrapfly" and not self.scrapfly_api_key: missing.append("SCRAPFLY_API_KEY (backend=scrapfly)") if missing: raise RuntimeError( "Clés manquantes: " + ", ".join(missing) + ". Copiez .env.example vers .env." ) return self config = Config()