Recherche en texte intégral

La recherche en texte intégral est une fonctionnalité qui permet de récupérer des documents contenant des termes ou des expressions spécifiques dans des ensembles de données textuelles, puis de classer les résultats en fonction de leur pertinence. Cette fonctionnalité pallie les limites de la recherche sémantique, qui peut passer à côté de termes précis, vous garantissant ainsi d'obtenir les résultats les plus précis et les plus pertinents d'un point de vue contextuel. De plus, elle simplifie les recherches vectorielles en acceptant la saisie de texte brut, convertissant automatiquement vos données textuelles en représentations vectorielles clairsemées sans qu'il soit nécessaire de générer manuellement ces représentations.

Utilisant l’algorithme BM25 pour le calcul de la pertinence, cette fonctionnalité est particulièrement utile dans les scénarios de génération augmentée par la recherche (RAG), où elle donne la priorité aux documents qui correspondent étroitement à des termes de recherche spécifiques.

En intégrant la recherche en texte intégral à la recherche vectorielle dense basée sur la sémantique, vous pouvez améliorer la précision et la pertinence des résultats de recherche. Pour plus d’informations, consultez la section Recherche hybride.

Implémentation de BM25

Milvus propose une recherche en texte intégral optimisée par l’algorithme de pertinence BM25, une fonction de notation largement adoptée dans les systèmes de recherche d’informations, et l’intègre dans le flux de travail de recherche afin de fournir des résultats textuels précis et classés par pertinence.

La recherche en texte intégral dans Milvus suit le flux de travail ci-dessous :

  1. Saisie de texte brut: vous insérez des documents textuels ou saisissez une requête en texte brut, sans qu’aucun modèle d’encodage ne soit nécessaire.

  2. Analyse du texte: Milvus utilise un analyseur pour traiter votre texte en termes significatifs pouvant être indexés et recherchés.

  3. Traitement par la fonction BM25: une fonction intégrée transforme ces termes en représentations vectorielles creuses optimisées pour le calcul de score BM25.

  4. Stockage dans une collection: Milvus stocke les représentations vectorielles clairsemées obtenues dans une collection afin de permettre une récupération et un classement rapides.

  5. Notation de pertinence BM25: au moment de la recherche, Milvus applique la fonction de notation BM25 pour calculer la pertinence des documents et renvoyer les résultats classés qui correspondent le mieux aux termes de la requête.

Full Text Search Recherche en texte intégral

Pour utiliser la recherche en texte intégral, suivez ces étapes principales :

  1. Créer une collection: configurez les champs requis et définissez une fonction BM25 qui convertit le texte brut en représentations clairsemées.

  2. Insérez les données: importez vos documents de texte brut dans la collection.

  3. Effectuez des recherches: utilisez une requête en langage naturel pour obtenir des résultats classés en fonction de la pertinence BM25.

Pour activer la recherche en texte intégral basée sur BM25, vous devez préparer une collection avec les champs requis, définir une fonction BM25 pour générer des vecteurs clairsemés, configurer un index, puis créer la collection.

Définir les champs du schéma

Le schéma de votre collection doit inclure au moins trois champs obligatoires :

  • Champ principal: identifie de manière unique chaque entité de la collection.

  • Champ de type chaîne (VARCHAR ou TEXT) : stocke les documents texte bruts. Vous devez définir l'enable_analyzer=True afin que Milvus puisse traiter le texte pour le classement par pertinence BM25. Par défaut, Milvus utilise l' standard analyzer pour l’analyse de texte. Pour configurer un autre analyzer, consultez la section Présentation des analyzers. Les exemples de cette page utilisent VARCHAR; pour les textes longs, vous pouvez définir le champ d’entrée comme TEXT et omettre max_length. Pour un exemple complet, consultez la section Champ de texte.

  • Champ de vecteurs clairsemés (SPARSE_FLOAT_VECTOR) : stocke les représentations clairsemées générées automatiquement par la fonction 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"
            }
        ]
    }'

Dans la configuration ci-dessus,

  • id: sert de clé primaire et est généré automatiquement avec auto_id=True.

  • text: stocke vos données textuelles brutes pour les opérations de recherche en texte intégral. Le champ peut utiliser VARCHAR pour le texte court ou TEXT pour les contenus sources volumineux.

  • sparse: un champ vectoriel réservé au stockage d’embeddings clairsemés générés en interne pour les opérations de recherche en texte intégral. Le type de données doit être SPARSE_FLOAT_VECTOR.

Définir la fonction BM25

La fonction BM25 convertit le texte tokenisé en vecteurs clairsemés prenant en charge le calcul de score BM25.

Définissez la fonction et ajoutez-la à votre schéma :

bm25_function = Function(
    name="text_bm25_emb", # Function name
    input_field_names=["text"], # Name of the VARCHAR or TEXT 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": {}
            }
        ]
    }'

Paramètre

Description

name

Nom de la fonction. Cette fonction convertit votre texte brut issu du champ « text » en vecteurs clairs compatibles avec BM25, qui seront stockés dans le champ « sparse ».

input_field_names

Nom du champ « VARCHAR » ou « TEXT » nécessitant une conversion de texte en vecteur clairsemé. Pour « FunctionType.BM25 », ce paramètre n’accepte qu’un seul nom de champ.

output_field_names

Nom du champ dans lequel les vecteurs creux générés en interne seront stockés. Pour FunctionType.BM25, ce paramètre n’accepte qu’un seul nom de champ.

function_type

Le type de fonction à utiliser. Doit être FunctionType.BM25.

