Back to "Χτίζοντας μια "δικηγόρος GPT" για το blog σας - Μέρος 3: Κατανόηση Ενσωμάτωση & Vector Database"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI AI-Article C# Embeddings LLM mostlylucid.blogllm Vector Database

Χτίζοντας μια "δικηγόρος GPT" για το blog σας - Μέρος 3: Κατανόηση Ενσωμάτωση & Vector Database

Wednesday, 12 November 2025

ΠΡΟΕΙ∆ΟΠΟΙΗΣΗ: ΤΙΣ ΠΡΟΘΕΣΜΙΕΣ ΠΟΥ "ΠΕΡΙΛΑΜΒΑΝΟΝΤΑΙ."

Πιθανότατα πολλά από αυτά που είναι κάτω δεν θα δουλέψουν; Εγώ τα δημιουργώ ως το πώς-για μένα και στη συνέχεια να κάνουμε όλα τα βήματα και να πάρει το δείγμα εφαρμογή εργασίας...Έχετε sneaky και να τους δει! Θα είναι πιθανώς έτοιμα μέσα Δεκεμβρίου.

## Εισαγωγή

Καλώς ήρθατε στο 3ο μέροςΈχουμε στοίβα GPU μας λειτουργεί (Μέρος 2), και κατανοούμε την αρχιτεκτονική (Μέρος 1****Τώρα ήρθε η ώρα να βουτήξουμε στη μαγεία που κάνει δυνατή τη σημασιολογική αναζήτηση:Συσκευές εγγραφής ή αναπαραγωγής του ήχου, έστω και με ενσωματωμένη συσκευή εγγραφής ή αναπαραγωγής του ήχου (εκτός από συσκευές εγγραφής ή αναπαραγωγής του ήχου).

και

Βάσεις δεδομένων διανυσματικών φορέων

ΣΗΜΕΙΩΣΗ: Αυτό είναι μέρος των πειραμάτων μου με AI (βοηθητική σύνταξη) + δικό μου μοντάζ.

Ίδια φωνή, ίδιος πραγματισμός, απλά γρηγορότερα δάχτυλα.

Εδώ είναι που τα πράγματα γίνονται ενδιαφέροντα.

Θα καταλάβουμε πώς να εκπροσωπούμε το κείμενο ως αριθμούς με έναν τρόπο που αιχμαλωτίζει το νόημα, όχι μόνο λέξεις-κλειδιά.

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

Είναι η διαφορά μεταξύ των θέσεων εύρεσης που περιέχουν τη λέξη "docker" εναντίον εύρεσης θέσεων που είναι σημασιολογικά σχετικά με τις έννοιες εμπορευματοκιβωτίων.

  • Τι Είναι η Ενσωμάτωση;
  • Η ενσωμάτωση είναι μια αριθμητική αναπαράσταση του κειμένου (ή των εικόνων, του ήχου κ.λπ.) ως διάνυσμα - ουσιαστικά μια λίστα αριθμών.
  • Η διαίσθηση
  • Φανταστείτε να σχεδιάζετε λέξεις σε ένα χώρο 2D βασισμένο στο νόημά τους:

**Παρόμοιες έννοιες σύμπλεγμα μαζί:**Κατοικίδια ζώα (κόκκινο/πράσινο) είναι κοντά το ένα στο άλλο

Τα οχήματα (μπλε) ομαδοποιούνται

Τεχνικοί όροι (κίτρινη) συστάδα

  • Οι άσχετες έννοιες είναι πολύ διαφορετικές.
  • Αληθινές προσθήκες
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

χρήση 384 έως 1536 διαστάσεις (όχι μόνο 2!), αιχμαλωτίζοντας πολύ περισσότερο απόχρωση.

Πώς Λειτουργεί**Ένα μοντέλο ενσωμάτωσης είναι ένα νευρωνικό δίκτυο εκπαιδευμένο για να χαρτογραφεί το κείμενο σε φορείς, έτσι ώστε:**Παρόμοιες έννοιες → κλείσιμο διανυσματικών φορέων

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);
}

Διαφορετικές έννοιες → μακρινοί φορείς:

  • 1.0Μέτρηση ομοιότητας
  • 0.0Χρησιμοποιούμε
  • -1.0ομοιότητα με συνημίτονο

για να μετρήσει πόσο κοντά είναι δύο φορείς::

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)

Η ομοιότητα του συνημίτονου κυμαίνεται από - 1 έως 1

= πανομοιότυπη έννοια

= άσχετο

= αντίθετη έννοια (σπάνια στην πράξη)

  • Παράδειγμα
  • Γιατί οι Ενσωμάτωσης Είναι Ιδανικές για τη Χρήση Μας
  • Θυμήσου, φτιάχνουμε βοηθό γραφής.
  • Όταν αρχίζω να γράφω:

