Mener une campagne de recherche
Un manifeste, un modèle délégué contenu dans le fichier opencode.jsonc, et un rapport qui retrace chaque affirmation à une page réellement collectée. Le guide pour campagne.py – l'utilisation principale réelle de l'outil.
campagne.py est une couche au-dessus du noyau de navigateur existant, et non un remplacement de celui-ci : la découverte SearXNG, une collecte HTTP légère, l’escalade vers rpa.py/shot.py uniquement pour les pages qui en ont besoin, et un rapport généré par un modèle délégué à partir de ce qui a réellement été collecté.
python3 /opt/dinoer/campagne.py --manifeste manifeste.json
Le manifeste
Seuls id_campagne et cibles sont obligatoires — tout le reste a une valeur par défaut.
{
"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
(découvrir et comparer des candidats pour un élément nommé), table_reference (créer ou étendre un tableau persistant et référencé de sites connus pour un sujet).
Chaque cible est une opération complètement isolée ; la défaillance d’une seule cible n’affecte jamais les autres, contrairement à une liste d’actions pour un scénario unique, qui s’arrête dès la première erreur.
Deux artefacts, deux objectifs
operations.jsonl (partagé, en écriture seule) enregistre chaque tentative, qu’elle soit réussie ou non, et est étiquetée intention="dinoer-campagne" — le même journal auquel tous les autres outils Dinoer écrivent.
<campaigns_dir>/<id_campagne>/collecte.jsonl (par campagne, nouveau) contient
une seule ligne pour chaque extraction réussie. C’est l’élément de base à partir duquel le rapport est construit, et c’est celui que vous vérifiez pour confirmer qu’une affirmation correspond bien à une page collectée.
Réorganiser le rapport automatique avant qu’il ne soit tronqué
Le rapport par défaut concatène le corpus dans l’ordre d’écriture des fichiers, tronqué à 60 000 caractères, sans classement. Deux champs de manifeste facultatifs corrigent cela, les deux réorganisent, aucun n’exclut :
{
"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 une étape textuelle sans appel de modèle — des pages qui
ne mentionnent clairement pas la fenêtre demandée et disparaissent à la fin, y compris
les formats de date numériques que certains widgets d’agenda utilisent au lieu du nom du mois.
sujet_synthese est une étape sémantique — un seul appel groupé d’intégration Ollama,
classant les pages en fonction de la similarité cosinus avec cette phrase. Utilisez les deux, pas seulement l’une.
Dans une campagne de référence réelle, l’étape textuelle seule a encore laissé un PDF
véritablement pertinent classé 27e sur 29 « pages probables » — toujours en dehors du budget
de troncature. L’étape sémantique l’a déplacé à l’intérieur. Mesuré par rapport à ce corpus,
et non supposé.
Une question ouverte est préférable à une question étroite
Pour une extraction unique et ciblée, en dehors d’une campagne complète :
python3 /opt/dinoer/campagne.py --extraire-cible "<demande>" \
--id-campagne <id> --format-extraction markdown
--extraire-cible accepte toute requête en langage naturel, et pas seulement une recherche de faits
trouve/valeur/url stricte. Dans une campagne de référence réelle, une question ouverte et descriptive permet au modèle délégué de juger lui-même s’il s’agissait d’un fait isolé ou d’un événement sur plusieurs jours : 17 des 47 requêtes ont renvoyé des résultats positifs et plus riches que ceux obtenus avec une formulation plus restrictive, en utilisant le même corpus.
Pour les résultats qui décrivent le même événement réel sur plusieurs pages,
lib/extraction.py::fusionner_evenements() les regroupe — appelez-la sur les
résultats positifs filtrés avant de rédiger un rapport final, plutôt que
de consolider manuellement. Une source unique décrivant légitimement plusieurs
événements distincts n’est pas une erreur ; la même source est citée dans chaque groupe auquel elle
appartient.
Le corpus est ce que le modèle est autorisé à laisser
Dinoer empêche le modèle délégué d’utiliser ses propres
websearch/webfetch outils pour l’ensemble de l’étape de synthèse, quel que soit le répertoire depuis lequel la campagne est lancée (opencode.jsonc à la racine du dépôt dit la même chose, et OPENCODE_CONFIG_CONTENT le porte à chaque appel).
Sans cela, une exécution réelle effectuait douze recherches propres, invisibles
dans le texte renvoyé. Le compte rendu complet, y compris ce qu’il ne couvre pas →
Respecter les objectifs, à l’échelle de la campagne
delai_min_secondes/delai_max_secondes espacez chaque appel à rechercher(), et non
pas seulement les récupérations de pages qui suivent ; une campagne réelle a découvert ce problème de manière concrète, et il est corrigé partout où le délai est censé s’appliquer maintenant.
revisite_apres_jours (par défaut, 30) empêche une campagne de réexaminer une
page simplement parce qu’un objectif ultérieur y fait référence à nouveau.
La même doctrine, en entier →
Le cache de recherche vectorielle
lib/cache_recherche.py, Grâce à ChromaDB, il évite de relancer des requêtes pour
les demandes quasi-dupliquées. --desactiver-cache l’ignore pour une seule exécution ;
--purger-cache ou --purger-cache-avant-jours N le suppriment explicitement.
En bref
- Seuls les champs
id_campagneetciblessont obligatoires ; tous les autres champs ont une valeur par défaut documentée. - Quatre types de cibles :
query,url,produit,table_reference. collecte.jsonlest l’artefact auquel une affirmation fait référence ; consultez-le, ne vous fiez pas uniquement au rapport.motifs_annee/motifs_moisplussujet_syntheseensemble, et non chacun séparément, corrigent le problème de troncature du rapport par défaut.- Une question ouverte
--extraire-cibledonne de meilleurs résultats qu’une recherche factuelle étroite sur des corpus réels. opencode.jsoncest ce qui permet de remonter au corpus à partir du rapport ; consultez sa limite réelle, et non seulement ses promesses.