Freigeben über


Verwenden von Vektorspeichern in .NET AI-Apps

Das 📦 Microsoft.Extensions.VectorData.Abstractions (MEVD)-Paket stellt eine einheitliche API zum Arbeiten mit Vektorspeichern in .NET bereit. Sie können denselben Code verwenden, um Einbettungen in verschiedenen Vektordatenbankanbietern zu speichern und zu durchsuchen.

In diesem Artikel erfahren Sie, wie Sie die wichtigsten Features der Bibliothek verwenden.

Voraussetzungen

Installieren der Pakete

Installieren Sie ein Anbieterpaket für Ihre Vektordatenbank. Das Microsoft.Extensions.VectorData.Abstractions Paket wird automatisch als transitive Abhängigkeit hinzugefügt. Im folgenden Beispiel wird der Speicheranbieter für Entwicklung und Tests verwendet:

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

Ersetzen Sie in Produktionsszenarien Microsoft.SemanticKernel.Connectors.InMemory mit dem Anbieter für Ihre Datenbank. Verfügbare Anbieter finden Sie unter Out-of-the-box Vector Store-Anbieter. (Trotz der Einbeziehung von "SemanticKernel" in die Anbieterpaketnamen haben diese Anbieter nichts mit dem semantischen Kernel zu tun und können überall in .NET verwendet werden, einschließlich Agent Framework.)

Definieren eines Datenmodells

Definieren Sie eine .NET-Klasse, um die Datensätze darzustellen, die Sie im Vektorspeicher speichern möchten. Verwenden Sie Attribute zum Kommentieren von Eigenschaften in der Klasse, je nachdem, ob sie den Primärschlüssel, allgemeine Daten oder Vektordaten darstellen. Im Folgenden finden Sie ein einfaches Beispiel:

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

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

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

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

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

Sie können Ihr Schema auch programmgesteuert mithilfe einer VectorStoreCollectionDefinition und als Alternative zur Verwendung von Attributen definieren. Dieser Ansatz ist nützlich, wenn Sie dasselbe Datenmodell mit unterschiedlichen Konfigurationen verwenden möchten oder wenn Sie der Datenmodellklasse keine Attribute hinzufügen können.

Weitere Informationen finden Sie unter Definieren des Datenmodells.

Erstellen eines Vektorspeichers

Erstellen Sie eine Instanz der VectorStore Implementierung für die ausgewählte Datenbank. Im folgenden Beispiel wird ein speicherinterner Vektorspeicher erstellt:

// Create an in-memory vector store (no external service required).
// For production, replace this with a connector for your preferred database.
var vectorStore = new InMemoryVectorStore();

Sammlung abrufen

Rufen Sie GetCollection auf dem VectorStore auf, um eine typisierte VectorStoreCollection<TKey,TRecord>-Referenz zu erhalten. Rufen Sie EnsureCollectionExistsAsync dann auf, um die Sammlung zu erstellen, wenn sie noch nicht vorhanden ist:

// Get a reference to a collection named "hotels".
VectorStoreCollection<int, Hotel> collection =
    vectorStore.GetCollection<int, Hotel>("hotels");

// Ensure the collection exists in the database.
await collection.EnsureCollectionExistsAsync();

Der Sammlungsname entspricht dem zugrunde liegenden Speicherkonzept für Ihre Datenbank (z. B. einer Tabelle in SQL Server, einem Index in Azure AI Search oder einem Container in Cosmos DB).

Upsert-Datensätze

Verwenden Sie UpsertAsync, um Datensätze in der Sammlung einzufügen oder zu aktualisieren. Wenn ein Datensatz mit demselben Schlüssel bereits vorhanden ist, wird er aktualisiert:

// Upsert records into the collection.
// In a real app, generate embeddings using an IEmbeddingGenerator.
// The CreateFakeEmbedding helper at the bottom of this file generates
// placeholder vectors for demonstration purposes only.
var hotels = new List<Hotel>
{
    new()
    {
        HotelId = 1,
        HotelName = "Seaside Retreat",
        Description = "A peaceful hotel on the coast with stunning ocean views.",
        DescriptionEmbedding = CreateFakeEmbedding(1),
        Tags = ["beach", "ocean", "relaxation"]
    },
    new()
    {
        HotelId = 2,
        HotelName = "Mountain Lodge",
        Description = "A cozy lodge in the mountains with hiking trails nearby.",
        DescriptionEmbedding = CreateFakeEmbedding(2),
        Tags = ["mountain", "hiking", "nature"]
    },
    new()
    {
        HotelId = 3,
        HotelName = "City Centre Hotel",
        Description = "A modern hotel in the heart of the city, close to attractions.",
        DescriptionEmbedding = CreateFakeEmbedding(3),
        Tags = ["city", "business", "urban"]
    }
};

foreach (Hotel h in hotels)
{
    await collection.UpsertAsync(h);
}

Von Bedeutung

In einer echten App wird empfohlen, MEVD Einbettungen generieren zu lassen, bevor die Datensätze gespeichert werden.

Abrufen von Datensätzen

Verwenden Sie GetAsync zum Abrufen eines einzelnen Datensatzes anhand seines Schlüssels. Um mehrere Datensätze abzurufen, übergeben Sie ein IEnumerable<TKey> an GetAsync:

// Get a specific record by its key.
Hotel? hotel = await collection.GetAsync(1);
if (hotel is not null)
{
    Console.WriteLine($"Hotel: {hotel.HotelName}");
    Console.WriteLine($"Description: {hotel.Description}");
}

So rufen Sie mehrere Datensätze gleichzeitig ab:

