# Comment planifier des tâches d’IA récurrentes avec cron et une API?

Date: 2024-09-20 · Author: Robert Scales · https://guidetoartificialintelligence.com/fr/apprendre/planifier-taches-ia-recurrentes-cron-api/

**Answer:** Écrivez un petit script qui fait une seule tâche bornée, comme résumer les derniers éléments d’un fil en un seul appel d’API, et laissez cron, le planificateur standard d’Unix, le lancer à heure fixe. Donnez au script des limites strictes (taille de l’entrée, jetons de sortie, délai d’expiration, budget mensuel de jetons, verrou contre les exécutions qui se chevauchent et interrupteur d’arrêt) et gardez la clé d’API dans une variable d’environnement, jamais dans le code.

Certaines des tâches d’IA les plus utiles sont ennuyeuses et régulières : un condensé du lundi tiré d’un fil de nouvelles, un résumé nocturne des demandes de soutien, une mise au propre hebdomadaire des notes de réunion. Vous n’avez pas besoin d’une plateforme pour cela. Sur n’importe quel Mac ou ordinateur Linux, ou sur un petit serveur loué, l’outil classique est **cron**, un planificateur qui lance des commandes à heure fixe. Associez-le à un court script qui appelle l’API d’un modèle d’IA, et vous avez un assistant qui travaille pendant que vous dormez.

Le danger tient à cette même caractéristique : il travaille pendant que vous dormez. Une faute de frappe dans l’horaire ou un script qui se comporte mal peut se répéter des milliers de fois avant que vous le remarquiez. Ce guide intègre donc les limites dès la première ligne.

## Comment cron lit un horaire

Chaque tâche est une ligne de votre **crontab** (table cron), que vous modifiez avec `crontab -e`. La ligne commence par cinq champs de temps, suivis de la commande :

```text
# minute  heure  jour-du-mois  mois  jour-de-la-semaine   commande
  0       7      *             *     1                    /home/me/digest/run.sh >> /home/me/digest/digest.log 2>&1
```

Cela veut dire : à la minute 0 de l’heure 7, à n’importe quelle date, n’importe quel mois, le jour de la semaine 1 (le lundi, puisque 0 est le dimanche). En clair, tous les lundis à 7 h. La partie `>> digest.log 2>&1` ajoute toute la sortie et toutes les erreurs à un fichier journal pour que vous puissiez voir ce qui s’est passé. Cron utilise le fuseau horaire de la machine; vérifiez-le donc sur un serveur.

Remarquez le peu qui sépare cette ligne d’une catastrophe : `* * * * *` veut dire toutes les minutes, soit 10 080 exécutions par semaine au lieu d’une.

## Le script : une seule tâche, des limites strictes

Ce condensé hebdomadaire lit un fil de nouvelles (RSS, un format standard que les sites Web utilisent pour publier leurs derniers éléments), garde les dix manchettes les plus récentes et demande à un modèle un court résumé en un seul appel. Il utilise la bibliothèque Python officielle `openai` (version 1.x).

```python
import fcntl, json, os, sys, urllib.request, xml.etree.ElementTree as ET
from datetime import date
from openai import OpenAI

FEED_URL = "https://example.org/news/rss.xml"   # votre fil
MAX_ITEMS = 10              # jamais plus de 10 manchettes
MAX_INPUT_CHARS = 6000      # plafond du texte envoyé au modèle
MONTHLY_TOKEN_BUDGET = 50_000
STATE = "usage.json"

if os.path.exists("STOP"):                      # interrupteur d’arrêt
    sys.exit("Fichier STOP présent; aucune exécution.")

lock = open("digest.lock", "w")                 # pas d’exécutions qui se chevauchent
try:
    fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
    sys.exit("L’exécution précédente est encore active; sortie.")

month = date.today().strftime("%Y-%m")
usage = json.load(open(STATE)) if os.path.exists(STATE) else {}
if usage.get(month, 0) >= MONTHLY_TOKEN_BUDGET:
    sys.exit("Budget mensuel de jetons atteint; aucun appel à l’API.")

with urllib.request.urlopen(FEED_URL, timeout=20) as r:
    root = ET.fromstring(r.read())
items = [(i.findtext("title") or "").strip() for i in root.iter("item")][:MAX_ITEMS]
text = "\n".join("- " + t for t in items)[:MAX_INPUT_CHARS]

client = OpenAI(timeout=60.0, max_retries=1)    # la clé vient de OPENAI_API_KEY
response = client.chat.completions.create(
    model="gpt-4o-mini",
    max_tokens=400,
    messages=[{"role": "user", "content":
        "Résume les manchettes de la semaine en 5 puces pour un lecteur pressé. "
        "Utilise seulement les manchettes fournies; n’ajoute aucun fait.\n\n" + text}],
)

usage[month] = usage.get(month, 0) + response.usage.total_tokens
json.dump(usage, open(STATE, "w"))
open(f"digest-{date.today()}.md", "w").write(response.choices[0].message.content)
print("Terminé. Jetons ce mois-ci :", usage[month])
```

