Costruire RAG con Milvus e Gemini
L'API Gemini e Google AI Studio consentono di iniziare a lavorare con i modelli più recenti di Google e di trasformare le proprie idee in applicazioni scalabili. Gemini fornisce l'accesso a potenti modelli linguistici come Gemini-2.5-Flash e Gemini-2.5-Pro per attività quali la generazione di testi, l'elaborazione di documenti, la visione, l'analisi audio e altro ancora. Offre anche Gemini Embedding 2, un modello di incorporazione multimodale che supporta testo, immagini, video, audio e documenti PDF con dimensioni di output flessibili tramite Matryoshka Representation Learning. L'API consente di inserire contesti lunghi con milioni di token, di mettere a punto i modelli per compiti specifici, di generare output strutturati come JSON e di sfruttare funzionalità come il recupero semantico e l'esecuzione di codice.
In questo tutorial vi mostreremo come costruire una pipeline RAG (Retrieval-Augmented Generation) con Milvus e Gemini. Utilizzeremo il modello Gemini per generare risposte basate su una determinata query, aumentata con informazioni rilevanti recuperate da Milvus.
Preparazione
Dipendenze e ambiente
Per prima cosa, installare i pacchetti necessari:
$ pip install --upgrade pymilvus milvus-lite google-genai requests tqdm
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).
Per prima cosa è necessario accedere alla piattaforma Google AI Studio e preparare la chiave api GEMINI_API_KEY come variabile d'ambiente.
import os
os.environ["GEMINI_API_KEY"] = "***********"
Preparare i dati
Nel nostro RAG utilizziamo le pagine FAQ della Documentazione Milvus 2.4.x come conoscenza privata, che è una buona fonte di dati per una semplice pipeline RAG.
Scaricare il file zip ed estrarre i documenti nella cartella milvus_docs.
$ wget https://github.com/milvus-io/milvus-docs/releases/download/v2.4.6-preview/milvus_docs_2.4.x_en.zip
$ unzip -q milvus_docs_2.4.x_en.zip -d milvus_docs
Carichiamo tutti i file markdown dalla cartella milvus_docs/en/faq. Per ogni documento, usiamo semplicemente "# " per separare il contenuto del file, che può separare approssimativamente il contenuto di ogni parte principale del file markdown.
from glob import glob
text_lines = []
for file_path in glob("milvus_docs/en/faq/*.md", recursive=True):
with open(file_path, "r") as file:
file_text = file.read()
text_lines += file_text.split("# ")
Preparare l'LLM e il modello di incorporamento
Utilizziamo gemini-2.5-flash come LLM e gemini-embedding-2-preview come modello di incorporamento. gemini-embedding-2-preview è l'ultimo modello di incorporamento multimodale di Google, che supporta testo, immagini, video, audio e documenti PDF con dimensioni di output flessibili (128-3.072) tramite Matryoshka Representation Learning.
Proviamo a generare una risposta di prova dall'LLM:
from google import genai
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
response = client.models.generate_content(
model="gemini-2.5-flash", contents="who are you"
)
print(response.text)
I am a large language model, trained by Google.
I'm designed to process and generate human-like text based on the vast amount of data I was trained on. This allows me to:
* Answer questions
* Provide summaries
* Generate creative content
* Translate languages
* And much more
I don't have personal experiences, feelings, or consciousness. I'm a tool designed to be helpful and informative.
Generare un embedding di prova e stamparne la dimensione e i primi elementi.
test_embeddings = client.models.embed_content(
model="gemini-embedding-2-preview", contents=["This is a test1", "This is a test2"]
)
embedding_dim = len(test_embeddings.embeddings[0].values)
print(embedding_dim)
print(test_embeddings.embeddings[0].values[:10])
3072
[-0.016769307, 0.013630492, 0.020277105, 0.0035285393, 0.003968259, -0.013498845, 0.028525498, 0.025498547, -0.021553498, 0.015233516]
Caricare i dati in Milvus
Creare la collezione
Inizializziamo il client Milvus e creiamo la nostra collezione:
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Come per l'argomento di MilvusClient:
- L'impostazione di
uricome file locale, ad esempio./milvus.db, è il metodo più conveniente, in quanto 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 esempio
http://localhost:19530, comeuri. - Se si desidera utilizzare Zilliz Cloud, il servizio cloud completamente gestito per Milvus, regolare
urietoken, che corrispondono all'endpoint pubblico e alla chiave Api di Zilliz Cloud.
Verificare se la raccolta esiste già e, in caso affermativo, eliminarla.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Creare una nuova raccolta con i parametri specificati.
Se non si specifica alcun campo, Milvus creerà automaticamente un campo predefinito id per la chiave primaria e un campo vector per memorizzare i dati vettoriali. Un campo JSON riservato viene utilizzato per memorizzare campi non definiti dalla mappa e i loro valori.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
# Strong consistency waits for all loads to complete, adding latency with large datasets
# consistency_level="Strong", # Strong consistency level
)
Inserire i dati
Si intersecano le righe di testo, si creano le incorporazioni e si inseriscono i dati in Milvus.
Ecco un nuovo campo text, che è un campo non definito nello schema della collezione. Verrà aggiunto automaticamente al campo dinamico JSON riservato, che può essere trattato come un campo normale ad alto livello.
from tqdm import tqdm
data = []
doc = client.models.embed_content(model="gemini-embedding-2-preview", contents=text_lines)
for i, line in enumerate(tqdm(text_lines, desc="Creating embeddings")):
data.append({"id": i, "vector": doc.embeddings[i].values, "text": line})
milvus_client.insert(collection_name=collection_name, data=data)
Creating embeddings: 100%|██████████| 72/72 [00:00<00:00, 337796.30it/s]
{'insert_count': 72, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71], 'cost': 0}
Costruire la RAG
Recuperare i dati per una query
Specifichiamo una domanda frequente su Milvus.
question = "How is data stored in milvus?"
Cerchiamo la domanda nella raccolta e recuperiamo le prime 3 corrispondenze semantiche.
quest_embed = client.models.embed_content(model="gemini-embedding-2-preview", contents=question)
search_res = milvus_client.search(
collection_name=collection_name,
data=[quest_embed.embeddings[0].values],
limit=3, # Return top 3 results
search_params={"metric_type": "IP", "params": {}}, # Inner product distance
output_fields=["text"], # Return the text field
)
Diamo un'occhiata ai risultati della ricerca della query
import json
retrieved_lines_with_distances = [
(res["entity"]["text"], res["distance"]) for res in search_res[0]
]
print(json.dumps(retrieved_lines_with_distances, indent=4))
[
[
" Where does Milvus store data?\n\nMilvus deals with two types of data, inserted data and metadata. \n\nInserted data, including vector data, scalar data, and collection-specific schema, are stored in persistent storage as incremental log. Milvus supports multiple object storage backends, including [MinIO](https://min.io/), [AWS S3](https://aws.amazon.com/s3/?nc1=h_ls), [Google Cloud Storage](https://cloud.google.com/storage?hl=en#object-storage-for-companies-of-all-sizes) (GCS), [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs), [Alibaba Cloud OSS](https://www.alibabacloud.com/product/object-storage-service), and [Tencent Cloud Object Storage](https://www.tencentcloud.com/products/cos) (COS).\n\nMetadata are generated within Milvus. Each Milvus module has its own metadata that are stored in etcd.\n\n###",
0.864
],
[
"Why is there no vector data in etcd?\n\netcd stores Milvus module metadata; MinIO stores entities.",
0.7923
],
[
"What is the maximum dataset size Milvus can handle?\n\n \nTheoretically, the maximum dataset size Milvus can handle is determined by the hardware it is run on, specifically system memory and storage:\n\n- Milvus loads all specified collections and partitions into memory before running queries. Therefore, memory size determines the maximum amount of data Milvus can query.\n- When new entities and and collection-related schema (currently only MinIO is supported for data persistence) are added to Milvus, system storage determines the maximum allowable size of inserted data.\n\n###",
0.7857
]
]
Utilizzare LLM per ottenere una risposta RAG
Convertire i documenti recuperati in un formato stringa.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Definire i prompt del sistema e dell'utente per il modello linguistico. Questo prompt viene assemblato con i documenti recuperati da Milvus.
from google.genai import types
SYSTEM_PROMPT = """
Human: You are an AI assistant. You are able to find answers to the questions from the contextual passage snippets provided.
"""
USER_PROMPT = f"""
Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.
<context>
{context}
</context>
<question>
{question}
</question>
"""
Utilizzare Gemini per generare una risposta basata sui prompt.
response = client.models.generate_content(
model="gemini-2.5-flash",
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT),
contents=USER_PROMPT,
)
print(response.text)
Milvus stores data in two main ways:
1. **Inserted Data:** This includes vector data, scalar data, and collection-specific schema. This type of data is stored in persistent storage as an incremental log. Milvus supports various object storage backends for this, such as MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS, and Tencent Cloud Object Storage (COS).
2. **Metadata:** Metadata is generated within Milvus by its various modules. Each module's metadata is stored in etcd.
Ricerca multimodale
Poiché gemini-embedding-2-preview mappa testo, immagini e altre modalità nello stesso spazio di incorporazione, è possibile eseguire ricerche multimodali, ad esempio utilizzando una query testuale per trovare le immagini più rilevanti.
Preparare i dati delle immagini
Scarichiamo una serie di diagrammi di architettura RAG dal repository Milvus Bootcamp per usarli come set di immagini.
import urllib.request
from pathlib import Path
image_dir = Path("images")
image_dir.mkdir(exist_ok=True)
image_files = [
"vanilla_rag.png",
"hyde.png",
"query_routing.png",
"self_reflection.png",
"hybrid_and_rerank.png",
"hierarchical_index.png",
]
base_url = "https://raw.githubusercontent.com/milvus-io/bootcamp/master/pics/advanced_rag/"
for fname in image_files:
path = image_dir / fname
if not path.exists():
urllib.request.urlretrieve(base_url + fname, path)
print(f"Downloaded {fname}")
else:
print(f"Already exists {fname}")
print(f"\nTotal images: {len(image_files)}")
Downloaded vanilla_rag.png
Downloaded hyde.png
Downloaded query_routing.png
Downloaded self_reflection.png
Downloaded hybrid_and_rerank.png
Downloaded hierarchical_index.png
Total images: 6
Incorporare le immagini e memorizzarle in Milvus
Leggiamo ogni immagine come byte e la passiamo a gemini-embedding-2-preview per generare embeddings, quindi la memorizziamo in una nuova collezione Milvus.
from google.genai import types
image_data = []
for fname in image_files:
path = image_dir / fname
with open(path, "rb") as f:
image_bytes = f.read()
result = client.models.embed_content(
model="gemini-embedding-2-preview",
contents=types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
)
image_data.append(
{
"id": len(image_data),
"vector": result.embeddings[0].values,
"filename": fname,
}
)
print(f"Embedded {fname}")
# Create a new collection for images
image_collection = "image_collection"
if milvus_client.has_collection(image_collection):
milvus_client.drop_collection(image_collection)
milvus_client.create_collection(
collection_name=image_collection,
dimension=len(image_data[0]["vector"]),
metric_type="IP",
)
milvus_client.insert(collection_name=image_collection, data=image_data)
print(f"\nInserted {len(image_data)} image embeddings (dim={len(image_data[0]['vector'])})")
Embedded vanilla_rag.png
Embedded hyde.png
Embedded query_routing.png
Embedded self_reflection.png
Embedded hybrid_and_rerank.png
Embedded hierarchical_index.png
Inserted 6 image embeddings (dim=3072)
Ricerca cross-modale: Query di testo → Risultati di immagini
Ora usiamo una query di testo per cercare tra le incorporazioni di immagini. Poiché sia il testo che le immagini sono mappati nello stesso spazio di embedding, possiamo confrontarli direttamente.
from IPython.display import display, Image
text_queries = [
"How does a basic RAG pipeline work?",
"What is the hypothetical document embedding approach?",
"How to combine hybrid search with reranking?",
]
for query in text_queries:
query_embed = client.models.embed_content(
model="gemini-embedding-2-preview", contents=query
)
results = milvus_client.search(
collection_name=image_collection,
data=[query_embed.embeddings[0].values],
limit=1,
search_params={"metric_type": "IP", "params": {}},
output_fields=["filename"],
)
best = results[0][0]
print(f"\nQuery: {query}")
print(f"Match: {best['entity']['filename']} (score: {best['distance']:.4f})")
display(Image(filename=str(image_dir / best["entity"]["filename"]), width=600))
Query: How does a basic RAG pipeline work?
Match: vanilla_rag.png (score: 0.5132)
Pipeline Vanilla RAG
Query: What is the hypothetical document embedding approach?
Match: hyde.png (score: 0.4756)
HyDE
Query: How to combine hybrid search with reranking?
Match: hybrid_and_rerank.png (score: 0.5271)
Recupero e riclassificazione ibridi
Ottimo! Abbiamo costruito con successo una pipeline RAG con Milvus e Gemini e abbiamo dimostrato una ricerca cross-modale utilizzando query testuali per recuperare immagini rilevanti, il tutto alimentato dallo spazio di incorporamento unificato di gemini-embedding-2-preview.