Dinoer

Eine Recherchekampagne durchführen

Das Manifest, die zwei Dateien, die eine Kampagne schreibt, die Felder, die den Bericht brauchbar machen, und wie man mit campagne.py einen bereits gesammelten Korpus befragt.

campagne.py sitzt über dem Browserkern: Suche über SearXNG, leichte HTTP-Erfassung, Wechsel zu rpa.py/shot.py nur für die Seiten, die einen Browser brauchen, und ein Bericht, den ein Modell aus den gesammelten Seiten schreibt.

# Installation per Git-Klon
/opt/dinoer/venv/bin/python3 /opt/dinoer/campagne.py --manifeste manifeste.json
# .deb-Paket
dinoer-campaign --manifeste manifeste.json

Das Manifest

Pflicht sind nur id_campagne und cibles; alle anderen Felder haben einen Standardwert. Das Beispiel, das in campagne.py dokumentiert ist:

{
  "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
}

Vier Zieltypen: query (eine SearXNG-Suche), url (eine feste Seite), produit (mehrere Kandidatenseiten zu einer benannten Sache sammeln und das Modell die auswählen lassen, die wirklich davon handelt), table_reference (eine gespeicherte Liste vertrauenswürdiger Websites zu einem Thema anlegen oder wiederverwenden und dann in jeder davon suchen).

Jedes Ziel wird für sich verarbeitet: Scheitert eines, laufen die anderen weiter, während die Aktionsliste eines Szenarios beim ersten Fehler anhält.

Zwei Dateien, zwei Zwecke

operations.jsonl ist das Betriebsjournal, das alle Dinoer-Programme teilen. Eine Kampagne fügt pro Versuch eine Zeile hinzu, erfolgreich oder nicht, markiert mit intention="dinoer-campagne".

<Kampagnenverzeichnis>/<id_campagne>/collecte.jsonl enthält eine Zeile pro behaltener Seite, und nur diese. Daraus entsteht der Bericht, und diese Datei öffnen Sie, um zu prüfen, dass eine Aussage von einer gesammelten Seite stammt. Das Kampagnenverzeichnis ist /var/log/dinoer/campagnes, sofern DINOER_CAMPAGNES_DIR oder der Schlüssel campagnes_dir der Konfiguration nichts anderes angeben.

Die Seiten ordnen, bevor der Bericht sie abschneidet

Standardmäßig hängt der Berichtsschritt die Seiten in der Reihenfolge ihres Schreibens aneinander und schneidet bei 60.000 Zeichen ab, ohne Rangfolge. Zwei optionale Felder des Manifests ordnen die Seiten neu; keines entfernt welche:

{
  "motifs_annee": ["2026"],
  "motifs_mois": ["août", "aout", "/08", "-08-"],
  "sujet_synthese": "concerts and festivals, south Finistère, summer 2026"
}

motifs_annee/motifs_mois ist ein Textfilter ohne Modell: Seiten, die den gesuchten Zeitraum offensichtlich nicht erwähnen, rücken ans Ende. Nehmen Sie Zahlenformen wie /08 in die Liste auf, denn manche Kalenderseiten schreiben den Monat nie aus. sujet_synthese ordnet die Seiten nach ihrer inhaltlichen Nähe zu diesem Satz, mit Embeddings von Ollama. Setzen Sie beide. Bei der Referenzkampagne ließ der Textfilter allein das offizielle Veranstaltungsprogramm auf Platz 27 der 29 behaltenen Seiten, hinter dem Schnitt; die Rangfolge nach Bedeutung holte es auf Platz 4.

Einem bereits gesammelten Korpus eine offene Frage stellen

/opt/dinoer/venv/bin/python3 /opt/dinoer/campagne.py --extraire-cible "<question>" \
  --id-campagne <id> --format-extraction markdown

--extraire-cible nimmt jede Frage in natürlicher Sprache an, nicht nur die Suche nach einer einzelnen Angabe; --corpus <Datei> lenkt es auf eine collecte.jsonl an anderer Stelle. Bei der regionalen Kampagne vom August 2026 lieferten 47 Aufrufe mit einer offenen Frage 17 positive Antworten. Auf einer Seite hatte die enge Frage von einem fünftägigen Festival nur ein Datum behalten; die offene lieferte das ganze Programm und vier weitere Veranstaltungen aus demselben Text.

Um Ergebnisse zu gruppieren, die dasselbe Ereignis auf mehreren Seiten beschreiben, führt lib/extraction.py::fusionner_evenements() sie zusammen und behält jede Quelladresse. Mit den 15 positiven Extraktionen dieser Kampagne nachgespielt, fand sie dieselben 11 Veranstaltungen wie die Zählung von Hand. Eine Seite, die mehrere Ereignisse beschreibt, wird in jeder Gruppe genannt, zu der sie gehört.

Das Modell, das den Bericht schreibt, bleibt dem Web fern

Bei jedem Aufruf entzieht Dinoer dem Modell die Werkzeuge websearch und webfetch, gleich aus welchem Verzeichnis die Kampagne gestartet wird. Ohne das hat ein Modell einmal von sich aus zwölf Websuchen gestartet, unsichtbar im zurückgegebenen Text. Was das abdeckt, und die Lücke, die bleibt →

Takt und Aktualität

delai_min_secondes/delai_max_secondes legen eine zufällige Pause vor jeder SearXNG-Anfrage und vor jeder abgerufenen Seite fest. revisite_apres_jours (standardmäßig 30) verhindert, dass eine Kampagne eine kürzlich erfasste Seite erneut abruft. Dieselben Regeln, vollständig →

Der Such-Cache

lib/cache_recherche.py speichert in ChromaDB die für jede Anfrage gesammelten Seiten. Eine spätere, inhaltlich ähnliche Anfrage wird daraus bedient, ohne neue Suche und ohne neuen Abruf; diese Seiten tragen im Korpus "source": "cache". --desactiver-cache umgeht den Cache für einen Lauf; --purger-cache und --purger-cache-avant-jours N leeren ihn. Sind ChromaDB oder Ollama nicht verfügbar, wird der Cache einfach verfehlt und die Kampagne läuft weiter.

Kurz gesagt

  • Pflicht sind nur id_campagne und cibles.
  • Vier Zieltypen: query, url, produit, table_reference.
  • collecte.jsonl ist die Datei, auf die sich eine Aussage zurückführen lässt: Öffnen Sie sie, statt sich nur auf den Bericht zu verlassen.
  • Setzen Sie motifs_annee/motifs_mois und sujet_synthese zusammen, damit der Bericht die nützlichen Seiten nicht abschneidet.
  • Eine offene Frage an --extraire-cible, Seite für Seite, holt mehr aus einem Korpus heraus als eine enge.
  • Das Modell, das den Bericht schreibt, wird vom Web ferngehalten, außer über bash.