Lisez-le comme une liste de garde-fous. Il n’y a pas de boucle autour de l’API : une exécution, une requête. L’entrée est plafonnée deux fois, par le nombre d’éléments et par le nombre de caractères, et la sortie par `max_tokens`. Les réglages de sécurité de la bibliothèque elle-même sont resserrés : par défaut, elle attend jusqu’à 10 minutes par requête et relance deux fois certains échecs; `timeout=60.0` et `max_retries=1` gardent donc une mauvaise journée courte. Le fichier de budget arrête la tâche avant l’appel à l’API une fois l’allocation du mois épuisée. Les chiffres de jetons sont donnés ici à titre d’illustration; fixez-les à partir de vos propres essais.

## Garder la clé hors du code

La bibliothèque lit votre clé dans la variable d’environnement `OPENAI_API_KEY`, ce que recommande OpenAI : le code peut être partagé, la clé, non. Le petit script d’enveloppe qu’appelle cron la charge depuis un fichier privé :

```sh
#!/bin/sh
cd /home/me/digest || exit 1
. ./secrets.env          # une ligne : export OPENAI_API_KEY="..."
exec /usr/bin/python3 digest.py
```

Rendez le script d’enveloppe exécutable (`chmod +x run.sh`), rendez `secrets.env` lisible par vous seul (`chmod 600 secrets.env`), ne le versez jamais dans un dépôt, et utilisez une clé créée uniquement pour cette tâche, afin de pouvoir la révoquer sans rien briser d’autre.

> **À essayer :** avant d’ajouter la ligne cron, lancez `./run.sh` à la main deux fois de suite, puis créez un fichier nommé `STOP` et relancez-le. Vous devriez obtenir un fichier de condensé, un nombre de jetons dans `usage.json` qui augmente à chaque exécution, puis un refus net une fois l’interrupteur d’arrêt en place.

## Comment éviter les coûts qui s’emballent et les boucles?

Recensez toutes les façons dont la tâche peut se répéter, et mettez un plafond sur chacune. Les horaires se répètent : vérifiez deux fois les cinq champs, et surveillez le journal après la première exécution. Les nouvelles tentatives se répètent : chaque nouvelle tentative est une requête de plus facturée, et c’est pourquoi ce script n’en permet qu’une. Les exécutions qui se chevauchent se répètent : si une exécution lente est encore en cours quand la suivante démarre, le fichier verrou fait sortir la deuxième. Votre propre code peut se répéter : si vous ajoutez plus tard une boucle, par exemple un appel par article, donnez-lui un nombre maximal fixe de passages et comptez les jetons à l’intérieur.

Ajoutez ensuite des freins à l’extérieur du script. Consultez régulièrement la page Usage de votre compte OpenAI et, si votre compte offre des limites de dépenses ou des alertes, réglez-les bien au-dessus d’un mois normal, mais bien en dessous d’un mois douloureux. Les conseils d’OpenAI sur la sécurité des clés expliquent clairement pourquoi : une clé divulguée ou mal utilisée consomme votre quota, et la solution est de la remplacer immédiatement. Enfin, gardez l’interrupteur d’arrêt assez simple pour l’utiliser depuis un téléphone : supprimer la ligne cron avec `crontab -e`, ou créer le fichier `STOP`, met fin à la tâche à sa prochaine exécution. L’emballement le moins coûteux est celui qui ne peut pas commencer.

## Sources

- cron: https://en.wikipedia.org/w/index.php?title=Cron&oldid=1238615222
- OpenAI Python API library (README): https://github.com/openai/openai-python/blob/4b3023466df27de4d19a93b4efbd14977254ecc0/README.md
- Best Practices for API Key Safety: https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