**"Σε αυτή τη θέση, θα δείξω πώς να χρησιμοποιείτε το πλαίσιο οντότητας με..."**Το σύστημα θα πρέπει να βρει παρελθόν θέσεις σχετικά με:

Πλαίσιο οντότητας

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

Μοτίβα πρόσβασης στη βάση δεδομένων

Διαμορφώσεις ORM

  1. Σχετικά θέματα ASP.NETΌχι μόνο οι λέξεις-κλειδιά ταιριάζουν!
  2. **Θα πρέπει να κατανοήσει ότι οι "κεντρικές μεταναστεύσεις EF" σχετίζονται ακόμα και αν δεν λέει "Πλαίσιο Εστίας."**Συμβιβασμοί εναντίον Παραδοσιακής Αναζήτησης
  3. Επιλέγοντας ένα Μοντέλο ΕνσωμάτωσηςΥπάρχουν πολλά μοντέλα ενσωμάτωσης.
  4. **Χρειαζόμαστε ένα που:**Λειτουργεί τοπικά

(ιδιωτικότητα + ταχύτητα)

Λειτουργεί με το ONNX Runtime |-------|------------|------|---------|-------| | (ώστε να μπορούμε να χρησιμοποιήσουμε GPU μας) | 384 | 80MB | Good | Very Fast ⚡⚡⚡ | | Καλή ποιότητα | 768 | 420MB | Better | Fast ⚡⚡ | | (Ακριβής σημασιολογική κατανόηση) | 384 | 133MB | Better | Very Fast ⚡⚡⚡ | | Σωστό μέγεθος | 768 | 436MB | Best | Fast ⚡⚡ | | (384-768 διαστάσεις είναι καλή ισορροπία) | 1536 | N/A (API) | Excellent | Slow (network) ⚡ |

Δημοφιλείς επιλογές: Μοντέλο Διαστάσεις Μέγεθος Ποιότητα Ταχύτητα

  • Όλα-MiniLM-L6-v2
  • all-mpnet-based-v2
  • bge-mall-en-v1.5
  • bge-based-en-v1.5

OpenAI text-embedding-ada-002

Η πρότασή μου

bge-based-en-v1.5:

pip install optimum[exporters]

Μοντέλο ανοιχτής πηγής τελευταίας τεχνολογίας:

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

768 διαστάσεις (καλή ισορροπία)

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

Λειτουργεί τέλεια με το ONNX Runtime: Δωρεάν και τρέχει σε τοπικό επίπεδο

Μετατροπή σε μορφή ONNX

Τα περισσότερα μοντέλα είναι σε μορφή PyTorch.

Χρειαζόμαστε το ONNX για το C#.

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

Εγκατάσταση Optimum (βιβλιοθήκη Python για μετατροπή)

  • OnnxRuntime.GpuΜετάτρεψε BGE μοντέλο
  • Microsoft.ML.TokenizersΑυτό δημιουργεί:

Κατεβάστε αν δεν θέλετε να μετατρέψετε

Πολλά μοντέλα προ-μετατρέπονται και διατίθενται στο Hugging Face με το "onx" στο όνομα.

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);
    }
}

Χρησιμοποιώντας Ενσωμάτωση σε C#

  1. **Ας φτιάξουμε μια πρακτική γεννήτρια ενσωμάτωσης χρησιμοποιώντας το ONNX Runtime.**Ρύθμιση έργου[101, 8667, 2088, 102]

    • Γιατί αυτά τα πακέτα;
      • Εκτελέστε το μοντέλο σε GPU[- Μετατροπή κειμένου σε συμβολικές ταυτότητες (model input format)[Πρώτα η Τοκενοποίηση
  2. **Πριν την ενσωμάτωση, πρέπει να επισημάνουμε (μετατροπή κειμένου σε αριθμούς):**Τι συμβαίνει εδώ;

    • Κωδικοποίηση
      • "Hello world" →
  3. Κάθε αριθμός είναι μια συμβολική ταυτότητα από το λεξιλόγιο του μοντέλουΕιδικές μάρκες: 101 =

    • 1CLS], 102 =
    • 0SEP]
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

Επικόλληση

  • Τα μοντέλα περιμένουν είσοδο σταθερού μήκους
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();
    }
}

