Mener une campagne de recherche
Le manifeste, les deux fichiers qu'écrit une campagne, les champs qui rendent le rapport utilisable, et comment interroger avec campagne.py un corpus déjà collecté.
campagne.py se place au-dessus du cœur de navigation : découverte par
SearXNG, collecte HTTP légère, passage à rpa.py/shot.py pour les seules
pages qui demandent un navigateur, et un rapport rédigé par un modèle à partir
des pages collectées.
# Installation par clone Git
/opt/dinoer/venv/bin/python3 /opt/dinoer/campagne.py --manifeste manifeste.json
# Paquet .deb
dinoer-campaign --manifeste manifeste.json
Le manifeste
Seuls id_campagne et cibles sont obligatoires ; tous les autres champs ont
une valeur par défaut. L’exemple documenté dans campagne.py :
{
"id_campagne": "concerts-finistere-2026-07-28",
"cibles": [
{"type": "query", "valeur": "concerts finistere sud ete"},
{"type": "url", "valeur": "https://exemple.fr/agenda"},
{"type": "produit", "valeur": "boulangerie Corentin a Quimper", "max_candidats": 5},
{"type": "table_reference", "valeur": "mairies du Finistere sud", "cle_thematique": "mairies_finistere_sud"}
],
"max_resultats": 15,
"max_pages_par_hostname": 3,
"delai_min_secondes": 4.5,
"delai_max_secondes": 8.5,
"revisite_apres_jours": 30
}
Quatre types de cibles : query (une recherche SearXNG), url (une page
fixe), produit (collecter plusieurs pages candidates pour une chose nommée et
faire choisir au modèle celle qui en parle vraiment), table_reference
(constituer ou réutiliser une liste enregistrée de sites de confiance pour un
sujet, puis chercher dans chacun).
Chaque cible est traitée à part : une cible qui échoue n’arrête pas les autres, alors que la liste d’actions d’un scénario s’arrête au premier échec.
Deux fichiers, deux usages
operations.jsonl est le journal des opérations commun à tous les
programmes de Dinoer. Une campagne y ajoute une ligne par tentative, réussie
ou non, marquée intention="dinoer-campagne".
<répertoire des campagnes>/<id_campagne>/collecte.jsonl compte une ligne
par page retenue, et seulement celles-là. Le rapport est construit à partir de
lui, et c’est le fichier à ouvrir pour vérifier qu’une affirmation vient d’une
page collectée. Le répertoire des campagnes est /var/log/dinoer/campagnes,
sauf si DINOER_CAMPAGNES_DIR ou la clé campagnes_dir de la configuration
en désignent un autre.
Réordonner les pages avant que le rapport ne les coupe
Par défaut, l’étape du rapport met les pages bout à bout dans leur ordre d’écriture et coupe à 60 000 caractères, sans classement. Deux champs facultatifs du manifeste réordonnent les pages ; aucun n’en retire :
{
"motifs_annee": ["2026"],
"motifs_mois": ["août", "aout", "/08", "-08-"],
"sujet_synthese": "concerts and festivals, south Finistère, summer 2026"
}
motifs_annee/motifs_mois est un filtre sur le texte, sans aucun modèle :
les pages qui ne mentionnent manifestement pas la période demandée passent en
fin de liste. Mettez dans la liste des formes numériques comme /08, car
certaines pages d’agenda n’écrivent jamais le mois en lettres.
sujet_synthese classe les pages selon leur proximité de sens avec cette
phrase, au moyen des embeddings d’Ollama. Utilisez les deux. Sur la
campagne de référence, le filtre sur le texte laissait seul le programme
officiel des manifestations à la 27ᵉ place des 29 pages retenues, au-delà de
la coupe ; le classement par le sens l’a remonté à la 4ᵉ.
Poser une question ouverte à un corpus déjà collecté
/opt/dinoer/venv/bin/python3 /opt/dinoer/campagne.py --extraire-cible "<question>" \
--id-campagne <id> --format-extraction markdown
--extraire-cible accepte n’importe quelle question en langage naturel, pas
seulement la recherche d’un fait ; --corpus <fichier> le dirige vers un
collecte.jsonl situé ailleurs. Sur la campagne territoriale d’août 2026,
47 appels avec une question ouverte ont donné 17 réponses positives. Sur une
page, la question étroite n’avait retenu qu’une date d’un festival de cinq
jours ; la question ouverte a rendu le programme entier, et quatre autres
événements du même texte.
Pour regrouper les résultats qui décrivent le même événement sur plusieurs
pages, lib/extraction.py::fusionner_evenements() les fusionne en gardant
chaque adresse source. Rejouée sur les 15 extractions positives de cette
campagne, elle a retrouvé les 11 événements du décompte fait à la main. Une
page qui décrit plusieurs événements est citée dans chaque groupe auquel elle
appartient.
Le modèle qui rédige le rapport reste à l’écart du web
À chaque appel, Dinoer retire au modèle ses outils websearch et webfetch,
quel que soit le répertoire d’où la campagne est lancée. Sans cela, un modèle
a un jour lancé douze recherches web de lui-même, invisibles dans le texte
qu’il a rendu. Ce que cela couvre, et la faille qui reste →
Rythme et fraîcheur
delai_min_secondes/delai_max_secondes fixent une pause aléatoire avant
chaque requête SearXNG et avant chaque page récupérée. revisite_apres_jours
(30 par défaut) empêche une campagne de récupérer de nouveau une page collectée
récemment. Les mêmes règles, en entier →
Le cache de recherche
lib/cache_recherche.py enregistre dans ChromaDB les pages collectées pour
chaque requête. Une requête ultérieure proche par le sens est servie à partir
de lui, sans nouvelle recherche ni nouvelle récupération ; ces pages portent
"source": "cache" dans le corpus. --desactiver-cache contourne le cache
pour une exécution ; --purger-cache et --purger-cache-avant-jours N le
vident. Si ChromaDB ou Ollama sont indisponibles, le cache est simplement
manqué et la campagne continue.
En bref
- Seuls
id_campagneetciblessont obligatoires. - Quatre types de cibles :
query,url,produit,table_reference. collecte.jsonlest le fichier auquel se rattache une affirmation : ouvrez-le plutôt que de vous fier au seul rapport.- Renseignez ensemble
motifs_annee/motifs_moisetsujet_synthesepour que le rapport ne coupe pas les pages utiles. - Une question ouverte à
--extraire-cible, page par page, tire davantage d’un corpus qu’une question étroite. - Le modèle qui rédige le rapport est tenu à l’écart du web, sauf par
bash.