Dinoer

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_campagne et cibles sont obligatoires.
  • Quatre types de cibles : query, url, produit, table_reference.
  • collecte.jsonl est le fichier auquel se rattache une affirmation : ouvrez-le plutôt que de vous fier au seul rapport.
  • Renseignez ensemble motifs_annee/motifs_mois et sujet_synthese pour 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.