Usare il tuo database vettoriale come datastore JSON (o relazionale)
TL;DR: I database vettoriali supportano operazioni CRUD su formati di dati "tradizionali" come JSON. Se sei uno sviluppatore indipendente o un piccolo team e non vuoi gestire molte componenti diverse dell'infrastruttura dati, puoi usare Milvus o Zilliz Cloud (il Milvus gestito) come unico datastore e migrare facilmente le collection senza vettori verso database diversi man mano che cresci.
Spinta dalla popolarità di ChatGPT e di altri modelli linguistici autoregressivi, la ricerca vettoriale è esplosa in popolarità nell'ultimo anno. Di conseguenza, abbiamo visto molte aziende e organizzazioni salire sul carro della ricerca vettoriale, dai provider di database NoSQL come MongoDB (tramite Atlas Vector Search) ai database relazionali tradizionali come Postgres (tramite pgvector). Il messaggio generale che sento intorno a questi plugin di ricerca vettoriale è in gran parte lo stesso e suona più o meno così: gli sviluppatori dovrebbero restare con noi, visto che potete memorizzare tabelle/JSON oltre ai vettori, quindi non c'è bisogno di gestire più componenti dell'infrastruttura!
Questo tipo di affermazione mi fa sempre ridere, perché è chiaramente confezionata da team di marketing poco sofisticati. Non solo la tecnologia alla base della ricerca vettoriale è enormemente diversa dalle strategie di storage e interrogazione nei database relazionali e NoSQL, ma ormai è piuttosto noto che i database vettoriali possono memorizzare relazioni, documenti JSON e altre fonti di dati strutturati. Il primo punto è difficile da illustrare in modo conciso senza una profonda conoscenza preliminare dei sistemi di gestione di database, ma il secondo punto è abbastanza facile da mostrare tramite alcuni brevi frammenti di codice di esempio. Questo è ciò a cui è dedicato questo post del blog.
Configurazione
Milvus memorizza i dati in unità note come collection, analoghe alle tabelle nei database relazionali. Ogni collection può avere il proprio schema, e gli schemi hanno un campo vettoriale di dimensionalità fissa, ad esempio 768 per gli embedding vettoriali basati su e5-base. Creiamo una collection per memorizzare documenti JSON anziché dati vettoriali. Per illustrare meglio questo punto, ho omesso alcuni dei passaggi iniziali e finali, come la chiamata a collections.connect:
from pymilvus import Collection, CollectionSchema, DataType, FieldSchema
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True, max_length=100),
FieldSchema(name="_unused", dtype=DataType.FLOAT_VECTOR, dim=1)
]
schema = CollectionSchema(fields, "NoSQL data", enable_dynamic_field=True)
collection = Collection("json_data", schema)
collection.load()
Qui abbiamo specificato che questa collection usi le capacità di schema dinamico di Milvus, consentendo alla collection di accettare payload JSON come dati "extra" associati a ogni riga.
Spero non ti aspettassi altro, perché è tutto qui: è davvero così semplice.
Operazioni CRUD
Puoi interagire con la collection che abbiamo creato sopra come faresti con qualsiasi altro database. A titolo di esempio, scarichiamo l'elenco degli attuali senatori degli Stati Uniti (in formato JSON) da govtrack:
import requests
r = requests.get("https://www.govtrack.us/api/v2/role?current=true&role_type=senator")
data = r.json()["objects"]
len(data)
100
Possiamo memorizzare questi documenti direttamente in Milvus con appena un minimo di manipolazione dei dati:
rows = [{"_unused": [0]} | d for d in data]
collection.insert(rows)
(insert count: 100, delete count: 0, upsert count: 0, ...
Da qui, possiamo eseguire query direttamente su quei dati:
collection.query(
expr="party like 'Dem%'",
limit=1,
output_fields=["person"]
)
[{'person': {'bioguideid': 'C000127',
'birthday': '1958-10-13',
'cspanid': 26137,
'fediverse_webfinger': None,
'firstname': 'Maria',
'gender': 'female',
'gender_label': 'Female',
'lastname': 'Cantwell',
'link': 'https://www.govtrack.us/congress/members/maria_cantwell/300018',
'middlename': '',
'name': 'Sen. Maria Cantwell [D-WA]',
'namemod': '',
'nickname': '',
'osid': 'N00007836',
'pvsid': None,
'sortname': 'Cantwell, Maria (Sen.) [D-WA]',
'twitterid': 'SenatorCantwell',
'youtubeid': 'SenatorCantwell'},
'id': 447376465724036249}]
Senza specificare alcun campo vettoriale, abbiamo interrogato il nostro database per ottenere il primo risultato in cui il campo party nel payload JSON di accompagnamento ha "Dem" (Democratico) come prefisso.
Si spera che questo passaggio dimostri la capacità di eseguire ricerche di dati strutturati in Milvus, ma non è una query particolarmente utile. Troviamo tutti i senatori del mio stato di origine, l'Oregon:
collection.query(
expr="state in ['OR']",
limit=10,
output_fields=["person"]
)
[{'person': {'bioguideid': 'M001176',
'birthday': '1956-10-24',
'cspanid': 1029842,
'fediverse_webfinger': None,
'firstname': 'Jeff',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Merkley',
'link': 'https://www.govtrack.us/congress/members/jeff_merkley/412325',
'middlename': '',
'name': 'Sen. Jeff Merkley [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00029303',
'pvsid': None,
'sortname': 'Merkley, Jeff (Sen.) [D-OR]',
'twitterid': 'SenJeffMerkley',
'youtubeid': 'SenatorJeffMerkley'},
'id': 447376465724036286},
{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
Anche se abbiamo specificato limit=10, sono stati restituiti solo due documenti (poiché ogni stato ha solo due senatori). Restringiamo ulteriormente la nostra query per ottenere solo il senatore senior:
collection.query(
expr="state in ['OR'] and senator_rank in ['senior']",
limit=10,
output_fields=["person"]
)
[{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
Forse vorrei aggiornare il profilo del senatore Ron Wyden con qualche informazione in più. Possiamo farlo facilmente recuperando l'intero documento con output_fields=["*"], aggiornando il documento risultante e reinserendolo nel nostro database senza la vecchia chiave primaria:
expr = "state in ['OR'] and senator_rank in ['senior']"
res = collection.query(
expr=expr,
limit=10,
output_fields=["*"]
)
res[0].update({
"elected_in": "1996",
"college": "Stanford University",
"college_major": "Political Science"
})
del res[0]["id"]
(insert count: 1, delete count: 0, upsert count: 0, ...
collection.delete(expr)
collection.insert(res)
Vediamo se ha funzionato come previsto.
collection.query(
expr=expr,
limit=10,
output_fields=["elected_in", "college", "college_major"]
)
[{'elected_in': '1996',
'college': 'Stanford University',
'college_major': 'Political Science',
'id': 447376465724036353}]
I dati corrispondono effettivamente agli aggiornamenti che abbiamo apportato.
Lo script completo
Ecco l'intero script dall'inizio alla fine per comodità, senza le query superflue. Ho usato il nostro piano gratuito di Zilliz Cloud invece di milvus-lite:
from milvus import default_server
from pymilvus import connections
from pymilvus import Collection, CollectionSchema, DataType, FieldSchema
import requests
# Uncomment this if you're using `milvus-lite`
#default_server.start()
#connections.connect(host="127.0.0.1", port=default_server.listen_port)
# Uncomment this if you're using Zilliz Cloud
connections.connect(
uri=os.environ["ZILLIZ_URI"],
token=os.environ["ZILLIZ_TOKEN"]
)
# Create the schema for our new collection. We set turn on Milvus' dynamic
# schema capability in order to store arbitratily large (or small) JSON blogs
# in each row.
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True, max_length=100),
FieldSchema(name="_unused", dtype=DataType.FLOAT_VECTOR, dim=1)
]
schema = CollectionSchema(fields, "Milvus as a JSON datastore", enable_dynamic_field=True)
# Now let's creat the collection.
collection = Collection("json_data", schema)
index_params = {
"index_type": "AUTOINDEX",
"metric_type": "L2",
"params": {}
}
collection.create_index(
field_name="_unused",
index_params=index_params
)
collection.load()
# Insert US senator JSON data into our newly formed collection.
r = requests.get("https://www.govtrack.us/api/v2/role?current=true&role_type=senator")
data = r.json()["objects"]
rows = [{"_unused": [0]} | d for d in data]
collection.insert(rows)
# Fetch the first Democrat in the database
top_k = 1
collection.query(
expr="state in ['OR'] and senator_rank in ['senior']",
limit=top_k,
output_fields=["person"]
)
[{'person': {'bioguideid': 'W000779',
'birthday': '1949-05-03',
'cspanid': 1962,
'fediverse_webfinger': None,
'firstname': 'Ron',
'gender': 'male',
'gender_label': 'Male',
'lastname': 'Wyden',
'link': 'https://www.govtrack.us/congress/members/ron_wyden/300100',
'middlename': '',
'name': 'Sen. Ron Wyden [D-OR]',
'namemod': '',
'nickname': '',
'osid': 'N00007724',
'pvsid': None,
'sortname': 'Wyden, Ron (Sen.) [D-OR]',
'twitterid': 'RonWyden',
'youtubeid': 'senronwyden'},
'id': 447376465724036331}]
Provalo tu stesso!
pymongo -> milvusmongo
Un'ultima nota prima di concludere: ho creato un piccolo pacchetto Python chiamato milvusmongo che implementa le funzionalità CRUD più basilari di pymongo tra le raccolte. Usa Milvus come database sottostante invece di MongoDB. Come pymongo, milvusmongo supporta sia l'accesso in stile dizionario sia quello in stile attributo e astrae la logica aggiuntiva necessaria per le chiamate CRUD (cioè insert_one, update_one e delete_one). Puoi installarlo con pip install milvusmongo:
% pip install milvus
% pip install milvusmongo
Una volta fatto, puoi avviare un'istanza Milvus incorporata e usarla insieme a milvusmongo per eseguire query sui dati JSON. Ad esempio:
from milvus import default_server
default_server.start()
from milvusmongo import MongoClient
client = MongoClient("127.0.0.1", 19530)
client.insert_one(my_document)
Tieni presente che questa libreria ha lo scopo di dimostrare la flessibilità di Milvus come datastore, piuttosto che fungere da qualcosa che dovresti usare in ambienti di produzione su larga scala.
Parole finali
Milvus non è qui per sostituire i database NoSQL o i motori di ricerca testuale lessicale; siamo qui per offrirti la migliore esperienza possibile di ricerca vettoriale/filtrata. Ancora più importante, siamo qui per contribuire ad accelerare l'adozione della ricerca vettoriale come tecnologia: è per questo che l'open source è una parte così centrale del nostro ethos.
Ma questo non significa che non supportiamo altri tipi di dati. Come sviluppatore individuale o piccola startup, sei libero di usare Milvus come tuo unico archivio dati. Potrai sempre ottimizzare l'uso della tua infrastruttura in seguito, man mano che cresci. Milvus ti fornirà capacità di ricerca vettoriale best-in-class, mentre altri database servono a memorizzare, indicizzare e cercare altre forme di dati. Quando la tua applicazione inizierà a richiedere carichi di lavoro più complessi (come join o aggregazioni), sarà allora che vorrai prendere in considerazione l'uso di datastore diversi.
Continua a leggere

Introducing Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud
We're announcing the general availability of Customer-Managed Encryption Keys (CMEK) on Zilliz Cloud.

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.

Vector Databases vs. Hierarchical Databases
Use a vector database for AI-powered similarity search; use a hierarchical database for organizing data in parent-child relationships with efficient top-down access patterns.



