درس 4 — Tester, évaluer, exploiter

Tester la veille et la planifier chaque semaine

6 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Tester la veille et la planifier chaque semaine

Simuler une recherche ancrée

Le faux modèle de test_veille.py renvoie, comme Gemini avec google_search, un texte et des métadonnées d'ancrage :

files/test_veille.py
META = types.GroundingMetadata(
    grounding_chunks=[types.GroundingChunk(web=types.GroundingChunkWeb(uri="https://exemple.test/a", title="exemple.test")),
                      types.GroundingChunk(web=types.GroundingChunkWeb(uri="https://autre.test/b", title="autre.test"))],
    grounding_supports=[types.GroundingSupport(segment=types.Segment(text="ACME baisse ses prix de 10 %."),
                                               grounding_chunk_indices=[1])])

class FauxModele(BaseLlm):
    script: list = []
    vus: list = []

    async def generate_content_async(self, llm_request, stream=False):
        self.vus.append(llm_request)
        part, meta = self.script.pop(0)
        yield LlmResponse(content=types.Content(role="model", parts=[part]), grounding_metadata=meta)

Le test test_flux_complet_simule utilise deux faux modèles (veilleur et chercheur) et vérifie trois comportements :

flowchart LR
  A["1. chercheur_web → faits_sources : chunk 1 relié à la source 2"] --> B["2. enregistrer_signal(source_ids=[9]) → erreur"]
  B --> C["3. enregistrer_signal(source_ids=[2]) → ok"]
  C --> D["rapport : 1 signal, source autre.test/b"]
files/test_veille.py
# 1. la recherche renvoie chaque fait relié à SA source (chunk 1 -> source 2)
assert reponses[0]["faits_sources"] == [{"texte": "ACME baisse ses prix de 10 %.", "sources": [2]}]
# 2. une source inventée (9) est refusée, le modèle doit corriger
assert "erreur" in reponses[1]
assert len(etat["signaux"]) == 1 and etat["signaux"][0]["sources"] == [2]

Que garantit le test simulé que le test réel ne peut pas garantir ?

Résultat obtenu

Tests du 2026-10-15, sans clé :

test_veille.py::test_rapport_markdown PASSED                             [ 50%]
test_veille.py::test_flux_complet_simule PASSED                          [100%]
============================== 2 passed in 1.58s ===============================

Veille réelle du même jour (gemini-3.5-flash, trois concurrents) : 4 recherches, 10 signaux enregistrés, 19 sources collectées dont 13 citées. Synthèse produite par le modèle (extrait) :

Scaleway adopte une posture très offensive avec l'événement *ai-PULSE*, concrétisée par des partenariats
stratégiques majeurs (AMD, Nvidia), l'ouverture de nouvelles zones GPU hors de France (Suède, Danemark) [...]
Hetzner concentre ses efforts sur la transparence technique et la maintenance lourde de ses infrastructures réseau.

Relire et évaluer une veille

Une veille ne se teste pas par une égalité exacte. Avant diffusion, un relecteur applique une grille simple, par exemple sur 5 signaux tirés au hasard :

CritèreQuestionConstat sur la veille du 2026-10-15
Exactitudela source dit-elle bien ce que résume le signal ?à vérifier en ouvrant les liens
Périodele fait date-t-il de la période demandée ?non pour deux signaux de notre première exécution (datés du 2026-08-26 et du 2026-09-01) : il faut filtrer les dates
Pertinenceun incident d'exploitation est-il un signal ?classé « autre », utile mais secondaire
Sourceschaque signal cite-t-il une page précise ?oui après l'ajout de faits_sources

Planifier en local avec cron

cron est le planificateur standard de Linux et macOS. Ligne à ajouter avec crontab -e (chemin à adapter) : chaque lundi à 7 h.

0 7 * * 1 cd /chemin/vers/files && .venv/bin/python veille.py >> veille.log 2>&1

Les cinq champs sont minute, heure, jour du mois, mois, jour de la semaine (1 = lundi). Le poste doit être allumé à cette heure ; sinon, la veille est simplement sautée.

Planifier sur Google Cloud

Sur Google Cloud, le schéma courant est un job Cloud Run (un conteneur qui s'exécute puis s'arrête) déclenché par Cloud Scheduler :

flowchart LR
  CS["Cloud Scheduler : 0 7 * * 1"] -->|"POST .../jobs/lab-veille:run"| J["Job Cloud Run lab-veille"]
  J --> G["Gemini (clé dans Secret Manager, ou Vertex AI)"]
  J --> B["Bucket Cloud Storage : rapports/"]
# Commandes à adapter : NON exécutées dans ce lab sans GCP.
gcloud run jobs deploy lab-veille --source . --region europe-west1 \
  --command python --args veille.py \
  --set-secrets GOOGLE_API_KEY=gemini-api-key:latest \
  --labels formation-lab=veille-concurrentielle

gcloud scheduler jobs create http lab-veille-hebdo --location europe-west1 \
  --schedule "0 7 * * 1" --time-zone "Europe/Paris" \
  --uri "https://run.googleapis.com/v2/projects/PROJECT_ID/locations/europe-west1/jobs/lab-veille:run" \
  --http-method POST \
  --oauth-service-account-email PROJECT_NUMBER-compute@developer.gserviceaccount.com
cron localCloud Scheduler + job Cloud Run
Machinedoit être alluméerien à maintenir
Secretsfichier .env localSecret Manager
Historique des exécutionsveille.logjournaux Cloud Logging, relances automatiques possibles
Coûtaucun en plus du modèlequelques exécutions par mois, généralement faible

Que signifie la ligne cron « 0 7 * * 1 » ?

Exercice : refuser les signaux hors période

Faites en sorte que enregistrer_signal refuse un signal dont la date est antérieure au début de la période. Indice : lancer_veille peut créer la session avec un état initial.

Voir la solution

Dans lancer_veille, créez la session avec la date de début :

from datetime import timedelta
debut = (jour - timedelta(days=config.get("jours", 7))).isoformat()
s = await runner.session_service.create_session(app_name="veille", user_id="veille", state={"debut": debut})

Dans enregistrer_signal, avant d'enregistrer :

debut = tool_context.state.get("debut", "")
if date_signal[:len(debut)] < debut[:len(date_signal)]:
    return {"erreur": f"signal hors période : il doit dater du {debut} ou après"}

Comparer les préfixes gère les dates partielles (2026-10 contre 2026-10-08) : un signal « octobre 2026 » est accepté, un signal d'août est refusé. Ajoutez un test : un appel avec date_signal="2026-08-26" doit renvoyer une erreur.

Je sais faire

  • Simuler une réponse ancrée avec types.GroundingMetadata pour tester sans clé
  • Relire une veille avec une grille et transformer les défauts en règles de code
  • Planifier un script chaque semaine avec cron
  • Décrire le couple Cloud Scheduler + job Cloud Run et ses pièges (disque éphémère, secrets)