Generazione Aumentata dal Recupero (RAG) con Milvus e LlamaIndex

Open In Colab GitHub Repository

Questa guida mostra come costruire un sistema di Retrieval-Augmented Generation (RAG) utilizzando LlamaIndex e Milvus.

Il sistema RAG combina un sistema di recupero con un modello generativo per generare nuovo testo in base a un prompt dato. Il sistema recupera prima i documenti rilevanti da un corpus utilizzando Milvus e poi utilizza un modello generativo per generare nuovo testo sulla base dei documenti recuperati.

LlamaIndex è un framework di dati semplice e flessibile per collegare fonti di dati personalizzate a grandi modelli linguistici (LLM). Milvus è il database vettoriale open-source più avanzato al mondo, costruito per alimentare le applicazioni di ricerca di similarità e di intelligenza artificiale.

In questo quaderno mostreremo una rapida dimostrazione dell'uso di MilvusVectorStore.

Prima di iniziare

Installare le dipendenze

Gli snippet di codice di questa pagina richiedono le dipendenze pymilvus e llamaindex. È possibile installarle utilizzando i seguenti comandi:

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

Se si utilizza Google Colab, per abilitare le dipendenze appena installate potrebbe essere necessario riavviare il runtime. (Fare clic sul menu "Runtime" nella parte superiore dello schermo e selezionare "Riavvia sessione" dal menu a discesa).

Configurazione di OpenAI

Per prima cosa aggiungiamo la chiave openai api. Questo ci permetterà di accedere a chatgpt.

import openai

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

Preparare i dati

È possibile scaricare i dati di esempio con i seguenti comandi:

! 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'

Per iniziare

Generare i dati

Come primo esempio, generiamo un documento dal file paul_graham_essay.txt. Si tratta di un singolo saggio di Paul Graham intitolato What I Worked On. Per generare i documenti utilizzeremo 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

Creare un indice sui dati

Ora che abbiamo un documento, possiamo creare un indice e inserire il documento. Per l'indice useremo un MilvusVectorStore. MilvusVectorStore accetta alcuni argomenti:

argomenti di base

  • uri (str, optional): L'URI a cui connettersi, sotto forma di "https://address:port" per il servizio Milvus o Zilliz Cloud, o "path/to/local/milvus.db" per il Milvus locale lite. Il valore predefinito è "./milvus_llamaindex.db".
  • token (str, optional): Il token per l'accesso. Vuoto se non si usa rbac, se si usa rbac sarà molto probabilmente "username:password".
  • collection_name (str, optional): Il nome della raccolta in cui verranno memorizzati i dati. Il valore predefinito è "llamalection".
  • overwrite (bool, optional): Sovrascrivere o meno la raccolta esistente con lo stesso nome. L'impostazione predefinita è False.

campi scalari, compresi id doc e testo

  • doc_id_field (str, optional): Il nome del campo doc_id per la raccolta. Il valore predefinito è DEFAULT_DOC_ID_KEY.
  • text_key (str, optional): Quale testo chiave è memorizzato nella raccolta passata. Utilizzato quando si porta la propria raccolta. Il valore predefinito è DEFAULT_TEXT_KEY.
  • scalar_field_names (list, optional): I nomi dei campi scalari extra da includere nello schema della collezione.
  • scalar_field_types (list, optional): I tipi dei campi scalari extra.

campo denso

  • enable_dense (bool): Un flag booleano per abilitare o disabilitare l'incorporazione densa. L'impostazione predefinita è True.
  • dim (int, optional): La dimensione dei vettori di incorporamento per la collezione. Richiesto quando si crea una nuova collezione con enable_sparse è False.
  • embedding_field (str, optional): Il nome del campo di incorporamento denso per l'insieme, predefinito a DEFAULT_EMBEDDING_KEY.
  • index_config (dict, optional): La configurazione usata per costruire l'indice di incorporamento denso. Per impostazione predefinita, Nessuno.
  • search_config (dict, optional): Configurazione utilizzata per la ricerca nell'indice denso di Milvus. Si noti che deve essere compatibile con il tipo di indice specificato da index_config. Il valore predefinito è Nessuno.
  • similarity_metric (str, optional): La metrica di somiglianza da utilizzare per l'incorporazione densa; attualmente supporta IP, COSINE e L2.

