Aufbau einer Anwalts-GPT-" für Ihren BlogTeil 3: das Einbetten von Vektordatenbanken verstehen (Deutsch (German))

Aufbau einer Anwalts-GPT-" für Ihren BlogTeil 3: das Einbetten von Vektordatenbanken verstehen

Wednesday, 12 November 2025

//

21 minute read

Einleitung

Willkommen auf Teil -3! , wir haben unseren GPU-Stack arbeiten -( ,TeilUnd wir verstehen die Architektur.TeilJetzt ist es Zeit, in die Magie einzutauchen, die semantische Suche möglich macht. Einbettungen und Vektordatenbanken.

HINWEIS:-Dies ist Teil meiner Experimente mit KI (-unterstütztes Abfassen-)- Meine eigene Bearbeitung - .- Dieselbe Stimme - gleiche Pragmatismus - gerade schnellere Finger - .-

Dies ist, wo die Dinge werden interessant...We...'.re gehen zu verstehen, wie man Text als Zahlen in einer Weise darstellen, die Bedeutung erfasst ,... nicht nur Schlüsselwörter...It.MSC4.s den Unterschied zwischen der Suche nach Posts, die das Wort enthalten.

Was sind Einbettungen?

Eine Einbettung ist eine numerische Darstellung von Texten oder Bildern, die im Wesentlichen eine Liste von Zahlen darstellen.

Die Intuition

Stellen Sie sich vor, Worte in einem Raum auf der Grundlage ihrer Bedeutung zu plotten.

graph TD
    subgraph "2D Embedding Space (simplified)"
        A[cat: 0.8, 0.2]
        B[kitten: 0.7, 0.3]
        C[dog: 0.6, 0.1]
        D[puppy: 0.5, 0.2]
        E[car: -0.5, 0.8]
        F[vehicle: -0.6, 0.7]
        G[database: 0.1, -0.7]
        H[SQL: 0.2, -0.8]
    end

    class A,B cats
    class C,D dogs
    class E,F vehicles
    class G,H tech

    classDef cats stroke:#333
    classDef dogs stroke:#333
    classDef vehicles stroke:#333
    classDef tech stroke:#333

Ähnliche Konzepte cluster zusammen

  • Haustiere sind nahe beieinander
  • Fahrzeuge sind gruppiert
  • Technologische Begriffe
  • Unzusammenhängende Konzepte sind weit auseinander

Reale Einbettungen Verwenden Sie 384, um die Abmessungen von 1536, nicht nur das Erfassen von mehr Nuancen von ., zu nutzen

Wie es funktioniert

Ein Embedding-Modell ist ein neuronales Netzwerk, das darauf trainiert wird, Text zu Vektoren so zu kartieren, dass .

  • Ähnliche Bedeutungen Vektoren schließen
  • Verschiedene Bedeutungen entfernte Vektoren
graph LR
    A[Text: 'Docker container'] --> B[Embedding Model]
    B --> C[Vector: 384 floats]

    D[Text: 'containerization'] --> B
    B --> E[Vector: 384 floats]

    C -.Similar.-> E

    F[Text: 'chocolate cake'] --> B
    B --> G[Vector: 384 floats]

    C -.Very Different.-> G

    class B model
    class C,E similar
    class G different

    classDef model stroke:#333,stroke-width:4px
    classDef similar stroke:#333
    classDef different stroke:#333

Vergleichsmessung

Wir benutzen Cosin-Ähnlichkeit zu messen, wie nahe zwei Vektoren sind

public static float CosineSimilarity(float[] vectorA, float[] vectorB)
{
    if (vectorA.Length != vectorB.Length)
        throw new ArgumentException("Vectors must have same length");

    // Dot product: sum of element-wise multiplication
    float dotProduct = 0;
    for (int i = 0; i < vectorA.Length; i++)
    {
        dotProduct += vectorA[i] * vectorB[i];
    }

    // Magnitude of each vector: sqrt(sum of squares)
    float magnitudeA = 0;
    float magnitudeB = 0;
    for (int i = 0; i < vectorA.Length; i++)
    {
        magnitudeA += vectorA[i] * vectorA[i];
        magnitudeB += vectorB[i] * vectorB[i];
    }
    magnitudeA = MathF.Sqrt(magnitudeA);
    magnitudeB = MathF.Sqrt(magnitudeB);

    // Cosine similarity: dot product / (magnitude_a * magnitude_b)
    return dotProduct / (magnitudeA * magnitudeB);
}

Cosine-Ähnlichkeit reicht von --1- bis zu -1-:

  • 1.0 [Name und Anschrift des Empfängers]
  • 0.0 Nicht verwandte
  • -1.0 Im Gegensatz zu den anderen in der Praxis verwendeten Begriffen:

Beispiel:

var dockerEmbed = new float[] { 0.5f, 0.3f, -0.2f, 0.8f };  // "Docker container"
var containerEmbed = new float[] { 0.45f, 0.35f, -0.18f, 0.75f };  // "containerization"
var cakeEmbed = new float[] { -0.7f, 0.1f, 0.9f, -0.3f };  // "chocolate cake"

Console.WriteLine(CosineSimilarity(dockerEmbed, containerEmbed));  // ~0.95 (very similar!)
Console.WriteLine(CosineSimilarity(dockerEmbed, cakeEmbed));  // ~0.15 (unrelated)

Warum Einbettungen perfekt für unseren Anwendungsfall sind

Erinnere dich daran, dass wir uns an einen Schreibassistenten wenden, wenn ich beginne zu schreiben.

In diesem Beitrag zeigen ,, wie man Entity Framework mit verwenden

Das System sollte vergangene Beiträge über finden

  • Rahmen für die Einrichtung
  • Datenbankzugriffsmuster
  • ORM-Konfigurationen
  • Verwandte Themen im Zusammenhang mit ASP-.-NET

Nicht nur Schlüsselwort-Matches Es sollte verstehen, dass Core-Migrationen von " das Ergebnis sind, auch wenn es nicht sagt, dass 'e nicht "eEntity Framework ist.

Einbettungen vs. traditionelle Suche

graph TB
    subgraph "Traditional Keyword Search"
        A1[Query: 'Docker setup'] --> B1[Find: 'Docker' OR 'setup']
        B1 --> C1[❌ Misses: 'containerization guide']
        B1 --> D1[❌ Misses: 'running containers']
        B1 --> E1[✅ Finds: 'Docker setup tutorial']
    end

    subgraph "Embedding-Based Semantic Search"
        A2[Query: 'Docker setup'] --> B2[Generate embedding]
        B2 --> C2[Find similar embeddings]
        C2 --> D2[✅ Finds: 'containerization guide']
        C2 --> E2[✅ Finds: 'running containers']
        C2 --> F2[✅ Finds: 'Docker setup tutorial']
    end

    class B2,C2 semantic

    classDef semantic stroke:#333,stroke-width:2px

Auswahl eines Einbettungsmodells

Es gibt viele Einbettungsmodelle. Wir brauchen eins, das...

  1. Wird lokal ausgeführt ,( , privacy , + , speed , ) ,
  2. Funktioniert mit ONNX Runtime So können wir unsere GPU-)
  3. Gute Qualität Das semantische Verständnis für die Semantik ist korrekt.
  4. Rechte Größe Die Abmessungen sind gut ausgewogen.

