Comment créer un RAG avec Milvus, QwQ-32B et Ollama
Les modèles d’IA évoluent rapidement, et QwQ-32B d’Alibaba fait récemment une entrée remarquée. Avec seulement 32 milliards de paramètres, ce modèle de raisonnement de taille moyenne offre des performances impressionnantes en raisonnement mathématique, en écriture créative et en génération de code, rivalisant avec des modèles beaucoup plus grands comme DeepSeek-R1. Son efficacité et sa précision sur différents benchmarks en font une option convaincante pour un large éventail d’applications d’IA.
11.jpeg
Figure 1 : Performances de QwQ-32B par rapport à d’autres modèles de premier plan (Source)
Au-delà de ses capacités, QwQ-32B se distingue par son accessibilité. Contrairement à certains modèles massifs qui nécessitent du matériel spécialisé, il fonctionne efficacement sur des GPU grand public comme le RTX 4090, ce qui en fait un excellent choix pour les développeurs et les chercheurs recherchant une IA de haute qualité sans ressources à l’échelle de l’entreprise. Cependant, en tant que modèle dense, QwQ-32B peut parfois rencontrer des difficultés avec le raisonnement complexe sur de longs textes et peut présenter des hallucinations, en particulier lors du traitement de fenêtres de contexte étendues.
Pour atténuer ces défis et améliorer sa fiabilité, nous pouvons intégrer QwQ-32B à la génération augmentée par récupération (RAG). Dans ce tutoriel, nous verrons comment créer un système RAG en utilisant QwQ-32B, Milvus (une base de données vectorielle haute performance) et Ollama. À la fin, vous disposerez d’un pipeline d’IA rationalisé et puissant, qui équilibre efficacité, précision et évolutivité.
Avant d’entrer dans les détails de la création d’une application RAG, passons rapidement en revue toutes les technologies que nous utiliserons pour ce tutoriel.
QwQ-32B vs. DeepSeek-R1
QwQ-32B et DeepSeek-R1 sont tous deux spécialisés dans le raisonnement, mais ce dernier adopte une architecture Mixture-of-Experts (MoE), tandis que QwQ-32B est un modèle dense classique.
- Les modèles MoE excellent dans les scénarios à forte intensité de connaissances (par exemple, les systèmes Q&A, la recherche d’informations) et le traitement de données à grande échelle, où différents experts gèrent des sous-ensembles de données distincts afin d’améliorer l’efficacité. Cependant, leur nombre massif de paramètres nécessite des ressources cloud ou des serveurs dédiés.
- Les modèles denses, bien qu’intensifs en calcul, sont mieux adaptés aux tâches de raisonnement profond et cohérent (par exemple, raisonnement logique complexe, compréhension approfondie de lecture) et à la conception d’algorithmes lorsque la performance en temps réel n’est pas indispensable. Leur taille compacte permet un déploiement local, mais ils peuvent parfois produire des messages redondants et inutiles.
| Modèle dense (QwQ-32B) | Modèle MoE (DeepSeek-R1) | |
|---|---|---|
| Avantages | Complexité d’entraînement plus faible ; processus simple | Efficacité computationnelle élevée (active des experts partiels pendant l’inférence) |
| Avantages | Raisonnement cohérent ; engagement complet des neurones pour la compréhension contextuelle | Capacité du modèle évolutive grâce à l’expansion des experts |
| Inconvénients | Coûts computationnels élevés pour l’entraînement et l’inférence | Entraînement complexe (nécessite des réseaux de gating et un équilibrage de charge des experts) |
| Inconvénients | Évolutivité limitée ; sujet au surapprentissage ; coûts de stockage/déploiement élevés | Surcoût de routage (calcul supplémentaire pour les décisions de gating) |
Aucune des deux architectures n’est parfaite. Le choix doit dépendre des exigences de la tâche, des caractéristiques des données, des ressources computationnelles disponibles et des contraintes budgétaires.
Je pense que nous nous orienterons vers des approches hybrides dans un avenir proche — en utilisant MoE pour la récupération initiale des connaissances et le traitement grossier, puis des modèles denses pour le raisonnement approfondi et l’affinement, afin d’obtenir de meilleures performances.
Pourquoi Milvus ?
Milvus est une base de données vectorielle open source, haute performance et hautement évolutive, capable de stocker, d’indexer et de rechercher des données non structurées à l’échelle du milliard grâce à des vector embeddings de grande dimension. Elle est idéale pour créer des applications d’IA modernes telles que la génération augmentée par récupération (RAG), la recherche sémantique, la recherche multimodale et les systèmes de recommandation.
Pour atténuer les hallucinations possibles de QwQ-32B (en réalité, celles possibles des LLM), Milvus stocke des connaissances externes ou privées et fournit des informations contextuelles au modèle QwQ-32B. Cela garantit que le modèle QwQ-32B peut générer des résultats plus précis.
Pourquoi Ollama ?
Ollama est une plateforme open source qui simplifie le déploiement local et la gestion des grands modèles de langage (LLMs). Elle offre une expérience conviviale, sans cloud, permettant de télécharger, d’installer et d’interagir facilement avec les modèles sans nécessiter de compétences techniques avancées. Elle permet aux utilisateurs de déployer rapidement des modèles via de simples outils en ligne de commande et une intégration Docker, et prend en charge la gestion des Modelfile afin de rationaliser le contrôle de version et la réutilisation des modèles.
De plus, Ollama propose une riche bibliothèque de modèles — allant des modèles à usage général aux modèles spécifiques à un domaine. Elle offre une compatibilité multiplateforme et matérielle — prenant en charge macOS, Linux, Windows et les déploiements de conteneurs Docker — avec détection automatique du GPU et priorisation de l’accélération. Elle fournit également des outils adaptés aux développeurs, tels qu’une REST API et un Python SDK, facilitant l’intégration des modèles dans diverses applications.
Et elle garantit la confidentialité des données et la flexibilité, permettant aux utilisateurs d’affiner, d’optimiser et de déployer des solutions basées sur l’IA entièrement sur leurs machines.
Maintenant, commençons à construire un pipeline RAG simple avec QwQ-32B comme modèle de langage, Milvus comme base de données vectorielle, et Ollama comme framework.
Préparation
Dépendances et environnement
! pip install pymilvus ollama
Remarque : Si vous utilisez Google, pour activer les dépendances qui viennent d’être installées, vous devrez peut-être redémarrer l’environnement d’exécution (cliquez sur le menu "Runtime" en haut de l’écran, puis sélectionnez "Restart session" dans le menu déroulant).
Préparer les données
Nous utilisons les pages FAQ de la documentation Milvus 2.4.x comme connaissances privées 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 depuis le dossier milvus_docs/en/faq. Pour chaque document, nous utilisons simplement "# " pour séparer le contenu du fichier, ce qui permet de séparer approximativement le contenu de chaque partie principale 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éparer le LLM et le modèle d’embedding
Ollama prend en charge plusieurs modèles à la fois pour les tâches basées sur les LLM et la génération d’embeddings, ce qui facilite le développement d’applications RAG. Pour cette configuration :
- Nous utiliserons QwQ (32B) comme LLM pour les tâches de génération de texte.
- Pour la génération d’embeddings, nous utiliserons mxbai-embed-large, un modèle de 334 M de paramètres optimisé pour la similarité sémantique.
Avant de commencer, assurez-vous que les deux modèles sont téléchargés localement :
! ollama pull mxbai-embed-large
! ollama pull qwq
Une fois ces modèles prêts, nous pouvons procéder à la mise en œuvre de workflows de génération pilotée par LLM et de récupération basée sur les embeddings.
import ollama
from ollama import Client
ollama_client = Client(host="http://localhost:11434")
def emb_text(text):
response = ollama_client.embeddings(model="mxbai-embed-large", prompt=text)
return response["embedding"]
Générez un embedding de test et affichez sa dimension ainsi que ses premiers éléments.
test_embedding = emb_text("This is a test")
embedding_dim = len(test_embedding)
print(embedding_dim)
print(test_embedding[:10])
1024
[0.23217937350273132, 0.42540550231933594, 0.19742339849472046, 0.4618139863014221, -0.46017369627952576, -0.14087969064712524, -0.18214142322540283, -0.07724273949861526, 0.40015509724617004, 0.8331164121627808]
Charger les données dans Milvus
Créer la collection
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Concernant la configuration des paramètres de MilvusClient :
- Définir le
uricomme un fichier local, par exemple./milvus.db, est la méthode la plus pratique, car cela utilise automatiquement Milvus Lite pour stocker toutes les données dans ce fichier. - Si vous disposez d’un grand volume de données, vous pouvez configurer un serveur Milvus plus performant sur docker or kubernetes. Dans cette configuration, veuillez utiliser le
uridu serveur, par exemplehttp://localhost:19530, comme votreuri. - Si vous souhaitez utiliser Zilliz Cloud, le service cloud entièrement géré pour Milvus, ajustez le
uriet letoken, qui correspondent au Public Endpoint and Api key dans Zilliz Cloud.
Vérifiez si la collection existe déjà et supprimez-la si c’est le cas.
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 nous ne spécifions 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 dans le schéma et leurs valeurs.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Insérer les données
Parcourez les lignes de texte, créez des embeddings, puis insérez les données dans Milvus.
Voici un nouveau champ text, qui est un champ non 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 à haut niveau.
from tqdm import tqdm
data = []
for i, line in enumerate(tqdm(text_lines, desc="Creating embeddings")):
data.append({"id": i, "vector": emb_text(line), "text": line})
milvus_client.insert(collection_name=collection_name, data=data)
Creating embeddings: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████| 72/72 [00:06<00:00, 11.86it/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 pipeline RAG
Récupérer des données pour une requête
Spécifions une question fréquente sur Milvus.
question = "How is data stored in milvus?"
Recherchez la question dans la collection et récupérez les 3 meilleures correspondances sémantiques.
search_res = milvus_client.search(
collection_name=collection_name,
data=[
emb_text(question)
], # Use the `emb_text` function to convert the question to an embedding vector
limit=3, # Return top 3 results
search_params={"metric_type": "IP", "params": {}}, # Inner product distance
output_fields=["text"], # Return the text field
)
Jetons un œil aux résultats de recherche de la 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###",
231.9922637939453
],
[
"How does Milvus flush data?\n\nMilvus returns success when inserted data are loaded to the message queue. However, the data are not yet flushed to the disk. Then Milvus' data node writes the data in the message queue to persistent storage as incremental logs. If `flush()` is called, the data node is forced to write all data in the message queue to persistent storage immediately.\n\n###",
226.54090881347656
],
[
"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###",
210.63682556152344
]
]
Utiliser un LLM pour obtenir une réponse RAG
Convertissez les documents récupérés au format chaîne.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Définissez les prompts système et utilisateur pour le LLM. Ce prompt est assemblé avec les documents récupérés depuis Milvus.
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 le modèle QwQ (32B) fourni par Ollama pour générer une réponse basée sur les prompts.
from ollama import chat
from ollama import ChatResponse
response: ChatResponse = ollama_client.chat(
model="qwq",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT},
],
)
print(response["message"]["content"])
<think>
D’accord, je dois répondre à la question « Comment les données sont-elles stockées dans Milvus ? » en utilisant le contexte fourni. Je vais commencer par lire attentivement le contexte.
Tout d’abord, en examinant la première section de contexte intitulée « Où Milvus stocke-t-il les données ? ». Elle mentionne que Milvus gère deux types de données : les données insérées et les métadonnées.
Les données insérées incluent les vecteurs, les données scalaires et le schéma de collection, stockés sous forme de journaux incrémentaux dans un stockage persistant. Les backends pris en charge sont listés, comme MinIO, S3, GCS, etc. Je dois donc noter que les données insérées vont vers ces stockages d’objets via des journaux incrémentaux.
Ensuite, les métadonnées sont stockées dans etcd pour chaque module Milvus. C’est simple : les métadonnées dans etcd.
Le deuxième contexte sur le vidage des données explique que les données vont d’abord dans la file de messages, puis vers le stockage persistant lorsqu’elles sont vidées. Mais cela concerne peut-être davantage le processus que l’endroit où elles sont stockées, donc il faut peut-être mentionner que l’insertion se fait d’abord dans la file de messages avant d’être écrite.
La troisième section sur la taille maximale du jeu de données mentionne les contraintes matérielles : la mémoire pour les données interrogeables et le stockage pour la persistance. Cela pourrait être lié à la manière dont les données sont gérées en termes de mémoire par rapport au disque. L’utilisateur a demandé « comment les données sont stockées », ce qui pourrait inclure à la fois où (comme les stockages d’objets, etcd) et le processus (file de messages puis journaux persistants).
En rassemblant tout cela, la réponse devrait mentionner :
- Deux types : données insérées et métadonnées.
Pour les données insérées :
- Stockées sous forme de journaux incrémentaux dans un stockage persistant en utilisant des backends comme MinIO, S3, etc.
- D’abord chargées dans des files de messages, puis écrites sur disque. Le flush() force l’écriture immédiate si nécessaire.
Métadonnées :
- Stockées dans etcd pour chaque module.
De plus, la partie matérielle indique que la mémoire limite la taille des données interrogeables et que le stockage (comme les stockages d’objets) limite la taille totale du jeu de données. Il faut donc peut-être mentionner ces facteurs dans la façon dont les données sont stockées, en tenant compte des contraintes de capacité.
Je devrais structurer cela clairement, peut-être avec des puces ou des sections sur les données insérées par rapport aux métadonnées, puis les processus comme le vidage, et enfin les limitations liées au matériel.
</think>
Les données dans Milvus sont stockées de deux manières principales :
1. **Données insérées** :
- Incluent les données vectorielles, les données scalaires et le schéma de collection.
- Stockées sous forme de **journaux incrémentaux** dans un **stockage persistant**, pris en charge par des backends de stockage d’objets tels que MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS et Tencent Cloud Object Storage (COS).
- Initialement chargées dans une **file de messages** lors de l’insertion. Milvus renvoie un succès lors de l’insertion dans la file, mais n’écrit pas immédiatement sur le disque. Un processus en arrière-plan écrit ensuite ces données dans le stockage persistant. La méthode `flush()` peut forcer l’écriture immédiate de toutes les données en file d’attente vers le stockage.
2. **Métadonnées** :
- Générées en interne par les modules Milvus (par exemple, configurations de collection, partitions).
- Stockées dans **etcd**, un magasin clé-valeur distribué.
**Considérations matérielles** :
- **Mémoire** : La quantité de données que Milvus peut interroger est limitée par la mémoire système, car il charge les collections/partitions spécifiées en mémoire pour les requêtes.
- **Capacité de stockage** : La taille maximale du jeu de données est contrainte par le backend de stockage sous-jacent (par exemple, le stockage d’objets), qui stocke toutes les données insérées et le schéma de manière incrémentale.
Excellent ! Nous avons réussi à construire un pipeline RAG avec Milvus, QWQ-32B et Ollama.
Conclusion
En intégrant ces technologies, nous pouvons construire un système RAG qui exploite Milvus pour un stockage et une récupération efficaces des données, ainsi que les capacités de raisonnement de QwQ-32B pour générer des réponses précises et contextuellement pertinentes. Ollama simplifie le processus de déploiement, permettant une configuration fluide et efficace. Cette combinaison est particulièrement bénéfique pour les applications nécessitant une récupération et une génération d’informations en temps réel, telles que le tutorat assisté par IA, la résolution de problèmes basée sur la logique, et plus encore.
Nous espérons qu’en suivant ce tutoriel, vous pourrez créer des systèmes RAG adaptés à vos besoins et tirer réellement profit de vos propres créations.
Continuer à lire

Migrating from S3 Vectors to Zilliz Cloud: Unlocking the Power of Tiered Storage
Learn how Zilliz Cloud bridges cost and performance with tiered storage and enterprise-grade features, and how to migrate data from AWS S3 Vectors to Zilliz Cloud.

Announcing the General Availability of Single Sign-On (SSO) on Zilliz Cloud
SSO is GA on Zilliz Cloud, delivering the enterprise-grade identity management capabilities your teams need to deploy vectorDB with confidence.

Optimizing Embedding Model Selection with TDA Clustering: A Strategic Guide for Vector Databases
Discover how Topological Data Analysis (TDA) reveals hidden embedding model weaknesses and helps optimize vector database performance.



