So erstellen Sie RAG mit Milvus, QwQ-32B und Ollama
KI-Modelle entwickeln sich schnell weiter, und Alibabas QwQ-32B sorgt in letzter Zeit für einen starken Auftritt. Mit nur 32 Milliarden Parametern liefert dieses mittelgroße Reasoning-Modell beeindruckende Leistung bei mathematischem Schlussfolgern, kreativem Schreiben und Codegenerierung—und konkurriert mit deutlich größeren Modellen wie DeepSeek-R1. Seine Effizienz und Genauigkeit über Benchmarks hinweg machen es zu einer überzeugenden Option für eine breite Palette von KI-Anwendungen.
11.jpeg
Abbildung 1: Leistung von QwQ-32B im Vergleich zu anderen führenden Modellen (Quelle)
Über seine Fähigkeiten hinaus zeichnet sich QwQ-32B durch seine Zugänglichkeit aus. Im Gegensatz zu einigen riesigen Modellen, die spezialisierte Hardware erfordern, läuft es effizient auf Consumer-GPUs wie der RTX 4090, was es zu einer ausgezeichneten Wahl für Entwickler und Forscher macht, die hochwertige KI ohne Ressourcen auf Unternehmensniveau suchen. Als dichtes Modell kann QwQ-32B jedoch manchmal Schwierigkeiten mit komplexem Reasoning über lange Texte haben und Halluzinationen aufweisen, insbesondere beim Umgang mit erweiterten Kontextfenstern.
Um diese Herausforderungen zu mindern und seine Zuverlässigkeit zu erhöhen, können wir QwQ-32B mit Retrieval-Augmented Generation (RAG) integrieren. In diesem Tutorial gehen wir durch, wie man ein RAG-System mit QwQ-32B, Milvus (einer leistungsstarken Vektordatenbank) und Ollama erstellt. Am Ende verfügen Sie über eine schlanke, leistungsstarke KI-Pipeline, die Effizienz, Genauigkeit und Skalierbarkeit ausbalanciert.
Bevor wir ins Detail gehen, wie man eine RAG-Anwendung erstellt, werfen wir kurz einen Blick auf alle Technologien, die wir für dieses Tutorial verwenden werden.
QwQ-32B vs. DeepSeek-R1
Sowohl QwQ-32B als auch DeepSeek-R1 sind auf Reasoning spezialisiert, aber Letzteres verwendet eine Mixture-of-Experts (MoE)-Architektur, während QwQ-32B ein klassisches dichtes Modell ist.
- MoE-Modelle zeichnen sich in wissensintensiven Szenarien aus (z. B. Q&A-Systeme, Information Retrieval) und bei der Verarbeitung großer Datenmengen, bei der verschiedene Experten unterschiedliche Datenuntergruppen bearbeiten, um die Effizienz zu verbessern. Ihre enorme Parameteranzahl erfordert jedoch Cloud- oder dedizierte Serverressourcen.
- Dichte Modelle sind zwar rechenintensiv, eignen sich jedoch besser für tiefgehende, kohärente Reasoning-Aufgaben (z. B. komplexes logisches Schlussfolgern, vertieftes Leseverständnis) und Algorithmendesign, bei denen Echtzeitleistung kein Muss ist. Ihre kompakte Größe ermöglicht eine lokale Bereitstellung, kann aber manchmal redundante und unnötige Nachrichten erzeugen.
| Dichtes Modell (QwQ-32B) | MoE-Modell (DeepSeek-R1) | |
|---|---|---|
| Vorteile | Geringere Trainingskomplexität; unkomplizierter Prozess | Hohe Recheneffizienz (aktiviert während der Inferenz nur einen Teil der Experten) |
| Vorteile | Kohärentes Reasoning; vollständige Neuronenaktivierung für kontextuelles Verständnis | Skalierbare Modellkapazität durch Expertenerweiterung |
| Nachteile | Hohe Rechenkosten für Training und Inferenz | Komplexes Training (erfordert Gating-Netzwerke und Lastverteilung zwischen Experten) |
| Nachteile | Begrenzte Skalierbarkeit; anfällig für Overfitting; hohe Speicher-/Bereitstellungskosten | Routing-Overhead (zusätzliche Berechnung für Gating-Entscheidungen) |
Keine der beiden Architekturen ist perfekt. Die Wahl sollte von den Aufgabenanforderungen, den Dateneigenschaften, den verfügbaren Rechenressourcen und den Budgetbeschränkungen abhängen.
Ich glaube, dass wir in naher Zukunft hybride Ansätze sehen werden—MoE für die anfängliche Wissensabfrage und grobe Verarbeitung und anschließend Dense Models für tiefgehendes Reasoning und Verfeinerung, um bessere Leistungen zu erzielen.
Warum Milvus?
Milvus ist eine Open-Source-, leistungsstarke und hoch skalierbare Vektordatenbank, die unstrukturierte Daten im Milliardenmaßstab durch hochdimensionale Vector Embeddings speichern, indizieren und durchsuchen kann. Sie eignet sich perfekt für den Aufbau moderner KI-Anwendungen wie Retrieval Augmented Generation (RAG), semantische Suche, multimodale Suche und Empfehlungssysteme.
Um mögliche Halluzinationen von QwQ-32B (tatsächlich die möglichen Halluzinationen von LLMs) zu mindern, speichert Milvus externes oder privates Wissen und stellt Kontextinformationen mit dem QwQ-32B-Modell bereit. Dadurch wird sichergestellt, dass das QwQ-32B-Modell genauere Ergebnisse generieren kann.
Warum Ollama?
Ollama ist eine Open-Source-Plattform, die die lokale Bereitstellung und Verwaltung von Large Language Models (LLMs) vereinfacht. Sie bietet eine benutzerfreundliche, cloudfreie Erfahrung und ermöglicht müheloses Herunterladen, Installieren und Interagieren mit Modellen, ohne fortgeschrittene technische Kenntnisse zu erfordern. Sie ermöglicht es Nutzern, Modelle schnell über einfache Befehlszeilentools und Docker-Integration bereitzustellen, und unterstützt die Modelfile-Verwaltung, um Versionskontrolle und Wiederverwendung von Modellen zu optimieren.
Darüber hinaus bietet Ollama eine umfangreiche Bibliothek von Modellen—von allgemeinen bis hin zu domänenspezifischen. Es bietet plattformübergreifende und Hardware-Kompatibilität—mit Unterstützung für macOS, Linux, Windows und Docker-Container-Bereitstellungen—mit automatischer GPU-Erkennung und Priorisierung der Beschleunigung. Es stellt außerdem entwicklerfreundliche Tools wie REST API und Python SDK bereit, die eine einfache Integration von Modellen in verschiedene Anwendungen ermöglichen.
Und es gewährleistet Datenschutz und Flexibilität, sodass Nutzer KI-gesteuerte Lösungen vollständig auf ihren eigenen Maschinen feinabstimmen, optimieren und bereitstellen können.
Beginnen wir nun damit, eine einfache RAG-Pipeline mit QwQ-32B als Sprachmodell, Milvus als Vektordatenbank und Ollama als Framework aufzubauen.
Vorbereitung
Abhängigkeiten und Umgebung
! pip install pymilvus ollama
Hinweis: Wenn Sie Google verwenden, müssen Sie möglicherweise die Laufzeit neu starten (klicken Sie oben auf dem Bildschirm auf das Menü "Runtime" und wählen Sie im Dropdown-Menü "Restart session" aus), um die gerade installierten Abhängigkeiten zu aktivieren.
Daten vorbereiten
Wir verwenden die FAQ-Seiten aus der Milvus Documentation 2.4.x als privates Wissen in unserem RAG, was eine gute Datenquelle für eine einfache RAG-Pipeline ist.
Laden Sie die ZIP-Datei herunter und extrahieren Sie die Dokumente in den Ordner 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
Wir laden alle Markdown-Dateien aus dem Ordner milvus_docs/en/faq. Für jedes Dokument verwenden wir einfach "# ", um den Inhalt in der Datei zu trennen, wodurch der Inhalt jedes Hauptteils der Markdown-Datei grob getrennt werden kann.
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("# ")
LLM und Embedding Model vorbereiten
Ollama unterstützt mehrere Modelle sowohl für LLM-basierte Aufgaben als auch für die Embedding-Generierung, wodurch die Entwicklung von RAG-Anwendungen vereinfacht wird. Für dieses Setup:
- Wir verwenden QwQ (32B) als unser LLM für Aufgaben zur Textgenerierung.
- Für die Embedding-Generierung verwenden wir mxbai-embed-large, ein Modell mit 334 Mio. Parametern, das für semantische Ähnlichkeit optimiert ist.
Stellen Sie vor dem Start sicher, dass beide Modelle lokal abgerufen wurden:
! ollama pull mxbai-embed-large
! ollama pull qwq
Wenn diese Modelle bereit sind, können wir mit der Implementierung von LLM-gesteuerter Generierung und Embedding-basierten Retrieval-Workflows fortfahren.
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"]
Generieren Sie ein Test-Embedding und geben Sie dessen Dimension und die ersten paar Elemente aus.
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]
Daten in Milvus laden
Die Collection erstellen
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Zur Konfiguration der MilvusClient-Parameter:
- Das Festlegen der
urials lokale Datei, z. B../milvus.db, ist die bequemste Methode, da dadurch automatisch Milvus Lite verwendet wird, um alle Daten in dieser Datei zu speichern. - Wenn Sie eine große Datenmenge haben, können Sie einen leistungsfähigeren Milvus-Server auf docker or kubernetes einrichten. Verwenden Sie in diesem Setup bitte die Server-
uri, z. B.http://localhost:19530, als Ihreuri. - Wenn Sie Zilliz Cloud, den vollständig verwalteten Cloud-Dienst für Milvus, verwenden möchten, passen Sie die
uriund dastokenan, die dem Public Endpoint and Api key in Zilliz Cloud entsprechen.
Prüfen Sie, ob die Collection bereits existiert, und löschen Sie sie, falls dies der Fall ist.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Erstellen Sie eine neue Collection mit den angegebenen Parametern.
Wenn wir keine Feldinformationen angeben, erstellt Milvus automatisch ein standardmäßiges id-Feld für den Primärschlüssel und ein vector-Feld zum Speichern der Vektordaten. Ein reserviertes JSON-Feld wird verwendet, um nicht im Schema definierte Felder und deren Werte zu speichern.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Inner product distance
consistency_level="Strong", # Strong consistency level
)
Daten einfügen
Iterieren Sie durch die Textzeilen, erstellen Sie Embeddings und fügen Sie die Daten anschließend in Milvus ein.
Hier gibt es ein neues Feld text, das ein nicht definiertes Feld im Collection-Schema ist. Es wird automatisch dem reservierten dynamischen JSON-Feld hinzugefügt, das auf hoher Ebene wie ein normales Feld behandelt werden kann.
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}
Eine RAG-Pipeline erstellen
Daten für eine Abfrage abrufen
Legen wir eine häufige Frage zu Milvus fest.
question = "How is data stored in milvus?"
Suchen Sie nach der Frage in der Collection und rufen Sie die semantischen Top-3-Treffer ab.
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
)
Werfen wir einen Blick auf die Suchergebnisse der Abfrage.
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
]
]
LLM verwenden, um eine RAG-Antwort zu erhalten
Konvertieren Sie die abgerufenen Dokumente in ein Zeichenkettenformat.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Definieren Sie System- und Benutzer-Prompts für LLM. Dieser Prompt wird mit den aus Milvus abgerufenen Dokumenten zusammengestellt.
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>
"""
Verwenden Sie das von Ollama bereitgestellte Modell QwQ (32B), um basierend auf den Prompts eine Antwort zu generieren.
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>
Okay, ich muss die Frage „How is data stored in Milvus?“ anhand des bereitgestellten Kontexts beantworten. Ich beginne damit, den Kontext sorgfältig durchzulesen.
Zuerst schaue ich mir den ersten Kontextabschnitt mit dem Titel „Where does Milvus store data?“ an. Darin wird erwähnt, dass Milvus zwei Arten von Daten verarbeitet: eingefügte Daten und Metadaten.
Eingefügte Daten umfassen Vektoren, skalare Daten und das Collection-Schema, die als inkrementelle Logs im persistenten Speicher gespeichert werden. Die unterstützten Backends werden aufgelistet, z. B. MinIO, S3, GCS usw. Ich sollte also erwähnen, dass eingefügte Daten über inkrementelle Logs in diesen Objektspeichern landen.
Dann werden Metadaten in etcd für jedes Milvus-Modul gespeichert. Das ist klar — Metadaten in etcd.
Der zweite Kontext zum Flushen von Daten erklärt, dass Daten zunächst in die Message Queue gelangen und dann beim Flushen in den persistenten Speicher geschrieben werden. Das betrifft eher den Prozess als den Speicherort, daher sollte ich vielleicht erwähnen, dass das Einfügen zuerst in die Message Queue erfolgt, bevor die Daten ausgeschrieben werden.
Der dritte Abschnitt zur maximalen Dataset-Größe erwähnt Hardware-Einschränkungen: Speicher für abfragbare Daten und Speicherplatz für Persistenz. Das könnte damit zusammenhängen, wie Daten hinsichtlich Arbeitsspeicher vs. Festplatte verwaltet werden. Der Nutzer fragte „how is data stored“, was sowohl den Ort (z. B. Objektspeicher, etcd) als auch den Prozess (Message Queue, dann persistente Logs) einschließen könnte.
Alles zusammengenommen sollte die Antwort Folgendes erwähnen:
- Zwei Typen: eingefügte Daten und Metadaten.
Für eingefügte Daten:
- Werden als inkrementelle Logs im persistenten Speicher gespeichert, unter Verwendung von Backends wie MinIO, S3 usw.
- Werden zunächst in Message Queues geladen und dann auf die Festplatte geschrieben. Die Methode flush() erzwingt bei Bedarf das sofortige Schreiben.
Metadaten:
- Werden in etcd für jedes Modul gespeichert.
Außerdem sagt der Hardware-Teil, dass der Arbeitsspeicher die Größe der abfragbaren Daten begrenzt und der Speicherplatz (wie die Objektspeicher) die gesamte Dataset-Größe begrenzt. Vielleicht sollte ich diese Faktoren als Teil davon erwähnen, wie Daten gespeichert werden, unter Berücksichtigung von Kapazitätsbeschränkungen.
Ich sollte dies klar strukturieren, vielleicht mit Aufzählungspunkten oder Abschnitten zu eingefügten Daten vs. Metadaten, dann Prozesse wie Flushen und schließlich Einschränkungen basierend auf der Hardware.
</think>
Daten in Milvus werden hauptsächlich auf zwei Arten gespeichert:
1. **Eingefügte Daten**:
- Umfassen Vektordaten, skalare Daten und das Collection-Schema.
- Werden als **inkrementelle Logs** in **persistentem Speicher** gespeichert, unterstützt durch Objektspeicher-Backends wie MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS und Tencent Cloud Object Storage (COS).
- Werden beim Einfügen zunächst in eine **Message Queue** geladen. Milvus gibt nach dem Einfügen in die Queue eine Erfolgsmeldung zurück, schreibt jedoch nicht sofort auf die Festplatte. Ein Hintergrundprozess schreibt diese Daten später in den persistenten Speicher. Die Methode `flush()` kann das sofortige Schreiben aller in der Queue befindlichen Daten in den Speicher erzwingen.
2. **Metadaten**:
- Werden intern von Milvus-Modulen erzeugt (z. B. Collection-Konfigurationen, Partitionen).
- Werden in **etcd** gespeichert, einem verteilten Key-Value-Store.
**Hardware-Überlegungen**:
- **Arbeitsspeicher**: Die Datenmenge, die Milvus abfragen kann, ist durch den Systemspeicher begrenzt, da es angegebene Collections/Partitionen für Abfragen in den Arbeitsspeicher lädt.
- **Speicherkapazität**: Die maximale Dataset-Größe wird durch das zugrunde liegende Speicher-Backend (z. B. Objektspeicher) begrenzt, das alle eingefügten Daten und Schemas inkrementell speichert.
Großartig! Wir haben erfolgreich eine RAG-Pipeline mit Milvus, QWQ-32B und Ollama erstellt.
Fazit
Durch die Integration dieser Technologien können wir ein RAG-System aufbauen, das Milvus für effiziente Datenspeicherung und -abfrage sowie die Reasoning-Fähigkeiten von QwQ-32B nutzt, um genaue und kontextuell relevante Antworten zu generieren. Ollama vereinfacht den Bereitstellungsprozess und ermöglicht eine nahtlose und effiziente Einrichtung. Diese Kombination ist besonders vorteilhaft für Anwendungen, die Informationsabruf und Generierung in Echtzeit erfordern, wie KI-gestütztes Tutoring, logikbasierte Problemlösung und mehr.
Wir hoffen, dass Sie durch das Befolgen dieses Tutorials RAG-Systeme erstellen können, die auf Ihre Bedürfnisse zugeschnitten sind, und wirklich von Ihren eigenen Kreationen profitieren.
Weiterlesen

From Vector Database to Vector Lakebase
Zilliz offers a fully managed Vector Lakebase powered by Milvus, unifying real-time vector search, lake-scale discovery, and Al data operations.

Zilliz Cloud Now Available in AWS Europe (Ireland)
Zilliz Cloud launches in AWS eu-west-1 (Ireland) — bringing low-latency vector search, EU data residency, and full GDPR-ready infrastructure to European AI teams. Now live across 30 regions on five cloud providers.

What is the K-Nearest Neighbors (KNN) Algorithm in Machine Learning?
KNN is a supervised machine learning technique and algorithm for classification and regression. This post is the ultimate guide to KNN.