Beliebte Optionen

-Modell Abmessungen -Größe Qualität - Geschwindigkeit
INSGESAMT --MiniLM--6--v.2- Sehr schnell, sehr schnell.
Gesamtes --mpnet--Basis---v.2- Weitere Informationen: http:europa.eu.int/comm/index_de.htm
bge---small---en-MSC2-v-1.5- 384, , 133, Mb, Mzk4, besser Msksk5, sehr schnell ⚡⚡⚡, ,
bge---base, --en-MSC2-v-1.5-e 768, kurz , kurz 436 und
OpenAI-Text – - – Embedding –- – -002 – Weitere Informationen: http://europa.eu.int/comm/index_de.htm

Meine Empfehlung: bge---base, --en-MSC2-v-1.5-e

  • Zustand des Open-Source-Modells
  • Abmessungen des Messgeräts
  • Funktioniert großartig mit ONNX Runtime
  • Kostenlos und läuft lokal

Konvertieren in ONNX Format

Die meisten Modelle sind im PyTorch-Format. Wir brauchen ONNX für C.#..

Optimale Installierung der Python-Bibliothek für die Konvertierung:

pip install optimum[exporters]

BGE-Modell konvertieren:

optimum-cli export onnx --model BAAI/bge-base-en-v1.5 --task feature-extraction bge-base-en-onnx/

Dies erzeugt ,:'

bge-base-en-onnx/
    model.onnx           # The neural network
    tokenizer.json       # Text → tokens converter
    tokenizer_config.json
    special_tokens_map.json
    config.json