Εάν το κείμενο είναι σύντομο: μαξιλάρι με μηδενικά:

  1. Εάν το κείμενο είναι μακρύ: truncateΜάσκα προσοχής
  2. - Λέει το μοντέλο που οι μάρκες είναι πραγματικές vs padding= επεξεργασία αυτού του σημείου
  3. **= αγνοήστε (είναι padding)**Κατηγορία γεννήτριας προσθήκης
  4. **Τώρα ο πλήρης αγωγός ενσωμάτωσης:**Ανάλυση κωδικών
  5. Μοντέλο φόρτωσης- Δημιουργεί συνεδρία ONNX με υποστήριξη GPU
  6. Τοκενοποίηση- Μετατρέπει το κείμενο σε συμβολικές ταυτότητες

Δημιουργία τενσόρ

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
  • Σχήματα δεδομένων για την εισαγωγή μοντέλου

  • Συνέpiεια

    • Διευθύνει το νευρικό δίκτυο.
  • Μέση συγκέντρωση

- Μέσοι όροι προσθήκης → ενιαία προσθήκη πρόταση

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;
    }
}

Ομαλοποίηση:

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
  • Κάνει διάνυσμα μήκος 1 (απλουστεύει τον υπολογισμό ομοιότητας)

Εξηγήθηκε η Σημασία Συγκέντρωσης

Είμαστε κατά μέσο όρο επειδή:

Κάθε κουπόνι έχει τη δική του προσθήκη

Χρειαζόμαστε μια προσθήκη για όλη την πρόταση.

Η διαγραφή αιχμαλωτίζει το γενικό νόημα:

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;
    }
}

Παράδειγμα χρήσηςΈξοδος

Πανέμορφο!

  • Σημασιολογικά παρόμοιο περιεχόμενο έχει υψηλή ομοιότητα, το άσχετο περιεχόμενο είναι χαμηλό.
  • Βάση δεδομένων διάνυσμαName

Τώρα έχουμε ενσωμάτωση.

Πρέπει να αποθηκεύσουμε εκατομμύρια και να ψάξουμε αποτελεσματικά.

Το Πρόβλημα

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

Ας πούμε ότι έχουμε 1000 αναρτήσεις στο blog, το καθένα σε 10 κομμάτια = 10.000 καταχωρήσεις.:

  • Αφελής αναζήτηση
  • Πρόβλημα
  • : This is O(n) - we check every impending.

Αργά!

Για 10,000 παρεμβολές × 768 διαστάσεις η κάθε μία:

~30 εκατομμύρια πράξεις κινητής θέσης

  1. ~50-100ms ακόμη και σε γρήγορο CPU
  2. Χρειαζόμαστε κάτι πιο γρήγορο.
  3. Διανυσματική λύση βάσης δεδομένων
  4. Οι βάσεις δεδομένων Vector χρησιμοποιούν έξυπνες δομές δεδομένων (όπως HNSW - Hierarchical Navigable Small World γραφήματα) για να βρουν τους κοντινότερους γείτονες σε O(log n) χρόνο.

Σύγκριση ταχύτητας |----------|------------|------------|-------------|---------| | Αφελής αναζήτηση: 50-100ms για φορείς 10K | ✅ Excellent | Docker | Very Fast | Apache 2.0 | | Διάνυσμα DB (HNSW): 1-5ms για φορείς 10K | ✅ (via Npgsql) | Postgres extension | Fast | PostgreSQL License | | 10-50x γρηγορότερα! | ✅ Good | Docker | Very Fast | BSD-3 | | Και ζυγίζει: εκατομμύρια διανυσματικά στοιχεία χρειάζονται μόνο ~10-20ms. | ⚠️ Limited | Docker/K8s | Very Fast | Apache 2.0 | | Επιλέγοντας μια βάση δεδομένων Vector | ❌ Python-first | Docker | Fast | Apache 2.0 |

Για το πρότζεκτ μας, χρειαζόμαστε:

  • Καλή βιβλιοθήκη πελατών C#Qdrant.Client)
  • Εύκολο στην ανάπτυξη (γιατρός)
  • Καλή απόδοση
  • Δωρεάν / ανοικτή πηγή
  • Database C# Support Develoption Performance License

QdrantCity name (optional, probably does not need a translation)

  • pgvector
  • Υγρασία
  • ΜίλβουςCity name (optional, probably does not need a translation)

ΧρώμαΗ επιλογή μου: QdrantΕξαιρετικός πελάτης C# (

Απλή ανάπτυξη Docker

Χτισμένο ειδικά για διανυσματική αναζήτηση (δεν ασφαλίζεται)

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

Σπουδαία τεκμηρίωση:

  • 6333Ενεργή ανάπτυξη
  • 6334Εναλλακτική λύση: pgvector

**Χρησιμοποιούμε ήδη το PostgreSQL για το blog!**Θα μπορούσε να κρατήσει τα πάντα σε μια βάση δεδομένων./qdrant_storageΛίγο λιγότερο performant αλλά απλούστερη αρχιτεκτονική

