Génération améliorée par récupération (RAG) avec Milvus et LlamaIndex

Open In Colab GitHub Repository

Ce guide montre comment construire un système de génération améliorée par récupération (RAG) en utilisant LlamaIndex et Milvus.

Le système RAG combine un système de recherche avec un modèle génératif pour générer un nouveau texte basé sur une invite donnée. Le système récupère d'abord les documents pertinents d'un corpus à l'aide de Milvus, puis utilise un modèle génératif pour générer un nouveau texte basé sur les documents récupérés.

LlamaIndex est un cadre de données simple et flexible permettant de connecter des sources de données personnalisées à de grands modèles de langage (LLM). Milvus est la base de données vectorielles open-source la plus avancée au monde, conçue pour alimenter les applications de recherche de similarité et d'intelligence artificielle.

Dans ce carnet, nous allons présenter une démonstration rapide de l'utilisation du MilvusVectorStore.

Avant de commencer

Installer les dépendances

Les extraits de code de cette page nécessitent les dépendances pymilvus et llamaindex. Vous pouvez les installer à l'aide des commandes suivantes :

$ pip install pymilvus>=2.4.2 milvus-lite
$ pip install llama-index-vector-stores-milvus
$ pip install llama-index

Si vous utilisez Google Colab, pour activer les dépendances qui viennent d'être installées, vous devrez peut-être redémarrer le moteur d'exécution. (Cliquez sur le menu "Runtime" en haut de l'écran, et sélectionnez "Restart session" dans le menu déroulant).

Configuration d'OpenAI

Commençons par ajouter la clé api openai. Cela nous permettra d'accéder à chatgpt.

import openai

openai.api_key = "sk-***********"

Préparer les données

Vous pouvez télécharger des échantillons de données à l'aide des commandes suivantes :

! mkdir -p 'data/'
! wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham_essay.txt'
! wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/10k/uber_2021.pdf' -O 'data/uber_2021.pdf'

Démarrage

Générer nos données

Comme premier exemple, générons un document à partir du fichier paul_graham_essay.txt. Il s'agit d'un seul essai de Paul Graham intitulé What I Worked On. Pour générer les documents, nous utiliserons le SimpleDirectoryReader.

from llama_index.core import SimpleDirectoryReader

# load documents
documents = SimpleDirectoryReader(
    input_files=["./data/paul_graham_essay.txt"]
).load_data()

print("Document ID:", documents[0].doc_id)
Document ID: 95f25e4d-f270-4650-87ce-006d69d82033

Créer un index sur les données

Maintenant que nous avons un document, nous pouvons créer un index et insérer le document. Pour l'index, nous utiliserons un MilvusVectorStore. MilvusVectorStore prend en compte quelques arguments :

arguments de base

  • uri (str, optional): L'URI à laquelle se connecter, sous la forme de "https://address:port" pour Milvus ou le service Zilliz Cloud, ou "path/to/local/milvus.db" pour Milvus local. La valeur par défaut est "./milvus_llamaindex.db".
  • token (str, optional): Le jeton de connexion. Vide si l'on n'utilise pas rbac, si l'on utilise rbac, il s'agira probablement de "username:password".
  • collection_name (str, optional): Le nom de la collection où les données seront stockées. La valeur par défaut est "llamalection".
  • overwrite (bool, optional): Si l'on veut écraser une collection existante portant le même nom. La valeur par défaut est False.

champs scalaires, y compris l'identifiant du document et le texte

  • doc_id_field (str, optional): Le nom du champ doc_id pour la collection. La valeur par défaut est DEFAULT_DOC_ID_KEY.
  • text_key (str, optional): La clé dans laquelle le texte est stocké dans la collection transmise. Utilisé lorsque vous apportez votre propre collection. La valeur par défaut est DEFAULT_TEXT_KEY.
  • scalar_field_names (list, optional): Les noms des champs scalaires supplémentaires à inclure dans le schéma de la collection.
  • scalar_field_types (list, optional): Les types des champs scalaires supplémentaires.

champ dense

  • enable_dense (bool): Un indicateur booléen permettant d'activer ou de désactiver l'intégration dense. La valeur par défaut est True.
  • dim (int, optional): La dimension des vecteurs d'intégration pour la collection. Requis lors de la création d'une nouvelle collection si enable_sparse est False.
  • embedding_field (str, optional): Le nom du champ d'intégration dense pour la collection, par défaut DEFAULT_EMBEDDING_KEY.
  • index_config (dict, optional): La configuration utilisée pour construire l'index d'intégration dense. La valeur par défaut est None.
  • search_config (dict, optional): La configuration utilisée pour la recherche dans l'index dense Milvus. Notez qu'elle doit être compatible avec le type d'index spécifié par index_config. La valeur par défaut est None.
  • similarity_metric (str, optional): La métrique de similarité à utiliser pour l'intégration dense, actuellement IP, COSINE et L2.

champ sparse

  • enable_sparse (bool): Un indicateur booléen permettant d'activer ou de désactiver l'incorporation éparse. La valeur par défaut est False.
  • sparse_embedding_field (str): Le nom du champ d'intégration éparse, par défaut DEFAULT_SPARSE_EMBEDDING_KEY.
  • sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): Si enable_sparse est True, cet objet doit être fourni pour convertir le texte en un encodage clairsemé. Si None, c'est la fonction d'incorporation éparse par défaut (BGEM3SparseEmbeddingFunction) qui sera utilisée.
  • sparse_index_config (dict, optional): La configuration utilisée pour construire l'index d'intégration éparse. La valeur par défaut est None.