Downloaden, wenn Sie nicht konvertieren möchten: Viele Modelle sind vorumgewandelt und auf Hugging Face mit dem Namen "" erhältlich.

Verwendung von Einbetten in C-#

Lassen Sie uns einen praktischen Einbettungsgenerator mit ONNX-Laufzeit bauen.

Projekt einrichten

mkdir EmbeddingTest
cd EmbeddingTest
dotnet new console
dotnet add package Microsoft.ML.OnnxRuntime.Gpu --version 1.16.3
dotnet add package Microsoft.ML.Tokenizers --version 0.1.0-preview.23511.1

Warum diese Pakete?

  • OnnxRuntime.Gpu Starten Sie das Modell auf der GPU
  • Microsoft.ML.Tokenizers Umwandeln von Text in Token-IDs Format )

Tokenisierung zuerst

Vor dem Einbetten müssen wir Text in Zahlen umwandeln.

using Microsoft.ML.Tokenizers;
using System;
using System.Linq;

public class SimpleTokenizer
{
    private readonly Tokenizer _tokenizer;

    public SimpleTokenizer(string tokenizerPath)
    {
        // Load the tokenizer.json file
        _tokenizer = Tokenizer.CreateTokenizer(tokenizerPath);
    }

    public (long[] InputIds, long[] AttentionMask) Tokenize(string text, int maxLength = 512)
    {
        // Tokenize the text
        var encoding = _tokenizer.Encode(text);

        // Get token IDs
        var ids = encoding.Ids.Select(i => (long)i).ToArray();

        // Pad or truncate to maxLength
        var inputIds = new long[maxLength];
        var attentionMask = new long[maxLength];

        int length = Math.Min(ids.Length, maxLength);

        // Copy actual tokens
        Array.Copy(ids, inputIds, length);

        // Set attention mask (1 = real token, 0 = padding)
        for (int i = 0; i < length; i++)
        {
            attentionMask[i] = 1;
        }

        return (inputIds, attentionMask);
    }
}

Was geschieht hier?

  1. Kodierung ,- , , " , hallo Welt [101, 8667, 2088, 102]

    • Jede Zahl ist eine Token-ID aus dem Vokabular des Modells.
    • Besondere Merkmale ,: , 101 , = , [CLS-], 102= [WETTBEWERBSPOLITIK
  2. Polsterung Modelle erwarten einen festen Eingang für -

    • Wenn der Text kurz ist, wird das Pad mit Nullen
    • Wenn der Text lang ist
  3. Aufmerksamkeitsmaske Erklärt dem Modell, welche Token real sind vs Polsterung

    • 1 Verarbeiten Sie dieses Zeichen
    • 0 Nichtzutreffendes streichen.
graph LR
    A["Text: 'Docker setup'"] --> B[Tokenizer]
    B --> C[Token IDs:<br/>101, 12849, 12229, 102]
    C --> D[Pad to 512]
    D --> E[Input IDs:<br/>101, 12849, 12229, 102, 0, 0,...]
    D --> F[Attention Mask:<br/>1, 1, 1, 1, 0, 0,...]

    E --> G[Feed to Model]
    F --> G

    class B,G process

    classDef process stroke:#333,stroke-width:2px

Einbetten Generator Klasse

Jetzt die vollständige Einbettung Pipeline-.

using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Collections.Generic;
using System.Linq;

public class EmbeddingGenerator : IDisposable
{
    private readonly InferenceSession _session;
    private readonly SimpleTokenizer _tokenizer;
    private readonly int _embeddingDimension;

    public EmbeddingGenerator(string modelPath, string tokenizerPath, bool useGpu = true)
    {
        // Setup session options
        var options = new SessionOptions();
        if (useGpu)
        {
            options.AppendExecutionProvider_CUDA(0);
        }

        // Load model
        _session = new InferenceSession(modelPath, options);

        // Load tokenizer
        _tokenizer = new SimpleTokenizer(tokenizerPath);

        // Get embedding dimension from model output shape
        var outputMetadata = _session.OutputMetadata["last_hidden_state"];
        _embeddingDimension = outputMetadata.Dimensions[2]; // Usually 768 for base models
    }

    public float[] GenerateEmbedding(string text)
    {
        // Step 1: Tokenize
        var (inputIds, attentionMask) = _tokenizer.Tokenize(text);

        // Step 2: Create input tensors
        var inputIdsTensor = new DenseTensor<long>(inputIds, new[] { 1, inputIds.Length });
        var attentionMaskTensor = new DenseTensor<long>(attentionMask, new[] { 1, attentionMask.Length });

        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input_ids", inputIdsTensor),
            NamedOnnxValue.CreateFromTensor("attention_mask", attentionMaskTensor)
        };

        // Step 3: Run inference
        using var results = _session.Run(inputs);

        // Step 4: Extract embeddings from output
        var outputTensor = results.First().AsTensor<float>();

        // Output shape is [batch_size, sequence_length, embedding_dim]
        // We want [batch_size, embedding_dim] by mean pooling

        return MeanPooling(outputTensor, attentionMask);
    }

    private float[] MeanPooling(Tensor<float> outputTensor, long[] attentionMask)
    {
        int seqLength = outputTensor.Dimensions[1];
        int embeddingDim = outputTensor.Dimensions[2];

        var embedding = new float[embeddingDim];
        int tokenCount = 0;

        // Average across all non-padded tokens
        for (int seq = 0; seq < seqLength; seq++)
        {
            if (attentionMask[seq] == 0) continue; // Skip padding

            tokenCount++;
            for (int dim = 0; dim < embeddingDim; dim++)
            {
                embedding[dim] += outputTensor[0, seq, dim];
            }
        }

        // Divide by count to get mean
        for (int dim = 0; dim < embeddingDim; dim++)
        {
            embedding[dim] /= tokenCount;
        }

        // Normalize to unit length (common practice)
        return Normalize(embedding);
    }

    private float[] Normalize(float[] vector)
    {
        float magnitude = 0;
        foreach (var val in vector)
        {
            magnitude += val * val;
        }
        magnitude = MathF.Sqrt(magnitude);

        var normalized = new float[vector.Length];
        for (int i = 0; i < vector.Length; i++)
        {
            normalized[i] = vector[i] / magnitude;
        }

        return normalized;
    }

    public void Dispose()
    {
        _session?.Dispose();
    }
}

