SPB Git

spb/ka6 Public

ka6 — bot FLAGSHIP du Groupe KA : explorateur structuré du web québécois. Crawling concurrent robuste, Claude Sonnet+Haiku, graphe de connaissances, dashboard temps réel.

Python 97.8% Shell 2.2%
3.5 KB · 75 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher2# Contact: contact@spboucher.ai3"""Configuration centrale de ka6 (chargée depuis .env).45ka6 = bot FLAGSHIP du Groupe KA : ~3x plus robuste et performant que ka46(concurrence élevée, robustesse retries + coupe-circuit), avec un objectif initial7de découverte des INFLUENCEURS et CRÉATEURS DE CONTENU québécois.8"""910from __future__ import annotations1112import os13from dataclasses import dataclass, field1415from dotenv import load_dotenv1617load_dotenv()181920def _env_bool(key: str, default: bool) -> bool:21    return os.getenv(key, str(default)).strip().lower() in ("1", "true", "yes", "on")222324@dataclass25class Config:26    """Paramètres runtime. Toutes les clés proviennent de l'environnement."""2728    anthropic_api_key: str = field(default_factory=lambda: os.getenv("ANTHROPIC_API_KEY", ""))29    firecrawl_api_key: str = field(default_factory=lambda: os.getenv("FIRECRAWL_API_KEY", ""))30    scrapfly_api_key: str = field(default_factory=lambda: os.getenv("SCRAPFLY_API_KEY", ""))3132    # Contrôleur agentique : modèle PLUS PUISSANT (planification/raisonnement)33    model: str = field(default_factory=lambda: os.getenv("KA_BOT_MODEL", "claude-sonnet-5"))34    # Extraction en masse : modèle rapide/économique35    extract_model: str = field(default_factory=lambda: os.getenv("KA_BOT_EXTRACT_MODEL", "claude-haiku-4-5-20251001"))36    db_path: str = field(default_factory=lambda: os.getenv("KA_BOT_DB", "ka6.db"))37    # Crawling CONCURRENT — flagship : ~3x plus de parallélisme38    concurrency: int = field(default_factory=lambda: int(os.getenv("KA_BOT_CONCURRENCY", "16")))39    # Expansion agressive : liens auto-ajoutés à la file par page40    frontier_per_page: int = field(default_factory=lambda: int(os.getenv("KA_BOT_FRONTIER", "16")))41    # Robustesse : nb max de tentatives par URL avant abandon (dead-letter)42    max_attempts: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_ATTEMPTS", "2")))43    # Coupe-circuit : abandon d'un domaine après N échecs consécutifs dans un run44    domain_fail_threshold: int = field(default_factory=lambda: int(os.getenv("KA_BOT_DOMAIN_FAILS", "4")))4546    # Backend de scrape : auto | firecrawl | scrapfly47    scraper_backend: str = field(default_factory=lambda: os.getenv("KA_BOT_SCRAPER", "auto").strip().lower())48    scrapfly_render_js: bool = field(default_factory=lambda: _env_bool("KA_BOT_SCRAPFLY_RENDER_JS", False))4950    firecrawl_base: str = "https://api.firecrawl.dev"51    scrapfly_base: str = "https://api.scrapfly.io"52    request_delay: float = field(default_factory=lambda: float(os.getenv("KA_BOT_DELAY", "0.35")))53    respect_robots: bool = field(default_factory=lambda: _env_bool("KA_BOT_RESPECT_ROBOTS", True))54    max_pages_per_run: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_PAGES", "320")))55    max_context_tokens: int = field(default_factory=lambda: int(os.getenv("KA_BOT_MAX_CONTEXT_TOKENS", "180000")))5657    user_agent: str = "ka6-bot/1.0 (+contact@spboucher.ai)"5859    def validate(self) -> "Config":60        missing = []61        if not self.anthropic_api_key:62            missing.append("ANTHROPIC_API_KEY")63        if not self.firecrawl_api_key:64            missing.append("FIRECRAWL_API_KEY (requis pour search/map)")65        if self.scraper_backend == "scrapfly" and not self.scrapfly_api_key:66            missing.append("SCRAPFLY_API_KEY (backend=scrapfly)")67        if missing:68            raise RuntimeError(69                "Clés manquantes: " + ", ".join(missing) + ". Copiez .env.example vers .env."70            )71        return self727374config = Config()75