Source docs/openviking.fr.md · 1de96aa

Mémoire OpenViking

Cette page s’adresse aux administrateurs qui veulent relier Libris à un serveur OpenViking, utilisé comme mémoire sémantique externe de leurs livres. Elle explique ce qu’apporte OpenViking, comment le mettre en place, ce que Libris y écrit, ce qu’un passage a le droit d’y relire et comment l’entretenir.

OpenViking est facultatif. Libris conserve chaque mémoire dans sa propre base de données (PostgreSQL en production), et le backend par défaut, internal, la lit directement. OpenViking est un index sémantique supplémentaire des mêmes mémoires :

  • tout ce qui y est écrit peut être reconstruit à tout moment à partir de la base de données ;
  • une défaillance d’OpenViking ne supprime ni n’annule jamais un résultat enregistré dans Libris. Avec le backend hybrid ou openviking, la traduction continue simplement avec la mémoire de la base de données, et l’inspecteur de contexte le signale. Il en va de même quand OpenViking ne peut pas être interrogé : sa clé enregistrée est devenue illisible (la SECRET_KEY a changé ; l’erreur de récupération de l’inspecteur demande d’enregistrer de nouveau la clé) ou la Recherche sémantique est désactivée.

Mise en place

  1. Faites tourner un serveur OpenViking que Libris peut joindre sur votre réseau privé. Gardez-le privé et entretenez-le séparément.

  2. Dans Paramètres › Mémoire · OpenViking (administrateurs), renseignez :

    ChampSignification
    URL OpenVikingURL de base du serveur.
    Racine dédiée viking://Un sous-dossier dédié sous viking://resources/ (par défaut viking://resources/epub-translator). La racine elle-même est refusée.
    Clé APIEnvoyée dans X-API-Key. Enregistrée chiffrée ; plus jamais affichée.
    AuthentificationAPI key (recommandé), ou le mode Trusted gateway (de confiance), qui envoie en plus les en-têtes de compte et d’utilisateur que vous indiquez (X-OpenViking-Account, X-OpenViking-User).
    Budget contexte, Budget retrievalLe plafond du contexte facultatif de chaque appel, quel que soit le backend de mémoire (par défaut 32000 tokens estimés : un appel prend les trois quarts de ce que la fenêtre de son provider laisse, jusqu’à ce plafond ; l’ancien défaut 12000 se lit 32000), et la part qui peut venir d’OpenViking (par défaut 6000).
    Score minimalSeuil de pertinence des résultats de recherche (par défaut 0.15).
    Timeout (secondes)Secondes par appel (par défaut 20).
    Recherche sémantique (find), Recherche approfondie (search)Les modes de recherche d’OpenViking que Libris a le droit d’utiliser.

    Tester la connexion vérifie que le serveur répond, que la clé est acceptée et que la racine peut être interrogée. Les trois premiers réglages peuvent aussi venir de OPENVIKING_URL, OPENVIKING_API_KEY et OPENVIKING_ROOT_URI ; les valeurs enregistrées dans l’interface l’emportent (voir la configuration).

  3. Choisissez le backend de mémoire par volume (les Réglages du livre) ou comme valeur par défaut d’une série :

    BackendComportement
    internalLa base de données seule. Rien n’est écrit dans OpenViking.
    openvikingLes mémoires sont recopiées dans OpenViking et relues depuis lui.
    hybridLes deux : la recherche passe par OpenViking quand il répond, et par la base de données sinon.

L’API d’automatisation accepte les mêmes valeurs dans pipeline.context_backend (guide de l’API).

Ce que Libris écrit

Libris écrit au moyen d’opérations replace idempotentes sur des URI stables, sans attendre l’indexation. Chaque identifiant présent dans une URI est un identifiant de base de données choisi par Libris, jamais un titre ni un chemin tiré d’un livre :