// Get multiple records by their keys.
IAsyncEnumerable<Hotel> hotelBatch = collection.GetAsync([1, 2, 3]);
await foreach (Hotel h in hotelBatch)
{
    Console.WriteLine($"Batch hotel: {h.HotelName}");
}

Verwenden Sie SearchAsync, um Datensätze zu finden, die semantisch einer Abfrage ähneln. Übergeben Sie den Einbettungsvektor für Ihre Abfrage und die Anzahl der zurückzugebenden Ergebnisse:

// Search for the top 2 hotels most similar to the query embedding.
IAsyncEnumerable<VectorSearchResult<Hotel>> searchResults =
    collection.SearchAsync(queryEmbedding, top: 2);

await foreach (VectorSearchResult<Hotel> result in searchResults)
{
    Console.WriteLine($"Found: {result.Record.HotelName} (score: {result.Score:F4})");
}

Jeder VectorSearchResult<TRecord> enthält den übereinstimmenden Datensatz und eine Ähnlichkeitsbewertung. Höhere Bewertungen deuten auf eine engere semantische Übereinstimmung hin.

Suchergebnisse filtern

Verwenden Sie VectorSearchOptions<TRecord> zum Filtern von Suchergebnissen vor dem Vektorvergleich. Sie können nach jeder eigenschaft filtern, die mit IsIndexed = true:

// Filter results before the vector comparison.
// Only properties marked with IsIndexed = true can be used in filters.
var searchOptions = new VectorSearchOptions<Hotel>
{
    Filter = h => h.HotelName == "Seaside Retreat"
};

IAsyncEnumerable<VectorSearchResult<Hotel>> filteredResults =
    collection.SearchAsync(queryEmbedding, top: 2, searchOptions);

await foreach (VectorSearchResult<Hotel> result in filteredResults)
{
    Console.WriteLine($"Filtered: {result.Record.HotelName} (score: {result.Score:F4})");
}

Filter werden als LINQ-Ausdrücke ausgedrückt. Die unterstützten Vorgänge variieren je nach Anbieter, aber alle Anbieter unterstützen gemeinsame Vergleiche wie Gleichheit, Ungleichheit und logische && und ||.

Steuern des Suchverhaltens mit VectorSearchOptions

Wird VectorSearchOptions<TRecord> verwendet, um verschiedene Aspekte des Vektorsuchverhaltens zu steuern:

// Use VectorSearchOptions to control paging and vector inclusion.
var pagedOptions = new VectorSearchOptions<Hotel>
{
    Skip = 1,           // Skip the first result (useful for paging).
    IncludeVectors = false  // Don't include vector data in results (default).
};

IAsyncEnumerable<VectorSearchResult<Hotel>> pagedResults =
    collection.SearchAsync(queryEmbedding, top: 2, pagedOptions);

await foreach (VectorSearchResult<Hotel> result in pagedResults)
{
    Console.WriteLine($"Paged: {result.Record.HotelName}");
}

In der folgenden Tabelle werden die verfügbaren Optionen beschrieben:

Auswahl Beschreibung
Filter Ein LINQ-Ausdruck zum Filtern von Datensätzen vor dem Vektorvergleich.
VectorProperty Die zu durchsuchende Vektoreigenschaft. Erforderlich, wenn das Datenmodell mehrere Vektoreigenschaften aufweist.
Skip Anzahl der Ergebnisse, die vor der Rückgabe übersprungen werden sollen. Nützlich für das Paging. Der Standardwert ist 0.
IncludeVectors Gibt an, ob Vektordaten in die zurückgegebenen Datensätze einbezogen werden sollen. Das Weglassen von Vektoren reduziert die Datenübertragung. Der Standardwert ist false.

Weitere Informationen finden Sie unter Vektorsuchoptionen.

Verwenden Sie die integrierte Einbettungsgenerierung

Anstatt vor jedem Upsert manuell Einbettungen zu generieren, können Sie eine IEmbeddingGenerator auf einem Vektorspeicher oder einer Sammlung konfigurieren. Wenn Sie dies tun, deklarieren Sie Ihre Vektoreigenschaft als string-Typ (wie im Quelltext), und der Store generiert die Einbettung automatisch.

Weitere Informationen finden Sie unter Let the vector store generate embeddings.

Einige Vektorspeicher unterstützen die Hybridsuche, die die Vektorähnlichkeit mit dem Stichwortabgleich kombiniert. Dieser Ansatz kann die Ergebnisrelevanz im Vergleich zur Vektorsuche verbessern.

Um die Hybridsuche zu verwenden, überprüfen Sie, ob Ihre Sammlung IKeywordHybridSearchable<TRecord> implementiert wird. Nur Anbieter für Datenbanken, die dieses Feature unterstützen, implementieren diese Schnittstelle.

Weitere Informationen finden Sie unter Hybridsuche mit Vektorspeicheranbietern.

Löschen von Datensätzen

Um einen einzelnen Datensatz nach Schlüssel zu löschen, verwenden Sie Folgendes DeleteAsync:

// Delete a record by its key.
await collection.DeleteAsync(3);

Löschen einer Sammlung

Um eine gesamte Sammlung aus dem Vektorspeicher zu entfernen, verwenden Sie Folgendes EnsureCollectionDeletedAsync:

// Delete the entire collection from the vector store.
await collection.EnsureCollectionDeletedAsync();

Vektorspeicheranbieter wechseln

Da alle Anbieter dieselbe VectorStore abstrakte Klasse implementieren, können Sie zwischen ihnen wechseln, indem Sie den konkreten Typ beim Start ändern. Der Sammlungs- und Suchcode bleibt größtenteils gleich. Einige Anpassungen sind jedoch in der Regel erforderlich, z. B. weil unterschiedliche Datenbanken unterschiedliche Datentypen unterstützen.