Come creare un RAG con Milvus, QwQ-32B e Ollama
I modelli di IA si stanno evolvendo rapidamente, e QwQ-32B di Alibaba ha fatto di recente un ingresso notevole. Con soli 32 miliardi di parametri, questo modello di ragionamento di medie dimensioni offre prestazioni impressionanti nel ragionamento matematico, nella scrittura creativa e nella generazione di codice, rivaleggiando con modelli molto più grandi come DeepSeek-R1. La sua efficienza e accuratezza nei benchmark lo rendono un’opzione interessante per un’ampia gamma di applicazioni di IA.
11.jpeg
Figura 1: Le prestazioni di QwQ-32B rispetto ad altri modelli leader (Fonte)
Oltre alle sue capacità, QwQ-32B si distingue per la sua accessibilità. A differenza di alcuni modelli enormi che richiedono hardware specializzato, funziona in modo efficiente su GPU di livello consumer come la RTX 4090, rendendolo un’ottima scelta per sviluppatori e ricercatori che cercano un’IA di alta qualità senza risorse su scala enterprise. Tuttavia, essendo un modello denso, QwQ-32B può talvolta avere difficoltà con il ragionamento complesso su testi lunghi e può manifestare allucinazioni, in particolare quando gestisce finestre di contesto estese.
Per mitigare queste sfide e migliorarne l’affidabilità, possiamo integrare QwQ-32B con la Retrieval-Augmented Generation (RAG). In questo tutorial, illustreremo come creare un sistema RAG utilizzando QwQ-32B, Milvus (un database vettoriale ad alte prestazioni) e Ollama. Alla fine, avrai una pipeline di IA snella e potente che bilancia efficienza, accuratezza e scalabilità.
Prima di entrare nei dettagli su come creare un’applicazione RAG, passiamo rapidamente in rassegna tutte le tecnologie che useremo per questo tutorial.
QwQ-32B vs. DeepSeek-R1
Sia QwQ-32B sia DeepSeek-R1 sono specializzati nel ragionamento, ma quest’ultimo adotta un’architettura Mixture-of-Experts (MoE), mentre QwQ-32B è un classico modello denso.
- I modelli MoE eccellono in scenari ad alta intensità di conoscenza (ad es. sistemi di Q&A, recupero delle informazioni) e nell’elaborazione di dati su larga scala, dove diversi esperti gestiscono sottoinsiemi di dati distinti per migliorare l’efficienza. Tuttavia, il loro enorme numero di parametri richiede risorse cloud o server dedicati.
- I modelli densi, sebbene computazionalmente intensivi, sono più adatti a compiti di ragionamento profondi e coerenti (ad es. ragionamento logico complesso, comprensione approfondita della lettura) e alla progettazione di algoritmi dove le prestazioni in tempo reale non sono indispensabili. Le loro dimensioni compatte consentono il deployment locale, ma talvolta possono produrre messaggi ridondanti e non necessari.
| Modello denso (QwQ-32B) | Modello MoE (DeepSeek-R1) | |
|---|---|---|
| Vantaggi | Minore complessità di training; processo lineare | Elevata efficienza computazionale (attiva esperti parziali durante l’inferenza) |
| Vantaggi | Ragionamento coerente; coinvolgimento completo dei neuroni per la comprensione contestuale | Capacità del modello scalabile tramite espansione degli esperti |
| Svantaggi | Elevati costi computazionali per training e inferenza | Training complesso (richiede reti di gating e bilanciamento del carico degli esperti) |
| Svantaggi | Scalabilità limitata; incline all’overfitting; elevati costi di archiviazione/deployment | Overhead di instradamento (calcolo aggiuntivo per le decisioni di gating) |
Nessuna delle due architetture è perfetta. La scelta dovrebbe dipendere dai requisiti del compito, dalle caratteristiche dei dati, dalle risorse computazionali disponibili e dai vincoli di budget.
Credo che nel prossimo futuro vedremo approcci ibridi: usare MoE per il recupero iniziale della conoscenza e l’elaborazione grossolana, e poi modelli dense per il ragionamento profondo e il perfezionamento, così da ottenere prestazioni migliori.
Perché Milvus?
Milvus è un database vettoriale open-source, ad alte prestazioni e altamente scalabile, in grado di archiviare, indicizzare e cercare dati non strutturati su scala di miliardi tramite embedding vettoriali ad alta dimensionalità. È perfetto per creare moderne applicazioni di IA come retrieval augmented generation (RAG), ricerca semantica, ricerca multimodale e sistemi di raccomandazione.
Per mitigare le possibili allucinazioni di QwQ-32B (di fatto, quelle possibili degli LLM), Milvus archivia conoscenza esterna o privata e fornisce informazioni contestuali con il modello QwQ-32B. Questo garantisce che il modello QwQ-32B possa generare risultati più accurati.
Perché Ollama?
Ollama è una piattaforma open-source che semplifica la distribuzione e la gestione locale dei large language models (LLMs). Offre un’esperienza intuitiva e senza cloud, consentendo download, installazione e interazione dei modelli senza sforzo e senza richiedere competenze tecniche avanzate. Permette agli utenti di distribuire rapidamente i modelli tramite semplici strumenti da riga di comando e integrazione Docker, e supporta la gestione dei Modelfile per semplificare il controllo delle versioni e il riutilizzo dei modelli.
Inoltre, Ollama offre una ricca libreria di modelli, da quelli general-purpose a quelli specifici per dominio. Fornisce compatibilità multipiattaforma e hardware, supportando macOS, Linux, Windows e distribuzioni in container Docker, con rilevamento automatico della GPU e prioritizzazione dell’accelerazione. Offre anche strumenti adatti agli sviluppatori come REST API e Python SDK, facilitando l’integrazione dei modelli in varie applicazioni.
E garantisce privacy dei dati e flessibilità, consentendo agli utenti di effettuare fine-tuning, ottimizzare e distribuire soluzioni basate sull’IA interamente sulle proprie macchine.
Ora, iniziamo a costruire una semplice pipeline RAG con QwQ-32B come modello linguistico, Milvus come database vettoriale e Ollama come framework.
Preparazione
Dipendenze e ambiente
! pip install pymilvus ollama
Nota: se stai usando Google, per abilitare le dipendenze appena installate, potrebbe essere necessario riavviare il runtime (fai clic sul menu "Runtime" nella parte superiore dello schermo e seleziona "Restart session" dal menu a discesa).
Preparare i dati
Usiamo le pagine FAQ della Milvus Documentation 2.4.x come conoscenza privata nel nostro RAG, che è una buona fonte di dati per una semplice pipeline RAG.
Scarica il file zip ed estrai 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 nel file, il che può separare approssimativamente il contenuto di ciascuna 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 embedding
Ollama supporta più modelli sia per attività basate su LLM sia per la generazione di embedding, rendendo semplice sviluppare applicazioni RAG. Per questa configurazione:
- Useremo QwQ (32B) come nostro LLM per le attività di generazione di testo.
- Per la generazione di embedding, useremo mxbai-embed-large, un modello da 334M parametri ottimizzato per la similarità semantica.
Prima di iniziare, assicurati che entrambi i modelli siano scaricati localmente:
! ollama pull mxbai-embed-large
! ollama pull qwq
Con questi modelli pronti, possiamo procedere a implementare flussi di lavoro di generazione guidata da LLM e retrieval basato su embedding.
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"]
Genera un embedding di test e stampa la sua dimensione e i primi elementi.
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]
Caricare i dati in Milvus
Creare la Collection
from pymilvus import MilvusClient
milvus_client = MilvusClient(uri="./milvus_demo.db")
collection_name = "my_rag_collection"
Per quanto riguarda la configurazione dei parametri di MilvusClient:
- Impostare
uricome file locale, ad es../milvus.db, è il metodo più comodo, poiché utilizza automaticamente Milvus Lite per archiviare tutti i dati in questo file. - Se hai una grande quantità di dati, puoi configurare un server Milvus più performante su docker o kubernetes. In questa configurazione, usa il
uridel server, ad es.http://localhost:19530, come tuouri. - Se vuoi usare Zilliz Cloud, il servizio cloud completamente gestito per Milvus, modifica
urietoken, che corrispondono a Public Endpoint e Api key in Zilliz Cloud.
Controlla se la collection esiste già ed eliminala se esiste.
if milvus_client.has_collection(collection_name):
milvus_client.drop_collection(collection_name)
Crea una nuova collection con i parametri specificati.
Se non specifichiamo alcuna informazione sui campi, Milvus creerà automaticamente un campo id predefinito per la chiave primaria e un campo vector per archiviare i dati vettoriali. Un campo JSON riservato viene utilizzato per archiviare campi non definiti nello schema e i loro valori.
milvus_client.create_collection(
collection_name=collection_name,
dimension=embedding_dim,
metric_type="IP", # Distanza del prodotto interno
consistency_level="Strong", # Livello di consistenza forte
)
Inserire i dati
Itera attraverso le righe di testo, crea gli embedding e quindi inserisci i dati in Milvus.
Ecco un nuovo campo text, che è un campo non definito nello schema della collection. Verrà aggiunto automaticamente al campo dinamico JSON riservato, che può essere trattato come un normale campo a livello alto.
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}
Creare una pipeline RAG
Recuperare dati per una query
Specifichiamo una domanda frequente su Milvus.
question = "How is data stored in milvus?"
Cerca la domanda nella collection e recupera le prime 3 corrispondenze semantiche.
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
)
Diamo un’occhiata ai risultati di 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###",
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
]
]
Usare LLM per ottenere una risposta RAG
Converti i documenti recuperati in formato stringa.
context = "\n".join(
[line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]
)
Definisci i prompt di sistema e utente per LLM. Questo prompt è assemblato con i documenti recuperati da 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>
"""
Usa il modello QwQ (32B) fornito da Ollama per generare una risposta basata sui prompt.
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, devo rispondere alla domanda "Come vengono archiviati i dati in Milvus?" usando il contesto fornito. Inizio leggendo attentamente il contesto.
Innanzitutto, guardando la prima sezione del contesto intitolata "Dove archivia i dati Milvus?". Menziona che Milvus gestisce due tipi di dati: dati inseriti e metadati.
I dati inseriti includono vettori, dati scalari e schema della collection, archiviati come log incrementali nello storage persistente. I backend supportati sono elencati, come MinIO, S3, GCS, ecc. Quindi dovrei notare che i dati inseriti vanno in questi object storage tramite log incrementali.
Poi i metadati sono archiviati in etcd per ciascun modulo Milvus. È semplice: metadati in etcd.
Il secondo contesto sul flushing dei dati spiega che i dati prima vanno nella coda di messaggi e poi nello storage persistente quando viene eseguito il flush. Ma questo riguarda più il processo che il luogo in cui sono archiviati, quindi forse menzionare che l'inserimento avviene prima nella coda di messaggi prima di essere scritto.
La terza sezione sulla dimensione massima del dataset menziona vincoli hardware: memoria per i dati interrogabili e storage per la persistenza. Questo potrebbe riguardare il modo in cui i dati sono gestiti in termini di memoria rispetto al disco. L'utente ha chiesto "come vengono archiviati i dati", il che potrebbe includere sia dove (come object store, etcd) sia il processo (coda di messaggi poi log persistenti).
Mettendo tutto insieme, la risposta dovrebbe menzionare:
- Due tipi: dati inseriti e metadati.
Per i dati inseriti:
- Archiviati come log incrementali nello storage persistente usando backend come MinIO, S3 ecc.
- Inizialmente caricati nelle code di messaggi, poi scritti su disco. Il flush() forza la scrittura immediata se necessario.
Metadati:
- Archiviati in etcd per ciascun modulo.
Inoltre, la parte sull'hardware dice che la memoria limita la dimensione dei dati interrogabili e lo storage (come gli object store) limita la dimensione totale del dataset. Quindi forse menzionare questi fattori come parte di come i dati sono archiviati, considerando i vincoli di capacità.
Dovrei strutturarlo chiaramente, magari con punti elenco o sezioni su dati inseriti vs metadati, poi processi come il flushing e infine limitazioni basate sull'hardware.
</think>
I dati in Milvus sono archiviati in due modi principali:
1. **Dati Inseriti**:
- Includono dati vettoriali, dati scalari e schema della collection.
- Archiviati come **log incrementali** nello **storage persistente**, supportato da backend di object storage come MinIO, AWS S3, Google Cloud Storage (GCS), Azure Blob Storage, Alibaba Cloud OSS e Tencent Cloud Object Storage (COS).
- Inizialmente caricati in una **coda di messaggi** quando vengono inseriti. Milvus restituisce un esito positivo al momento dell'inserimento nella coda, ma non scrive immediatamente su disco. Un processo in background scrive successivamente questi dati nello storage persistente. Il metodo `flush()` può forzare la scrittura immediata di tutti i dati in coda nello storage.
2. **Metadati**:
- Generati internamente dai moduli Milvus (ad es., configurazioni delle collection, partizioni).
- Archiviati in **etcd**, un archivio distribuito chiave-valore.
**Considerazioni Hardware**:
- **Memoria**: La quantità di dati che Milvus può interrogare è limitata dalla memoria di sistema, poiché carica in memoria collection/partizioni specificate per le query.
- **Capacità di Storage**: La dimensione massima del dataset è vincolata dal backend di storage sottostante (ad es., object storage), che archivia incrementalmente tutti i dati inseriti e lo schema.
Ottimo! Abbiamo costruito con successo una pipeline RAG con Milvus, QWQ-32B e Ollama.
Conclusione
Integrando queste tecnologie, possiamo costruire un sistema RAG che sfrutta Milvus per un'archiviazione e un recupero efficienti dei dati e le capacità di ragionamento di QwQ-32B per generare risposte accurate e contestualmente pertinenti. Ollama semplifica il processo di deployment, consentendo una configurazione fluida ed efficiente. Questa combinazione è particolarmente vantaggiosa per applicazioni che richiedono recupero e generazione di informazioni in tempo reale, come tutoring assistito dall'AI, risoluzione di problemi basata sulla logica e altro.
Ci auguriamo che seguendo questo tutorial tu possa creare sistemi RAG su misura per le tue esigenze e trarre davvero beneficio dalle tue creazioni.
Continua a leggere

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.

DeepSeek-OCR Explained: Optical Compression for Scalable Long-Context and RAG Systems
Discover how DeepSeek-OCR uses visual tokens and Contexts Optical Compression to boost long-context LLM efficiency and reshape RAG performance.

Smarter Autoscaling in Zilliz Cloud: Always Optimized for Every Workload
With the latest upgrade, Zilliz Cloud introduces smarter autoscaling—a fully automated, more streamlined, elastic resource management system.