<root>/<owner_id>/series/<series_id>/volumes/<project_id>/events/<memory_id>.json   événement, volume d’une série
<root>/<owner_id>/series/<series_id>/volumes/<project_id>/book.md …                 catalogue de ce volume
<root>/<owner_id>/standalone/<project_id>/events/<memory_id>.json                   événement, volume indépendant
<root>/<owner_id>/standalone/<project_id>/book.md …                                 catalogue de ce volume

Événements

Un événement est une mémoire du livre : l’analyse d’un passage, son état narratif, ou une décision humaine validée. Son document est calculé à partir de la base de données au moment où il est écrit, si bien qu’OpenViking contient toujours l’événement actuel à sa place actuelle.

ChampSignification
schema_version2
owner_id, series_id, project_id, volume_numberL’endroit auquel appartient la mémoire.
chapter_id, chapter_position, chapter_numberSon chapitre, son rang dans le volume et le numéro donné par l’auteur.
segment_id, positionLe passage et sa position narrative dans le volume.
typeanalysis, narrative ou human_decision.
identitiesLes personnages nommés par la mémoire (noms canoniques et qui les connaît).
validatedUne personne l’a validée.
created_atDate de création de la mémoire.
contentLa mémoire elle-même.

La file d’envoi ne transporte que le travail d’écriture d’un document ; les écritures en échec sont retentées avec un délai croissant (une demi-heure environ au maximum).

Documents de catalogue

Pour chaque volume qui a une analyse ou une Book Bible, Libris publie aussi un petit catalogue nommé, rafraîchi toutes les MEMORY_CATALOG_INTERVAL_SECONDS (60) secondes quand quelque chose a changé :

DocumentContenu
book.mdTitre, auteur, langues, avancement de l’analyse et liens vers les autres documents.
book-bible.jsonLa Book Bible (synthèse éditoriale), sans les personnages.
characters.json et characters-NNNN.jsonIdentités, alias, rôle, description, genre, pronoms, style de parole, et si une personne les a confirmés.
relationships.json et relationships-NNNN.jsonRelations entre personnages, avec preuves, provenance et validation.

Ce sont des projections compactes ; la base de données conserve les données complètes et exactes. Les documents de catalogue ne sont jamais utilisés comme preuve narrative (voir ci-dessous).

Ce qu’un passage peut lire

Un passage du volume N à la position P effectue sa recherche avec target_uri réglé sur le dossier volumes de sa série, ou sur son propre dossier events pour un volume indépendant. Cela ne fait que restreindre la recherche côté serveur. Libris ne conserve ensuite un résultat que si :

  1. son URI est exactement celle d’un événement que la base de données admet pour ce passage : une mémoire du même volume à une position antérieure (y compris l’analyse validée par une personne du passage lui-même), ou une mémoire d’un volume antérieur de la même série, du même propriétaire et de la même paire de langues (numéro de volume inférieur à N) ;
  2. ce n’est ni une analyse humaine remplacée depuis, ni une décision humaine portant sur un passage modifié depuis ;
  3. le document relu est identique à l’événement calculé à partir de la base de données.

Tout le reste est rejeté et listé dans l’inspecteur de contexte avec son motif (outside_narrative_allowlist, low_relevance, differs_from_canonical_event, invalid_structured_memory, retrieval_budget) : passages ultérieurs, volumes ultérieurs, résumés de dossiers, catalogues, documents d’un autre propriétaire, documents périmés ou altérés. La Book Bible et les fiches de personnages sont un savoir éditorial construit à partir d’une lecture complète : c’est pourquoi elles ne sont jamais admises comme preuve de ce qu’un passage peut savoir. Les flux continus de webnovel et les volumes non numérotés n’ont pas de « volume antérieur » : ils s’appuient sur leurs propres chapitres.

État d’OpenViking et de la file d’envoi

