105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
index.py — Indexation « mini‑fiches » SANS découpage
|
||
====================================================
|
||
|
||
Objectif : chaque fichier (chapitre) devient **un seul** passage, afin de
|
||
préserver l’intégrité des tableaux, listes, etc. — conformément à votre
|
||
organisation manuelle.
|
||
|
||
Caractéristiques :
|
||
• Incrémental : seuls les fichiers nouveaux ou modifiés sont ré‑encodés.
|
||
• Paramètres CLI :
|
||
--root racine des fiches (défaut : Corpus)
|
||
--index nom du fichier idx (défaut : corpus.idx)
|
||
--meta nom du fichier méta (défaut : corpus.meta.json)
|
||
• Extensions prises : .md .markdown .txt
|
||
• Embeddings : BGE‑M3 (FlagEmbedding) en CPU, normalisés L2.
|
||
|
||
Usage :
|
||
python index.py # première indexation (tous les fichiers)
|
||
python index.py # relance instantanée (rien à faire)
|
||
touch Corpus/…/nouveau.md
|
||
python index.py # encode seulement 1 fichier
|
||
"""
|
||
|
||
import argparse, json, os, time
|
||
from pathlib import Path
|
||
|
||
import faiss, numpy as np
|
||
from FlagEmbedding import BGEM3FlagModel
|
||
from rich import print
|
||
|
||
# --------------------- CLI --------------------------------------------------
|
||
parser = argparse.ArgumentParser(description="Indexation incrémentale des mini‑fiches (1 fichier = 1 passage).")
|
||
parser.add_argument("--root", default="Corpus", help="Répertoire racine des fiches")
|
||
parser.add_argument("--index", default="corpus.idx", help="Nom du fichier FAISS")
|
||
parser.add_argument("--meta", default="corpus.meta.json", help="Nom du méta JSON")
|
||
args = parser.parse_args()
|
||
|
||
ROOT = Path(args.root).expanduser()
|
||
INDEX_F = Path(args.index)
|
||
META_F = Path(args.meta)
|
||
EXTS = {".md", ".markdown", ".txt"}
|
||
|
||
print(f"[dim]Racine : {ROOT} | Index : {INDEX_F}[/]")
|
||
|
||
# ------------------------ lire méta existant -------------------------------
|
||
old_meta = []
|
||
old_mtime = {}
|
||
if INDEX_F.exists() and META_F.exists():
|
||
try:
|
||
old_meta = json.load(META_F.open())
|
||
old_mtime = {m["path"]: m["mtime"] for m in old_meta}
|
||
except Exception as e:
|
||
print(f"[yellow]Avertissement : impossible de lire l'ancien méta : {e}. On repart de zéro.[/]")
|
||
old_meta = []
|
||
old_mtime = {}
|
||
|
||
# ------------------------ scanner les fichiers -----------------------------
|
||
files = [fp for fp in ROOT.rglob("*") if fp.suffix.lower() in EXTS]
|
||
files.sort()
|
||
|
||
new_docs, new_meta = [], []
|
||
kept_meta = [] # meta non modifiés
|
||
|
||
for fp in files:
|
||
path_str = str(fp.relative_to(ROOT))
|
||
mtime = int(fp.stat().st_mtime)
|
||
if path_str in old_mtime and old_mtime[path_str] == mtime:
|
||
# déjà indexé, rien à faire
|
||
kept_meta.append(next(m for m in old_meta if m["path"] == path_str))
|
||
continue
|
||
# fichier nouveau ou modifié
|
||
txt = fp.read_text(encoding="utf-8")
|
||
new_docs.append(txt)
|
||
new_meta.append({"path": path_str, "mtime": mtime})
|
||
|
||
print(f"Nouveaux/Modifiés : {len(new_docs)} | Conservés : {len(kept_meta)}")
|
||
if not new_docs and INDEX_F.exists():
|
||
print("Index déjà à jour ✔︎")
|
||
exit(0)
|
||
|
||
# ------------------------ embeddings BGE‑M3 ---------------------------------
|
||
model = BGEM3FlagModel("BAAI/bge-m3", device="cpu")
|
||
emb = model.encode(new_docs)
|
||
if isinstance(emb, dict):
|
||
emb = next(v for v in emb.values() if isinstance(v, np.ndarray))
|
||
emb = emb / np.linalg.norm(emb, axis=1, keepdims=True)
|
||
emb = emb.astype("float32")
|
||
|
||
# ------------------------ mise à jour FAISS --------------------------------
|
||
if INDEX_F.exists():
|
||
idx = faiss.read_index(str(INDEX_F))
|
||
else:
|
||
idx = faiss.IndexFlatIP(emb.shape[1])
|
||
|
||
idx.add(emb)
|
||
faiss.write_index(idx, str(INDEX_F))
|
||
|
||
# ------------------------ enregistrer le nouveau méta ----------------------
|
||
all_meta = kept_meta + new_meta
|
||
json.dump(all_meta, META_F.open("w"), ensure_ascii=False, indent=2)
|
||
|
||
print(f"Index mis à jour ✔︎ | Total passages : {idx.ntotal}")
|