Partager via


Qu’est-ce que les entrepôts de vecteurs de noyau sémantique ? (Aperçu)

Avertissement

La fonctionnalité de magasin vectoriel Semantic Kernel est en version RC et des améliorations nécessitant des changements cassants peuvent encore survenir dans des circonstances limitées avant la sortie.

Avertissement

La fonctionnalité Semantic Kernel Vector Store est en préversion, et des améliorations nécessitant des modifications majeures peuvent encore survenir dans certains cas avant la sortie finale.

Conseil

Si vous recherchez des informations sur les connecteurs Memory Store hérités, veuillez vous reporter à la page Memory Stores.

Les bases de données vectorielles ont de nombreux cas d’usage dans différents domaines et applications qui impliquent le traitement du langage naturel (NLP), la vision par ordinateur (CV), les systèmes de recommandation (RS) et d’autres domaines qui nécessitent une compréhension sémantique et une correspondance des données.

Un cas d’usage pour stocker des informations dans une base de données vectorielle consiste à permettre aux modèles de langage volumineux (LLMs) de générer des réponses plus pertinentes et cohérentes. Les grands modèles linguistiques sont souvent confrontés à des défis tels que la génération d’informations inexactes ou non pertinentes ; absence de cohérence factuelle ou de bon sens ; répéter ou contredire eux-mêmes ; être biaisé ou offensif. Pour vous aider à surmonter ces défis, vous pouvez utiliser une base de données vectorielle pour stocker des informations sur différentes rubriques, mots clés, faits, opinions et/ou sources liées à votre domaine ou genre souhaité. La base de données vectorielle vous permet de trouver efficacement le sous-ensemble d’informations relatives à une question ou une rubrique spécifique. Vous pouvez ensuite transmettre les informations de la base de données vectorielle avec votre requête à votre modèle linguistique volumineux afin de générer un contenu plus précis et plus pertinent.

Par exemple, si vous souhaitez écrire un billet de blog sur les dernières tendances de l’IA, vous pouvez utiliser une base de données vectorielle pour stocker les dernières informations sur cette rubrique et transmettre les informations avec la demande à un LLM afin de générer un billet de blog qui tire parti des dernières informations.

Le Semantic Kernel et .NET fournissent une abstraction pour interagir avec les magasins de vecteurs et une liste d’implémentations prêtes à l’emploi qui implémentent ces abstractions pour différentes bases de données. Les fonctionnalités incluent la création, la liste et la suppression de collections d’enregistrements et le chargement, la récupération et la suppression d’enregistrements. L’abstraction facilite l’expérimentation d’un magasin vectoriel gratuit ou hébergé localement, puis de basculer vers un service lorsque vous avez besoin d’effectuer un scale-up.

Les implémentations prêtes à l’emploi peuvent être utilisées avec le noyau sémantique, mais ne dépendent pas de la pile noyau sémantique principale et peuvent donc être utilisées de manière complètement indépendante si nécessaire. Les imlementations fournies par le noyau sémantique sont appelées « connecteurs ».

Génération augmentée par la recherche (RAG) avec des magasins vectoriels

L'abstraction du stockage vectoriel est une API de bas niveau permettant l'ajout et la récupération de données à partir de stockages vectoriels. Semantic Kernel prend en charge l’utilisation de n’importe quelle implémentation de magasin vectoriel pour la RAG. Cela est obtenu en encapsulant IVectorSearchable<TRecord> et en l’exposant en tant qu’implémentation de recherche de texte.

Conseil

Pour en savoir plus sur l’utilisation des magasins de vecteurs pour RAG, consultez Comment utiliser des magasins de vecteurs avec la recherche de texte du noyau sémantique.

Conseil

Pour en savoir plus sur la recherche de texte, consultez Qu’est-ce que la recherche de texte du noyau sémantique ?

Conseil

Pour en savoir plus sur la façon d’ajouter rapidement RAG à votre agent, consultez Ajout d’une génération augmentée de récupération (RAG) à des agents de noyau sémantique.

Abstraction du magasin vectoriel

Les abstractions du magasin vectoriel sont fournies dans le package nuget Microsoft.Extensions.VectorData.Abstractions. Voici les principales classes et interfaces de base abstraites.

Microsoft.Extensions.VectorData.VectorStore

VectorStore contient des opérations qui s’étendent sur toutes les collections du magasin vectoriel, par exemple ListCollectionNames. Elle offre également la possibilité d’obtenir des instances VectorStoreCollection<TKey, TRecord>.