Για αυτή τη σειρά, θα χρησιμοποιήσω

dotnet add package Qdrant.Client --version 1.7.0

QdrantCity name (optional, probably does not need a translation)

Γιατί είναι φτιαγμένο με σκοπό και πιο εύκολο να καταλάβεις τις έννοιες.

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");
    }
}

Αλλά θα δείξω στον Πγκβέκτορ ως εναλλακτική λύση.:

  • SizeΡύθμιση του Qdrant
  • DistanceDocker Development
    • Distance.CosineΛιμάνια
    • Distance.Euclid- REST API
    • Distance.Dot- GRPC API (πιο γρήγορα, θα χρησιμοποιήσουμε αυτό)

Αποθήκευση

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");
    }
}

: Επιμένει στα δεδομένα:

  • σε ξενιστή
  • Ρύθμιση πελάτη C#
  • Δημιουργία Συλλογής

Μια συλλογή είναι σαν έναν πίνακα - κατέχει φορείς μιας συγκεκριμένης διάστασης.:

  • Βασικές παράμετροι
    • Πρέπει να ταιριάζει με το μοντέλο ενσωμάτωσης σας (768 για bge-base)

- Πώς να μετρήσετε την ομοιότητα:

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; }
}

- Συναισθηματική ομοιότητα (πιο συχνή):

  • limit- Ευκλείδεια απόσταση
  • scoreThreshold- Προϊόν κουκκίδας
  • filterΕισαγωγή διακλαδωτών

Εξήγησε το φορτηγό

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();
        }
    }
}

Όπως τα μεταδεδομένα που συνδέονται σε κάθε φορέα:

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...

Μπορεί να αποθηκεύσει οτιδήποτε: μετά τον τίτλο, chunk κείμενο, ημερομηνία, κατηγορίες

Ανιχνεύσιμο και φιλτραρισμένο!

Εισαγωγή παρτίδας

Πολύ πιο γρήγορα από έναν προς έναν.

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;
    }
}

Το Qdrant χειρίζεται τις παρτίδες 100-1000 αποτελεσματικά

  • Ψάχνοντας Διανύσματα
  • Χαρακτηριστικά αναζήτησης
    • Επιστρέψτε πάνω Κ πιο παρόμοια

- Επιστροφή μόνο εάν η ομοιότητα υπερβαίνει το όριο

  • Φίλτρο με μεταδεδομένα (π.χ. μόνο συγκεκριμένες θέσεις αναζήτησης)
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);
    }
}

Πλήρες παράδειγμα: Σωλήνας αναζήτησης

Έξοδος

Τέλεια!

CREATE EXTENSION vector;

Βρήκε σχετικές δημοσιεύσεις για Docker και ASP.NET Core, αν και η ακριβής φράση δεν εμφανίστηκε.

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);

Βελτιστοποίηση απόδοσης

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();
}

Παρτίδα Ενσωμάτωσης Γενιά

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;
}

**<=>Μην δημιουργείς παρεμβολές μία προς μία.**Μαζευτείτε! **1 - distance**Γιατί να κάνω μάζεμα;

Χρήση GPU: κρατά GPU απασχολημένος

Αποδοτικότητα μνήμης: επαναπροσδιορίζει τα αποθέματα μνήμης

  1. ✅ What embeddings are and why they enable semantic search
  2. ✅ How to choose and convert an embedding model to ONNX
  3. ✅ Generating embeddings in C# with ONNX Runtime
  4. ✅ Vector database concepts (HNSW, similarity search)
  5. ✅ Using Qdrant for vector storage and search
  6. ✅ Alternative: pgvector in PostgreSQL
  7. ✅ Performance optimization (batching, caching)

Εντοπισμός προόδου: ανατροφοδότηση χρήστη

Κλειδαριές Ενσωμάτωσης

Μην αναγεννάτε καταχωρήσεις για αμετάβλητο περιεχόμενο!**pgvector Alternative**Αν θέλετε να κρατήσετε τα πάντα στο PostgreSQL:

  • Εγκατάσταση επέκτασης pgvector
  • Δημιουργία πίνακα
  • Εισαγωγή από το C#
  • Αναζήτηση με pgvectorχειριστής
  • : Η απόσταση συνημίτονου (η χαμηλότερη είναι πιο παρόμοια)
  • : Μετατροπή σε βαθμολογία ομοιότητας (υψηλότερη είναι καλύτερη)

Περίληψη

Καλύψαμε:

QdrantCity name (optional, probably does not need a translation)

Μέρος 2: Ρύθμιση GPU & CUDA σε C#Μέρος 3: Κατανόηση των βάσεων δεδομένων & διάνυσμα!

logo

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