Tout ingénieur qui tient un blog technique a vécu ça : une idée solide en tête, les notes ouvertes dans un onglet, et quatre heures plus tard, rien d’écrit. Pas parce qu’on n’a pas les compétences, mais parce que l’écriture coûte du contexte. Et le contexte, on le garde pour le code. Alors j’ai décidé de ne plus choisir.
AgenticBlog est le pipeline que j’ai construit pour automatiser la découverte, la rédaction et la publication de contenu technique. Open source, orchestré avec LangGraph, pilotable depuis un terminal. Pas un wrapper ChatGPT avec un bouton “Générer”. Une vraie pipeline multi-agents avec boucle de critique, mémoire éditoriale, et sortie en trois formats.
Le problème réel
Le défi n’est pas de générer du texte, ça, n’importe quel LLM le fait en deux secondes. Le défi, c’est de générer du contenu pertinent, avec une voix cohérente, qui parle d’un sujet d’actualité, qui est techniquement juste, et qui ne ressemble pas à un article de marketing raté.
Pour ça, il faut plusieurs étapes séquentielles avec des décisions intermédiaires. Un LLM tout seul ne peut pas savoir ce qui est trending en Kubernetes cette semaine, évaluer la densité technique d’un article source, décider si ça vaut la peine d’en parler, le rédiger avec ma voix, et le faire relire par un panel critique. C’est là qu’un orchestrateur multi-agents entre en jeu, et c’est exactement pour ça que j’ai choisi LangGraph plutôt que d’enchaîner des appels LLM à la main.
L’architecture LangGraph
J’ai choisi LangGraph pour une raison très précise : je voulais un StateGraph avec checkpointing SQLite natif. Ça me permet de reprendre un run interrompu en plein milieu du pipeline, si le fetcher plante à 2h du matin, je relance avec --resume et ça repart exactement là où ça s’était arrêté. Aucune perte, aucune re-exécution des étapes déjà complétées.
Le pipeline suit ce flow :
scraper → filter → selector → fetcher
→ writer ↔ critic (max 3 itérations) → formatter → output_saver
Chaque agent échange via des ACPMessage, un pattern Agent Communication Protocol. Chaque message a un sender, un receiver, un msg_type (task, result, feedback, approve, reject) et un content. L’état global PipelineState est un TypedDict avec un champ messages en append-only, grâce à operator.add comme reducer LangGraph :
class PipelineState(TypedDict):
messages: Annotated[list[ACPMessage], operator.add]
draft: str
critic_feedback: str
iteration_count: int
critique_approved: bool
best_draft: NotRequired[str] # meilleur draft vu jusqu'ici
best_score: NotRequired[int]
stagnation_count: NotRequired[int]
On n’écrase jamais l’historique des messages, on accumule. C’est volontaire : chaque agent peut lire l’ensemble des décisions prises avant lui.
Le scraper tire les articles depuis une vingtaine de flux RSS organisés par catégorie : infra (Kubernetes, ByteByteGo, Netflix Tech Blog), security (Project Zero, PortSwigger, Reddit r/netsec), ai (HuggingFace, LangChain, LocalLLaMA), cloud, africa. Le filter passe chaque article au LLM avec un score de pertinence, seuil configurable à 6/10. Le selector prend le top 5 filtré et applique un score composite (pertinence LLM + fraîcheur + densité) pour choisir l’article source. Le fetcher récupère le contenu complet en trois stratégies dans l’ordre : scraping direct, fallback Jina AI Reader, fallback summary RSS.
Le critique multi-personas
C’est la partie qui m’a pris le plus de temps à calibrer, et c’est de loin ce qui différencie AgenticBlog d’un simple “génère-moi un article”. Au lieu d’un critic unique qui donne une note, j’ai implémenté un système de débat : le LLM génère d’abord trois personas contextualisés au sujet de l’article, un staff engineer technique, un DevOps lead pragmatique, un éducateur. Ces personas débattent en deux rounds indépendants, puis un synthesizer transforme le transcript en corrections actionnables pour le writer.
Itération critic = 8 appels LLM :
1 génération de personas (premier appel seulement, réutilisés ensuite)
3 personas × 2 rounds = 6 appels DEBATE_MODEL (modèle léger configurable)
1 synthesizer LLM_MODEL → JSON de feedback
Le seuil d’approbation est fixé à 7/10 dans le code, pas dans le prompt, pas dans le JSON retourné par le LLM. C’est un choix délibéré. Si le LLM retourne "approved": false mais un score de 8, il est ignoré. L’autorité sur la décision d’approbation reste dans le code Python, pas dans le modèle. Ça évite des situations absurdes où un LLM trop sévère bloquerait indéfiniment la pipeline.
J’ai aussi ajouté un tracking du meilleur draft. Si le score régresse entre deux itérations, et ça arrive, le writer peut “corriger” un point et introduire une régression ailleurs, on revient automatiquement à la version précédente avant de passer au formatter. Et si le score stagne sur deux itérations consécutives, le writer reçoit une instruction explicite : arrête de deviner sur la technique, ancre tes assertions directement sur ce que dit l’article source, ajoute des avertissements là où tu ne peux pas vérifier.
# graph.py, routage conditionnel après le critic
def should_continue_writing(state: PipelineState) -> str:
if state["critique_approved"]:
return "formatter"
if state["iteration_count"] >= MAX_CRITIQUE_ITERATIONS:
return "formatter" # Force exit, on prend le meilleur draft
return "writer"
Lancer le pipeline
git clone https://github.com/Tutanka01/AgenticBlog
cp .env.example .env
# Remplir LLM_BASE_URL, LLM_API_KEY, LLM_MODEL
pip install -r requirements.txt
# Run sur la catégorie AI
python main.py --category ai
# Reprendre un run interrompu
python main.py --resume
# Lister les runs passés
python main.py --list
AgenticBlog est compatible avec n’importe quel backend OpenAI-compatible : OpenRouter, Ollama, llama.cpp, OpenAI directement, il suffit de changer LLM_BASE_URL dans le .env. Le modèle par défaut est Gemini Flash, cheap et rapide pour le débat ; on peut surcharger DEBATE_MODEL avec un modèle encore plus léger pour réduire les coûts.
À la fin d’un run, vous avez quatre fichiers dans output/{date}/{run_id[:8]}/ : l’article blog en Markdown avec front matter YAML, un post LinkedIn adapté, un script YouTube en quatre parties (hook / problème / solution / CTA), et un run_metadata.json avec les scores, le nombre d’itérations, les tokens consommés, et la méthode de fetch utilisée.
Avec le frontend React + FastAPI, vous pouvez aussi visualiser le pipeline en temps réel via SSE. L’API spawne main.py en subprocess et streame les logs ligne par ligne, chaque node s’affiche avec son statut dans un graphe @xyflow/react. Utile pour debugger une pipeline qui boucle ou un critic qui ne converge pas.
Ce que ça change concrètement
AgenticBlog ne remplace pas la réflexion, il remplace le sale boulot : scanner des dizaines de flux, décider ce qui vaut la peine d’être traité, rédiger un premier jet, l’itérer trois fois. Ça, ça prenait deux à trois heures. Maintenant ça se fait la nuit pendant que je dors, et le matin j’ai un draft à relire, pas une page blanche.
Mais je ne publie pas les articles tels quels, et je ne vous conseille pas de le faire non plus. J’édite toujours, sur le ton, sur les détails que seule mon expérience de terrain peut apporter. AgenticBlog me donne une base solide et contextualisée. Le jugement éditorial reste le mien. C’est ça la vraie valeur d’un agent : il fait la partie répétitive, vous faites la partie humaine.
Et sur ce, le code est sur GitHub, la licence est MIT, les PRs sont les bienvenues. Si vous avez des flux RSS à proposer pour la catégorie africa, je suis preneur.