Code-Aufgliederung:

  1. Beladung des Modells Erstellen von ONNX-Sitzungen mit Unterstützung der GPU
  2. Zeichenbildung Umwandelt Text in Token-IDs
  3. Tensor-Erstellung Daten für Modelleingaben
  4. Schlussfolgerung Das neuronale Netzwerk wird betrieben
  5. Mittlere Bündelung Durchschnittliche Token-Einbettungen Einbettung eines einzelnen Satzes
  6. Normalisierung Ermöglicht die Länge des Vektors, um die Ähnlichkeit zu berechnen.

Mittlere Bündelung erklärt

graph TB
    A[Model Output:<br/>Token Embeddings] --> B["Token 0 (CLS):<br/>(0.1, 0.5, -0.3, ...)"]
    A --> C["Token 1 (Docker):<br/>(0.4, 0.2, -0.1, ...)"]
    A --> D["Token 2 (setup):<br/>(0.3, 0.6, -0.2, ...)"]
    A --> E["Token 3 (SEP):<br/>(0.2, 0.3, -0.4, ...)"]

    B --> F[Average]
    C --> F
    D --> F
    E --> F

    F --> G["Sentence Embedding:<br/>(0.25, 0.4, -0.25, ...)"]

    class A input
    class F process
    class G output

    classDef input stroke:#333
    classDef process stroke:#333,stroke-width:2px
    classDef output stroke:#333,stroke-width:2px

Wir durchschnittlich, weil

  • Jedes Token hat seine eigene Einbettung
  • Wir brauchen EINE Einbettung für den ganzen Satz
  • Mittelung erfasst die allgemeine Bedeutung

Anwendungsbeispiel

using System;

class Program
{
    static void Main(string[] args)
    {
        using var embedder = new EmbeddingGenerator(
            modelPath: "bge-base-en-onnx/model.onnx",
            tokenizerPath: "bge-base-en-onnx/tokenizer.json",
            useGpu: true
        );

        // Generate embeddings
        var embedding1 = embedder.GenerateEmbedding("Docker containerization tutorial");
        var embedding2 = embedder.GenerateEmbedding("Setting up containers with Docker");
        var embedding3 = embedder.GenerateEmbedding("Baking a chocolate cake");

        Console.WriteLine($"Embedding dimension: {embedding1.Length}");
        Console.WriteLine($"First 5 values: {string.Join(", ", embedding1.Take(5).Select(f => f.ToString("F4")))}");

        // Calculate similarities
        float sim12 = CosineSimilarity(embedding1, embedding2);
        float sim13 = CosineSimilarity(embedding1, embedding3);

        Console.WriteLine($"\nSimilarity (Docker vs Containers): {sim12:F4}");  // ~0.85
        Console.WriteLine($"Similarity (Docker vs Cake): {sim13:F4}");  // ~0.10
    }

