Ricerca a testo completo
La ricerca full text è una funzione che recupera i documenti contenenti termini o frasi specifiche nei dataset di testo, classificando poi i risultati in base alla rilevanza. Questa funzione supera le limitazioni della ricerca semantica, che potrebbe trascurare termini precisi, garantendo la ricezione dei risultati più accurati e contestualmente rilevanti. Inoltre, semplifica le ricerche vettoriali accettando input di testo grezzo, convertendo automaticamente i dati testuali in embedding sparsi senza la necessità di generare manualmente embedding vettoriali.
Utilizzando l'algoritmo BM25 per il punteggio di rilevanza, questa funzione è particolarmente preziosa negli scenari di retrieval-augmented generation (RAG), dove dà priorità ai documenti che corrispondono strettamente a termini di ricerca specifici.
Integrando la ricerca full text con la ricerca vettoriale densa basata sulla semantica, è possibile migliorare l'accuratezza e la pertinenza dei risultati della ricerca. Per ulteriori informazioni, consultare la sezione Ricerca ibrida.
Implementazione di BM25
Milvus offre una ricerca full text basata sull'algoritmo di rilevanza BM25, una funzione di punteggio ampiamente adottata nei sistemi di information retrieval, e Milvus la integra nel flusso di ricerca per fornire risultati di testo accurati e classificati per rilevanza.
La ricerca di testi completi in Milvus segue il seguente flusso di lavoro:
Inserimento del testo grezzo: L'utente inserisce documenti di testo o fornisce una query utilizzando testo semplice, senza bisogno di modelli di incorporamento.
Analisi del testo: Milvus utilizza un analizzatore per elaborare il testo in termini significativi che possono essere indicizzati e ricercati.
Elaborazione della funzione BM25: Una funzione integrata trasforma questi termini in rappresentazioni vettoriali rade ottimizzate per il punteggio BM25.
Memorizzazione delle collezioni: Milvus memorizza le incorporazioni rade risultanti in una raccolta per un rapido recupero e classificazione.
Punteggio di rilevanza BM25: Al momento della ricerca, Milvus applica la funzione di punteggio BM25 per calcolare la rilevanza dei documenti e restituire i risultati classificati che meglio corrispondono ai termini della query.
Ricerca a testo completo
Per utilizzare la ricerca full text, seguire i seguenti passaggi principali:
Creare una raccolta: Impostare i campi richiesti e definire una funzione BM25 che converta il testo grezzo in embedding sparsi.
Inserire i dati: Inserire i documenti di testo grezzo nella raccolta.
Eseguire ricerche: Utilizzare il testo della query in linguaggio naturale per recuperare i risultati classificati in base alla rilevanza di BM25.
Creare una raccolta per la ricerca di testo completo BM25
Per abilitare la ricerca full text con BM25, è necessario preparare una raccolta con i campi richiesti, definire una funzione BM25 per generare vettori sparsi, configurare un indice e quindi creare la raccolta.
Definire i campi dello schema
Lo schema della raccolta deve includere almeno tre campi obbligatori:
Campo primario: Identifica in modo univoco ogni entità della raccolta.
Campo testo (
VARCHAR): Memorizza documenti di testo grezzo. Deve essere impostatoenable_analyzer=Truein modo che Milvus possa elaborare il testo per la classificazione di rilevanza BM25. Per impostazione predefinita, Milvus utilizza l'analizzatorestandardper l' analisi del testo. Per configurare un analizzatore diverso, fare riferimento a Panoramica dell'analizzatore.Campo vettoriale sparso (
SPARSE_FLOAT_VECTOR): Memorizza le incorporazioni rade generate automaticamente dalla funzione BM25.
from pymilvus import MilvusClient, DataType, Function, FunctionType
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
schema = client.create_schema()
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True) # Primary field
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True) # Text field
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR) # Sparse vector field; no dim required for sparse vectors
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
.build();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text")
.dataType(DataType.VarChar)
.maxLength(1000)
.enableAnalyzer(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("sparse")
.dataType(DataType.SparseFloatVector)
.build());
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/column"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/index"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
schema := entity.NewSchema()
schema.WithField(entity.NewField().
WithName("id").
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true).
WithIsAutoID(true),
).WithField(entity.NewField().
WithName("text").
WithDataType(entity.FieldTypeVarChar).
WithEnableAnalyzer(true).
WithMaxLength(1000),
).WithField(entity.NewField().
WithName("sparse").
WithDataType(entity.FieldTypeSparseVector),
)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
const schema = [
{
name: "id",
data_type: DataType.Int64,
is_primary_key: true,
},
{
name: "text",
data_type: "VarChar",
enable_analyzer: true,
enable_match: true,
max_length: 1000,
},
{
name: "sparse",
data_type: DataType.SparseFloatVector,
},
];
console.log(res.results)
export schema='{
"autoId": true,
"enabledDynamicField": false,
"fields": [
{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 1000,
"enable_analyzer": true
}
},
{
"fieldName": "sparse",
"dataType": "SparseFloatVector"
}
]
}'
Nella configurazione precedente,
id: serve come chiave primaria ed è generato automaticamente conauto_id=True.text: memorizza i dati del testo grezzo per le operazioni di ricerca full text. Il tipo di dati deve essereVARCHAR, poichéVARCHARè il tipo di dati stringa di Milvus per la memorizzazione del testo.sparse: un campo vettoriale riservato alla memorizzazione delle incorporazioni sparse generate internamente per le operazioni di ricerca full text. Il tipo di dati deve essereSPARSE_FLOAT_VECTOR.
Definire la funzione BM25
La funzione BM25 converte il testo tokenizzato in vettori sparsi che supportano il punteggio BM25.
Definire la funzione e aggiungerla allo schema:
bm25_function = Function(
name="text_bm25_emb", # Function name
input_field_names=["text"], # Name of the VARCHAR field containing raw text data
output_field_names=["sparse"], # Name of the SPARSE_FLOAT_VECTOR field reserved to store generated embeddings
function_type=FunctionType.BM25, # Set to `BM25`
)
schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;
import java.util.*;
schema.addFunction(Function.builder()
.functionType(FunctionType.BM25)
.name("text_bm25_emb")
.inputFieldNames(Collections.singletonList("text"))
.outputFieldNames(Collections.singletonList("sparse"))
.build());
function := entity.NewFunction().
WithName("text_bm25_emb").
WithInputFields("text").
WithOutputFields("sparse").
WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
const functions = [
{
name: 'text_bm25_emb',
description: 'bm25 function',
type: FunctionType.BM25,
input_field_names: ['text'],
output_field_names: ['sparse'],
params: {},
},
];
export schema='{
"autoId": true,
"enabledDynamicField": false,
"fields": [
{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 1000,
"enable_analyzer": true
}
},
{
"fieldName": "sparse",
"dataType": "SparseFloatVector"
}
],
"functions": [
{
"name": "text_bm25_emb",
"type": "BM25",
"inputFieldNames": ["text"],
"outputFieldNames": ["sparse"],
"params": {}
}
]
}'
Parametro |
Descrizione |
|---|---|
|
Il nome della funzione. Questa funzione converte il testo grezzo del campo |
|
Il nome del campo |
|
Il nome del campo in cui verranno memorizzati i vettori sparsi generati internamente. Per |
|
Il tipo di funzione da utilizzare. Deve essere |
Se più campi di VARCHAR richiedono l'elaborazione di BM25, definire una funzione BM25 per ogni campo, ciascuna con un nome e un campo di uscita unici.
Configurare l'indice
Dopo aver definito lo schema con i campi necessari e la funzione incorporata, configurare l'indice per la collezione.
index_params = client.prepare_index_params()
index_params.add_index(
field_name="sparse",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25",
params={
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
)
import io.milvus.v2.common.IndexParam;
Map<String,Object> params = new HashMap<>();
params.put("inverted_index_algo", "DAAT_MAXSCORE");
params.put("bm25_k1", 1.2);
params.put("bm25_b", 0.75);
List<IndexParam> indexes = new ArrayList<>();
indexes.add(IndexParam.builder()
.fieldName("sparse")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.BM25)
.extraParams(params)
.build());
indexOption := milvusclient.NewCreateIndexOption("my_collection", "sparse",
index.NewAutoIndex(entity.MetricType(entity.BM25)))
.WithExtraParam("inverted_index_algo", "DAAT_MAXSCORE")
.WithExtraParam("bm25_k1", 1.2)
.WithExtraParam("bm25_b", 0.75)
const index_params = [
{
field_name: "sparse",
metric_type: "BM25",
index_type: "SPARSE_INVERTED_INDEX",
params: {
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
},
];
export indexParams='[
{
"fieldName": "sparse",
"metricType": "BM25",
"indexType": "AUTOINDEX",
"params":{
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
}
]'
Parametro |
Descrizione |
|---|---|
|
Il nome del campo vettoriale da indicizzare. Per la ricerca full text, questo dovrebbe essere il campo che memorizza i vettori sparsi generati. In questo esempio, impostare il valore su |
|
Il tipo di indice da creare. |
|
Il valore di questo parametro deve essere impostato su |
|
Un dizionario di parametri aggiuntivi specifici dell'indice. |
|
L'algoritmo usato per costruire e interrogare l'indice. Valori validi:
|
|
Controlla la saturazione della frequenza dei termini. Valori più alti aumentano l'importanza delle frequenze dei termini nella classificazione dei documenti. Intervallo di valori: [1.2, 2.0]. |
|
Controlla la misura in cui la lunghezza del documento viene normalizzata. In genere si utilizzano valori compresi tra 0 e 1, con un valore predefinito comunemente intorno a 0,75. Un valore di 1 significa nessuna normalizzazione della lunghezza, mentre un valore di 0 significa normalizzazione completa. |
Creare l'insieme
Creare ora l'insieme utilizzando i parametri dello schema e dell'indice definiti.
client.create_collection(
collection_name='my_collection',
schema=schema,
index_params=index_params
)
import io.milvus.v2.service.collection.request.CreateCollectionReq;
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("my_collection")
.collectionSchema(schema)
.indexParams(indexes)
.build();
client.createCollection(requestCreate);
err = client.CreateCollection(ctx,
milvusclient.NewCreateCollectionOption("my_collection", schema).
WithIndexOptions(indexOption))
if err != nil {
fmt.Println(err.Error())
// handle error
}
await client.create_collection(
collection_name: 'my_collection',
schema: schema,
index_params: index_params,
functions: functions
);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
Inserire i dati di testo
Dopo aver impostato la collezione e l'indice, si è pronti a inserire i dati di testo. In questo processo, è sufficiente fornire il testo grezzo. La funzione integrata definita in precedenza genera automaticamente il vettore sparse corrispondente per ogni voce di testo.
client.insert('my_collection', [
{'text': 'information retrieval is a field of study.'},
{'text': 'information retrieval focuses on finding relevant information in large datasets.'},
{'text': 'data mining and information retrieval overlap in research.'},
])
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
Gson gson = new Gson();
List<JsonObject> rows = Arrays.asList(
gson.fromJson("{\"text\": \"information retrieval is a field of study.\"}", JsonObject.class),
gson.fromJson("{\"text\": \"information retrieval focuses on finding relevant information in large datasets.\"}", JsonObject.class),
gson.fromJson("{\"text\": \"data mining and information retrieval overlap in research.\"}", JsonObject.class)
);
client.insert(InsertReq.builder()
.collectionName("my_collection")
.data(rows)
.build());
// go
await client.insert({
collection_name: 'my_collection',
data: [
{'text': 'information retrieval is a field of study.'},
{'text': 'information retrieval focuses on finding relevant information in large datasets.'},
{'text': 'data mining and information retrieval overlap in research.'},
]);
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"data": [
{"text": "information retrieval is a field of study."},
{"text": "information retrieval focuses on finding relevant information in large datasets."},
{"text": "data mining and information retrieval overlap in research."}
],
"collectionName": "my_collection"
}'
Eseguire una ricerca full text
Una volta inseriti i dati nella raccolta, è possibile eseguire ricerche full text utilizzando query di testo grezzo. Milvus converte automaticamente la query in un vettore sparse e classifica i risultati della ricerca utilizzando l'algoritmo BM25, per poi restituire i risultati topK (limit).
È possibile evidenziare i termini corrispondenti nei risultati della ricerca configurando un evidenziatore di testo. Per maggiori informazioni, vedere Evidenziatore di testo.
res = client.search(
collection_name='my_collection',
data=['whats the focus of information retrieval?'],
anns_field='sparse',
output_fields=['text'], # Fields to return in search results; sparse field cannot be output
limit=3,
)
print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.response.SearchResp;
Map<String,Object> searchParams = new HashMap<>();
SearchResp searchResp = client.search(SearchReq.builder()
.collectionName("my_collection")
.data(Collections.singletonList(new EmbeddedText("whats the focus of information retrieval?")))
.annsField("sparse")
.topK(3)
.searchParams(searchParams)
.outputFields(Collections.singletonList("text"))
.build());
annSearchParams := index.NewCustomAnnParam()
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection", // collectionName
3, // limit
[]entity.Vector{entity.Text("whats the focus of information retrieval?")},
).WithConsistencyLevel(entity.ClStrong).
WithANNSField("sparse").
WithAnnParam(annSearchParams).
WithOutputFields("text"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
fmt.Println("text: ", resultSet.GetColumn("text").FieldData().GetScalars())
}
await client.search(
collection_name: 'my_collection',
data: ['whats the focus of information retrieval?'],
anns_field: 'sparse',
output_fields: ['text'],
limit: 3,
)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--data-raw '{
"collectionName": "my_collection",
"data": [
"whats the focus of information retrieval?"
],
"annsField": "sparse",
"limit": 3,
"outputFields": [
"text"
],
"searchParams":{
"params":{}
}
}'
Parametro |
Descrizione |
|---|---|
|
Un dizionario contenente i parametri di ricerca. |
|
Percentuale di termini di scarsa importanza da ignorare durante la ricerca. Per i dettagli, fare riferimento a Vettore sparso. |
|
Testo grezzo della query in linguaggio naturale. Milvus converte automaticamente la query di testo in vettori sparsi usando la funzione BM25 - non fornire vettori precalcolati. |
|
Il nome del campo che contiene i vettori sparsi generati internamente. |
|
Elenco dei nomi dei campi da restituire nei risultati della ricerca. Supporta tutti i campi , tranne quello dei vettori sparsi che contengono le incorporazioni generate da BM25. I campi di output più comuni includono il campo della chiave primaria (ad esempio, |
|
Numero massimo di top match da restituire. |
FAQ
È possibile visualizzare o accedere ai vettori sparsi generati dalla funzione BM25 nella ricerca full text?
No, i vettori sparsi generati dalla funzione BM25 non sono direttamente accessibili o visualizzabili nella ricerca full text. Ecco i dettagli:
La funzione BM25 genera internamente vettori sparsi per la classificazione e il recupero.
Questi vettori sono memorizzati nel campo sparse, ma non possono essere inclusi nella ricerca full text.
output_fieldsÈ possibile produrre solo i campi di testo originali e i metadati (come
id,text).
Esempio:
# ❌ This throws an error - you cannot output the sparse field
client.search(
collection_name='my_collection',
data=['query text'],
anns_field='sparse',
output_fields=['text', 'sparse'] # 'sparse' causes an error
limit=3,
search_params=search_params
)
# ✅ This works - output text fields only
client.search(
collection_name='my_collection',
data=['query text'],
anns_field='sparse',
output_fields=['text']
limit=3,
search_params=search_params
)
Perché devo definire un campo vettoriale sparse se non posso accedervi?
Il campo vettoriale sparse serve come indice di ricerca interno, simile agli indici dei database con cui gli utenti non interagiscono direttamente.
Motivazione del progetto:
Separazione delle preoccupazioni: L'utente lavora con il testo (input/output), Milvus gestisce i vettori (elaborazione interna).
Prestazioni: I vettori sparsi precalcolati permettono di classificare velocemente BM25 durante le interrogazioni.
Esperienza utente: Astrazione di complesse operazioni vettoriali dietro una semplice interfaccia testuale.
Se avete bisogno di accedere ai vettori:
Utilizzate le operazioni manuali sui vettori sparsi invece della ricerca full-text
Creare collezioni separate per flussi di lavoro vettoriali sparsi personalizzati
Per maggiori dettagli, consultare Sparse Vector.