classeur hybride

  • hybrid_ranker (str): Spécifie le type de classificateur utilisé dans les requêtes de recherche hybrides. Actuellement, seul ["RRFRanker", "WeightedRanker"] est pris en charge. La valeur par défaut est "RRFRanker".

  • hybrid_ranker_params (dict, optional): Paramètres de configuration du classificateur hybride. La structure de ce dictionnaire dépend du classificateur spécifique utilisé :

    • Pour "RRFRanker", il doit comprendre les éléments suivants
      • "k" (int) : Paramètre utilisé dans la fusion réciproque des rangs (RRF). Cette valeur est utilisée pour calculer les scores de classement dans le cadre de l'algorithme RRF, qui combine plusieurs stratégies de classement en un seul score afin d'améliorer la pertinence de la recherche.
    • Pour "WeightedRanker", il attend :
      • "weights" (liste de flottants) : Une liste d'exactement deux poids :
        1. Le poids pour le composant d'intégration dense.
        2. Ces poids sont utilisés pour ajuster l'importance des composantes denses et éparses des encastrements dans le processus de recherche hybride. Par défaut, le dictionnaire est vide, ce qui signifie que le classificateur fonctionnera avec ses paramètres prédéfinis par défaut.

autres

  • collection_properties (dict, optional): Propriétés de la collection telles que TTL (Time-To-Live) et MMAP (memory mapping). La valeur par défaut est None. Il peut s'agir de
    • "collection.ttl.seconds" (int) : Lorsque cette propriété est définie, les données de la collection actuelle expirent dans le délai spécifié. Les données périmées de la collection seront nettoyées et ne seront pas prises en compte dans les recherches ou les requêtes.
    • "mmap.enabled" (bool) : Permet d'activer ou non le stockage en mémoire au niveau de la collection.
  • index_management (IndexManagement): Spécifie la stratégie de gestion d'index à utiliser. La valeur par défaut est "create_if_not_exists".
  • batch_size (int): Configure le nombre de documents traités dans un lot lors de l'insertion de données dans Milvus. La valeur par défaut est DEFAULT_BATCH_SIZE.
  • consistency_level (str, optional): Niveau de cohérence à utiliser pour une collection nouvellement créée. La valeur par défaut est "Session".
# Create an index over the documents
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.milvus import MilvusVectorStore


vector_store = MilvusVectorStore(uri="./milvus_demo.db", dim=1536, overwrite=True)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)

