Eigene Dokumente durchsuchbar machen mit den Vector Data Extensions

Eigene Dokumente durchsuchbar machen mit den Vector Data Extensions

- Matt

Ein Sprachmodell kennt die eigene Dokumentation nicht. Der übliche Ausweg heißt RAG: Man sucht vorab die passenden Textstellen heraus und legt sie dem Modell mit in den Prompt. Der Teil, der dabei Arbeit macht, ist die Suche, denn sie soll nach Bedeutung finden und nicht nach Zeichenketten.

Dafür gibt es seit dem Frühjahr Microsoft.Extensions.VectorData, das dieselbe Rolle für Vektordatenbanken spielt wie IChatClient für Sprachmodelle: eine Abstraktion, hinter der Qdrant, Azure AI Search, SQLite oder auch nur eine Liste im Arbeitsspeicher stecken kann. Die Rolle von IChatClient für Sprachmodelle ist dieselbe.

Ein Datensatz

Die Ablage wird über Attribute an einer gewöhnlichen Klasse beschrieben:

using Microsoft.Extensions.VectorData;

public sealed class Abschnitt
{
    [VectorStoreKey]
    public Guid Id { get; set; }

    [VectorStoreData]
    public string Titel { get; set; } = "";

    [VectorStoreData]
    public string Text { get; set; } = "";

    [VectorStoreVector(1536)]
    public string? Inhalt => Text;
}

Die Zahl bei VectorStoreVector ist die Dimension des Embedding-Modells und muss dazu passen. Bei text-embedding-3-small sind es 1536. Nimmt man ein anderes Modell, muss die Zahl mit, sonst quittiert der Store das beim Anlegen der Collection.

Befüllen und suchen

var collection = vectorStore.GetCollection<Guid, Abschnitt>("doku");
await collection.EnsureCollectionExistsAsync();

await collection.UpsertAsync(new Abschnitt
{
    Id = Guid.NewGuid(),
    Titel = "Rechnung stornieren",
    Text = "Eine Rechnung kann bis zum Monatsabschluss storniert werden ..."
});

await foreach (var treffer in collection.SearchAsync("Wie mache ich eine Rechnung rückgängig?", top: 3))
{
    Console.WriteLine($"{treffer.Score:F3}  {treffer.Record.Titel}");
}

Bemerkenswert ist, was hier fehlt: Nirgends wird ein Embedding berechnet. Ist beim Store ein IEmbeddingGenerator hinterlegt, passiert das beim Schreiben und beim Suchen von selbst. Die Frage “Wie mache ich eine Rechnung rückgängig?” findet den Abschnitt über “stornieren”, obwohl kein Wort übereinstimmt. Genau darum geht es.

Der Teil, über den keiner schreibt

Die Bibliothek nimmt einem die Datenbank ab. Sie nimmt einem nicht ab, die Dokumente sinnvoll zu zerlegen, und das ist die eigentliche Arbeit.

Zu große Abschnitte liefern Treffer, in denen die relevante Stelle untergeht. Zu kleine reißen den Zusammenhang auseinander, und man bekommt einen Satz ohne das Kapitel, zu dem er gehört. Bei mir hat sich bewährt, an Überschriften zu schneiden statt nach Zeichenzahl, und den Titel des Abschnitts in den eingebetteten Text mit aufzunehmen. Klingt banal, hat die Trefferqualität aber deutlicher verbessert als jeder Wechsel des Embedding-Modells.

Der zweite Punkt: Ähnlichkeit ist nicht Richtigkeit. Der Score sagt, wie nah sich zwei Texte im Vektorraum sind, nicht ob die Antwort dort drinsteht. Ohne eine untere Schranke bekommt man zu jeder Frage drei Treffer, auch zu einer, die die Doku überhaupt nicht behandelt. Das Modell baut daraus dann bereitwillig eine falsche Antwort. Eine Schwelle, unterhalb derer man ehrlich “weiß ich nicht” sagt, ist deshalb kein Detail, sondern der Unterschied zwischen brauchbar und gefährlich.