    static float CosineSimilarity(float[] a, float[] b)
    {
        // Since vectors are normalized, dot product = cosine similarity
        float dot = 0;
        for (int i = 0; i < a.Length; i++)
        {
            dot += a[i] * b[i];
        }
        return dot;
    }
}

Ausgabe:

Embedding dimension: 768
First 5 values: 0.0123, -0.0456, 0.0789, -0.0234, 0.0567

Similarity (Docker vs Containers): 0.8542
Similarity (Docker vs Cake): 0.1023

Schöner, semantähnlicher Inhalt hat eine hohe Ähnlichkeit... nicht verwandter Inhalt ist niedrig...

Vektordatenbanken

Jetzt haben wir Embeddings-.. Wir müssen Millionen von ihnen speichern und effizient suchen.

Das Problem

Lassen Sie uns sagen, dass wir Blog-Posts haben.

Naive Suche:

float bestSimilarity = -1;
int bestIndex = -1;

for (int i = 0; i < 10000; i++)
{
    float sim = CosineSimilarity(queryEmbedding, storedEmbeddings[i]);
    if (sim > bestSimilarity)
    {
        bestSimilarity = sim;
        bestIndex = i;
    }
}

ProblemstellungWir prüfen, ob jede Einbettung von . langsam ist.

Für die Einbettungen von ×, 768 und :

  • 1 Mio. Floating Point-Operationen
  • Auch auf der schnellen CPU

Wir brauchen etwas schneller.

Vektor-Datenbanklösung

Vektor-Datenbanken verwenden clevere Datenstrukturen wie HNSW Hierarchische navigierbare kleine Weltgraphen, um nächste Nachbarn zu finden.

graph TB
    A[Query Embedding] --> B[Vector Database]
    B --> C{HNSW Index}

    C --> D[Layer 2:<br/>Coarse Search]
    D --> E[Layer 1:<br/>Refined Search]
    E --> F[Layer 0:<br/>Exact Search]

    F --> G[Top K Results]

    H[10,000 vectors] -.Indexed.-> C

    class B db
    class C index
    class G results

    classDef db stroke:#333,stroke-width:4px
    classDef index stroke:#333,stroke-width:2px
    classDef results stroke:#333,stroke-width:2px

Geschwindigkeitsvergleich:

  • Naive Suche - :---50-100-ms für Vektoren - 10--K
  • Vektor-DB für Vektoren
  • 10-50x schneller als !

Und es skaliert... Millionen Vektoren nimmt immer noch nur...

Auswahl einer Vektordatenbank

Wir brauchen für unser Projekt "C".,"

  1. Gute Client-Bibliothek von C.#
  2. Einfach zu implementieren
  3. Gute Leistung
  4. Freie Open Source

Datenverarbeitung Datenbank-Unterstützung Software-Dienstleistungslizenz-Unterstützung |----------|------------|------------|-------------|---------| | Qdrant Sehr schnell |A Apache 2.0A Sehr schnell | pgvector Weitere Informationen: http:europa.eu.int/comm/index_de.htm | Schwitzen Sehr schnell |BSD--3 | Milvus Begrenzte Menge an Docker, /, MSC4, sehr schnell Apachen, MKS8 | Chroma |-Python-Python---Erster Docker mit schnellem |-A Apache mit schnellem 2.0-|

Meine Wahl: :-Qdrant

  • Ausgezeichneter Client von C.M.S.K.Qdrant.Client)
  • Einfache Docker-Einführung
  • Speziell für die Vektorsuche gebaut, auf der ) nicht verschraubt
  • Tolle Dokumentation
  • Aktive Entwicklung

Alternativer pgvector

  • Wir verwenden bereits PostgreSQL für den Blog.
  • Kann alles in einer Datenbank aufbewahren
  • Etwas weniger performante, aber einfachere Architektur

Für diese Serie verwenden Sie: Qdrant weil es ,''s Zweck gebaut und einfacher zu verstehen die Konzepte MSC2' Aber I''ll zeigen pgvector als Alternative , .'

Qdrant einrichten

Docker-Einsatz

docker run -p 6333:6333 -p 6334:6334 \
    -v $(pwd)/qdrant_storage:/qdrant/storage \
    qdrant/qdrant

Häfen:

  • 6333 REST-API
  • 6334 Die gRPC API verwendet diese Version.

