Écrit par

Sales Engineer at InterSystems
Article Sylvain Guilbaud · Avr 20 8m read

Recherche vectorielle avec Embedded Python dans InterSystems IRIS

La vectorisation a notamment pour objectif de rendre le texte non structuré plus exploitable par les machines. Les embeddings vectoriels y parviennent en codant la sémantique du texte sous forme de vecteurs numeric à haute dimensionalité, qui peuvent être utilisés par des algorithmes de recherche avancés (généralement un algorithme de plus proche voisin approximatif tel que Hierarchical Navigable Small World). Cela améliore non seulement notre capacité à interagir avec du texte non structuré de manière programmatique, mais permet également de le rendre recherchable par contexte et par sens, au-delà de ce qui est littéralement capturé par des mots-clés.

Dans cet article, je vais vous présenter une implémentation simple de recherche vectorielle qui a été développée par Kwabena Ayim-Aboagye et moi-même à l’aide d' Embedded Python dans InterSystems IRIS for Health. Je vais également aborder brièvement l’utilisation d’ Embedded Python et du SQL dynamique en général, ainsi que comment tirer parti des fonctionnalités de recherche vectorielle offertes de manière native par IRIS.

 

Détails de l'environnement :

  • Système d'exploitation : Windows Server 2025
  • InterSystems IRIS for Health 2025.1
  • VS Code / InterSystems Server Manager
  • Python 3.13.7
  • Bibliothèques Python : pandas, ollama, iris**
  • Ollama 0.12.3 et modèle all-minilm
  • SQL dynamique
  • Base de données d'exemple contenant du texte non structuré (poèmes classiques)

