Open In Colab GitHub Repository

Développez des applications RAG avec Milvus et Gemini

L'API Gemini et Google AI Studio vous aident à vous lancer avec les derniers modèles de Google et à transformer vos idées en applications évolutives. Gemini donne accès à de puissants modèles linguistiques tels que Gemini-2.5-Flash et Gemini-2.5-Pro pour des tâches telles que la génération de texte, le traitement de documents, la vision, l’analyse audio, et bien plus encore. Il propose également Gemini Embedding 2, un modèle d’embedding multimodal prenant en charge le texte, les images, la vidéo, l’audio et les documents PDF, avec des dimensions de sortie flexibles grâce à l’apprentissage de représentation Matryoshka (Matryoshka Representation Learning). L’API vous permet d’introduire un contexte long comportant des millions de tokens, d’affiner les modèles pour des tâches spécifiques, de générer des sorties structurées telles que JSON, et d’exploiter des fonctionnalités telles que la recherche sémantique et l’exécution de code.

Dans ce tutoriel, nous allons vous montrer comment construire un pipeline RAG (Retrieval-Augmented Generation) avec Milvus et Gemini. Nous utiliserons le modèle Gemini pour générer des réponses à partir d’une requête donnée, enrichies d’informations pertinentes extraites de Milvus.

Préparation

Dépendances et environnement

Commencez par installer les paquets requis :

$ pip install --upgrade pymilvus milvus-lite google-genai requests tqdm

Si vous utilisez Google Colab, pour activer les dépendances que vous venez d’installer, vous devrez peut-être redémarrer le runtime (cliquez sur le menu « Runtime » en haut de l’écran, puis sélectionnez « Redémarrer la session » dans le menu déroulant).

Vous devez d’abord vous connecter à la plateforme Google AI Studio et définir la clé API GEMINI_API_KEY en tant que variable d’environnement.

import os

os.environ["GEMINI_API_KEY"] = "***********"

Préparez les données

Nous utilisons les pages FAQ de la documentation Milvus 2.4.x comme base de connaissances privée dans notre RAG, ce qui constitue une bonne source de données pour un pipeline RAG simple.

Téléchargez le fichier zip et extrayez les documents dans le dossier 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

Nous chargeons tous les fichiers Markdown du dossier milvus_docs/en/faq. Pour chaque document, nous utilisons simplement « # » pour séparer le contenu du fichier, ce qui permet de délimiter grossièrement les différentes parties principales du fichier 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("# ")

Préparation du LLM et du modèle d’embedding

Nous utilisons le dossier « gemini-2.5-flash » comme LLM, et le modèle « gemini-embedding-2-preview » comme modèle d’embedding. « gemini-embedding-2-preview » est le dernier modèle d’embedding multimodal de Google, prenant en charge le texte, les images, la vidéo, l’audio et les documents PDF avec des dimensions de sortie flexibles (128–3 072) via le Matryoshka Representation Learning.

Essayons de générer une réponse de test à partir du 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.

Générons un encodage test et affichons sa dimension ainsi que ses premiers éléments.

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]

Charger les données dans Milvus

Créer la collection

Initialisons le client Milvus et configurons notre collection :

from pymilvus import MilvusClient

milvus_client = MilvusClient(uri="./milvus_demo.db")

collection_name = "my_rag_collection"

En ce qui concerne l’argument « MilvusClient » :

  • Définir l’ 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 disposez d’un volume important de données, 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 uri.
  • Si vous souhaitez utiliser Zilliz Cloud, le service cloud entièrement géré pour Milvus, modifiez les paramètres « uri » et « token », qui correspondent respectivement au point de terminaison public et à la clé API dans Zilliz Cloud.

Vérifiez si la collection existe déjà et supprimez-la le cas échéant.

if milvus_client.has_collection(collection_name):
    milvus_client.drop_collection(collection_name)

Créez une nouvelle collection avec les paramètres spécifiés.

Si vous ne spécifiez aucune information de champ, Milvus créera automatiquement un champ « id » par défaut pour la clé primaire, ainsi qu’un champ « vector » pour stocker les données vectorielles. Un champ JSON réservé est utilisé pour stocker les champs non définis par le schéma et leurs valeurs.

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
)

Insérer des données

Parcourez les lignes de texte, créez des représentations, puis insérez les données dans Milvus.

Voici un nouveau champ « text », qui n’est pas défini dans le schéma de la collection. Il sera automatiquement ajouté au champ dynamique JSON réservé, qui peut être traité comme un champ normal à un niveau élevé.

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}

Construire un RAG

Récupérer les données pour une requête

Spécifions une question fréquente concernant Milvus.

question = "How is data stored in milvus?"

Recherchons cette question dans la collection et récupérons les trois meilleurs résultats sémantiques.

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
)

Examinons les résultats de recherche de cette requête

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
    ]
]

Utiliser un LLM pour obtenir une réponse RAG

Convertissons les documents récupérés au format chaîne de caractères.

context = "\n".join(
    [line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)

Définissons les invites système et utilisateur pour le modèle linguistique. Cette invite est constituée à partir des documents récupérés depuis 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>
"""

Utilisez Gemini pour générer une réponse à partir des invites.

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.

Comme l'gemini-embedding-2-preview e mappe le texte, les images et d'autres modalités dans le même espace d'embedding, nous pouvons effectuer une recherche intermodale — par exemple, en utilisant une requête textuelle pour trouver les images les plus pertinentes.

Préparation des données d’images

Nous téléchargeons un ensemble de schémas d’architecture RAG depuis le référentiel Milvus Bootcamp afin de l’utiliser comme jeu de données d’images.

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

Intégrer les images et les stocker dans Milvus

Nous lisons chaque image sous forme d’octets et la transmettons à gemini-embedding-2-preview pour générer des représentations, puis nous les stockons dans une nouvelle collection 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)

Recherche cross-modale : requête textuelle → résultats sous forme d’images

Utilisons maintenant une requête textuelle pour effectuer une recherche parmi les représentations d’images. Le texte et les images étant tous deux mappés dans le même espace de représentation, nous pouvons les comparer directement.

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)

Vanilla RAG Pipeline Pipeline RAG de base

Query: What is the hypothetical document embedding approach?
Match: hyde.png (score: 0.4756)

HyDE HyDE

Query: How to combine hybrid search with reranking?
Match: hybrid_and_rerank.png (score: 0.5271)

Hybrid Retrieval and Reranking Récupération et reclassement hybrides

Parfait ! Nous avons réussi à construire un pipeline RAG avec Milvus et Gemini, et avons démontré la recherche cross-modale à l’aide de requêtes textuelles pour récupérer des images pertinentes — le tout grâce à l’espace d’embedding unifié de gemini-embedding-2-preview.