LagerungBeständige Daten zu ./qdrant_storage auf dem Gastgeber

Client-Einrichtung

dotnet add package Qdrant.Client --version 1.7.0

Erstellen einer Sammlung

Eine Sammlung ist wie eine Tabelle, die Vektoren einer bestimmten Dimension enthält.

using Qdrant.Client;
using Qdrant.Client.Grpc;

public class QdrantSetup
{
    private readonly QdrantClient _client;

    public QdrantSetup(string host = "localhost", int port = 6334)
    {
        _client = new QdrantClient(host, port);
    }

    public async Task CreateCollectionAsync(string collectionName, ulong vectorSize)
    {
        // Check if collection exists
        var collections = await _client.ListCollectionsAsync();
        if (collections.Any(c => c.Name == collectionName))
        {
            Console.WriteLine($"Collection '{collectionName}' already exists");
            return;
        }

        // Create collection
        await _client.CreateCollectionAsync(
            collectionName: collectionName,
            vectorsConfig: new VectorParams
            {
                Size = vectorSize,  // 768 for bge-base
                Distance = Distance.Cosine  // Cosine similarity
            }
        );

        Console.WriteLine($"Created collection '{collectionName}' with {vectorSize} dimensions");
    }
}

Schlüsselparameter:

  • Size Muss mit Ihrem Einbettungsmodell übereinstimmen.
  • Distance Wie kann man Ähnlichkeiten messen?
    • Distance.Cosine Cosine-Ähnlichkeit, die am häufigsten auftritt
    • Distance.Euclid Euklidische Entfernung
    • Distance.Dot Punktprodukt

Vektoren einfügen

using Qdrant.Client.Grpc;
using System.Collections.Generic;

public class QdrantInserter
{
    private readonly QdrantClient _client;

    public QdrantInserter(QdrantClient client)
    {
        _client = client;
    }

    public async Task InsertBlogChunkAsync(
        string collectionName,
        ulong id,
        float[] embedding,
        string blogPostSlug,
        string chunkText,
        int chunkIndex)
    {
        var point = new PointStruct
        {
            Id = id,
            Vectors = embedding,
            Payload =
            {
                ["blog_post_slug"] = blogPostSlug,
                ["chunk_text"] = chunkText,
                ["chunk_index"] = chunkIndex,
                ["timestamp"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
            }
        };

        await _client.UpsertAsync(collectionName, new[] { point });
    }

    public async Task InsertBatchAsync(
        string collectionName,
        List<(ulong id, float[] embedding, Dictionary<string, object> payload)> points)
    {
        var qdrantPoints = points.Select(p => new PointStruct
        {
            Id = p.id,
            Vectors = p.embedding,
            Payload = { p.payload }
        }).ToList();

        // Batch insert for efficiency
        await _client.UpsertAsync(collectionName, qdrantPoints);

        Console.WriteLine($"Inserted {points.Count} points");
    }
}

Payload erklärt:

  • Wie Metadaten, die jedem Vektor beigefügt sind
  • Kann alles speichern, was der Titel des Beitrags trägt.
  • Durchsuchbar und filterbar

Stapeleinführung:

  • Viel schneller als ein ,-'von ,-'one
  • Qdrant verarbeitet Chargen von -100-1000Ã effizient

Vektoren suchen

public class QdrantSearcher
{
    private readonly QdrantClient _client;

    public QdrantSearcher(QdrantClient client)
    {
        _client = client;
    }

    public async Task<List<SearchResult>> SearchAsync(
        string collectionName,
        float[] queryEmbedding,
        int topK = 10)
    {
        var searchResult = await _client.SearchAsync(
            collectionName: collectionName,
            vector: queryEmbedding,
            limit: (ulong)topK,
            scoreThreshold: 0.7f  // Only return if similarity > 0.7
        );

        return searchResult.Select(r => new SearchResult
        {
            Id = r.Id.Num,
            Score = r.Score,
            BlogPostSlug = r.Payload["blog_post_slug"].StringValue,
            ChunkText = r.Payload["chunk_text"].StringValue,
            ChunkIndex = (int)r.Payload["chunk_index"].IntegerValue
        }).ToList();
    }