Processus :

      0. Configuration de l'environnement ; installation complète

  1. Définition d'une table auxiliaire

  2. Définissons une classe RegisteredObject pour nos méthodes de vectorisation, qui seront écrites en Embedded Python. Concentrons-nous d'abord sur la méthode VectorizeTable(), qui contiendra une fonction pilote (du même nom) et quelques fonctions de traitement auxiliaires, toutes écrites en Python.

    • La fonction pilote suit le processus comme suit:

      1. Chargement depuis IRIS dans un Pandas Dataframe (via la fonction de soutien load_table())
      2. Génération d'une colonne d'intégration (via la méthode de classe de soutien GetEmbeddingString, à l'aide de laquelle des embeddings seront également générés ultérieurement pour les requêtes)
        • Conversion de la colonne d'encodage en une chaîne compatible avec le type vectorial d'IRIS
      3. Écriture du DataFrame dans la table auxiliaire
      4. Création d'un index HNSW pour la table auxiliaire
    • La méthode de classe VectorizeTable() appelle ensuite simplement la fonction pilote:

    • Examinons cela étape par étape :

    1. Importation de la table depuis IRIS dans un Pandas Dataframe

      • def load_table(sample_size='*') -> pd.DataFrame:
            sql = f"SELECT * FROM SQLUser.SamplePoetry{f' LIMIT {sample_size}' if sample_size != '*' else ''}"
            result_set = iris.sql.exec(sql)
            df = result_set.dataframe()
        <span class="mention"># Les saisies sans texte ne seront ni vectorisées ni recherchables</span>
        <span class="mention">for</span> index, row <span class="mention">in</span> df.iterrows():
            <span class="mention">if</span> row[<span class="mention">'poem'</span>] == <span class="mention">' '</span> <span class="mention">or</span> row[<span class="mention">'poem'</span>] <span class="mention">is</span> <span class="mention">None</span>:
                df = df.drop(index)
        
        <span class="mention">return</span> df</code></pre></li><li data-list-item-id="eefcc1d39f96038e122e461e7526ba90b">Cette fonction utilise la méthode <code>dataframe()</code> method of <a href="https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&amp;CLASSNAME=%25SYS.Python.SQLResultSet#METHOD_dataframe" target="_blank">des objets SQLResultSet d' Embedded Python</a></li><li data-list-item-id="ea4ede0aa2a09dabf2902a06278aa79df"><code>load_table()</code> accepte un argument facultatif <code>sample_size</code> à des fins de test. Il existe également un filtre pour les saisies sans texte non structuré. Bien que notre base de données d'exemple soit soigneusement sélectionnée et complète, certains cas d'utilisation peuvent nécessiter la vectorisation d'ensembles de données pour lesquels on ne peut pas supposer que chaque ligne ait des données pour toutes les colonnes (par exemple, des réponses à des enquêtes au moyen de questions ignorées). Plutôt que de mettre en œuvre un vecteur "null" ou vide, nous avons choisi d’exclure ces lignes de la recherche vectorielle en les supprimant à cette étape du processus.</li><li data-list-item-id="e821e2611fe70c080e7a022d8f3c21f1b">*Il est à noter que <code>iris</code> correspond au <a href="https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GEPYTHON_reference" target="_blank">Module InterSystems IRIS Python</a>. Il fonctionne comme une API permettant d’accéder aux classes et méthodes IRIS, d’interagir avec la base de données, etc</li><li data-list-item-id="ea69a4d07e45b85e56b943c780424979e">*Il est à noter que <code>SQLUser</code> correspond au <a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_tables#GSQL_tables_schemadefault" target="_blank">schéma par défaut à l’échelle du système</a> qui <a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_defpersobj#GOBJ_defpersobj_sqlproj_pkg" target="_blank">correspond au package par défaut</a><code>User</code>.</li></ul></li><li class="ck-list-marker-bold" data-list-item-id="e9ca354b9ae137911dc78afd93da5b132"><h4><strong>Génération d'une colonne d'intégration (méthode de soutien)</strong></h4><ul><li data-list-item-id="ea097b9c25eb08247c89cab9fcfac9e6d"><pre class="codeblock-container" idlang="0" lang="ObjectScript" tabsize="4"><code class="language-plaintext language-cls hljs cos"><span class="mention">ClassMethod</span> GetEmbeddingString(aurg <span class="mention">As</span> <span class="mention">%String</span>) <span class="mention">As</span> <span class="mention">%String</span> [ Language = python ]
        {
        
        import iris
        
        import ollama
        response = ollama.embed(model='all-minilm',input=[ aurg ])
        
        embedding_str = str(response.embeddings[0])
        

        return embedding_str }

      • Nous avons installé Ollama sur notre machine virtuelle, chargé le modèle d'embeddings all-minilm, et généré des embeddings à l'aide de la bibliothèque Python d'Ollama. Cela nous a permis d'exécuter le modèle en local et de générer des embeddings sans clé API.

      • GetEmbeddingString renvoie l'embedding sous forme de chaîne de caractères, car TO_VECTOR attend par défaut que l'argument data soit une chaîne de caractères ; nous y reviendrons plus en détail.

      • *Il est à noter qu’Embedded Python intégré fournit une syntaxe permettant d’appeler d’autres méthodes ObjectScript définies dans la classe actuelle (similaire à self en Python). L’exemple précédent utilise la syntaxe iris.cls(name) pour obtenir une référence à la classe ObjectScript actuelle et invoquer GetEmbeddingString (méthode ObjectScript) à l’aide de VectorizeTable (méthode Embedded Python intégrée à la méthode ObjectScript).

    2. Inscription des embeddings du cadre de données dans la table IRIS

      • # Écriture du cadre de données dans une nouvelle table
        
        print("Loading data into table...")
        
        for index, row in df.iterrows():
        
        sql = iris.sql.prepare("INSERT INTO SQLUser.SamplePoetryVectors (ID, EMBEDDING) VALUES (?, TO_VECTOR(?, decimal))")
        
        rs = sql.execute(row['id'], row['embedding'])
        

        print("Data loaded into table.")

      • Ici, nous utilisons Dynamic SQL pour remplir SamplePoetryVectors ligne par ligne. Comme nous avons précédemment déclaré la propriété EMBEDDING comme étant de type %Library.Vector, il nous faut utiliser TO_VECTOR pour convertir les embeddings au type de données natif VECTOR d'InterSystems IRIS lors de l'insertion. Nous avons assuré la compatibilité avec TO_VECTOR en convertissant préalablement les embeddings en chaînes de caractères.

        • Le module python iris nous permet une fois de plus de tirer parti du Dynamic SQL dans notre fonction Embedded Python.
    3. Create a HNSW Index

      • # Creation d'index
        
        iris.sql.exec("CREATE INDEX HNSWIndex ON TABLE SQLUser.SamplePoetryVectors (EMBEDDING) AS HNSW(Distance='Cosine')")
        
        print("Index created.")
        
      • InterSystems IRIS implémentera de manière native un graphe HNSW destiné à être utilisé dans les méthodes de recherche vectorielle lorsqu'un index HNSW est créé sur une colonne compatible. Les méthodes de recherche vectorielle disponibles via InterSystems IRIS sont VECTOR_DOT_PRODUCT et VECTOR_COSINE. Une fois l'index créé, InterSystems IRIS l'utilisera pour optimiser la méthode de recherche vectorielle correspondante lorsqu'il sera appelé dans les requêtes suivantes. Les valeurs par défaut des paramètres pour un index HNSW sont Distance = CosineM = 16 et efConstruction = 200.
      • Il est à noter que VECTOR_COSINE normalise implicitement ses vecteurs d'entrée ; nous n'avons donc pas eu besoin d'effectuer de normalisation avant de les insérer dans la table pour que nos requêtes de recherche vectorielle soient évaluées correctement !
  3. Implementation de la méthode de classe VectorSearch()

    •    

    1. Génération d'un encodage pour la chaîne de requête

      • # Génération d'embedding du paramètre de recherche
        
        search_vector = iris.cls(name).GetEmbeddingString(aurg)
        
      • Réutilisation de la méthode de classe GetEmbeddingString
    2. Préparation et exécution d'une requête utilisant VECTOR_COSINE

      • # Préparation et exécution de l'instruction SQL
        
        stmt = iris.sql.prepare(
        
        """SELECT top 5 p.poem, p.title, p.author
        
        FROM SQLUser.SamplePoetry AS p
        
        JOIN SQLUser.SamplePoetryVectors AS v
        
        ON p.ID = v.ID
        
        ORDER BY VECTOR_COSINE(v.embedding, TO_VECTOR(?)) DESC"""
        
        )
        
        results = stmt.execute(search_vector)
        
      • Nous utilisons ici une instruction JOIN pour combiner le texte poétique avec son vecteur embedding correspondant, afin de pouvoir classer les résultats en fonction de leur similarité sémantique.
    3. Affichage de résultats

      • results_df = pd.DataFrame(results)
        pd.set_option('display.max_colwidth', 25)
        
        results_df.rename(columns={0: 'Poem', 1: 'Title', 2: 'Author'}, inplace=True)
        

        print(results_df)

      • Les options de mise en forme de pandas sont utilisées pour ajuster l'affichage dans le terminal IRIS ::