Dans Réglages › Mémoire · OpenViking, la carte État d’OpenViking affiche :

  • si OpenViking est joignable : le worker interroge son /health au plus une fois par minute ; s’il est injoignable, depuis quand et la cause (ConnectError, HTTP 503…) ;
  • les événements en attente d’envoi et les événements en erreur (volumes en openviking ou hybrid seulement), la dernière erreur, les erreurs les plus fréquentes et le prochain envoi prévu ;
  • Relancer les événements en erreur : chaque événement en échec part au prochain passage du worker, sans attendre son délai.

Quand OpenViking passe de joignable à injoignable, ou l’inverse, le worker l’inscrit une fois au journal (openviking=unreachable previous=reachable reason=ConnectError, openviking=reachable previous=unreachable requeued=140). Quand il redevient joignable, les événements en erreur sont remis en file aussitôt, sans attendre leur délai.

Le même état forme l’élément openviking de GET /health, pour un administrateur connecté ou pour tous avec PUBLIC_HEALTH_DETAILS=true (voir l’exploitation, en anglais). Par l’API (administrateurs) : GET /api/settings/memory/status et POST /api/settings/memory/outbox/retry.

Maintenance

Sur la page d’une série, l’onglet Mémoire (Mémoire OpenViking de la série) affiche, par volume, les événements en attente, écrits et en échec, avec les dernières erreurs, et propose :

  • Resynchroniser : retenter immédiatement tout ce qui est en attente, sans attendre le délai de temporisation ;
  • Reconstruire : réécrire chaque événement et chaque catalogue de la série à partir de la base de données, y compris les mémoires dont la rétention a déjà supprimé les lignes de file ;
  • Réindexer : demander à OpenViking de recalculer son index du dossier de la série.

Sur un volume, l’onglet Book Bible comporte un panneau Mémoire OpenViking avec l’état de la file, la racine, des liens qui ouvrent les documents que contient réellement OpenViking (lus à travers Libris, sans exposer la clé), et ces actions :

  • Synchroniser le livre et le graphe : remettre le catalogue en file (fonctionne même pendant une analyse) ;
  • Vérifier dans OpenViking : relire les documents de catalogue et les comparer avec ce que Libris a écrit, puis les chercher dans l’index (cela confirme les documents trouvés, pas l’indexation de chaque événement) ;
  • Réindexer dans OpenViking et Reconstruire depuis la base, comme pour une série.

Les mêmes actions sont disponibles via l’API de l’interface : GET /api/series/{id}/memory, POST /api/series/{id}/memory/{resync|rebuild|reindex}, GET /api/projects/{id}/memory/status, POST /api/projects/{id}/memory/{synchronize|rebuild|reindex|check}.

Quand un volume entre dans une série ou en sort, ou change de numéro, ses événements et son catalogue sont réécrits automatiquement au nouvel emplacement ; en attendant, ses résultats distants sont rejetés et la traduction s’appuie sur la base de données. La même réécriture automatique déplace les volumes écrits selon l’ancienne arborescence <root>/<owner_id>/<project_id>/… : aucune étape manuelle n’est nécessaire après une mise à jour.

Nettoyage des volumes et séries supprimés

Par défaut, Libris ne supprime jamais rien dans OpenViking. Supprimer un volume ou une série, ou déplacer un volume, laisse les anciens documents en place ; la vérification par la base de données ne les admet simplement plus.

Un administrateur peut activer le nettoyage dans Paramètres › Mémoire · OpenViking, carte Nettoyage d’OpenViking : cochez Effacer les documents OpenViking à la suppression, puis Enregistrer le nettoyage (ou définissez OPENVIKING_CLEANUP_ON_DELETE=true ; une valeur enregistrée dans l’interface l’emporte jusqu’à Revenir aux valeurs de l’environnement). Rien n’est effacé tant que l’URL OpenViking est vide. Quand il est activé :

  • supprimer un volume met en file l’effacement de ses dossiers : son emplacement actuel (…/series/<series_id>/volumes/<project_id> ou …/standalone/<project_id>), l’ancien emplacement <root>/<owner_id>/<project_id>, et tout autre emplacement où, d’après la file d’envoi, il a été écrit (un volume qui a changé de série) ;
  • supprimer une série (possible une fois qu’il ne lui reste aucun volume) met en file l’effacement de <root>/<owner_id>/series/<series_id> en entier.

