spb/ka4 Public
ka4 — explorateur structuré du web québécois (édition agressive, Groupe KA). Crawling concurrent, Claude Sonnet+Haiku, Firecrawl/Scrapfly, graphe de connaissances.
Python 97.8%
Shell 2.2%
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