recherche_semantique.py — RAG léger pour les spécialités

Bienvenue sur CFTPfr.fr Forums recherche_semantique.py — RAG léger pour les spécialités

  • Ce sujet est vide.
Affichage de 1 message (sur 1 au total)
  • Auteur
    Messages
  • #260
    Mario Da Conceicao
    Maître des clés

    Nouveau composant du 01/08/2026 — recherche sémantique (par sens) pour remplacer la recherche par mot-clé simple de gestionnaire_specialites.py. Basé sur une recherche des meilleures pratiques actuelles (RAG léger, sans dépendance lourde type base de données vectorielle externe — volontairement gardé simple et autonome pour rester cohérent avec un système minimal).

    Principe

    Chaque paragraphe d’un document de spécialité est transformé en une "empreinte numérique" qui capture son sens (via un petit modèle d’embeddings, ex: nomic-embed-text, ~140M paramètres, compatible llama.cpp/GGUF — assez léger pour tourner à côté du modèle principal). Une question posée à MIRA est transformée de la même façon, puis on cherche les passages dont l’empreinte est la plus proche — donc par SENS, pas par mots identiques.

    import json
    from pathlib import Path
    import math
    
    class RechercheSemantique:
        """
        Recherche par sens (RAG léger) pour les spécialités de MIRA.
        Remplace la recherche par mot-clé simple par une vraie compréhension
        du sens de la question, sans dépendance lourde (pas de base de données
        vectorielle externe — un simple fichier JSON suffit à notre échelle).
        """
    
        def __init__(self, moteur_embeddings):
            """
            moteur_embeddings : objet exposant une méthode encoder(texte) -> vecteur,
            fourni par le moteur llama.cpp une fois configuré avec un modèle
            d'embeddings léger (ex: nomic-embed-text).
            """
            self.moteur = moteur_embeddings
    
        def _cosinus(self, v1, v2):
            """Mesure à quel point deux empreintes de sens se ressemblent (0 à 1)."""
            produit = sum(a * b for a, b in zip(v1, v2))
            norme1 = math.sqrt(sum(a * a for a in v1))
            norme2 = math.sqrt(sum(b * b for b in v2))
            if norme1 == 0 or norme2 == 0:
                return 0
            return produit / (norme1 * norme2)
    
        def indexer_documents(self, dossier_documents, fichier_index):
            """
            Découpe chaque document d'une spécialité en paragraphes, calcule leur
            empreinte de sens, et sauvegarde l'index. À relancer seulement quand
            un document change (pas à chaque question).
            """
            index = []
            for fichier in Path(dossier_documents).glob("*.txt"):
                contenu = fichier.read_text(encoding="utf-8", errors="replace")
                paragraphes = [p.strip() for p in contenu.split("\n\n") if p.strip()]
                for paragraphe in paragraphes:
                    vecteur = self.moteur.encoder(paragraphe)
                    index.append({
                        "fichier": fichier.name,
                        "texte": paragraphe,
                        "vecteur": vecteur
                    })
            with open(fichier_index, "w", encoding="utf-8") as f:
                json.dump(index, f)
            return len(index)
    
        def chercher(self, question, fichier_index, top_n=3):
            """
            Trouve les passages les plus proches du SENS de la question posée,
            pas seulement ceux qui contiennent les mêmes mots.
            """
            with open(fichier_index, "r", encoding="utf-8") as f:
                index = json.load(f)
    
            vecteur_question = self.moteur.encoder(question)
            resultats = []
            for entree in index:
                score = self._cosinus(vecteur_question, entree["vecteur"])
                resultats.append((score, entree))
    
            resultats.sort(key=lambda x: x[0], reverse=True)
            return [r[1] for r in resultats[:top_n]]

    Recommandation sur la qualité du modèle principal

    Comme la vitesse n’est pas une contrainte pour ce projet, on installera Phi-4 Mini dans une version moins compressée que la normale (Q6 ou Q8 plutôt que Q4) — plus lente, mais plus fidèle. C’est cohérent avec la priorité donnée à la précision plutôt qu’à la rapidité.

    Ce qui reste volontairement à nous, pas copié

    Contrairement aux solutions grand public (Ollama, LM Studio, de grosses piles avec bases de données vectorielles séparées), cette version reste 100% intégrée à l’architecture déjà posée (spécialités, garde-fou, adaptation matérielle) — pas une couche ajoutée par-dessus un outil générique, mais une pièce pensée spécifiquement pour MIRA.

Affichage de 1 message (sur 1 au total)
  • Vous devez être connecté pour répondre à ce sujet.
Retour en haut