campo sparse

  • enable_sparse (bool): Un flag booleano per abilitare o disabilitare l'incorporazione rada. L'impostazione predefinita è False.
  • sparse_embedding_field (str): Il nome del campo sparse embedding, predefinito a DEFAULT_SPARSE_EMBEDDING_KEY.
  • sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): Se enable_sparse è True, questo oggetto deve essere fornito per convertire il testo in un'incorporazione rada. Se None, verrà utilizzata la funzione di incorporamento rado predefinita (BGEM3SparseEmbeddingFunction).
  • sparse_index_config (dict, optional): La configurazione utilizzata per costruire l'indice di incorporamento sparse. L'impostazione predefinita è Nessuno.

ranker ibrido

  • hybrid_ranker (str): Specifica il tipo di ranker utilizzato nelle query di ricerca ibride. Attualmente supporta solo ["RRFRanker", "WeightedRanker"]. L'impostazione predefinita è "RRFRanker".

  • hybrid_ranker_params (dict, optional): Parametri di configurazione per il ranker ibrido. La struttura di questo dizionario dipende dal ranker specifico utilizzato:

    • Per "RRFRanker", dovrebbe includere:
      • "k" (int): Un parametro utilizzato nella Reciprocal Rank Fusion (RRF). Questo valore viene utilizzato per calcolare i punteggi di rango come parte dell'algoritmo RRF, che combina più strategie di classificazione in un unico punteggio per migliorare la rilevanza della ricerca.
    • Per "WeightedRanker", si aspetta:
      • "weights" (elenco di float): Un elenco di esattamente due pesi:
        1. Il peso per la componente densa dell'incorporazione.
        2. Questi pesi sono usati per regolare l'importanza delle componenti dense e rade delle incorporazioni nel processo di recupero ibrido. L'impostazione predefinita è un dizionario vuoto, il che implica che il classificatore opererà con le impostazioni predefinite.

altri

  • collection_properties (dict, optional): Le proprietà della collezione, come TTL (Time-To-Live) e MMAP (memory mapping). L'impostazione predefinita è Nessuna. Potrebbe includere:
    • "collection.ttl.seconds" (int): Una volta impostata questa proprietà, i dati della raccolta corrente scadono nel tempo specificato. I dati scaduti nell'insieme saranno ripuliti e non saranno coinvolti nelle ricerche o nelle query.
    • "mmap.enabled" (bool): Abilita o meno la memorizzazione con mappatura di memoria a livello di raccolta.
  • index_management (IndexManagement): Specifica la strategia di gestione degli indici da utilizzare. Per impostazione predefinita, "create_if_not_exists".
  • batch_size (int): Configura il numero di documenti elaborati in un batch quando si inseriscono i dati in Milvus. Il valore predefinito è DEFAULT_BATCH_SIZE.
  • consistency_level (str, optional): Quale livello di consistenza utilizzare per una raccolta appena creata. Il valore predefinito è "Sessione".
# 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)

Per i parametri di MilvusVectorStore:

  • L'impostazione di uri come file locale, ad esempio./milvus.db, è il metodo più conveniente, poiché utilizza automaticamente Milvus Lite per memorizzare tutti i dati in questo file.
  • Se si dispone di una grande quantità di dati, è possibile configurare un server Milvus più performante su docker o kubernetes. In questa configurazione, utilizzare l'uri del server, ad esempiohttp://localhost:19530, come uri.
  • Se si desidera utilizzare Zilliz Cloud, il servizio cloud completamente gestito per Milvus, è necessario impostare uri e token, che corrispondono all'endpoint pubblico e alla chiave Api di Zilliz Cloud.

Interrogare i dati

Ora che il nostro documento è memorizzato nell'indice, possiamo interrogarlo. L'indice utilizzerà i dati memorizzati al suo interno come base di conoscenza per 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.

Il test successivo mostra che la sovrascrittura rimuove i dati precedenti.

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.

Il prossimo test mostra l'aggiunta di ulteriori dati a un indice già esistente.

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

Filtraggio dei metadati

È possibile generare risultati filtrando fonti specifiche. L'esempio seguente illustra il caricamento di tutti i documenti dalla directory e il successivo filtraggio in base ai metadati.

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)

Vogliamo recuperare solo i documenti del file 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.

Questa volta otteniamo un risultato diverso se recuperiamo i documenti dal file 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.