    public async Task<List<SearchResult>> SearchWithFilterAsync(
        string collectionName,
        float[] queryEmbedding,
        string blogPostSlug,  // Only search within this post
        int topK = 5)
    {
        var filter = new Filter
        {
            Must =
            {
                new Condition
                {
                    Field = new FieldCondition
                    {
                        Key = "blog_post_slug",
                        Match = new Match { Keyword = blogPostSlug }
                    }
                }
            }
        };

        var searchResult = await _client.SearchAsync(
            collectionName: collectionName,
            vector: queryEmbedding,
            filter: filter,
            limit: (ulong)topK
        );

        return searchResult.Select(r => new SearchResult
        {
            Id = r.Id.Num,
            Score = r.Score,
            BlogPostSlug = r.Payload["blog_post_slug"].StringValue,
            ChunkText = r.Payload["chunk_text"].StringValue,
            ChunkIndex = (int)r.Payload["chunk_index"].IntegerValue
        }).ToList();
    }
}

public class SearchResult
{
    public ulong Id { get; set; }
    public float Score { get; set; }
    public string BlogPostSlug { get; set; }
    public string ChunkText { get; set; }
    public int ChunkIndex { get; set; }
}

Suchfunktionen:

  • limit Zurück nach oben K am ähnlichsten
  • scoreThreshold Nur zurück, wenn die Ähnlichkeit den Schwellenwert überschreitet
  • filter Filtern nach Metadaten (e-.e-g-.,e-such nur bestimmte Beiträge

Vollständiges Beispiel: :-Suche Pipeline

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Setup
        var embedder = new EmbeddingGenerator(
            "bge-base-en-onnx/model.onnx",
            "bge-base-en-onnx/tokenizer.json",
            useGpu: true
        );

        var client = new QdrantClient("localhost", 6334);
        var searcher = new QdrantSearcher(client);

        // User query
        string query = "How do I set up Docker with ASP.NET Core?";

        // Generate query embedding
        Console.WriteLine($"Searching for: {query}");
        var queryEmbedding = embedder.GenerateEmbedding(query);

        // Search
        var results = await searcher.SearchAsync(
            collectionName: "blog_embeddings",
            queryEmbedding: queryEmbedding,
            topK: 5
        );

        // Display results
        Console.WriteLine($"\nFound {results.Count} results:\n");

        foreach (var result in results)
        {
            Console.WriteLine($"Score: {result.Score:F4}");
            Console.WriteLine($"Post: {result.BlogPostSlug}");
            Console.WriteLine($"Chunk: {result.ChunkText.Substring(0, Math.Min(100, result.ChunkText.Length))}...");
            Console.WriteLine();
        }
    }
}

Ausgabe:

Searching for: How do I set up Docker with ASP.NET Core?

Found 5 results:

Score: 0.8923
Post: dockercomposedevdeps
Chunk: In this post, I'll show you how to set up a development environment using Docker Compose. This is p...

Score: 0.8654
Post: dockercompose
Chunk: Docker Compose is a tool for defining and running multi-container Docker applications. With Compose...

Score: 0.8102
Post: addingentityframeworkforblogpostspt1
Chunk: You can set it up either as a windows service or using Docker as I presented in a previous post on...

Score: 0.7891
Post: imagesharpwithdocker
Chunk: When running ASP.NET Core applications in Docker containers, you may encounter issues with ImageSha...

Score: 0.7654
Post: selfhostingseq
Chunk: I use Docker Compose to run all my services. Here's the relevant part of my docker-compose.yml file...

Perfekt, es fand relevante Beiträge über Docker und ASP...NET Core... obwohl die genaue Phrase erschien.

Leistungsoptimierung

Batch Einbettung Generation

Nicht erzeugen Embeddings one--von ,-eine , . , Batch sie , !

public class BatchEmbeddingGenerator
{
    private readonly EmbeddingGenerator _embedder;

    public BatchEmbeddingGenerator(EmbeddingGenerator embedder)
    {
        _embedder = embedder;
    }

    public List<float[]> GenerateBatch(List<string> texts, int batchSize = 32)
    {
        var embeddings = new List<float[]>();

        for (int i = 0; i < texts.Count; i += batchSize)
        {
            var batch = texts.Skip(i).Take(batchSize).ToList();

            foreach (var text in batch)
            {
                embeddings.Add(_embedder.GenerateEmbedding(text));
            }

            Console.WriteLine($"Processed {Math.Min(i + batchSize, texts.Count)} / {texts.Count}");
        }

        return embeddings;
    }
}

Warum Batching?

  • GPU-Auslastung – :® hält GPU beschäftigt
  • Speicher-Effizienz - Wiederverwendung von Puffern
  • Fortschritts-Tracking - Feedback der Nutzer

Caching-Einbetten

Nicht regenerieren Embeddings für unveränderten Inhalt

