Un système RAG (Retrieval-Augmented Generation) local pour votre coffre Obsidian, tournant nativement en Python sur macOS et utilisant MLX-LM (Apple Silicon) comme moteur IA local et ChromaDB comme base vectorielle.
ObsiRAG vous permet d'interagir avec l'intégralité de votre coffre Obsidian via un chat en langage naturel — le tout en local, sans envoyer vos données dans le cloud.
Exemples de requêtes :
- "Quelles sont mes dernières notes ? Fais une synthèse de cette semaine."
- "Quelles sont les notes où je parle de X ou Y ?"
- "Qu'est-ce que j'ai appris ce mois-ci sur le sujet Z ?"
- 100% local : vos notes ne quittent jamais votre machine
- Coffre en lecture seule : ObsiRAG ne modifie jamais vos notes Obsidian existantes
- Accès complet au coffre : pas de fenêtre contextuelle limitée, l'ensemble du coffre est exploitable
- Insights générés automatiquement : chaque note du coffre donne lieu à des questions perspicaces auxquelles le LLM répond en combinant votre coffre et le web — les réponses sont sauvegardées en Markdown avec provenance et références citées
- Synapses : connexions implicites découvertes entre vos notes par similarité sémantique — des ponts thématiques que vous n'auriez pas forcément tracés vous-même, sauvegardés comme notes dans votre coffre
- Artefacts traçables : les insights et synapses générés indiquent leur provenance (Coffre, Web, ou Coffre et Web) et sont eux-mêmes interrogeables dans le chat
- Déploiement natif macOS : service launchd, environnement Python isolé (venv)
Les captures principales ci-dessous donnent une vue rapide des surfaces produit aujourd'hui exposées par ObsiRAG.
Vue d'exploitation pour vérifier l'état du backend, l'indexation et l'activité de l'auto-learner.
Vue de reprise des fils de discussion avec historique, navigation et recherche locale.
Vue de conversation enrichie avec note principale, sources et provenance.
Vue d'exploration du coffre par connexions, synapses et filtrage.
Vue d'un artefact généré avec question, réponse, tags et provenance.
- docs/architecture.md — architecture actuelle, frontières entre modules, invariants et flux runtime
- docs/conversation-management.md — gestion des conversations, relances, note dominante, garde-fous anti hors-sujet et formatage des réponses
- docs/performances.md — mesures de performances, comparaison MLX/Ollama et recommandations matérielles
- docs/performance-roadmap.md — feuille de route performance sprintable, gates Go/No-Go Rust et KPIs de pilotage
- docs/performance-tracker.md — suivi d'execution hebdomadaire (statuts, mesures, blocages, decisions)
- docs/improvements-report.md — rapport a jour des travaux d'amelioration, validations et impacts produit
Pour standardiser la verification locale apres un lot de changements frontend, backend ou UI heritee :
./scripts/validate_local.shCette commande unique enchaine automatiquement :
- redemarrage controle via
./scripts/post_restart_check.sh, - tests UI cibles (
pytest --no-cov), - verification rapide des logs de demarrage.
Pour lancer la validation exhaustive (suite complete des tests) :
./scripts/validate_local.sh --fullPour une boucle de dev tres rapide (subset critique sans suite complete) :
./scripts/validate_local.sh --smokePour la suite de non-regression ultra-courte (7 tests @pytest.mark.nrt, < 2s) :
./scripts/validate_local.sh --nrt
./scripts/validate_local.sh --nrt --no-restartPour relancer rapidement les validations sans redémarrage (quand seuls les changements code/tests sont concernés) :
./scripts/validate_local.sh --no-restart
./scripts/validate_local.sh --full --no-restart
./scripts/validate_local.sh --smoke --no-restartChaque execution de validate_local.sh produit maintenant des rapports machine-readable dans logs/validation/ :
- un rapport JUnit XML (
*.junit.xml) pour integration CI, - un resume JSON (
*.json) avec mode, statut, duree et code de sortie, - deux pointeurs stables :
latest.junit.xmletlatest.json.
Pour rediriger ces artefacts dans un autre dossier :
./scripts/validate_local.sh --smoke --report-dir /tmp/obsirag-validationEn cas de refactor majeur au-dela de la couche UI, completer ensuite avec la suite complete :
source .venv/bin/activate
pytest --no-covLe runtime macOS peut maintenant fonctionner avec un frontend Expo web statique servi directement par l'API FastAPI, ce qui evite de garder un serveur Expo de developpement resident pour l'usage quotidien.
Pour generer ce build :
./scripts/build_expo_web.shEnsuite ./start.sh detecte automatiquement obsirag-expo/dist/index.html et sert l'interface sur le meme port que l'API.
Pour verifier qu'un export web complet reste demarrable dans un navigateur reel, sans erreur console bloquante et avec disparition correcte du shell de preboot :
cd obsirag-expo
npm run test:web-exportCe smoke test automatise :
npm run web:export,- le service local du repertoire
dist/avec fallback SPA, - l'ouverture de la page dans Chromium headless via Playwright,
- la verification qu'un rendu utile apparait dans
#root, - l'absence d'erreurs
pageerroretconsole.errorpendant le bootstrap, - et la disparition effective du shell
#obsirag-preboot.
Ce test est egalement execute en CI sur les changements touches dans obsirag-expo/.
Le runtime principal Expo + FastAPI peut etre utilise depuis une autre machine du reseau local ou via Tailscale, a condition d'annoncer une URL publique cohérente au client.
Dans .env, verifier au minimum les variables suivantes :
API_PUBLIC_BASE_URL=http://tailscale-host.example.ts.net:8000API_PUBLIC_BASE_URLdoit pointer vers le nom DNS Tailscale, le nom mDNS local ou un host LAN stable de la machine qui heberge ObsiRAG.STREAMLIT_SERVER_ADDRESS=0.0.0.0ne concerne que l'interface legacy optionnelle. Cette variable ne pilote pas le runtime Expo + FastAPI.- Eviter
localhostou127.0.0.1dans la configuration saisie depuis une autre machine : ces adresses designent la machine cliente, pas le Mac qui heberge ObsiRAG.
- Interface Expo web en mode developpement :
http://tailscale-host.example.ts.net:8081 - Interface Expo web exportee et servie par FastAPI :
http://tailscale-host.example.ts.net:8000 - Backend API :
http://tailscale-host.example.ts.net:8000
Dans l'ecran de configuration Expo, les saisies suivantes sont supportees :
tailscale-host.example.ts.net:8000http://tailscale-host.example.ts.net:8000
Un script de diagnostic leger permet de verifier les listeners locaux, l'API, Expo web et l'URL publique annoncee :
./scripts/check_remote_access.sh --host tailscale-host.example.ts.netExemples :
./scripts/check_remote_access.sh --host nom-de-machine.local
./scripts/check_remote_access.sh --host tailscale-host.example.ts.net --api-port 8000 --expo-port 8081Ce script ne remplace pas un test depuis la machine distante, mais il permet de detecter rapidement :
- un bind local seulement,
- une URL publique mal configuree dans
.env, - une API ou un frontend non joignable,
- un decalage entre les ports exposes et ceux annonces au client.
Interface conversationnelle connectée à MLX-LM (inférence locale Apple Silicon, sans serveur externe) ou à Euria selon le provider choisi dans la conversation. Les requêtes sont traitées en combinant récupération sémantique, synthèse par l'IA, enrichissement NER et, quand nécessaire, une recherche web explicite.
Le chat conserve maintenant un contexte conversationnel exploitable pour la récupération et pas seulement pour la génération finale.
- Relances résolues dans le fil : une question courte comme "tu as plus de détail sur les objectifs" ou "et la durée de la mission ?" est rattachée automatiquement au sujet précédent si ce sujet est identifiable dans l'échange
- Note principale : pour les questions mono-sujet, ObsiRAG identifie la note la plus dominante sur le thème et la fait remonter en priorité dans le contexte envoyé au modèle
- Sources plus lisibles : la réponse affiche désormais la note principale au-dessus des sources, et cette note est marquée comme Principale dans la liste détaillée
- Provider par conversation : le composeur Expo permet d'activer
Avec Euriafil par fil sans changer la configuration globale du backend - Badges lisibles : chaque réponse assistant peut afficher son provider effectif (
via MLXouvia Euria) et sa provenance (coffre,web,web + coffre) - Garde-fou anti hors-sujet : si une requête mono-sujet ne retrouve aucun chunk lexicalement fiable, ObsiRAG répond directement "Cette information n'est pas dans ton coffre." au lieu de laisser partir le modèle sur un faux contexte
Quand le coffre ne suffit pas, le backend peut enrichir la réponse avec une recherche web explicite :
- Aperçu de requête : la réponse conserve la requête reformulée, un résumé et la liste des sources web retenues
- Provenance visible : les artefacts et messages distinguent les contenus venant du coffre, du web ou d'un mode hybride
- Fallback Euria documenté : quand Euria est sélectionné, le backend peut d'abord produire une réponse directe ou ancrée dans le coffre, puis basculer vers une recherche web native Euria et une consolidation DDG si le coffre ne suffit pas
- Entités détectées dans la conversation : le backend retourne des
entityContextsenrichis (type, relation avec la réponse, notes liées, image éventuelle, connaissances DDG si disponibles) - Usage côté Expo : ces informations sont exploitées par l'interface mobile/web pour afficher les sources, les entités clés et relancer une recherche ciblée sans quitter la conversation
La détection d'entités du chat ne repose pas sur la seule question utilisateur. Le backend analyse le texte combiné de la question et de la réponse générée, ce qui permet de capter à la fois les entités explicitement demandées et celles réellement mobilisées dans la réponse.
- Validation prioritaire WUDD.ai : les entités reconnues sont d'abord rapprochées de l'index WUDD.ai pour obtenir un nom canonique, un type stable et, quand disponible, une image associée
- Fallback spaCy : si certaines entités ne sont pas trouvées dans WUDD.ai, un fallback spaCy complète la détection pour éviter de perdre les noms saillants présents dans l'échange
- Limitation contrôlée : le backend retient jusqu'à 10 entités par échange, et associe jusqu'à 3 notes liées par entité pour rester lisible côté interface
- Ancrage dans le coffre : chaque entité est rapprochée des notes sources candidates pour retrouver une ligne de preuve, un extrait et la note la plus pertinente quand elle existe
- Explication de relation : après détection, le backend génère une phrase courte expliquant pourquoi l'entité est liée au sujet de la conversation, afin d'éviter une simple liste de noms sans contexte
- Enrichissement UI : l'interface peut ensuite afficher le type de l'entité, son image éventuelle, ses notes liées, son explication de relation et sa provenance dans la réponse
En pratique, cela permet par exemple de faire ressortir dans le chat des personnes, organisations, lieux ou produits cités dans la réponse, puis de les relier directement aux notes du coffre déjà concernées par ces entités.
Les réponses mono-sujet sont désormais structurées en Markdown avec des intertitres courts pour améliorer la lisibilité dans le chat :
### Aperçu de ...### Détails utiles
Les synthèses multi-thèmes conservent leur structure d'étude existante avec plusieurs chapitres.
Le mécanisme de gestion des conversations, des relances et de la note dominante est documenté dans docs/conversation-management.md.
Lorsque la réponse du LLM contient un bloc Mermaid, le chat affiche un bouton de visualisation intégré qui ouvre le diagramme dans un viewer dédié — sans quitter l'interface.
Visualisation interactive du réseau de vos notes sous forme de graphe interactif (rendu Pyvis sur fond sombre). Chaque nœud est une note, chaque arête une connexion.
Ce qui est affiché :
- Nœuds : chaque note du coffre est un nœud coloré selon son dossier d'appartenance (palette de 8 couleurs distinctes). La taille du nœud est proportionnelle à son nombre de connexions — les notes les plus référencées apparaissent plus grandes
- Arêtes (connexions) : les
[[wikilinks]]entre notes forment les arêtes du graphe - Tooltip au survol : titre, date de modification, tags et deux boutons d'action — ouvrir la note dans le visualiseur intégré ou directement dans Obsidian
- Métriques en en-tête : nombre de nœuds, connexions, densité du graphe et nombre de notes filtrées
- Top 5 nœuds les plus connectés : liste sous le graphe avec leur score de centralité, avec bouton d'ouverture directe
Filtres disponibles (barre latérale) :
- Recherche texte : filtrage du graphe par texte libre sur les titres et chemins de notes
- Par dossier (tous ou sélection multiple)
- Par tag Obsidian (sélection multiple)
- Par type de note (notes utilisateur, insights, synapses, synthèses, conversations...)
- Par récence pour concentrer l'exploration sur les notes les plus récemment modifiées
- Sélecteur de note alphabétique pour ouvrir directement une note dans le visualiseur
Dans l'interface Expo, le module graphe expose aussi une recherche dans la liste de notes, des blocs spotlight et notes récentes, ainsi que des résumés par dossier, tag et type pour accélérer l'exploration.
Le graphe est mis en cache 5 minutes et recalculé à la demande via le bouton 🔄. Il est également exporté en JSON (data/graph/knowledge_graph.json) pour un usage externe éventuel.
Chaque note du coffre est consultable dans un visualiseur Markdown intégré, accessible depuis :
- Le graphe Cerveau — bouton Ouvrir dans ObsiRAG au survol d'un nœud, ou via le top 5 des nœuds les plus connectés
- Les résultats du chat — bouton d'ouverture directe dans un message de réponse
- La page Note directement — sélecteur alphabétique en barre latérale
Ce qui est affiché :
- Rendu Markdown complet (titres, listes, code, callouts Obsidian…)
[[Wikilinks]]cliquables : chaque lien interne navigue vers la note cible dans le même visualiseur- Rétroliens : toutes les notes du coffre qui référencent la note affichée
- Tags et métadonnées du frontmatter YAML
La liste de sélection est triée par ordre alphabétique. En cas de doublons de titre, le chemin complet est affiché pour distinguer les notes.
Un processus léger tourne en arrière-plan et :
- Détecte les notes récemment modifiées dans le coffre
- Détermine le champ sémantique de chaque note (domaine, concepts-clés, angle traité) pour ancrer la génération dans le bon univers lexical
- Génère des questions perspicaces strictement alignées avec ce champ sémantique
- Répond via RAG sur le coffre — et complète avec le web si la réponse est insuffisante
- N'utilise que des sources fiables (Wikipédia, presse de référence, institutions, revues scientifiques…)
- Sauvegarde les insights en Markdown dans
obsirag/insights/avec indication de provenance et références citées - Génère une synthèse hebdomadaire le dimanche dans
obsirag/synthesis/— si le Mac était en veille au moment prévu, la synthèse est lancée automatiquement dès le réveil
Les artefacts générés sont indexés et deviennent eux-mêmes interrogeables dans le chat.
Le système est conçu pour fonctionner sans pénaliser l'utilisation normale de la machine : les appels LLM sont espacés (pause configurable entre chaque note et chaque question), le nombre de notes traitées par cycle est limité, et tout tourne dans un thread d'arrière-plan isolé. La machine reste pleinement disponible pendant le traitement.
Par défaut, le backend API n'autorise plus le chargement MLX en tâche de fond. Pour réactiver l'auto-learner complet, définir
AUTOLEARN_ALLOW_BACKGROUND_LLM=truedans.env.
Pour isoler l'auto-learner du process API, lancer plutôt le worker dédié :
./scripts/run_autolearn_worker.shAvec ce mode, un crash MLX éventuel du worker n'arrête plus l'API FastAPI.
Gestion du cycle de vie du modèle LLM : l'auto-learner charge le modèle MLX au début de chaque cycle et le décharge à la fin si l'interface web est inactive — libérant ainsi la mémoire GPU/Metal entre les cycles. Si l'interface est ouverte, le modèle reste chargé pour répondre immédiatement aux requêtes chat.
| # | Traitement | Déclenchement | Description |
|---|---|---|---|
| 1 | Bulk initial | Une seule fois au 1er démarrage (après 120 s de délai) | Traite toutes les notes non-traitées (max AUTOLEARN_BULK_MAX_NOTES, défaut 20) : génère un insight Q&A et renomme la note avec un titre en français |
| 2 | Cycle autolearn | Toutes les AUTOLEARN_INTERVAL_MINUTES min (défaut 60), 5 min après le démarrage, uniquement entre AUTOLEARN_ACTIVE_HOUR_START et AUTOLEARN_ACTIVE_HOUR_END (défaut 8h–22h) |
Pass 1 : jusqu'à AUTOLEARN_MAX_NOTES_PER_RUN notes récentes (modifiées dans les 24 h). Pass 2 : jusqu'à AUTOLEARN_FULLSCAN_PER_RUN notes jamais traitées (full-scan) |
| 3 | Découverte de synapses | À la fin de chaque cycle autolearn | Trouve AUTOLEARN_SYNAPSE_PER_RUN paires de notes sémantiquement proches sans lien existant et génère une note de connexion dans obsirag/synapses/ |
| 4 | Synthèse hebdomadaire | Chaque dimanche à 20 h UTC (avec rattrapage si le Mac était en veille) | Résume les notes modifiées dans la semaine et crée une note dans obsirag/synthesis/ |
| 5 | Watcher de coffre | En temps réel (watchdog filesystem) | Détecte les créations / modifications / suppressions / renommages de fichiers .md et re-indexe dans ChromaDB avec un debounce |
Avant de générer des questions, l'auto-learner extrait le champ sémantique de la note :
Domaine: [domaine principal] | Concepts: [concept1, concept2, concept3] | Angle: [angle spécifique]
Ce champ est injecté comme contrainte explicite dans le prompt de génération, garantissant que les questions — et donc les insights produits — restent dans le même univers thématique que la note source. Une note sur la finance comportementale génère des questions sur les biais cognitifs et non sur un sujet adjacent que le LLM pourrait dériver.
Chaque insight généré est enrichi avec des entités nommées validées (personnes, organisations, pays, produits) issues de la liste officielle WUDD.ai. Le processus :
- Extrait les entités candidates par analyse spaCy du texte Q&A
- Valide chaque entité contre la liste officielle WUDD.ai (top 5 000 entités, triées par fréquence de mention) — les entités non reconnues sont ignorées
- Génère les tags Obsidian (
personne/,org/,lieu/,produit/…) en utilisant le nom canonique officiel - Insère une galerie d'images (table Markdown) avec la photo/logo de l'entité principale par type (PERSON, ORG, GPE, PRODUCT), depuis le cache Wikimedia de WUDD.ai
- Injecte
location: [lat, lng]dans le frontmatter YAML pour la géolocalisation Obsidian Map View (coordonnées Wikipedia)
Dépendance externe : WUDD.ai doit être accessible sur
WUDDAI_ENTITIES_URL(configurable dans.env). En cas d'indisponibilité, l'extraction spaCy seule est utilisée en fallback — les insights sont créés mais sans validation officielle. La liste est mise en cache localement pendant 24h.
Pour migrer les insights existants (tags + géolocalisation + galeries) :
.venv/bin/python scripts/migrate_insight_tags.py --dry-run # simulation
.venv/bin/python scripts/migrate_insight_tags.py # applicationPour renommer en batch les insights/synapses/syntheses selon un titre court généré par le LLM :
# Prévisualisation sans modification
.venv/bin/python scripts/rename_insights.py --dry-run
# Renommage avec LLM (tous les dossiers, pause 2 s entre appels)
.venv/bin/python scripts/rename_insights.py --sleep 2
# Cibler un seul dossier
.venv/bin/python scripts/rename_insights.py --dir insights
# Mode rapide sans LLM (retire uniquement le suffixe _YYYYMMDD)
.venv/bin/python scripts/rename_insights.py --no-llmLe script :
- Saute le frontmatter pour lire le corps de la note (évite que les tags YAML consomment le contexte LLM)
- Propage
[[ancien_titre]]→[[nouveau_titre]]dans tout le vault - Met à jour
synapse_index.json(pairesfp_a|||fp_b) - Re-indexe dans ChromaDB les fichiers modifiés
À tout moment, le bouton 💾 Sauvegarder cette conversation (affiché sous le chat dès qu'un échange existe) enregistre l'intégralité de la conversation en cours sous forme de note Markdown dans votre coffre.
- Le titre du fichier est généré par le LLM à partir des questions posées (4 à 8 mots, en français), selon la même logique de nommage que les insights : slug normalisé + horodatage
- Le fichier est créé dans
obsirag/conversations/YYYY-MM/et est immédiatement visible dans Obsidian - Le frontmatter contient les tags
conversationetobsirag - Chaque échange (question / réponse) est mis en forme en Markdown navigable
- La note est indexée par ObsiRAG au prochain cycle : les conversations passées deviennent elles-mêmes interrogeables dans le chat
Exemple de chemin : obsirag/conversations/2026-04/Connexions-entre-notes-ML_20260409_1423.md
Consultation des artefacts, synapses et synthèses générés, avec :
- Progression & estimation du temps restant : widget affichant le nombre de notes traitées, restantes, et une estimation de la durée nécessaire pour compléter le traitement — avec heure du prochain cycle en heure locale
- Historique des requêtes posées dans le chat
Le détail d'un artefact Insight affiche ensuite les tags, la provenance, les entités clés et le contenu question/réponse enrichi.
Toutes les notes ne donnent pas lieu à un insight. Voici les cas où une note est ignorée :
| Condition | Raison | Ce qui se passe |
|---|---|---|
| Note trop courte / mal indexée | Aucun chunk trouvé dans ChromaDB | La note n'est pas dans l'index vectoriel ; elle sera ignorée jusqu'à la prochaine réindexation |
| Aucune question générée | Le LLM n'a pas suivi le format attendu, ou le contenu est trop pauvre pour formuler une question | L'étape de génération de questions est sautée |
| Toutes les réponses QA ont échoué | Erreur LLM (contexte dépassé, modèle non disponible…) pour les 3 questions | L'insight n'est pas sauvegardé |
| Note mal parsée (YAML invalide) | Le frontmatter Obsidian contient des caractères illégaux ou est mal formé | La note n'est pas indexée du tout |
Une note génère un insight lorsque :
- Elle est présente et correctement indexée dans ChromaDB (au moins un chunk)
- Le LLM génère au moins une question orientée vers des connaissances externes pertinentes au domaine
- Au moins une réponse QA aboutit — soit via web (DDG + synthèse LLM), soit en fallback via RAG
- La réponse n'est pas détectée comme vide ou générique (filtres anti-réponse-creuse)
L'insight est sauvegardé dans obsirag/insights/YYYY-MM/ avec :
- Les questions générées et leurs réponses
- La provenance (Web, Coffre, ou Web+Coffre)
- Une synthèse des sources web lorsque des URLs ont été récupérées et analysées
Astuce : Si une note attendue ne produit pas d'insight, vérifiez qu'elle est bien indexée (bouton "Re-indexer le coffre" dans le chat) et que le modèle MLX est correctement chargé (page Paramètres).
En neurologie, une synapse est la jonction entre deux neurones : elle transmet un signal d'un neurone à l'autre, créant une connexion qui n'existait pas de façon anatomique directe. Le terme est utilisé ici par analogie : deux notes de votre coffre sont des "neurones", et ObsiRAG découvre une connexion implicite entre elles — une connexion qui n'a jamais été formalisée par un wikilink.
- Détection de paires : à chaque cycle, ObsiRAG tire aléatoirement des notes du coffre et cherche dans ChromaDB les notes sémantiquement proches (similarité cosinus au-dessus d'un seuil configurable), en excluant celles qui ont déjà un wikilink entre elles
- Mémoire des paires : chaque paire
Note A ↔ Note Best mémorisée dansdata/synapse_index.json— elle ne sera jamais retraitée deux fois - Génération du texte : le LLM reçoit un extrait des deux notes (600 premiers caractères chacune) et rédige une explication complète de la connexion implicite qui les unit, ainsi qu'une question à approfondir
- Fichier résultat : une note Markdown est créée dans
obsirag/synapses/nommée{NoteA}__{NoteB}_{date}.md, avec le score de similarité, la connexion expliquée et les extraits des deux notes sources
Les fichiers synapses contiennent des wikilinks vers chacune des deux notes sources, ce qui les intègre automatiquement dans le graphe de connaissances — révélant visuellement des ponts thématiques entre des notes que vous n'auriez peut-être pas rapprochées vous-même.
Le fichier est nommé automatiquement à partir du titre de la note source :
obsirag/insights/YYYY-MM/{titre_note}_{YYYYMMDD}.md
- Les caractères spéciaux sont supprimés, les espaces remplacés par
_, le tout tronqué à 60 caractères - La date du jour (heure locale) est ajoutée en suffixe
- Les fichiers sont regroupés par mois dans un sous-dossier
YYYY-MM/
Exemple : une note intitulée "La vitesse des LLMs", traitée le 7 avril 2026, produit :
obsirag/insights/2026-04/La_vitesse_des_LLMs_20260407.md
À chaque cycle, avant de créer un nouveau fichier, ObsiRAG cherche un insight existant pouvant être complété, selon deux critères :
- Même note source : le nom du fichier correspond au titre de la note (priorité maximale)
- Même thématique : au moins 2 tags entités NER en commun dans le frontmatter
Si un fichier correspondant est trouvé, les nouveaux Q&A sont ajoutés à la suite (numérotation continue ## Question N), la date "Mise à jour le" est rafraîchie, les tags NER sont fusionnés et la galerie d'images mise à jour. Sinon, un nouveau fichier est créé.
--- ← Frontmatter YAML
tags:
- insight
- {tags de la note source}
- {entités NER : personne/, org/, lieu/…}
location: [lat, lng] ← optionnel, si entité géolocalisable
---
# Insights : {titre de la note}
**Note source :** [[lien wikilink]]
**Générée le / Mise à jour le :** {date heure locale}
**Provenance :** Web | Coffre | Coffre et Web
## Entités clés ← galerie d'images des entités principales (WUDD.ai)
## Question 1
> {question générée}
{réponse LLM}
*Provenance / Notes consultées / Références web*
## Question 2 …
## Synthèse des sources web ← si des pages web ont été analysées
Une note Obsidian peut être longue et couvrir plusieurs sujets. Pour permettre une recherche précise, chaque note est découpée en morceaux (chunks) d'environ 300 mots, avec un léger chevauchement entre chaque morceau pour préserver le contexte aux jonctions.
Le découpage respecte la structure de la note : d'abord par section (## Titre), puis par paragraphe, puis par mots si nécessaire. Chaque chunk hérite des métadonnées de la note (titre, tags, dates, wikilinks, entités NER…).
Un chunk est un fragment de texte extrait d'une note, avec ses métadonnées associées. Il contient le texte brut, un identifiant unique {file_hash}_{index}, et toutes les métadonnées de la note source (titre, section, tags, dates, wikilinks, entités NER…). C'est l'unité atomique de recherche dans ChromaDB.
Le chunking est implémenté en Python pur — aucune API externe, aucune dépendance réseau. C'est du découpage de chaînes de caractères (split(), split("\n\n")) : rapide, déterministe, 100% local.
Note Obsidian
│
▼
[Parser] → liste de sections (chaque ## titre + son contenu)
│
▼
Pour chaque section :
│
├─ Section courte (≤ chunk_size mots) ?
│ └─→ 1 chunk direct
│
├─ Section longue avec plusieurs paragraphes (\n\n) ?
│ └─→ Fusion de paragraphes :
│ Accumuler les paragraphes un par un.
│ Quand le total dépasse chunk_size :
│ → fermer le chunk
│ → repartir avec les N derniers mots (overlap)
│ + le paragraphe suivant
│
└─ Section longue sans paragraphes (un seul bloc) ?
└─→ Fenêtre glissante :
Fenêtre de chunk_size mots,
avance de (chunk_size - overlap) mots à chaque pas
│
▼
Chaque chunk reçoit :
- le texte
- chunk_id = {file_hash}_{index}
- toutes les métadonnées de la note (titre, tags, dates, wikilinks, NER…)
À chaque rupture de chunk, les overlap derniers mots du chunk précédent sont répétés en tête du suivant. Cela évite de couper une phrase en deux et de perdre le fil du contexte lors de la recherche sémantique.
| Paramètre | Rôle |
|---|---|
chunk_size_words |
taille max d'un chunk en mots (~300) |
chunk_overlap_words |
chevauchement entre chunks (~30) |
Chaque chunk est transformé en un vecteur numérique — une liste de ~384 nombres — par le modèle paraphrase-multilingual-MiniLM-L12-v2 via sentence-transformers (calculs en local, CPU). Ce vecteur encode le sens du texte : deux passages sémantiquement proches produisent des vecteurs proches dans l'espace mathématique, même s'ils n'ont aucun mot en commun.
Les vecteurs et leurs métadonnées sont stockés dans ChromaDB, une base vectorielle locale. L'indexation est incrémentale : seules les notes nouvelles ou modifiées sont retraitées.
Quand vous posez une question dans le chat :
- La question est elle-même vectorisée
- ChromaDB identifie les chunks dont le vecteur est le plus proche → similarité cosinus
- Ces chunks (vos notes) sont injectés comme contexte dans le prompt envoyé à MLX-LM
- Le modèle génère une réponse ancrée dans votre coffre, pas dans ses seules connaissances pré-entraînées
C'est ce mécanisme qui permet de retrouver une note sur "les effets des écrans sur le sommeil" en posant la question "comment la lumière bleue affecte-t-elle le repos ?" — sans que ces mots exacts apparaissent dans la note.
- Index vectoriel incrémental (mise à jour uniquement des notes nouvelles/modifiées)
- Chunking adaptatif des notes
- Métadonnées légères pour le filtrage rapide avant recherche sémantique
ObsiRAG est conçu pour fonctionner en tâche de fond sur un MacBook Air M5 16 Go — la machine de référence du projet. L'ensemble du traitement (indexation, génération d'insights, synapses) tourne de façon transparente sans perturber l'utilisation normale : navigation web, rédaction dans Obsidian, appels visio.
Temps d'amorçage initial : pour un coffre d'environ 200 notes, comptez 1 à 2 jours pour que l'ensemble des insights soit généré.
Ce délai est intentionnel et s'explique par la mécanique du cycle :
- L'auto-learner se réveille toutes les 15 minutes et traite au maximum 3 notes nouvelles par cycle (full-scan)
- Le traitement complet d'une note avec MLX-LM (génération des questions + réponses + recherche web) prend de 2 à 5 minutes selon la complexité du contenu
- Résultat : 200 notes ÷ 3 notes/cycle × 15 min/cycle ≈ 17 heures de fonctionnement actif
Ces pauses sont délibérées — elles garantissent que le modèle MLX reste disponible pour le chat en temps réel. Les paramètres AUTOLEARN_FULLSCAN_PER_RUN et AUTOLEARN_INTERVAL_MINUTES dans .env permettent d'accélérer l'amorçage si vous le souhaitez.
Sur MacBook : ObsiRAG se remet automatiquement en marche à la sortie de veille (service launchd) — aucune intervention manuelle n'est nécessaire. L'auto-learner reprend son cycle là où il s'était arrêté, de façon totalement transparente.
Une fois l'amorçage terminé, seules les notes nouvelles ou récemment modifiées sont retraitées à chaque cycle — le fonctionnement courant est quasi-instantané.
Pour les détails de débit, temps de traitement par note et choix du modèle : voir docs/performances.md.
ObsiRAG utilise MLX-LM pour la génération locale, sans serveur externe. Le modèle tourne directement dans le processus Python, exploitant le GPU unifié Apple Silicon via le framework MLX.
Le modèle est géré dynamiquement pour minimiser l'empreinte mémoire :
| Événement | Comportement |
|---|---|
| Ouverture de l'interface web | Chargement immédiat du modèle (~2 s sur M5) |
| Utilisation du chat | Modèle maintenu en mémoire tant que l'UI est active |
| Inactivité UI > 2 min | Déchargement automatique (watchdog toutes les 30 s) |
| Début d'un cycle auto-learner | Chargement automatique si absent |
| Fin d'un cycle auto-learner | Déchargement si l'UI est inactive |
| Appel LLM sans modèle chargé | Chargement à la volée transparent (try-load automatique) |
Ce mécanisme garantit que le modèle ne consomme pas de mémoire GPU/Metal inutilement entre les sessions, tout en restant disponible instantanément dès qu'une requête arrive.
| Usage | Opération | Modèle configuré |
|---|---|---|
| Chat / RAG | Réponses aux questions sur le coffre | MLX_CHAT_MODEL (ex. mlx-community/Qwen2.5-7B-Instruct-4bit) |
| Génération de questions | Auto-learner — questions ancrées dans le champ sémantique | Même modèle que le chat |
| Synapses & synthèses | Connexions implicites, synthèse hebdomadaire | Même modèle que le chat |
| Embeddings | Vectorisation des notes et des requêtes | sentence-transformers local — paraphrase-multilingual-MiniLM-L12-v2 (384 dimensions) |
Un seul modèle de chat suffit pour tout. Configurer
MLX_CHAT_MODELdans.envavec le nom HuggingFace de la formemlx-community/<modele>-4bit. Le modèle est téléchargé automatiquement au premier démarrage.
Les modèles de la communauté mlx-community sur HuggingFace sont déjà convertis et quantizés pour MLX — aucune conversion manuelle n'est nécessaire.
| Opération | Ollama (avant) | MLX-LM (actuel) | Gain |
|---|---|---|---|
| Génération (tokens/s) | ~13 tok/s | ~27 tok/s | ×2 |
| Chargement du modèle | 30–60 s | ~2 s | ×20 |
| Dépendance serveur | Ollama daemon requis | Aucune | ✅ |
Mesures indicatives réalisées localement sur Mac M5 16 Go avec le pipeline réel d'ObsiRAG.
| Modèle | Prompt | TTFT | Débit | Temps total | Observation |
|---|---|---|---|---|---|
mlx-community/Qwen2.5-7B-Instruct-4bit |
1 requête courte | 2,137 s | 27,91 tok/s | 29,21 s | Meilleur équilibre |
mlx-community/Meta-Llama-3.1-8B-Instruct-4bit |
1 requête courte | 2,234 s | 25,53 tok/s | 47,72 s | Plus lent au premier chargement |
Mesure sur 3 prompts réels après un prompt d'échauffement distinct pour éviter le biais du cache de réponse.
| Modèle | TTFT moyen | Débit moyen | Temps total moyen | Tokens moyens/réponse | Lecture |
|---|---|---|---|---|---|
mlx-community/Qwen2.5-7B-Instruct-4bit |
2,277 s | 27,66 tok/s | 20,04 s | ~525 | Réponses plus riches et plus longues |
mlx-community/Meta-Llama-3.1-8B-Instruct-4bit |
2,425 s | 25,61 tok/s | 15,04 s | ~311 | Réponses plus courtes, pas plus rapides en débit |
Conclusion pratique sur cette machine : Qwen 2.5 7B reste le meilleur défaut pour ObsiRAG. Llama 3.1 8B n'a pas montré de gain qualitatif net en français et son avantage apparent à chaud vient surtout de réponses plus concises.
Pour reproduire ce test ou comparer d'autres candidats MLX :
python scripts/benchmark_model_shortlist.py
# Exemple avec une shortlist explicite
python scripts/benchmark_model_shortlist.py \
--model mlx-community/Qwen2.5-7B-Instruct-4bit \
--model google/gemma-4-e4b \
--model mlx-community/Meta-Llama-3.1-8B-Instruct-4bit| Composant | Technologie |
|---|---|
| Langage | Python 3.12 |
| Déploiement | macOS natif (launchd + Python venv) |
| IA | MLX-LM (Apple Silicon, sans serveur) |
| Base vectorielle | ChromaDB |
| Embeddings | sentence-transformers — paraphrase-multilingual-MiniLM-L12-v2 (384 dim, CPU) |
| Interface | Expo web + FastAPI |
| Graphe | NetworkX + Pyvis |
| Recherche web | DuckDuckGo Search (sources fiables) |
| Entités NER | spaCy + validation WUDD.ai (top 5 000 entités officielles) |
| Géolocalisation | Wikipedia Coordinates API → frontmatter location: (Obsidian Map View) |
| Coffre | Obsidian (lecture seule) |
| Artefacts générés | obsirag/insights/, obsirag/synthesis/, obsirag/synapses/, obsirag/conversations/ |
Paramètre .env |
Valeur par défaut | Rôle |
|---|---|---|
AUTOLEARN_ALLOW_BACKGROUND_LLM |
false | Autorise explicitement le chargement MLX par l'auto-learner en tâche de fond. À activer seulement si ce runtime est stable sur votre machine. |
AUTOLEARN_INTERVAL_MINUTES |
15 min | Fréquence du cycle — l'auto-learner se réveille toutes les 15 minutes |
AUTOLEARN_LOOKBACK_HOURS |
24 h | Fenêtre de détection — seules les notes modifiées dans les dernières 24h sont candidates |
AUTOLEARN_MIN_REPROCESS_DAYS |
7 jours | Délai de grâce — une note déjà traitée ne sera pas retraitée avant 7 jours |
Le premier cycle démarre 5 minutes après le démarrage de l'application, pour laisser le temps au modèle MLX de se charger.
Ces trois paramètres permettent d'adapter le comportement selon l'usage : un intervalle plus court (ex. 30 min) pour un coffre très actif, un lookback plus large (ex. 48h) pour rattraper des notes modifiées en dehors des heures habituelles, et un
MIN_REPROCESS_DAYSplus court si vous souhaitez qu'une note soit ré-enrichie plus fréquemment.
# Cloner le dépôt
git clone https://github.com/PatrickOstertagCH/obsirag.git
cd obsirag
# Configurer l'environnement
cp .env.example .env
# Éditer .env : renseigner VAULT_PATH, MLX_CHAT_MODEL, etc.
# Si vous activez Euria pour le chat : EURIA_URL et EURIA_BEARER
# Pour exposer l'interface legacy optionnelle sur le reseau : STREAMLIT_SERVER_ADDRESS=0.0.0.0
# Installer les dépendances Python et configurer le service
./setup.sh
# Démarrer l'application
./start.shLes surfaces suivantes sont alors disponibles :
- auto-learner ObsiRAG via
launchd(verifie et maintenu actif) - API backend Expo : http://localhost:8000
- interface Expo web : http://localhost:8081
Variables utiles pour Euria dans .env :
EURIA_URL=https://votre-endpoint-euria.example/api/chat
EURIA_BEARER=secret-tokenCompatibilite installation macOS : install_service.sh accepte aussi les alias URL et bearer et les propage au service API et au worker auto-learner.
Le modèle MLX est téléchargé automatiquement depuis HuggingFace au premier démarrage (~4 Go pour
Qwen2.5-7B-Instruct-4bit).
./stop.sh arrete l'API backend Expo et l'interface Expo web, mais laisse l'auto-learner launchd actif.
Pour afficher rapidement leur etat :
./status.shPour installer l'auto-learner comme service macOS persistant (démarrage automatique au login) :
./install_service.shL'auto-learner doit rester un service launchd persistant. ./install_service.sh installe ce worker en lancement automatique, ./start.sh vérifie qu'il est bien charge et relance seulement l'API backend Expo et l'interface Expo web, et ./stop.sh n'arrête pas l'auto-learner.
Pour exposer l'interface Expo web sur le reseau de la machine :
echo 'EXPO_WEB_PORT=8081' >> .env
./stop.sh
./start.shVous pourrez alors acceder a l'interface Expo web via http://IP_DE_LA_MACHINE:8081.
Projet actif — développé de façon créative et itérative avec Claude Code et GitHub Copilot
Le dépôt est public. Contributions et idées bienvenues.









