Code/index.py
2025-05-19 07:10:06 +02:00

105 lines
3.9 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
index.py — Indexation « minifiches » SANS découpage
====================================================
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 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 : 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
"""
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 minifiches (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 BGEM3 ---------------------------------
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}")