Update index.py

This commit is contained in:
Stéphan Peccini 2025-05-19 07:40:57 +02:00
parent 2c4931bdfe
commit e26fc3e20d

122
index.py
View File

@ -1,86 +1,111 @@
#!/usr/bin/env python3
"""
index.py Indexation « minifiches » SANS découpage
====================================================
index.py indexation hybride des minifiches
===========================================
Objectif : chaque fichier (chapitre) devient **un seul** passage, afin de
préserver lintégrité des tableaux, listes, etc. conformément à votre
organisation manuelle.
Caractéristiques :
Incrémental : seuls les fichiers nouveaux ou modifiés sont 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
1 fichier = 1 passage **si** le fichier WORD_LIMIT mots (par défaut : 600).
Audelà (rare : fiche ICS, ISG, etc.), on découpe en blocs ~CHUNK mots
avec chevauchement OVERLAP pour isoler les tableaux et valeurs numériques.
Incrémental : encode uniquement les fichiers nouveaux ou modifiés.
Embeddings : BGEM3 (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
python index.py --root Corpus # première construction
python index.py # relance rapide (0 s si rien)
Arguments :
--root dossier des fiches (déf. Corpus)
--index nom du fichier FAISS (déf. corpus.idx)
--meta fichier méta JSON (déf. corpus.meta.json)
--word WORD_LIMIT (déf. 600)
--chunk CHUNK (déf. 350)
"""
import argparse, json, os, time
import argparse, json, re, sys
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 minifiches (1 fichier = 1 passage).")
parser = argparse.ArgumentParser(description="Indexation hybride : 1 passage par fiche courte, découpe douce pour les longues.")
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")
parser.add_argument("--word", type=int, default=600, help="WORD_LIMIT : audelà on découpe (mots)")
parser.add_argument("--chunk", type=int, default=350, help="Taille des chunks quand on découpe (mots)")
args = parser.parse_args()
ROOT = Path(args.root).expanduser()
ROOT = Path(args.root)
INDEX_F = Path(args.index)
META_F = Path(args.meta)
WORD_LIMIT= args.word
CHUNK = args.chunk
OVERLAP = 50
EXTS = {".md", ".markdown", ".txt"}
print(f"[dim]Racine : {ROOT} | Index : {INDEX_F}[/]")
# ------------------------ lire méta existant -------------------------------
old_meta = []
old_mtime = {}
# ---------------- split helper --------------------------------------------
def split_long(text: str):
"""Découpe douce : blocs ~CHUNK mots, préserve tableaux."""
sentences = re.split(r"(?<=[.!?])\s+", text)
chunks, buf = [], []
for s in sentences:
if "|" in s or re.fullmatch(r"\s*-{3,}\s*", s):
if buf:
chunks.append(" ".join(buf))
buf = []
chunks.append(s)
continue
buf.append(s)
if len(" ".join(buf).split()) >= CHUNK:
chunks.append(" ".join(buf))
buf = buf[-OVERLAP:]
if buf:
chunks.append(" ".join(buf))
return chunks
# ------------------------ lire méta existant ------------------------------
old_meta = {}
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}
for m in json.load(META_F.open()):
old_meta[m["path"]] = m
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 = {}
print(f"[yellow]Avertissement : méta illisible ({e}), reconstruction complète.[/]")
old_meta = {}
# ------------------------ scanner les fichiers -----------------------------
# ------------------------ 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
new_docs, new_meta, kept_meta = [], [], []
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))
rel = str(fp.relative_to(ROOT))
mtime = int(fp.stat().st_mtime)
prev = old_meta.get(rel)
if prev and prev["mtime"] == mtime:
kept_meta.append(prev)
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():
txt = fp.read_text(encoding="utf-8")
words = len(txt.split())
if words <= WORD_LIMIT:
new_docs.append(txt)
new_meta.append({"path": rel, "part": 0, "mtime": mtime})
else:
for i, chunk in enumerate(split_long(txt)):
new_docs.append(chunk)
new_meta.append({"path": rel, "part": i, "mtime": mtime})
print(f"Nouveaux/Modifiés : {len(new_meta)} | Conservés : {len(kept_meta)}")
if not new_meta and INDEX_F.exists():
print("Index déjà à jour ✔︎")
exit(0)
sys.exit(0)
# ------------------------ embeddings BGEM3 ---------------------------------
# ------------------------ embeddings --------------------------------------
model = BGEM3FlagModel("BAAI/bge-m3", device="cpu")
emb = model.encode(new_docs)
if isinstance(emb, dict):
@ -88,7 +113,7 @@ if isinstance(emb, dict):
emb = emb / np.linalg.norm(emb, axis=1, keepdims=True)
emb = emb.astype("float32")
# ------------------------ mise à jour FAISS --------------------------------
# ------------------------ FAISS update ------------------------------------
if INDEX_F.exists():
idx = faiss.read_index(str(INDEX_F))
else:
@ -97,8 +122,7 @@ else:
idx.add(emb)
faiss.write_index(idx, str(INDEX_F))
# ------------------------ enregistrer le nouveau méta ----------------------
# ------------------------ save meta ---------------------------------------
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}")
print(f"Index mis à jour ✔︎ | Total passages : {idx.ntotal}")