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
-
Définition d'une table auxiliaire
-
La table d'embeddings
User.SamplePoetryVectorsa une clé étrangère surUser.SamplePoetryainsi qu'une propriétéEMBEDDINGtype%Library.Vector. Ollamaall-minilmgénère des embeddings de 384 dimensions ; nous avons donc imposé une contrainte de longueur en conséquence.
- *Veuillez noter que, puisque l'objectif est en fin de compte de tirer parti de HNSWIndex natif d'InterSystems IRIS et de méthodes de recherche vectorielle natives, , il est nécessaire d'avoir une colonne de type %Library.Vector (ou %Library.Embedding) de longueur fixe, de type decimal ou double pour l'indexation.
-
-
Définissons une classe
RegisteredObjectpour nos méthodes de vectorisation, qui seront écrites en Embedded Python. Concentrons-nous d'abord sur la méthodeVectorizeTable(), 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:
- Chargement depuis IRIS dans un Pandas Dataframe (via la fonction de soutien
load_table()) - 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
- Écriture du DataFrame dans la table auxiliaire
- Création d'un index HNSW pour la table auxiliaire
- Chargement depuis IRIS dans un Pandas Dataframe (via la fonction de soutien
-
La méthode de classe
VectorizeTable()appelle ensuite simplement la fonction pilote: -
Examinons cela étape par étape :
-
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&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. -
GetEmbeddingStringrenvoie l'embedding sous forme de chaîne de caractères, carTO_VECTORattend par défaut que l'argumentdatasoit 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 à
selfen Python). L’exemple précédent utilise la syntaxeiris.cls(name)pour obtenir une référence à la classe ObjectScript actuelle et invoquerGetEmbeddingString(méthode ObjectScript) à l’aide deVectorizeTable(méthode Embedded Python intégrée à la méthode ObjectScript).
-
-
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
SamplePoetryVectorsligne par ligne. Comme nous avons précédemment déclaré la propriétéEMBEDDINGcomme étant de type%Library.Vector, il nous faut utiliserTO_VECTORpour convertir les embeddings au type de données natifVECTORd'InterSystems IRIS lors de l'insertion. Nous avons assuré la compatibilité avecTO_VECTORen convertissant préalablement les embeddings en chaînes de caractères.- Le module python
irisnous permet une fois de plus de tirer parti du Dynamic SQL dans notre fonction Embedded Python.
- Le module python
-
-
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_PRODUCTetVECTOR_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 sontDistance = Cosine,M = 16etefConstruction = 200. - Il est à noter que
VECTOR_COSINEnormalise 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 !
-
-
-
Implementation de la méthode de classe
VectorSearch()-
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
-
-
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
JOINpour combiner le texte poétique avec son vecteur embedding correspondant, afin de pouvoir classer les résultats en fonction de leur similarité sémantique.
-
-
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 ::
-
-

