Consigli pratici e trucchi per sviluppatori che creano applicazioni RAG
La ricerca vettoriale non è priva di sforzo!
La ricerca vettoriale, nota anche come ricerca di similarità vettoriale o ricerca del vicino più prossimo, è una tecnica utilizzata nel recupero dei dati per applicazioni RAG e sistemi di recupero delle informazioni per trovare elementi o punti dati simili o strettamente correlati a un determinato vettore di query. Viene spesso presentata come semplice quando si gestiscono grandi dataset. La percezione generale è che si possano semplicemente inserire i dati in un modello di embedding per generare embedding vettoriali e poi trasferire questi vettori nel proprio database vettoriale per recuperare i risultati desiderati.
come eseguire una ricerca vettoriale
Molti fornitori di database vettoriali promuovono le proprie capacità con descrittori come "facile," "intuitivo," e "semplice." Sostengono che sia possibile ottenere risultati significativi con solo poche righe di codice, evitando le complessità del machine learning, dell'IA, dei processi ETL o della regolazione dettagliata del sistema.
E hanno ragione; la ricerca vettoriale è semplice quanto usare una libreria numerica di base come NumPy. Per dimostrare questo concetto, ho scritto una breve demo in appena dieci righe di codice Python utilizzando l'algoritmo dei k-nearest neighbors (KNN). Questo approccio semplice è efficace e accurato per applicazioni su piccola scala con dataset fino a mille o diecimila vettori.
import numpy as np
# Function to calculate Euclidean distance
def euclidean_distance(a, b):
return np.linalg.norm(a - b)
# Function to perform KNN
def knn(data, target, k):
# Calculate distances between target and all points in data
distances = [euclidean_distance(d, target) for d in data]
# Combine distances with data indices
distances = np.array(list(zip(distances, range(len(data)))))
# Sort by distance
sorted_distances = distances[distances[:, 0].argsort()]
# Get the top k closest indices
closest_k_indices = sorted_distances[:k, 1].astype(int)
# Return the top k closest vectors
return data[closest_k_indices]
Tuttavia, questo approccio non funzionerà quando il tuo dataset cresce fino a un livello modesto di oltre un milione o dieci milioni di vettori. Ciò avviene semplicemente perché le applicazioni del mondo reale devono interfacciarsi con gli utenti, essere disponibili e sono sempre molto più complicate. Creare un'applicazione reale scalabile richiede di considerare attentamente vari fattori oltre alla codifica, tra cui qualità della ricerca, scalabilità, disponibilità, multi-tenancy, costi, sicurezza e altro ancora!
Quindi, siamo onesti. Ricordi il detto, "Funziona sulla mia macchina?" La ricerca vettoriale non è diversa: il tuo prototipo funziona sempre; la ricerca vettoriale in produzione è spesso complessa, quindi quali sono le best practice per creare un'applicazione basata sulla ricerca vettoriale in produzione?
Per aiutarti ad affrontare queste sfide, condivideremo tre consigli essenziali per distribuire efficacemente il tuo database vettoriale nell'ambiente di produzione della tua applicazione RAG con Milvus:
Progetta uno schema efficace: Considera attentamente la struttura dei tuoi dati e il modo in cui verranno interrogati per creare uno schema che ottimizzi prestazioni e scalabilità.
Pianifica la scalabilità: Anticipa la crescita futura e progetta la tua architettura per gestire volumi di dati e traffico utenti in aumento.
Seleziona l'indice ottimale e ottimizza le prestazioni: Scegli il metodo di indicizzazione più adatto al tuo caso d'uso e monitora e regola continuamente le impostazioni delle prestazioni.
Seguendo queste best practice, sarai sulla buona strada per creare un'applicazione robusta ed efficiente basata sulla ricerca vettoriale. Nei commenti qui sotto, condividi le tue esperienze e qualsiasi consiglio aggiuntivo che hai trovato utile!
Progettare una strategia di schema efficace
Uno schema definisce la struttura di un database, inclusi tabelle, campi, relazioni e tipi di dati. Questo framework organizzato garantisce che i dati vengano archiviati in modo coerente e prevedibile, semplificando gestione, interrogazione e manutenzione. La selezione di uno schema appropriato è particolarmente critica per i database vettoriali come Milvus, che gestiscono vettori e vari tipi di dati strutturati, inclusi metadati e dati scalari. Questi dati possono migliorare la ricerca filtrata e ottimizzare i risultati complessivi della ricerca. Questa sezione esplorerà i fattori chiave da considerare nella scelta della strategia di schema più efficace.
Schema dinamico vs. schema fisso
Nei sistemi di database, gli schemi dinamici e fissi rappresentano due approcci principali alla strutturazione dei dati. Gli schemi dinamici offrono flessibilità, semplificando l'inserimento e il recupero dei dati senza la necessità di estesi processi di allineamento dei dati o ETL. Questo approccio è perfetto per applicazioni che richiedono rapidi cambiamenti della struttura dei dati. D'altra parte, gli sviluppatori apprezzano gli schemi fissi per la loro efficienza prestazionale e il risparmio di memoria, grazie ai loro formati di archiviazione compatti.
Un approccio a schema ibrido può avvantaggiare gli sviluppatori che lavorano su applicazioni di database vettoriali efficienti. Questo metodo combina la robustezza degli schemi fissi per i percorsi dati essenziali con la flessibilità degli schemi dinamici per adattarsi a diversi casi d'uso. Ad esempio, in un sistema di raccomandazione, elementi come i nomi dei prodotti e gli ID dei prodotti possono variare in importanza a seconda del contesto. Impiegando uno schema ibrido, gli sviluppatori possono garantire prestazioni ottimali dove necessario, mantenendo al contempo la capacità di adattarsi a requisiti dei dati in evoluzione.
Impostazione delle chiavi primarie e delle chiavi di partizione
Le chiavi primarie e di partizione sono due concetti importanti nei database vettoriali. Utilizzando il database vettoriale Milvus come esempio, possiamo approfondire il funzionamento di queste chiavi all'interno dei database vettoriali.
L'architettura di Milvus segmenta i dati in diversi componenti: ci sono campi fissi e dinamici (collettivamente indicati come payload), un campo vettoriale obbligatorio e campi di sistema come timestamp e identificatori univoci universali (UUID), che sono simili a quelli presenti nei database relazionali convenzionali.
Chiavi primarie: In Milvus, la chiave primaria spesso funge da identificatore univoco, che in un caso d'uso RAG può essere applicato a un ID di chunk. Questa chiave viene consultata frequentemente e può essere configurata per la generazione automatica. Svolge un ruolo nell'individuare e recuperare rapidamente specifiche voci di dati all'interno del database.
Chiavi di partizione: Quando crei una raccolta in Milvus, puoi specificare una chiave di partizione. Questa chiave consente a Milvus di archiviare le entità di dati in partizioni diverse in base ai loro valori di chiave, organizzando efficacemente i dati in segmenti gestibili. Un modo semplice per pensare alle chiavi di partizione è considerare l'uso di una chiave di partizione se esistono set di dati su cui desideri applicare filtri. Ad esempio, l'isolamento dei dati e la distribuzione efficiente sono necessari in situazioni multi-tenant, quindi archiviarli in partizioni separate può aiutare a raggiungere questo obiettivo. Le chiavi di partizione sono utili anche per la scalabilità, poiché la partizione dei dati in shard tramite hashing consente al database di gestire in modo più efficiente basi utenti su larga scala e multi-tenancy.
Sia le chiavi primarie sia le chiavi di partizione sono fondamentali per mantenere l'integrità strutturale e l'efficienza operativa dei database vettoriali, rendendole indispensabili per gestire dataset estesi e garantire un accesso e un recupero rapidi dei dati.
Scelta dei tipi di embedding vettoriali
Quando si scelgono gli embedding vettoriali per applicazioni RAG, è indispensabile scegliere il modello ML giusto per la creazione dei vettori e comprendere i diversi tipi di embedding disponibili: embedding densi, sparsi e binari.
Tre categorie popolari di embedding vettoriali
Embedding densi sono il tipo più comunemente utilizzato nelle applicazioni di database vettoriali per la ricerca di similarità semantica. Sono noti per la loro robustezza e applicabilità generale a vari tipi di dati. I modelli di embedding densi più popolari includono OpenAI, BGE e Cohere.
Embedding sparsi stanno guadagnando popolarità per la loro efficienza nella ricerca di dati fuori dominio. I recenti progressi in modelli come Splade e BGE M3 ne hanno migliorato l’utilità nelle ricerche eterogenee, rendendoli una scelta versatile per diverse applicazioni.
Embedding binari, caratterizzati dal loro formato binario (zeri e uno), sono progettati per essere efficienti in termini di memoria, rendendoli ideali per casi d’uso speciali come il sequenziamento proteico. Modelli come Meta ESM-2 vengono in genere utilizzati per generare questi embedding, fornendo soluzioni mirate per esigenze di ricerca specifiche.
Per garantire l’accuratezza dei risultati di ricerca per le applicazioni RAG, dobbiamo utilizzare più dei soli embedding densi. Pertanto, dobbiamo trovare soluzioni che supportino diversi algoritmi di indicizzazione per cercare in modo efficiente ed efficace diversi tipi di embedding vettoriali. Milvus supporta vari indici per la gestione di embedding densi, sparsi, binari e persino ibridi sparsi e densi, consentendo ricerche efficienti su varie dimensioni dei dati e garantendo prestazioni ottimali nelle applicazioni di database vettoriali.
Progettare il tuo schema: un esempio pratico
Mettiamo insieme questi elementi che abbiamo appena esaminato per aiutarci a progettare efficacemente un’architettura di schema che aumenterà l’accuratezza dei nostri risultati di ricerca:
| Nome campo | Tipo | Descrizione | Valore di esempio |
|---|---|---|---|
| chunkID | Int64 | Chiave primaria, identifica in modo univoco diverse parti di un documento | 123456789 |
| userID | Int64 | Chiave di partizione, il partizionamento dei dati si basa su userID per garantire che le ricerche avvengano all’interno di un singolo userID | 987654321 |
| docID | Int64 | Identificatore univoco per un documento, utilizzato per associare diversi chunk dello stesso documento | 555666777 |
| chunkData | varchar | Una parte del documento, contenente diverse centinaia di byte di testo | "Questa è una parte del documento..." |
| dynamicParams | JSON | Memorizza i parametri dinamici del documento, come nome, URL di origine, ecc. | {"name": "Documento di esempio", "source": "example.com"} |
| sparseVector | Formato specifico | Dati che rappresentano un vettore sparso. Il formato specifico avrà valori non nulli solo in determinate posizioni per rappresentare la sparsità. | [0.1, 0, 0, 0.8, 0.4] |
| denseVector | Formato specifico | Dati che rappresentano un vettore denso. Il formato specifico avrà un numero fisso di dimensioni con valori in ciascuna. | [0.2, 0.3, 0.4, 0.1] |
Una demo di schema per una tipica applicazione di Retrieval Augmented Generation (RAG)
Esaminando questo schema, è fondamentale notare la presenza di campi aggiuntivi, che vanno oltre la chiave primaria e il campo vettoriale. Questi campi extra svolgono un ruolo nella costruzione e nell’utilizzo del tuo database vettoriale. Approfondiamo:
Le basi: includono la tua chiave primaria (chunkID) e i tuoi embedding (denseVector), di cui ogni voce nel database ha bisogno.
Supporto multi-tenancy: abbiamo aggiunto un campo per partizionare i dati in base ai tenant della nostra soluzione con l’aggiunta di un userID. Questa aggiunta aiuta a segregare e gestire l’accesso ai dati su base per utente, migliorando sicurezza e personalizzazione.
Perfezionare i tuoi risultati di ricerca: abbiamo aggiunto alcuni altri campi per aiutarci con questo perfezionamento, tra cui:
docID: Questo campo indica l'origine del chunk e può essere utilizzato per sfruttare la funzionalità di ricerca per raggruppamento di Milvus. Nel nostro esempio, abbiamo suddiviso il nostro documento in chunk e memorizzato l'embedding vettoriale rappresentativo nel campo denseVector, e in questo campo (docID) memorizziamo le informazioni del documento associato. Puoi includere l'argomentogroup_by_fieldnell'operazione search() per raggruppare i risultati in base all'ID del documento, così da trovare documenti pertinenti invece di passaggi o chunk simili. Questo aiuta a restituire i documenti pertinenti anziché chunk separati dello stesso documento.dynamicParams: Questo campo può essere filtrato, consentendoti di gestire e recuperare i dati adatti alle tue esigenze. È un tesoro di metadati, come il nome del documento, l'URL di origine, ecc. Impostiamo il campo json per memorizzare più coppie chiave-valore in un unico campo.sparseVector: Questo campo contiene l'embedding sparso del chunk, che ci consente di eseguire una ricerca ANN e recuperare risultati in base a un valore scalare associato al vettore sparso.
Il diagramma mostra che possiamo raccogliere risultati da query separate e poi riordinarli per affinare i risultati di ricerca.
Progettando uno schema che includa questi campi aggiuntivi, puoi creare un database vettoriale più robusto e flessibile, adatto ai requisiti della tua applicazione RAG. Ti consente di sfruttare i punti di forza della ricerca vettoriale incorporando al contempo tecniche tradizionali di gestione e recupero dei dati.
Piano per la scalabilità
Una volta ottenuto successo con la tua applicazione RAG MVP, è il momento di iniziare a prepararsi per la distribuzione in produzione. Ciò implica anticipare la crescita futura e progettare la tua architettura in modo da gestire volumi di dati e traffico utenti in aumento.
Per garantire che la tua applicazione possa scalare in modo efficace, devi essere consapevole che la scalabilità nei database vettoriali pone sfide uniche rispetto ai database relazionali tradizionali, perché i dati sono memorizzati in un grande indice centralizzato. Questa configurazione può portare a due problemi principali: velocità di indicizzazione lente e qualità dell'indice degradata a causa di aggiornamenti frequenti, il che a sua volta può ridurre la qualità della ricerca.
La strategia di sharding di Milvus
Milvus può gestire queste sfide dividendo l'intero set di dati in segmenti gestibili; possiamo eseguire aggiornamenti ritardati o compattare i segmenti una volta che diventano instabili, mantenendo una qualità di ricerca costante. Questa segmentazione facilita un efficace bilanciamento del carico, consentendoci di distribuire le query in modo uniforme su tutti i core di elaborazione.
L'uso di partizioni per la multi-tenancy può anche aiutare con la scalabilità e le prestazioni, poiché le ricerche sono confinate ai dati rilevanti per la partizione. Questo approccio organizza efficacemente i dati e migliora sicurezza e privacy limitando la visibilità agli utenti appropriati. Inoltre, Milvus può gestire efficientemente fino a dieci miliardi di punti dati in una singola raccolta. È davvero tanto!
Per applicazioni multi-tenant con meno di 10.000 tenant, gestire i dati per raccolta offre un maggiore controllo sui dati. Tuttavia, le chiavi di partizione possono supportare efficacemente tenant illimitati segmentando dinamicamente i dati per servizi con milioni di utenti.
Milvus è un sistema distribuito progettato per gestire con facilità grandi volumi di query. E la parte migliore? Aggiungere semplicemente più nodi può aumentare significativamente le prestazioni, aprendo un mondo di possibilità per le tue applicazioni. Per set di dati più piccoli che inizialmente non richiedono risorse estese, aumentare le riserve di memoria—tipicamente da due a tre volte l'allocazione attuale—può effettivamente raddoppiare le query al secondo (QPS). Questo framework scalabile garantisce che, man mano che i tuoi dati crescono, le capacità del tuo database possano crescere insieme a essi, assicurando prestazioni efficienti e affidabili in ogni ambito.
Scegliere, valutare e ottimizzare il tuo indice
Durante la fase di prototipo, caricare tutti i dati in memoria è comune per un’elaborazione più rapida e uno sviluppo più semplice. Tuttavia, man mano che passi alla produzione e i tuoi dati crescono, diventa impraticabile archiviare tutto in memoria. Questo perché:
La memoria è limitata e costosa rispetto all’archiviazione su disco.
I dataset di grandi dimensioni possono superare la capacità di memoria disponibile.
Caricare tutti i dati in memoria può aumentare significativamente il tempo di avvio e il consumo di risorse.
Per gestire in modo efficiente dataset più grandi in produzione, devi scegliere una strategia di indicizzazione appropriata. L’indice giusto può ottimizzare le prestazioni della tua applicazione RAG in termini di velocità delle query, requisiti di archiviazione e latenza.
Indici supportati da Milvus
Questo diagramma aiuta a visualizzare le differenze tra vari indici in base a tre metriche chiave:
Query al secondo (QPS): misura quante query di ricerca l’indice può gestire al secondo, riflettendone throughput ed efficienza.
Archiviazione: rappresenta la quantità di spazio su disco necessaria per archiviare l’indice, che può influire sui costi dell’infrastruttura e sulla scalabilità.
La latenza si riferisce al tempo necessario per elaborare una singola query e restituire i risultati, influenzando la reattività della tua applicazione.
Confrontando queste metriche tra diversi indici, puoi decidere quale indice si adatta meglio al tuo caso d’uso specifico e ai requisiti di prestazioni.
In Milvus, forniamo un framework flessibile di selezione degli indici su misura per varie esigenze di archiviazione e prestazioni:
GPU Index è la nostra opzione principale per ambienti ad alte prestazioni, che supporta l’elaborazione e il recupero rapidi dei dati.
Memory Index è un’opzione di fascia intermedia che bilancia prestazioni e capacità, offrendo solidi tassi di query al secondo (QPS) e la possibilità di scalare fino a terabyte di archiviazione con una latenza media di circa 10 millisecondi.
Disk Index può gestire decine di terabyte con una latenza ragionevole di circa 100 millisecondi, adatto a dataset più grandi e meno sensibili al tempo. Milvus è l’unico database vettoriale open-source che supporta l’indice su disco.
Swap Index facilita lo scambio di dati tra S3 o altre soluzioni di archiviazione a oggetti e la memoria. Questo approccio riduce significativamente i costi—di circa dieci volte—gestendo al contempo efficacemente la latenza. I tempi di accesso tipici sono di circa 100 millisecondi, ma possono estendersi a pochi secondi per dati a cui si accede meno frequentemente ("più freddi"), rendendolo valido per casi d’uso offline e applicazioni sensibili ai costi.
Dopo aver selezionato un indice, puoi valutarne le prestazioni in base al tempo di costruzione, all’accuratezza, alle prestazioni e al consumo di risorse. Ad esempio, un indice non ottimizzato potrebbe supportare solo 20 query al secondo senza richiedere alcuna costruzione. Ottimizzare un indice potrebbe migliorare significativamente il QPS, potenzialmente aumentandolo di dieci volte a ogni iterazione di tuning, anche se al costo di un aumento del tempo di visualizzazione.
Per selezionare e perfezionare efficacemente il tuo indice, dovresti:
Scegliere il tipo di indice appropriato in base alle tue esigenze specifiche.
Regolare i parametri dell’indice per ottimizzare le prestazioni.
Eseguire benchmark sui tuoi casi d’uso per assicurarti che l’indice funzioni come previsto.
Regolare i parametri di ricerca per migliorare ulteriormente le prestazioni.
Se non sei sicuro del processo di ottimizzazione, sfrutta la potenza di strumenti di benchmarking come VectorDBBench. Questo strumento, sviluppato e reso open-source da Zilliz, può valutare tutti i principali database vettoriali. Ti consente di condurre esperimenti completi e perfezionare il tuo sistema per prestazioni ottimali.
Per un riferimento rapido, abbiamo preparato un pratico cheat sheet che delinea le prestazioni di ciascun indice nel nostro catalogo di indici GPU. Questa risorsa può aiutarti a ottimizzare le prestazioni e l'efficienza dei costi guidandoti verso l'indice più adatto alle esigenze della tua applicazione.
Un cheat sheet sugli indici
Riepilogo
In questa guida completa, abbiamo esplorato il mondo sfaccettato dei database vettoriali e gli approcci pratici necessari per massimizzarne l'efficienza e la scalabilità. Dalle basi della progettazione di uno schema alle complessità della gestione di grandi set di dati, abbiamo trattato le strategie essenziali e le best practice che gli sviluppatori devono conoscere quando lavorano con i database vettoriali.
Con l'evoluzione dei database vettoriali, rimanere informati su questi aspetti consentirà agli sviluppatori di creare applicazioni più robuste, efficienti e scalabili. Che tu sia un professionista esperto di database o un nuovo arrivato in questo campo, gli approfondimenti forniti qui ti aiuteranno a orientarti nelle complessità dei database vettoriali con maggiore sicurezza e competenza.
Continua a leggere

Milvus 2.6.x Now Generally Available on Zilliz Cloud, Making Vector Search Faster, Smarter, and More Cost-Efficient for Production AI
Milvus 2.6.x is now GA on Zilliz Cloud, delivering faster vector search, smarter hybrid queries, and lower costs for production RAG and AI applications.

Vector Databases vs. Document Databases
Use a vector database for similarity search and AI-powered applications; use a document database for flexible schema and JSON-like data storage.

Introducing DeepSearcher: A Local Open Source Deep Research
In contrast to OpenAI’s Deep Research, this example ran locally, using only open-source models and tools like Milvus and LangChain.