Microsoft.Extensions.VectorData.VectorStoreCollection<TKey, TRecord>

VectorStoreCollection<TKey, TRecord> représente une collection. Cette collection peut exister ou non, et la classe de base abstraite fournit des méthodes pour vérifier si la collection existe, la créer ou la supprimer. La classe de base abstraite fournit également des méthodes pour mettre à jour, obtenir et supprimer des enregistrements. Enfin, la classe de base abstraite hérite de IVectorSearchable<TRecord>, qui fournit des fonctionnalités de recherche vectorielle.

Microsoft.Extensions.VectorData.IVectorSearchable<TRecord>

  • SearchAsync<TRecord> peut être utilisé pour effectuer les opérations suivantes :
    • recherches vectorielles à partir d’une entrée pouvant être vectorisée par un générateur d’intégration enregistré ou par la base de données vectorielle lorsque celle-ci prend en charge cette fonctionnalité.
    • recherches vectorielles prenant un vecteur comme entrée.

Génération augmentée par la recherche (RAG) avec des magasins vectoriels

Les abstractions de magasin de vecteurs sont une API de bas niveau permettant d’ajouter et de récupérer des données à partir de magasins vectoriels. Semantic Kernel prend en charge l’utilisation de n’importe quelle implémentation de magasin vectoriel pour la RAG. Pour ce faire, vous pouvez encapsuler VectorSearchBase[TKey, TModel] avec VectorizedSearchMixin[Tmodel], VectorizableTextSearchMixin[TModel] ou VectorTextSearch[TModel] et l’exposer en tant qu’implémentation de recherche de texte.

Conseil

Pour en savoir plus sur l’utilisation des magasins de vecteurs pour RAG, consultez Comment utiliser des magasins de vecteurs avec la recherche de texte du noyau sémantique.

Conseil

Pour en savoir plus sur la recherche de texte, consultez Qu’est-ce que la recherche de texte du noyau sémantique ?

Abstraction du magasin vectoriel

Les principales interfaces de l’abstraction Vector Store sont les suivantes.

com.microsoft.semantickernel.data.vectorstorage.VectorStore

VectorStore contient des opérations qui s’étendent sur toutes les collections du magasin vectoriel, par exemple listCollectionNames. Elle offre également la possibilité d’obtenir des instances VectorStoreRecordCollection<Key, Record>.

com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordCollection<Key, Record>

VectorStoreRecordCollection<Key, Record> représente une collection. Cette collection peut ou non exister et l’interface fournit des méthodes pour vérifier si la collection existe, la créer ou la supprimer. L’interface fournit également des méthodes pour mettre à jour, obtenir et supprimer des enregistrements. Enfin, l’interface hérite de VectorizedSearch<Record>, qui fournit des capacités de recherche vectorielle.

com.microsoft.semantickernel.data.vectorsearch.VectorizedSearch<Record>

VectorizedSearch<Record> contient une méthode pour effectuer des recherches vectorielles. VectorStoreRecordCollection<Key, Record> hérite de VectorizedSearch<Record>, ce qui permet d'utiliser VectorizedSearch<Record> seul dans les cas où seule la recherche est nécessaire sans qu'il n'y ait besoin de gestion des enregistrements ou des collections.

com.microsoft.semantickernel.data.vectorsearch.VectorizableTextSearch<Record>

VectorizableTextSearch<Record> contient une méthode pour effectuer des recherches vectorielles où la base de données vectorielle a la possibilité de générer automatiquement des incorporations. Par exemple, vous pouvez appeler cette méthode avec une chaîne de texte et la base de données génère l’incorporation pour vous et effectue une recherche sur un champ vectoriel. Cela n’est pas pris en charge par toutes les bases de données vectorielles et est donc implémenté uniquement par les connecteurs sélectionnés.

Démarrer avec les magasins vectoriels

Importer les packages nuget nécessaires

Toutes les interfaces de magasin de vecteurs et toutes les classes associées à l’abstraction sont disponibles dans le Microsoft.Extensions.VectorData.Abstractions package nuget. Chaque implémentation de magasin de vecteurs est disponible dans son propre package nuget. Pour obtenir la liste des implémentations connues, consultez la Page des connecteurs prêts à l'emploi.

Le package d’abstractions peut être ajouté comme suit.

dotnet add package Microsoft.Extensions.VectorData.Abstractions