using System.Security.Cryptography;
using System.Text;

public class EmbeddingCache
{
    private readonly Dictionary<string, float[]> _cache = new();

    public float[] GetOrGenerate(string text, Func<string, float[]> generator)
    {
        string hash = ComputeHash(text);

        if (_cache.TryGetValue(hash, out var cached))
        {
            return cached;
        }

        var embedding = generator(text);
        _cache[hash] = embedding;

        return embedding;
    }

    private string ComputeHash(string text)
    {
        using var sha256 = SHA256.Create();
        var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
        return Convert.ToBase64String(bytes);
    }
}

pgvector Alternative

Wenn Sie alles in PostgreSQL-:-Halten möchten

pgvector-Erweiterung installieren

CREATE EXTENSION vector;

Tabelle erstellen

CREATE TABLE blog_embeddings (
    id SERIAL PRIMARY KEY,
    blog_post_slug VARCHAR(255),
    chunk_text TEXT,
    chunk_index INT,
    embedding VECTOR(768)  -- 768 dimensions
);

-- Create HNSW index for fast search
CREATE INDEX ON blog_embeddings USING hnsw (embedding vector_cosine_ops);

Inserieren von C.#

using Npgsql;
using Pgvector;

public async Task InsertEmbeddingAsync(
    string slug,
    string chunkText,
    int chunkIndex,
    float[] embedding)
{
    await using var conn = new NpgsqlConnection(connectionString);
    await conn.OpenAsync();

    await using var cmd = new NpgsqlCommand(
        "INSERT INTO blog_embeddings (blog_post_slug, chunk_text, chunk_index, embedding) VALUES ($1, $2, $3, $4)",
        conn
    )
    {
        Parameters =
        {
            new() { Value = slug },
            new() { Value = chunkText },
            new() { Value = chunkIndex },
            new() { Value = new Vector(embedding) }
        }
    };

    await cmd.ExecuteNonQueryAsync();
}

Suche mit pgvector

public async Task<List<SearchResult>> SearchAsync(float[] queryEmbedding, int topK = 10)
{
    await using var conn = new NpgsqlConnection(connectionString);
    await conn.OpenAsync();

    await using var cmd = new NpgsqlCommand(
        @"SELECT blog_post_slug, chunk_text, chunk_index,
                 1 - (embedding <=> $1) as similarity
          FROM blog_embeddings
          ORDER BY embedding <=> $1
          LIMIT $2",
        conn
    )
    {
        Parameters =
        {
            new() { Value = new Vector(queryEmbedding) },
            new() { Value = topK }
        }
    };

    var results = new List<SearchResult>();

    await using var reader = await cmd.ExecuteReaderAsync();
    while (await reader.ReadAsync())
    {
        results.Add(new SearchResult
        {
            BlogPostSlug = reader.GetString(0),
            ChunkText = reader.GetString(1),
            ChunkIndex = reader.GetInt32(2),
            Score = reader.GetFloat(3)
        });
    }

    return results;
}

<=> BetreiberDie Cosine-Distanz ist vergleichbarer mit der des ) **1 - distance**Umwandeln nach Ähnlichkeitspunktzahl Umrechnen um mehr ist besser

Zusammenfassung

Wir haben es mit : zu tun

  1. Was Einbettungen sind und warum sie semantische Suche ermöglichen
  2. Wie man ein Einbettungsmodell in ONNX auswählt und konvertiert
  3. Erzeugung von Einbettungen mit ONNX-Laufzeit
  4. Vektordatenbank-Konzepte zur Suche nach Ähnlichkeiten von (HNSW2
  5. Verwendung von Qdrant für Vektorspeicher und -suche
  6. Pgvector in PostgreSQL
  7. Leistungsoptimierung (Batching MS K2A Caching)A

Wir haben jetzt die Kerntechnologie, um semantisch ähnliche Blog-Inhalte zu finden.

Was ist das nächste?

Im **Teil: Aufbau der Ingestion Pipeline**Wir bauen die Ingestionspipeline

  • Lesen und Parsen von Markdown-Dateien Wir tun dies bereits für den Blog
  • Intelligente Chunking-Strategien
  • Einbettungen für alle Inhalte generieren
  • Einsetzen von Bulk in Qdrant
  • Bearbeitung von Updates und Löschungen
  • Überwachung der Fortschritte

Wir verarbeiten unseren gesamten Blog Corpus und verfügen über eine durchsuchbare Wissensbasis.

Reihennavigation

Aufkommen

Bis dann. Teil!

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.