Chaque semaine, 27 sources me déversent environ 300 articles à trier. Un pipeline les ramène à 10 minutes de lecture, tout seul, une IA au milieu, sans que j'ouvre mon terminal.
Un samedi, il a planté. Pas à cause de l'IA, mais à cause de la confiance que je lui avais donnée.
C'est ce que je veux partager ici. De mon point de vue, le travail d'un ingénieur ne se résume pas à écrire du code : c'est comprendre un problème et le résoudre. Qu'une IA tienne le clavier ou non n'y change rien. Ce samedi-là, si le pipeline m'a échappé, c'est que je n'avais pas assez compris le problème pour l'anticiper.
Pourquoi une veille technique
J'avais deux raisons de le construire.
La première : avoir une veille personnelle, taillée pour ce que je veux suivre en fonction de là où je veux aller. Le cloud, le DevOps et l'IA bougent chaque mois, et je ne voulais pas d'un fil générique de plus. Je voulais suivre ce qui compte pour mon parcours.
La seconde : expérimenter une vraie automatisation, de bout en bout. Quelque chose qui tourne seul, en production, avec les contraintes que ça implique.
Le problème concret, c'est le volume. 27 flux, cela fait environ 300 articles par semaine. Les lire tous est impossible, les ignorer serait pire. Ce qui manque, ce n'est pas le contenu, c'est le tri.
C'est là que l'IA entre en jeu, et seulement là. Pas parce que c'est à la mode, mais parce qu'elle résout la partie que des règles simples ne suffisent pas à traiter : lire les 300 articles, garder les pertinents, retirer les doublons, les résumer. Le reste du pipeline, la collecte des flux et la publication, c'est du Python classique.
Concrètement, chaque samedi, un cron GitHub Actions récupère les articles des 7 derniers jours, Claude Haiku sélectionne et résume les plus pertinents en français et en anglais, et le pipeline publie une édition sur mon portfolio. Trois scripts Python, quelques centimes par semaine. Il tourne depuis quatre mois, 19 éditions à ce jour. Les liens sont à la fin de l'article si tu les souhaites.
La place de l'IA dans le projet
J'ai fixé une règle dès le départ : l'IA fait le travail, mais rien n'est publié sans que je l'aie relu.
Le pipeline ne met rien en ligne tout seul. Il prépare l'édition et ouvre une Pull Request. Cette relecture me sert à deux choses. La première, c'est faire ma veille : relire l'édition, c'est déjà la lire, et c'est le but même du projet. La seconde, c'est vérifier le travail de l'IA, que les résumés correspondent aux articles et qu'aucun doublon n'est passé. Pour chaque article, la source d'origine reste accessible, si je veux creuser un peu plus.
En amont, même logique : la consigne est écrite dans le prompt, des résumés factuels, sans opinion ni commentaire. Je ne veux pas que l'IA interprète l'actualité à ma place, seulement qu'elle me donne le fait et sa portée.
Le crash
Deux semaines après la mise en production, un samedi matin, la CI m'alerte : le workflow s'arrête en erreur à l'étape de résumé. Aucune édition ce jour-là.
En soi, l'incident est mineur. La CI l'a rattrapé tout de suite, aucune donnée n'a été perdue, et il m'a fallu une demi-heure pour corriger. Mais la cause valait la peine que je m'y arrête, parce qu'elle est piégeuse.
Mon prompt demandait à Claude de résumer d'un coup la trentaine d'articles retenus, chacun avec son titre traduit, deux résumés et une catégorie, le tout dans une seule réponse JSON. Deux consignes y figuraient noir sur blanc :
Summarize ALL articles without exception.
Respond with ONLY valid JSON.
Cette semaine-là, la sélection était dense. La réponse dépassait le budget de tokens que j'avais fixé, 8192. Claude a écrit jusqu'à la limite, puis s'est arrêté, en plein milieu du JSON. Mon script a reçu un objet coupé net, impossible à lire.
Le problème ne venait pas de Claude, mais de moi. J'avais pris une consigne de prompt pour une garantie. Écrire « réponds en JSON valide » ne limite pas la longueur de la réponse, et « résume tout sans exception » la pousse justement à dépasser la limite. Un prompt exprime une intention, il ne garantit pas un résultat.
Mais il y avait pire. Mon parser masquait le problème : quand il ne trouvait pas de JSON exploitable, au lieu de lever une erreur, il repartait du début du texte avec une valeur par défaut.
# La version fautive : default=0 masque l'absence de JSON
start = min((text.find(c) for c in '[{' if text.find(c) != -1), default=0)
Ce comportement masquait le problème au lieu de le signaler. Le jour où la réponse a été franchement tronquée, tout a lâché d'un coup, sans indice pour remonter à la cause.
Ce que j'en ai retenu
La sortie d'une IA est une donnée non fiable, à traiter comme telle. On sait valider une saisie utilisateur, gérer un timeout réseau, se méfier d'une API tierce. La réponse d'un modèle demande la même prudence : elle n'est pas déterministe, et rien ne garantit qu'elle tienne dans la place prévue. Je la valide et je la parse défensivement, je ne la considère plus comme une valeur sûre venue de mon propre code.
Une erreur doit se voir. Ma première correction n'a pas été de rendre le pipeline plus intelligent, mais de le faire échouer franchement. Si la réponse ne contient pas de JSON exploitable, le script lève désormais une erreur explicite, avec le début du texte reçu. Un bug visible se corrige en quelques minutes ; un bug silencieux, lui, peut coûter des jours.
candidates = [text.find(c) for c in '[{' if text.find(c) != -1]
if not candidates:
raise ValueError(f"Pas de JSON trouvé dans la réponse : {text[:200]}")
start = min(candidates)
Le vrai problème venait de la conception, pas du réglage. J'ai monté max_tokens à 16384 et ajouté 3 tentatives. C'est utile, mais cela ne fait que repousser le plafond : le jour où le radar retiendra plus d'articles ou des résumés plus longs, le même bug reviendra. La bonne correction, celle que je ferais en repartant de zéro, n'est pas d'agrandir la réponse, mais de ne plus en demander une seule et énorme. Découper en paquets de 5 ou 10 articles, traiter un article par appel, ou imposer un schéma de sortie. Borner ce qu'on demande au modèle plutôt que d'espérer que ça passe.
for attempt in range(3):
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=16384, # avant : 8192
messages=[{"role": "user", "content": prompt}])
try:
return extract_json(response.content[0].text)
except (json.JSONDecodeError, ValueError) as e:
print(f"Tentative {attempt + 1}/3 échouée : {e}")
if attempt == 2:
raise
Comprendre le problème reste mon travail, que l'IA écrive le code ou non. L'IA m'a fait gagner beaucoup de temps sur la partie ingrate, trier 300 articles chaque semaine. Mais le jour où je lui ai fait confiance sans vérifier, elle m'a lâché sans prévenir. De mon point de vue, un ingénieur n'est pas seulement là pour écrire du code, il est là pour comprendre un problème et le résoudre. Que ce soit une IA ou moi qui l'écrive n'y change rien : si je ne comprends pas ce qu'elle produit, ni où cela peut casser, je ne peux pas l'anticiper. C'est exactement ce qui s'est passé ce samedi-là.
Et maintenant
Le radar tourne toujours. Chaque samedi, sans moi, il ramène 300 articles à 10 minutes de lecture. Je suis passé d'une veille que je faisais quand j'y pensais, c'est-à-dire rarement, à une veille régulière que je lis vraiment.
Mais je ne le laisse pas seul pour autant. L'IA fait le gros du travail, je garde la main sur ce qui compte : ce qui est publié, ce qui est exact, et ce qui casse. C'est la leçon que je retiens pour le prochain projet où je mettrai une IA en production. C'est un excellent outil, à condition de rester celui qui comprend ce qu'il fait.
Si le sujet t'intéresse, l'édition de la semaine est sur arthurbernard.dev/fr/tech-radar, et le code est public sur github.com/TuroTheReal/weekly-tech-radar.