Définir votre modèle de données

Les abstractions de magasin vectoriel utilisent une approche axée sur le modèle pour interagir avec les bases de données. Cela signifie que la première étape consiste à définir un modèle de données mappé au schéma de stockage. Pour aider les implémentations à créer des collections d’enregistrements et à mapper au schéma de stockage, le modèle peut être annoté pour indiquer la fonction de chaque propriété.

using Microsoft.Extensions.VectorData;

public class Hotel
{
    [VectorStoreKey]
    public ulong HotelId { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public string HotelName { get; set; }

    [VectorStoreData(IsFullTextIndexed = true)]
    public string Description { get; set; }

    [VectorStoreVector(Dimensions: 4, DistanceFunction = DistanceFunction.CosineSimilarity, IndexKind = IndexKind.Hnsw)]
    public ReadOnlyMemory<float>? DescriptionEmbedding { get; set; }

    [VectorStoreData(IsIndexed = true)]
    public string[] Tags { get; set; }
}
from dataclasses import dataclass, field
from typing import Annotated
from semantic_kernel.data.vector import (
    DistanceFunction,
    IndexKind,
    VectorStoreField,
    vectorstoremodel,
)

@vectorstoremodel
@dataclass
class Hotel:
    hotel_id: Annotated[str, VectorStoreField('key')] = field(default_factory=lambda: str(uuid4()))
    hotel_name: Annotated[str, VectorStoreField('data', is_filterable=True)]
    description: Annotated[str, VectorStoreField('data', is_full_text_searchable=True)]
    description_embedding: Annotated[list[float], VectorStoreField('vector', dimensions=4, distance_function=DistanceFunction.COSINE, index_kind=IndexKind.HNSW)]
    tags: Annotated[list[str], VectorStoreField('data', is_filterable=True)]
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordData;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordKey;
import com.microsoft.semantickernel.data.vectorstorage.annotations.VectorStoreRecordVector;
import com.microsoft.semantickernel.data.vectorstorage.definition.DistanceFunction;
import com.microsoft.semantickernel.data.vectorstorage.definition.IndexKind;

import java.util.Collections;
import java.util.List;

public class Hotel {
    @VectorStoreRecordKey
    private String hotelId;

    @VectorStoreRecordData(isFilterable = true)
    private String name;

    @VectorStoreRecordData(isFullTextSearchable = true)
    private String description;

    @VectorStoreRecordVector(dimensions = 4, indexKind = IndexKind.HNSW, distanceFunction = DistanceFunction.COSINE_DISTANCE)
    private List<Float> descriptionEmbedding;

    @VectorStoreRecordData(isFilterable = true)
    private List<String> tags;

    public Hotel() { }

    public Hotel(String hotelId, String name, String description, List<Float> descriptionEmbedding, List<String> tags) {
        this.hotelId = hotelId;
        this.name = name;
        this.description = description;
        this.descriptionEmbedding = Collections.unmodifiableList(descriptionEmbedding);
        this.tags = Collections.unmodifiableList(tags);
    }