Pour les paramètres de MilvusVectorStore:

  • Définir uri comme un fichier local, par exemple./milvus.db, est la méthode la plus pratique, car elle utilise automatiquement Milvus Lite pour stocker toutes les données dans ce fichier.
  • Si vous avez des données à grande échelle, vous pouvez configurer un serveur Milvus plus performant sur docker ou kubernetes. Dans cette configuration, veuillez utiliser l'uri du serveur, par exemplehttp://localhost:19530, comme votre uri.
  • Si vous souhaitez utiliser Zilliz Cloud, le service cloud entièrement géré pour Milvus, ajustez les adresses uri et token, qui correspondent au point final public et à la clé Api dans Zilliz Cloud.

Interroger les données

Maintenant que notre document est stocké dans l'index, nous pouvons poser des questions à l'index. L'index utilisera les données stockées en lui-même comme base de connaissances pour chatgpt.

query_engine = index.as_query_engine()
res = query_engine.query("What did the author learn?")
print(res)
The author learned that philosophy courses in college were boring to him, leading him to switch his focus to studying AI.
res = query_engine.query("What challenges did the disease pose for the author?")
print(res)
The disease posed challenges for the author as it affected his mother's health, leading to a stroke caused by colon cancer. This resulted in her losing her balance and needing to be placed in a nursing home. The author and his sister were determined to help their mother get out of the nursing home and back to her house.

Le test suivant montre que l'écrasement supprime les données précédentes.

from llama_index.core import Document


vector_store = MilvusVectorStore(uri="./milvus_demo.db", dim=1536, overwrite=True)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
    [Document(text="The number that is being searched for is ten.")],
    storage_context,
)
query_engine = index.as_query_engine()
res = query_engine.query("Who is the author?")
print(res)
The author is the individual who created the context information.

Le test suivant montre l'ajout de données supplémentaires à un index déjà existant.

del index, vector_store, storage_context, query_engine

vector_store = MilvusVectorStore(uri="./milvus_demo.db", overwrite=False)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
query_engine = index.as_query_engine()
res = query_engine.query("What is the number?")
print(res)
The number is ten.
res = query_engine.query("Who is the author?")
print(res)
Paul Graham

Filtrage des métadonnées

Nous pouvons générer des résultats en filtrant des sources spécifiques. L'exemple suivant illustre le chargement de tous les documents du répertoire et leur filtrage ultérieur sur la base des métadonnées.

from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters

# Load all the two documents loaded before
documents_all = SimpleDirectoryReader("./data/").load_data()

vector_store = MilvusVectorStore(uri="./milvus_demo.db", dim=1536, overwrite=True)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents_all, storage_context)

Nous voulons récupérer uniquement les documents du fichier uber_2021.pdf.

filters = MetadataFilters(
    filters=[ExactMatchFilter(key="file_name", value="uber_2021.pdf")]
)
query_engine = index.as_query_engine(filters=filters)
res = query_engine.query("What challenges did the disease pose for the author?")

print(res)
The disease posed challenges related to the adverse impact on the business and operations, including reduced demand for Mobility offerings globally, affecting travel behavior and demand. Additionally, the pandemic led to driver supply constraints, impacted by concerns regarding COVID-19, with uncertainties about when supply levels would return to normal. The rise of the Omicron variant further affected travel, resulting in advisories and restrictions that could adversely impact both driver supply and consumer demand for Mobility offerings.

Nous obtenons un résultat différent lorsque nous extrayons les documents du fichier paul_graham_essay.txt.

filters = MetadataFilters(
    filters=[ExactMatchFilter(key="file_name", value="paul_graham_essay.txt")]
)
query_engine = index.as_query_engine(filters=filters)
res = query_engine.query("What challenges did the disease pose for the author?")

print(res)
The disease posed challenges for the author as it affected his mother's health, leading to a stroke caused by colon cancer. This resulted in his mother losing her balance and needing to be placed in a nursing home. The author and his sister were determined to help their mother get out of the nursing home and back to her house.