Dinoer

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_campagne et cibles sont obligatoires ; tous les autres champs ont une valeur par défaut documentée.
  • Quatre types de cibles : query, url, produit, table_reference.
  • collecte.jsonl est l’artefact auquel une affirmation fait référence ; consultez-le, ne vous fiez pas uniquement au rapport.
  • motifs_annee/motifs_mois plus sujet_synthese ensemble, et non chacun séparément, corrigent le problème de troncature du rapport par défaut.
  • Une question ouverte --extraire-cible donne de meilleurs résultats qu’une recherche factuelle étroite sur des corpus réels.
  • opencode.jsonc est ce qui permet de remonter au corpus à partir du rapport ; consultez sa limite réelle, et non seulement ses promesses.