Si plusieurs champs de texte nécessitent un traitement BM25, définissez une fonction BM25 par champ, chacune avec un nom et un champ de sortie uniques.

Configurer l’index

Après avoir défini le schéma avec les champs nécessaires et la fonction intégrée, configurez l’index de votre collection.

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
            }
        }
    ]'

Paramètre

Description

field_name

Nom du champ vectoriel à indexer. Pour la recherche en texte intégral, il doit s'agir du champ qui stocke les vecteurs clairsemés générés. Dans cet exemple, définissez la valeur sur sparse.

index_type

Type d’index à créer. Pour la recherche en texte intégral BM25 dans Milvus, définissez cette valeur sur SPARSE_INVERTED_INDEX. Pour plus d’informations, consultez SPARSE_INVERTED_INDEX.

metric_type

La valeur de ce paramètre doit être définie sur « BM25 » spécifiquement pour la fonctionnalité de recherche en texte intégral.

params

Un dictionnaire de paramètres supplémentaires spécifiques à l’index.

params.inverted_index_algo

L'algorithme utilisé pour la construction et l'interrogation de l'index inversé clairsemé BM25. Valeurs valides :

params.bm25_k1

Contrôle la saturation de la fréquence des termes. Des valeurs plus élevées augmentent l’importance de la fréquence des termes dans le classement des documents. Plage recommandée : [1,2 ; 2,0]. Valeur par défaut : 1,2.

params.bm25_b

Contrôle le degré de normalisation de la longueur des documents. On utilise généralement des valeurs comprises entre 0 et 1, la valeur par défaut étant 0,75. Une valeur de 0 signifie qu’il n’y a pas de normalisation de la longueur, tandis qu’une valeur de 1 signifie une normalisation complète de la longueur.

Créer la collection

Créez maintenant la collection à l’aide du schéma et des paramètres d’index définis.

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" \
--header "Request-Timeout: 10" \
-d "{
    \"collectionName\": \"my_collection\",
    \"schema\": $schema,
    \"indexParams\": $indexParams
}"

Insérer des données textuelles

Une fois votre collection et votre index configurés, vous êtes prêt à insérer des données textuelles. Pour cela, il vous suffit de fournir le texte brut. La fonction intégrée que nous avons définie précédemment génère automatiquement le vecteur creux correspondant à chaque entrée de texte.

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" \
--header "Request-Timeout: 10" \
-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"
}'

Une fois les données insérées dans votre collection, vous pouvez effectuer des recherches en texte intégral à l’aide de requêtes en texte brut. Milvus convertit automatiquement votre requête en vecteur creux et classe les résultats de recherche correspondants à l’aide de l’algorithme BM25, puis renvoie les topK (limit) résultats.

Vous pouvez mettre en évidence les termes correspondants dans les résultats de recherche en configurant un surligneur de texte. Consultez la section « Surligneur de texte » pour plus de détails.

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" \
--header "Request-Timeout: 10" \
--data-raw '{
    "collectionName": "my_collection",
    "data": [
        "whats the focus of information retrieval?"
    ],
    "annsField": "sparse",
    "limit": 3,
    "outputFields": [
        "text"
    ],
    "searchParams":{
        "params":{}
    }
}'

Paramètre

Description

search_params

Dictionnaire contenant les paramètres de recherche.

params.drop_ratio_search

Proportion de termes de faible importance à ignorer lors de la recherche. La valeur doit être comprise dans l'intervalle [0,0 ; 1,0). Pour plus de détails, consultez la section « Vecteur clairsemé ».

data

Texte brut de la requête en langage naturel. Milvus convertit automatiquement votre requête textuelle en vecteurs clairsemés à l’aide de la fonction BM25 — ne fournissez pas de vecteurs précalculés.

anns_field

Nom du champ contenant les vecteurs clairsemés générés en interne.

output_fields

Liste des noms de champs à renvoyer dans les résultats de recherche. Prend en charge tous les champs à l'exception du champ de vecteurs clairsemés contenant les représentations générées par BM25. Les champs de sortie courants incluent le champ de clé primaire (par exemple, id) et le champ de texte d'origine (par exemple, text). Pour plus d'informations, consultez la FAQ.

limit

Nombre maximal de résultats les plus pertinents à renvoyer.

FAQ

Non, les vecteurs creux générés par la fonction BM25 ne sont ni directement accessibles ni exportables dans la recherche en texte intégral. Voici les détails :

  • La fonction BM25 génère en interne des vecteurs creux à des fins de classement et de recherche

  • Ces vecteurs sont stockés dans le champ « sparse », mais ne peuvent pas être inclus dans output_fields

  • Vous ne pouvez exporter que les champs de texte d'origine et les métadonnées (telles que id, text)

Exemple :

# ❌ 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
)

Pourquoi dois-je définir un champ de vecteur clairsemé si je ne peux pas y accéder ?

Le champ de vecteurs creux sert d’index de recherche interne, à l’instar des index de base de données avec lesquels les utilisateurs n’interagissent pas directement.

Justification de la conception:

  • Séparation des préoccupations : vous travaillez avec du texte (entrée/sortie), Milvus gère les vecteurs (traitement interne)

  • Performances : les vecteurs creux précalculés permettent un classement BM25 rapide lors des requêtes

  • Expérience utilisateur : les opérations vectorielles complexes sont masquées derrière une interface textuelle simple

Si vous avez besoin d'accéder aux vecteurs:

  • Utilisez des opérations manuelles sur les vecteurs creux plutôt que la recherche en texte intégral

  • Créez des collections distinctes pour les workflows personnalisés impliquant des vecteurs creux

Pour plus de détails, consultez la section « Vecteurs creux ».