L’effacement est mis en file dans la même transaction que la suppression et exécuté par le worker, jamais par la requête : la suppression est immédiate même quand OpenViking est en panne. Le worker attend environ 30 secondes (pour qu’une écriture déjà en route arrive d’abord), puis, pour chaque dossier, liste ses documents et l’efface récursivement. Un échec est retenté avec un délai croissant (une heure au maximum) ; un worker redémarré reprend là où le précédent s’est arrêté. Un nettoyage mis en file pendant que l’option était activée s’exécute quand même si elle est désactivée ensuite. Si l’URL OpenViking est vidée, les nettoyages en attente attendent ; si la racine change, ils restent en attente avec l’erreur La racine OpenViking a changé depuis la suppression. et n’effacent rien. La carte compte désormais ces blocages à part des échecs retentables, sur l’ensemble de la file (pas seulement sur la page visible du journal). Chaque entrée en attente expose sa racine d’origine et un blocked_reason : root_changed, not_configured, retry_error, ou une chaîne vide.

Un administrateur peut Abandonner ce nettoyage après confirmation. Aucune requête distante n’est envoyée, les dossiers et les résultats antérieurs ne sont pas touchés, et la ligne reste dans le journal sous la mention Nettoyage abandonné. Cette transition est refusée dès qu’un worker a pris en charge le nettoyage, même si son bail a expiré : la reprise doit d’abord se terminer. Retenter ne peut pas réactiver une ligne abandonnée. Libris ne redirige délibérément pas une ancienne suppression vers une nouvelle racine : rétablissez la configuration d’origine, ou abandonnez-la et lancez un nouvel essai à blanc sur la nouvelle racine avant de sélectionner quoi que ce soit à effacer.

Ce qu’un nettoyage peut effacer est borné deux fois :

  1. uniquement le dossier entier d’un élément situé directement sous la racine configurée : une série (<owner_id>/series/<series_id>), un volume (<owner_id>/series/<series_id>/volumes/<project_id>, <owner_id>/standalone/<project_id> ou <owner_id>/<project_id>), avec des identifiants de base de données uniquement. La racine, le dossier d’un propriétaire et tout le reste sont refusés ;
  2. au moment de l’effacement, la base de données doit confirmer que plus rien n’y vit. Un dossier de nouveau utilisé est conservé et consigné comme tel.

Orphelins

Les documents d’éléments supprimés avant l’activation du nettoyage restent en place. Dans la même carte, Chercher les orphelins (essai à blanc) liste les dossiers sous la racine que la base de données ne possède plus, avec le motif (volume_deleted, volume_moved, series_deleted, legacy_layout pour l’ancienne arborescence), et n’efface rien. Effacer ces N dossier(s) met ensuite leur effacement en file, après une confirmation ; chacun est de nouveau vérifié auprès de la base de données, au moment de sa mise en file comme au moment de son effacement. Les dossiers dont le nom n’est pas un identifiant Libris sont ignorés. L’essai à blanc et l’effacement fonctionnent que le nettoyage automatique soit activé ou non.

Rechercher les orphelins chaque jour, sans rien effacer est un réglage distinct, désactivé par défaut. Le worker enregistre en base la date, la racine, le nombre et les 500 premiers résultats ; la carte affiche la dernière date et le dernier nombre, ou un échec. Un redémarrage conserve la prochaine tentative, et plusieurs workers partagent la même prise en charge. La recherche planifiée s’exécute au plus une fois toutes les 24 heures, dure au plus 30 secondes et ne met jamais de suppression en file. Désactiver l’option pendant une recherche l’emporte sur son achèvement. Une recherche interrompue attend sa prochaine tentative quotidienne ; un essai à blanc manuel reste toujours possible plus tôt.