    public String getHotelId() { return hotelId; }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public List<Float> getDescriptionEmbedding() { return descriptionEmbedding; }
    public List<String> getTags() { return tags; }
}

Conseil

Pour plus d’informations sur la façon d’annoter votre modèle de données, reportez-vous à la définition de votre modèle de données.

Conseil

Pour une alternative à l’annotation de votre modèle de données, reportez-vous à la définition de votre schéma à l’aide d’une définition d’enregistrement.

Se connecter à votre base de données et sélectionner une collection

Une fois que vous avez défini votre modèle de données, l’étape suivante consiste à créer une instance VectorStore pour la base de données de votre choix et à sélectionner une collection d’enregistrements.

Dans cet exemple, nous allons utiliser Qdrant. Vous devez donc importer le package nuget Qdrant.

dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant --prerelease

Si vous souhaitez exécuter Qdrant localement à l’aide de Docker, utilisez la commande suivante pour démarrer le conteneur Qdrant avec les paramètres utilisés dans cet exemple.

docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant:latest

Pour vérifier que votre instance Qdrant est opérationnelle correctement, visitez le tableau de bord Qdrant intégré au conteneur Docker Qdrant : http://localhost:6333/dashboard

Étant donné que les bases de données prennent en charge de nombreux types de clés et d’enregistrements différents, nous vous permettent de spécifier le type de la clé et de l’enregistrement pour votre collection à l’aide de génériques. Dans notre cas, le type d’enregistrement sera la Hotel classe que nous avons déjà définie, et le type de clé sera ulong, car la HotelId propriété est un ulong et Qdrant prend uniquement en charge Guid ou ulong clés.

using Microsoft.SemanticKernel.Connectors.Qdrant;
using Qdrant.Client;

// Create a Qdrant VectorStore object
var vectorStore = new QdrantVectorStore(new QdrantClient("localhost"), ownsClient: true);

// Choose a collection from the database and specify the type of key and record stored in it via Generic parameters.
var collection = vectorStore.GetCollection<ulong, Hotel>("skhotels");

Étant donné que les bases de données prennent en charge de nombreux types de clés et d’enregistrements différents, nous vous permettent de spécifier le type de la clé et de l’enregistrement pour votre collection à l’aide de génériques. Dans notre cas, le type d’enregistrement sera la Hotel classe que nous avons déjà définie, et le type de clé sera str, car la HotelId propriété est un str et Qdrant prend uniquement en charge str ou int clés.

from semantic_kernel.connectors.qdrant import QdrantCollection

# Create a collection specify the type of key and record stored in it via Generic parameters.
collection: QdrantCollection[str, Hotel] = QdrantCollection(
    record_type=Hotel,
    collection_name="skhotels" # this is optional, you can also specify the collection_name in the vectorstoremodel decorator.
)

Étant donné que les bases de données prennent en charge de nombreux types de clés et d’enregistrements différents, nous vous permettent de spécifier le type de la clé et de l’enregistrement pour votre collection à l’aide de génériques. Dans notre cas, le type d’enregistrement sera la classe Hotel que nous avons déjà définie, et le type de clé sera String, car la propriété hotelId est un String et le magasin JDBC ne prend en charge que les clés String.

import com.microsoft.semantickernel.data.jdbc.JDBCVectorStore;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreOptions;
import com.microsoft.semantickernel.data.jdbc.JDBCVectorStoreRecordCollectionOptions;
import com.microsoft.semantickernel.data.jdbc.mysql.MySQLVectorStoreQueryProvider;
import com.mysql.cj.jdbc.MysqlDataSource;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Create a MySQL data source
        var dataSource = new MysqlDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sk");
        dataSource.setPassword("root");
        dataSource.setUser("root");

        // Create a JDBC vector store
        var vectorStore = JDBCVectorStore.builder()
            .withDataSource(dataSource)
            .withOptions(
                JDBCVectorStoreOptions.builder()
                    .withQueryProvider(MySQLVectorStoreQueryProvider.builder()
                        .withDataSource(dataSource)
                        .build())
                    .build()
            )
            .build();

        // Get a collection from the vector store
        var collection = vectorStore.getCollection("skhotels",
            JDBCVectorStoreRecordCollectionOptions.<Hotel>builder()
                .withRecordClass(Hotel.class)
                .build()
        );
    }
}

Conseil

Pour plus d’informations sur les types de clés et de champs pris en charge par chaque implémentation de Vector Store, reportez-vous à la documentation de chaque implémentation.

Créer la collection et ajouter des enregistrements

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

// Create the collection if it doesn't exist yet.
await collection.EnsureCollectionExistsAsync();

// Upsert a record.
string descriptionText = "A place where everyone can be happy.";
ulong hotelId = 1;

// Create a record and generate a vector for the description using your chosen embedding generation implementation.
await collection.UpsertAsync(new Hotel
{
    HotelId = hotelId,
    HotelName = "Hotel Happy",
    Description = descriptionText,
    DescriptionEmbedding = await GenerateEmbeddingAsync(descriptionText),
    Tags = new[] { "luxury", "pool" }
});

// Retrieve the upserted record.
Hotel? retrievedHotel = await collection.GetAsync(hotelId);

Créer la collection et ajouter des enregistrements

# Create the collection if it doesn't exist yet.
await collection.ensure_collection_exists()

# Upsert a record.
description = "A place where everyone can be happy."
hotel_id = "1"

await collection.upsert(Hotel(
    hotel_id = hotel_id,
    hotel_name = "Hotel Happy",
    description = description,
    description_embedding = await GenerateEmbeddingAsync(description),
    tags = ["luxury", "pool"]
))

# Retrieve the upserted record.
retrieved_hotel = await collection.get(hotel_id)
// Create the collection if it doesn't exist yet.
collection.createCollectionAsync().block();