Nettoyer chaque jour les vecteurs orphelins de la racine Libris est un autre réglage du même passage quotidien, désactivé par défaut. OpenViking peut garder des enregistrements vectoriels dont le document n’existe plus (lors de l’incident du 2026-09-24 : 1,9 million d’enregistrements pour environ 6 000 documents indexés). Avec OpenViking 0.4.21 ou plus, le worker appelle POST /api/v1/content/reindex avec {"uri": <racine>, "mode": "prune_orphans", "wait": true, "dry_run": …} sur la seule racine configurée. Essai à blanc : compter sans rien retirer est coché par défaut : ne le décochez qu’après avoir relu un essai à blanc. Le résultat (les compteurs renvoyés par OpenViking : scanned_records, would_delete_records en essai à blanc, deleted_records sinon, failed_records, duration_ms et le nombre d’avertissements) s’affiche dans la carte et s’inscrit au journal (openviking_prune status=done dry_run=True … scanned_records=… would_delete_records=…). L’appel attend au plus 5 minutes ; un échec est retenté le lendemain. Un serveur qui ne connaît pas ce mode (HTTP 400 ou 422) est noté une fois comme trop ancien et n’est plus interrogé avant que le réglage soit enregistré de nouveau (après une mise à jour). Par l’API : PUT /api/settings/memory/orphans/schedule avec {"prune": true} et {"prune_dry_run": false} ; seuls les champs envoyés changent.

Les inventaires manuels comme planifiés refusent un périmètre qui dépasse 500 requêtes de listage de dossier, 1 000 entrées dans un même dossier ou 10 000 dossiers d’éléments candidats. Un inventaire incomplet n’est jamais présenté comme « aucun orphelin ». Réduisez le périmètre de la racine quand cette limite est atteinte. Après un rapport planifié, lancez un nouvel essai à blanc manuel et sa confirmation explicite de suppression ; un ancien rapport n’est pas un ordre de suppression.

Journal

Le Journal des nettoyages de la carte liste les derniers nettoyages : ce qui a été supprimé (volume, série ou orphelins, avec son titre), l’état (en attente, en cours, terminé, abandonné), les tentatives et la dernière erreur, et, pour chaque dossier, s’il a été effacé (avec le nombre et les 100 premiers noms de ses documents), s’il était déjà absent ou s’il a été conservé (Détail des dossiers). Réessayer maintenant saute le délai d’attente. Tout ce qui a été effacé peut être réécrit à partir de la base de données (Reconstruire) tant que le volume existe encore.

Les mêmes actions sont disponibles via l’API de l’interface (administrateurs) : GET, PUT, DELETE /api/settings/memory/cleanup, GET /api/settings/memory/cleanups, POST /api/settings/memory/cleanups/{id}/retry, POST /api/settings/memory/orphans/scan (essai à blanc) et POST /api/settings/memory/orphans/clean (corps {"uris": [...]}, issu de l’essai à blanc). POST /api/settings/memory/cleanups/{id}/retire abandonne un nettoyage en attente sans rien supprimer. GET et PUT /api/settings/memory/orphans/schedule lisent ou modifient la recherche à blanc quotidienne ({"enabled": true}). La forme d’origine de la liste /cleanups est conservée ; les compteurs globaux blocked se trouvent dans /cleanup.

Rétention

RETENTION_OUTBOX_SENT_DAYS (7) supprime les lignes de file déjà écrites. Cela n’affecte ni la recherche, vérifiée auprès de la base de données, ni une reconstruction, qui recrée les lignes dont elle a besoin. Voir la rétention des données. Le journal des nettoyages est petit (une ligne par volume ou série supprimé, ou par nettoyage d’orphelins) et il est conservé.