// Upsert a record.
var description = "A place where everyone can be happy";
var hotelId = "1";
var hotel = new Hotel(
    hotelId, 
    "Hotel Happy", 
    description, 
    generateEmbeddingsAsync(description).block(), 
    List.of("luxury", "pool")
);

collection.upsertAsync(hotel, null).block();

// Retrieve the upserted record.
var retrievedHotel = collection.getAsync(hotelId, null).block();

Conseil

Pour plus d’informations sur la génération d'embeddings, consultez la génération d'embeddings.

// Placeholder embedding generation method.
async Task<ReadOnlyMemory<float>> GenerateEmbeddingAsync(string textToVectorize)
{
    // your logic here
}

// Generate a vector for your search text, using your chosen embedding generation implementation.
ReadOnlyMemory<float> searchVector = await GenerateEmbeddingAsync("I'm looking for a hotel where customer happiness is the priority.");

// Do the search.
var searchResult = collection.SearchAsync(searchVector, top: 1);

// Inspect the returned hotel.
await foreach (var record in searchResult)
{
    Console.WriteLine("Found hotel description: " + record.Record.Description);
    Console.WriteLine("Found record score: " + record.Score);
}

Effectuer une recherche vectorielle

La méthode de recherche peut être utilisée pour rechercher des enregistrements dans la collection. Il prend soit une chaîne de caractères, qui est ensuite vectorisée par la configuration d'incorporation dans le cadre du modèle ou de la collection, soit un vecteur déjà généré.

# Do a search.
search_result = await collection.search("I'm looking for a hotel where customer happiness is the priority.", vector_property_name="description_embedding", top=3)

# Inspect the returned hotels.
async for result in search_result.results:
    print(f"Found hotel description: {result.record.description}")

Créer une fonction de recherche

Pour créer une fonction de recherche simple qui peut être utilisée pour rechercher des hôtels, vous pouvez utiliser la create_search_function méthode sur la collection.

Le nom et la description, ainsi que les noms et descriptions des paramètres, sont utilisés pour générer une signature de fonction envoyée au LLM lorsque l’appel de fonction est utilisé. Cela signifie qu’il peut être utile de modifier ce paramètre pour que le GML génère l’appel de fonction correct.

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`."
)

Il existe beaucoup d’autres paramètres, par exemple, c’est ce que ressemble une version plus complexe, notez la personnalisation des paramètres et la string_mapper fonction utilisée pour convertir l’enregistrement en chaîne.

from semantic_kernel.function import KernelParameterMetadata

collection.create_search_function(
    function_name="hotel_search",
    description="A hotel search engine, allows searching for hotels in specific cities, "
    "you do not have to specify that you are searching for hotels, for all, use `*`.",
    search_type="keyword_hybrid", # default is "vector"
    parameters=[
        KernelParameterMetadata(
            name="query",
            description="The terms you want to search for in the hotel database.",
            type="str",
            is_required=True,
            type_object=str,
        ),
        KernelParameterMetadata(
            name="tags",
            description="The tags you want to search for in the hotel database, use `*` to match all.",
            type="str",
            type_object=str,
            default_value="*",
        ),
        KernelParameterMetadata(
            name="top",
            description="Number of results to return.",
            type="int",
            default_value=5,
            type_object=int,
        ),
    ],
    # finally, we specify the `string_mapper` function that is used to convert the record to a string.
    # This is used to make sure the relevant information from the record is passed to the LLM.
    string_mapper=lambda x: f"Hotel {x.record.hotel_name}: {x.record.description}. Tags: {x.record.tags} (hotel_id: {x.record.hotel_id}) ", 
)

Conseil

Pour plus d’exemples, notamment des exemples de bout en bout, consultez le référentiel d’exemples de noyau sémantique.

// Generate a vector for your search text, using your chosen embedding generation implementation.
// Just showing a placeholder method here for brevity.
var searchVector = generateEmbeddingsAsync("I'm looking for a hotel where customer happiness is the priority.").block();

// Do the search.
var searchResult = collection.searchAsync(searchVector, VectorSearchOptions.builder()
    .withTop(1).build()
).block();

Hotel record = searchResult.getResults().get(0).getRecord();
System.out.printf("Found hotel description: %s\n", record.getDescription());

Conseil

Pour plus d’informations sur la génération d'embeddings, consultez la génération d'embeddings.

Étapes suivantes