Back to "RAG-Architektur und Innenarchitektur - Wie es wirklich funktioniert"

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 LLM Machine Learning RAG Semantic Search

RAG-Architektur und Innenarchitektur - Wie es wirklich funktioniert

Saturday, 22 November 2025

Im TeilWir haben die Ursprünge von RAG-' behandelt, die Grundlagen von , und warum es wichtig ist, . Sie verstehen das High---Level-Konzept, welches relevante Informationen abruft, dann verwenden Sie es, um Antworten zu generieren.. Jetzt tauchen wir tief in die technische Architektur ein.— Genau wie RAG Systeme unter der Haube funktionieren, von Chunking-Strategien bis hin zu LLM Internalen wie Tokens und KV Caches....

Einleitung

Navigation der Serie : Dies ist Teil der RAG-Serie.

Wenn du nicht gelesen hast Teil 1,, empfehle ich dort zu beginnen, um : zu verstehen

  • Was RAG ist und warum es zählt
  • Die Geschichte von der Schlüsselwortsuche bis zum semantischen Verständnis
  • RAG vs fine---Tuning und andere Ansätze

Dieser Artikel geht davon aus, dass Sie diese Grundlagen verstehen und konzentriert sich auf Technische Architektur-Informationen zur Implementierung von , und LLM.

Wie die RAG funktioniert, das vollständige Bild

Lassen Sie 's brechen genau das, was in einem RAG-System geschieht, ab dem Moment, in dem Sie ein Dokument hinzufügen, wenn ein Benutzer eine Antwort erhält.

Phase der Erstellung der Wissensdatenbank

Bevor RAG alles abrufen kann, müssen Sie Ihre Wissensbasis indizieren.Dies ist ein One-S--Zeit-Prozess, obwohl Sie später neue Dokumente hinzufügen können.

flowchart TB
    A[Source Documents] -->|1. Extract Text| B[Text Extraction]
    B -->|2. Split into Chunks| C[Chunking Service]
    C -->|3. Generate Embeddings| D[Embedding Model]
    D -->|4. Store Vectors| E[Vector Database]

    B -.Metadata.-> E

    subgraph "Example: Blog Post"
        F["Understanding Docker: A containerization platform..."]
    end

    subgraph "Chunks"
        G["Chunk 1: Title + Intro"]
        H["Chunk 2: Benefits Section"]
    end

    subgraph "Embeddings"
        I["0.234, 0.891, 0.567, ..."]
        J["0.445, 0.123, 0.789, ..."]
    end

    F --> G
    F --> H
    G --> I
    H --> J

    style D stroke:#f9f,stroke-width:2px
    style E stroke:#bbf,stroke-width:2px

Schritt Textextraktion

Extrahieren Sie Klartext aus Ihren Quelldokumenten.

  • Markdown-Dateien Wie meine Blog-Beiträge
  • PDFs zur Dokumentation
  • HTML für Web-Scraping
  • Datenbankaufzeichnungen
  • E-Mail-Adressen - Chatprotokolle - , usw.

Beispiel aus meinem Blog

// From MarkdownRenderingService
public string ExtractPlainText(string markdown)
{
    // Remove code blocks
    var withoutCode = Regex.Replace(markdown, @"```[\s\S]*?```", "");

    // Convert markdown to plain text
    var document = Markdown.Parse(withoutCode);
    var plainText = document.ToPlainText();

    return plainText.Trim();
}

Schritt 2:-Cunking

Dies ist, wo die meisten RAG-Implementierungen fehlschlagen... du kannst '.t nur auf Absatzgrenzen teilen... du brauchst semantisch kohärente Brocken...

Warum Krümeln zählt

  • LLMs haben Token-Grenzwerte
  • Kleinere Brocken - genaueres Retrieval
  • Aber Brocken müssen genügend Kontext enthalten, um sinnvoll zu sein

Schlechtes Brocken

Chunk 1: "Docker is a containerization platform. It allows you"
Chunk 2: "to package applications with their dependencies. This"
Chunk 3: "ensures consistency across environments."

Guter Brocken, :

Chunk 1: "Docker is a containerization platform. It allows you to package applications with their dependencies. This ensures consistency across environments."

Chunk 2: "Benefits of Docker:
- Isolation: Each container runs in its own environment
- Portability: Containers run anywhere Docker is installed
- Efficiency: Lightweight compared to virtual machines"

Beispiel aus meiner semantischen Suchimplementierung

public class TextChunker
{
    private const int TargetChunkSize = 500; // ~500 words
    private const int ChunkOverlap = 50;     // 50 words overlap

    public List<Chunk> ChunkDocument(string text, string sourceId)
    {
        var chunks = new List<Chunk>();

        // Split on section boundaries first (## headers in markdown)
        var sections = SplitOnHeaders(text);

        foreach (var section in sections)
        {
            // If section is small enough, keep it whole
            if (section.WordCount < TargetChunkSize)
            {
                chunks.Add(new Chunk
                {
                    Text = section.Text,
                    SourceId = sourceId,
                    SectionHeader = section.Header
                });
            }
            else
            {
                // Split large sections on sentence boundaries
                var subChunks = SplitOnSentences(section.Text, TargetChunkSize, ChunkOverlap);
                chunks.AddRange(subChunks.Select(c => new Chunk
                {
                    Text = c,
                    SourceId = sourceId,
                    SectionHeader = section.Header
                }));
            }
        }

        return chunks;
    }
}

Häufige Chunking-Strategien (MSK)

  • Behobene GrößeEinfach, aber durchbricht semantische Grenzen
  • Satz---basiertBeachtet die Grammatik, kann aber zu klein sein
  • Absatz---basiertNatürliche, aber variable Größe
  • **Abschnitt „-"**Am besten für strukturierte Inhalte
  • Schiebefenster mit ÜberlappungVergewissert sich, dass kein Kontext an Grenzen verloren geht

Schritt Einbettungen generieren

Embeddings sind die Magie, die semantische Suche möglich macht Eine Einbettung ist ein Vektor (.Array von Zahlen, die die Bedeutung des Textes darstellt

Schlüsselbegriff Ähnliche Bedeutungen Ähnliche Vektoren

"Docker container" → [0.234, -0.891, 0.567, ..., 0.123]
"containerization platform" → [0.221, -0.903, 0.534, ..., 0.119]
"apple fruit" → [0.891, 0.234, -0.567, ..., -0.789]

Die ersten beiden Vektoren würden sein............ im Vektorraum...... ..hohe Kosinus-Ähnlichkeit, während der dritte weit weg ist....

Wie Einbettungen erzeugt werden Moderne Einbettungsmodelle sind neuronale Netzwerke, die auf massiven Textdatensätzen trainiert werden, um semantische Beziehungen zu erlernen.

  • INSGESAMT --MiniLM--6--v.2-:-Dimensionen, schnelle und gute Qualität, was ich auf diesem Blog verwende
  • text---embedding - -3--klein Abmessungen von (-OpenAI-):-Mode
  • BGE---Basis,: , , 768 , Maße , , , Zustand , - der ,- , der .- , Art Open Source

Beispiel aus meinem ONNX-Einbettungsdienst

public async Task<float[]> GenerateEmbeddingAsync(string text)
{
    // Tokenize the input text
    var tokens = Tokenize(text);

    // Create input tensors for ONNX model
    var inputIds = CreateInputTensor(tokens);
    var attentionMask = CreateAttentionMaskTensor(tokens.Length);
    var tokenTypeIds = CreateTokenTypeIdsTensor(tokens.Length);

    // Run ONNX inference
    var inputs = new List<NamedOnnxValue>
    {
        NamedOnnxValue.CreateFromTensor("input_ids", inputIds),
        NamedOnnxValue.CreateFromTensor("attention_mask", attentionMask),
        NamedOnnxValue.CreateFromTensor("token_type_ids", tokenTypeIds)
    };

    using var results = _session.Run(inputs);

    // Extract the output (sentence embedding)
    var output = results.First().AsTensor<float>();
    var embedding = output.ToArray();

    // L2 normalize the vector for cosine similarity
    return NormalizeVector(embedding);
}

Warum Normalisierung zählt Nach der Normalisierung wird die Kosinusgleichheit zu einem einfachen Punktprodukt, das die Suche viel schneller macht.

Schritt Speichern Sie in der Vektordatenbank

Vektor-Datenbanken sind optimiert für das Speichern und Suchen von hohen - Dimensionsvektoren. Im Gegensatz zu herkömmlichen Datenbanken, die SQL-Abfragen verwenden.

Schlüsseloperationen

  • HochfahrenEin Vektor mit Metadaten hinzufügen oder aktualisieren
  • SuchenSuche K ähnlichste Vektoren zu einem Abfragevektor
  • FilternDie Vektorsuche mit Metadatenfiltern kombinieren

Beispiel Qdrant-Implementierung

public async Task IndexDocumentAsync(
    string id,
    float[] embedding,
    Dictionary<string, object> metadata)
{
    var point = new PointStruct
    {
        Id = new PointId { Uuid = id },
        Vectors = embedding,
        Payload =
        {
            ["title"] = metadata["title"],
            ["source"] = metadata["source"],
            ["chunk_index"] = metadata["chunk_index"],
            ["created_at"] = DateTime.UtcNow.ToString("O")
        }
    };

    await _client.UpsertAsync(
        collectionName: "blog_posts",
        points: new[] { point }
    );
}

Beliebte Vektordatenbanken

  • QdrantFast-, self-MSC2 hostable - ausgezeichnete Unterstützung von C.# - meine Wahl von )
  • pgvectorPostgreSQL-Erweiterung ( ist großartig, wenn Sie bereits Postgress verwenden
  • KiefernzapfenVerwaltete Dienste Preiswert, aber gut
  • Schwitzen:-Feature: --rich-,- gut für komplexe Schemata
  • ChromaDBPython, -, fokussiert

Wir erkunden das Einrichten dieser Datenbanken in kommenden Artikeln

Phase der Aufnahme von (, um relevante Informationen zu finden

Wenn ein Benutzer eine Frage stellt, muss das RAG-System die wichtigsten Informationen aus der Wissensdatenbank finden.

flowchart LR
    A["User Query:<br/>'How do I use Docker Compose?'"] --> B[Generate Query Embedding]
    B --> C["Query Vector:<br/>[0.445, -0.123, ...]"]
    C --> D[Vector Search]
    D --> E[Vector Database]
    E --> F[Top K Similar Chunks]
    F --> G["Results:<br/>1. Docker Compose Basics 0.92<br/>2. Multi-Container Setup 0.87<br/>3. Service Configuration 0.83"]

    style B stroke:#f9f,stroke-width:3px
    style D stroke:#bbf,stroke-width:3px

Schritt .1: . Erzeugen Sie Abfrage Einbetten

Die Frage des Benutzers wird mit Hilfe der gleiches Einbettungsmodell für die Indexierung verwendet wird, ist dies kritisch, verschiedene Modelle produzieren inkompatible Vektoren.

public async Task<List<SearchResult>> SearchAsync(string query, int limit = 10)
{
    // Same embedding model used for indexing
    var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(query);

    // Search in vector store
    var results = await _vectorStoreService.SearchAsync(
        queryEmbedding,
        limit
    );

    return results;
}

Schritt zur Ähnlichkeitssuche

Die Vektordatenbank berechnet die Ähnlichkeit zwischen dem Abfragevektor und allen gespeicherten Vektoren.

Cosine Ähnlichkeit Am beliebtesten für normalisierte Vektoren

similarity = (A · B) / (||A|| × ||B||)

Bereich: :----1--M1-- --- (--höher .=-- ähnlicher

Euklidische Entfernung Nicht normalisierte Vektoren

distance = sqrt(Σ(Ai - Bi)²)

Bereich: :- bis ∞-niedriger

Punkt Produkt Wenn Vektoren vor dem - normiert sind

similarity = A · B

Bereich: :----1--M1-- --- (--höher .=-- ähnlicher

Beispiel aus meinem Qdrant-Service

var searchResults = await _client.SearchAsync(
    collectionName: "blog_posts",
    vector: queryEmbedding,
    limit: (ulong)limit,
    scoreThreshold: 0.7f,  // Only return results with >70% similarity
    payloadSelector: true   // Include all metadata
);

return searchResults.Select(hit => new SearchResult
{
    Text = hit.Payload["text"].StringValue,
    Title = hit.Payload["title"].StringValue,
    Score = hit.Score,
    Source = hit.Payload["source"].StringValue
}).ToList();

Schritt Umbenennen von (Optional, aber Empfohlen

Der erste Retrieval ist schnell, aber ungefähr..-Reranking verwendet ein ausgefeilteres Modell, um die oberen K Ergebnisse zu rescore.

flowchart LR
    A[Vector Search:<br/>Top 50 Results] --> B[Reranking Model]
    B --> C[Reranked:<br/>Top 10 Results]

    style B stroke:#f9f,stroke-width:3px

Warum das Umranken hilft

  • Schnelle Einbettungsmodelle optimieren für die Geschwindigkeit und opfern etwas Genauigkeit.
  • Reranking-Modelle sind langsamer, aber genauer
  • Zweistufiger Ansatz balanciert Geschwindigkeit und Qualität

Beispiel für eine Neubewertung der Implementation

public async Task<List<SearchResult>> SearchWithRerankAsync(
    string query,
    int initialLimit = 50,
    int finalLimit = 10)
{
    // Stage 1: Fast vector search
    var candidates = await SearchAsync(query, initialLimit);

    // Stage 2: Precise reranking
    var rerankedResults = await _rerankingService.RerankAsync(
        query,
        candidates
    );

    return rerankedResults.Take(finalLimit).ToList();
}

Phase-Erstellung-Erzeugung-Erstellen der Antwort-)

Nun, da wir relevante Informationen haben, füttern wir es zusammen mit dem Benutzer an die LLM.

flowchart TB
    A[User Query] --> B[Retrieved Context 1]
    A --> C[Retrieved Context 2]
    A --> D[Retrieved Context 3]

    B --> E[Construct Prompt]
    C --> E
    D --> E
    A --> E

    E --> F["System: You are a helpful assistant...\n\nContext:\n1. Docker Compose allows...\n2. Services are defined...\n3. Volumes persist data...\n\nQuestion: How do I use Docker Compose?\n\nAnswer:"]

    F --> G[LLM]
    G --> H[Generated Answer with Citations]

    style E stroke:#f9f,stroke-width:2px
    style G stroke:#bbf,stroke-width:2px

Schritt 1:-Prompt Konstruktion

Dies ist, wo RAG wird zu einer art-.-Sie müssen die Eingabeaufforderung strukturieren, so dass die LLM-:-

  • Verwendet den bereitgestellten Kontext nicht sein internes Wissen
  • Zitierquellen, wenn möglich
  • Gibt zu, wenn der Kontext eine Antwort enthält
  • Behält einen konsistenten Ton, /, Stil

Beispiel prompte Vorlage aus meinem Lawyer GPT-System

public string BuildRAGPrompt(string query, List<SearchResult> context)
{
    var sb = new StringBuilder();

    sb.AppendLine("You are a technical writing assistant. Your task is to answer the user's question using ONLY the provided context from past blog posts.");
    sb.AppendLine();
    sb.AppendLine("CONTEXT:");
    sb.AppendLine("========");

    for (int i = 0; i < context.Count; i++)
    {
        sb.AppendLine($"[{i + 1}] {context[i].Title}");
        sb.AppendLine($"Source: {context[i].Source}");
        sb.AppendLine($"Content: {context[i].Text}");
        sb.AppendLine($"Relevance: {context[i].Score:P0}");
        sb.AppendLine();
    }

    sb.AppendLine("========");
    sb.AppendLine();
    sb.AppendLine("INSTRUCTIONS:");
    sb.AppendLine("- Answer the question using the provided context");
    sb.AppendLine("- Cite sources using [1], [2], etc.");
    sb.AppendLine("- If the context doesn't contain enough information, say so");
    sb.AppendLine("- Maintain the technical, practical tone of the blog");
    sb.AppendLine();
    sb.AppendLine($"QUESTION: {query}");
    sb.AppendLine();
    sb.AppendLine("ANSWER:");

    return sb.ToString();
}

Schritt: LLM-Schlussfolgerung

Die konstruierte Eingabeaufforderung geht an die LLM für die Generation.

  • Cloud-APIGoogle PaLM - anthropische Claude-,
  • Lokales ModellVerwendung von Lama- oder Fackelsharp

Beispiel für die Verwendung von lokalen LLM-:

public async Task<string> GenerateResponseAsync(string prompt)
{
    var result = await _llamaSharp.InferAsync(prompt, new InferenceParams
    {
        Temperature = 0.7f,      // Creativity (0 = deterministic, 1 = creative)
        TopP = 0.9f,             // Nucleus sampling
        MaxTokens = 500,         // Response length limit
        StopSequences = new[] { "\n\n", "User:", "Question:" }
    });

    return result.Text.Trim();
}

Schlüsselparameter erklärt

  • TemperaturKontrolliert die Randomität immer wählen Sie höchstwahrscheinlich ,
  • Oben PBerücksichtigen Sie nur Zeichen, die oben P Wahrscheinlichkeit Masse bilden
  • Max TokensLänge des Grenzansprechsystems
  • Sequenzen beendenWenn nicht mehr erzeugt werden soll

Schritt nach der Verarbeitung von -

Nachdem das LLM eine Antwort erzeugt, müssen wir häufig

  • Zitate extrahieren und in Links umwandeln
  • Format-Codeblöcke
  • Fügen Sie Metadaten hinzu
  • Protokollieren Sie die Interaktion zum Debuggen

Beispiel nach der Verarbeitung von :

public RAGResponse PostProcess(string llmOutput, List<SearchResult> sources)
{
    var response = new RAGResponse
    {
        Answer = llmOutput,
        Sources = new List<Source>()
    };

    // Extract citations like [1], [2]
    var citations = Regex.Matches(llmOutput, @"\[(\d+)\]");

    foreach (Match match in citations)
    {
        int index = int.Parse(match.Groups[1].Value) - 1;
        if (index >= 0 && index < sources.Count)
        {
            var source = sources[index];
            response.Sources.Add(new Source
            {
                Title = source.Title,
                Url = GenerateUrl(source.Source),
                RelevanceScore = source.Score
            });
        }
    }

    // Convert markdown citations to hyperlinks
    response.FormattedAnswer = Regex.Replace(
        llmOutput,
        @"\[(\d+)\]",
        m => {
            int index = int.Parse(m.Groups[1].Value) - 1;
            if (index >= 0 && index < sources.Count)
            {
                var url = GenerateUrl(sources[index].Source);
                return $"[[{m.Groups[1].Value}]]({url})";
            }
            return m.Value;
        }
    );

    return response;
}

LLM Internals verstehen :-Tokens®,-KV Cache- und Kontextfenster

Bevor wir in praktische Anwendungen umziehen, müssen Sie unbedingt wissen, wie LLMs intern arbeiten. Dieses Wissen hilft Ihnen, RAG-Systeme zu optimieren und häufige Fallstricke zu vermeiden.

Was sind Zeichen?

Tokens sind die grundlegenden Einheiten, die LLMs verarbeiten..-Text wird direkt an Modelle gefüttert.

Beispiel für Tokenisierung

Input:  "Understanding Docker containers"
Tokens: ["Under", "standing", " Docker", " containers"]

Verschiedene Modelle verwenden unterschiedliche Tokenisierungsstrategien

  • GPT-ModelleVerwendung von Byte--Pair-Encodierungsdateien mit dem Vokabular von ~50K
  • ClaudeÄhnliches BPE-Konzept
  • Llama ModelleSentencePiece Tokenization

Warum Tokenisierung für RAG-:e wichtig ist

public class TokenCounter
{
    // Rough approximation: 1 token ≈ 0.75 words (English)
    public int EstimateTokens(string text)
    {
        var wordCount = text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
        return (int)(wordCount / 0.75);
    }

    public int EstimateTokensAccurate(string text, ITokenizer tokenizer)
    {
        // Use actual tokenizer for precision
        return tokenizer.Encode(text).Count;
    }
}

Kontextfenster-Grenzen

  • GPT--3.5:-Zeichnungen
  • GPT.-4:, je nach Variante.
  • Claude-Marken des Sonetts der :
  • Llama-Marken von 3:, 8, K, MKS2, obwohl sie erweitert werden können

In RAG-Systemen müssen Sie fit sein

Total tokens = System prompt + Retrieved context + User query + Response buffer

Wenn dein RAG Dokumente von -500 herausruft, die jedes -, ausweisen, dass -mesK4auslesen nur für den Kontext vor der Abfrage und Antwort -!auslesen

Praktisches RAG-Tokenmanagement

public class ContextWindowManager
{
    private readonly int _maxContextTokens;
    private readonly int _systemPromptTokens;
    private readonly int _responseBufferTokens;

    public ContextWindowManager(
        int totalContextWindow = 4096,
        int systemPromptTokens = 300,
        int responseBufferTokens = 500)
    {
        _maxContextTokens = totalContextWindow;
        _systemPromptTokens = systemPromptTokens;
        _responseBufferTokens = responseBufferTokens;
    }

    public List<SearchResult> FitContextInWindow(
        List<SearchResult> retrievedDocs,
        string query)
    {
        var queryTokens = EstimateTokens(query);

        // Available tokens for retrieved context
        var availableForContext = _maxContextTokens
            - _systemPromptTokens
            - queryTokens
            - _responseBufferTokens;

        var selectedDocs = new List<SearchResult>();
        var currentTokens = 0;

        foreach (var doc in retrievedDocs.OrderByDescending(d => d.Score))
        {
            var docTokens = EstimateTokens(doc.Text);

            if (currentTokens + docTokens <= availableForContext)
            {
                selectedDocs.Add(doc);
                currentTokens += docTokens;
            }
            else
            {
                break; // Context window full
            }
        }

        return selectedDocs;
    }

    private int EstimateTokens(string text)
    {
        // Rule of thumb: 1 token ≈ 4 characters
        return text.Length / 4;
    }
}

Die geheime Waffe des KV-Cache-:-LMM'

Wenn ein LLM Text erzeugt, verarbeitet es nicht alles von Grund auf neu für jeden Token. Schlüssel---Wert-Cache sich zu erinnern, was es bereits berechnet hat

Wie Transformer arbeiten

Transformer verwenden einen Mechanismus, bei dem jeder Token-Mechanismus (MSK) alle vorherigen Token betrachtet, um den Kontext zu verstehen.

flowchart TB
    subgraph "Generation Step 1: 'Docker'"
        A1[Input: 'Docker'] --> B1[Compute K,V for 'Docker']
        B1 --> C1[Store in KV Cache]
        C1 --> D1[Generate: 'is']
    end

    subgraph "Generation Step 2: 'is'"
        A2[Input: 'is'] --> B2[Compute K,V for 'is']
        B2 --> C2[Store in KV Cache]
        C2 --> E2[Retrieve KV for 'Docker']
        E2 --> F2[Attend: 'is' to 'Docker']
        F2 --> D2[Generate: 'a']
    end

    subgraph "Generation Step 3: 'a'"
        A3[Input: 'a'] --> B3[Compute K,V for 'a']
        B3 --> C3[Store in KV Cache]
        C3 --> E3[Retrieve KV for 'Docker', 'is']
        E3 --> F3[Attend: 'a' to all previous]
        F3 --> D3[Generate: 'container']
    end

    D1 --> A2
    D2 --> A3

    style C1 stroke:#f9f,stroke-width:3px
    style C2 stroke:#f9f,stroke-width:3px
    style C3 stroke:#f9f,stroke-width:3px

Ohne KV-Cache, :

  • Schritt-Prozess-Zeichnungszeichen
  • Schritt Prozess Zeichen von Grund auf
  • Schritt Prozess Zeichen von Grund auf
  • Insgesamt

Mit KV-Cache - :

  • Schritt-Verarbeitungsvorgang - Zeichen des MSK-Caches
  • Schritt - Prozess - neue Token , , Wiederverwendung von zwischengespeicherten K ,, ,V , → , O ,(1) ,
  • Schritt - Prozess - neue Token , , Wiederverwendung von zwischengespeicherten K ,, ,V , → , O ,(1) ,
  • Insgesamt

Das macht Generation dramatisch schneller Der Unterschied zwischen den Tokens von /Sekunde und 100ekunde

Die KV Cache Baumstruktur

Der KV-Cache bildet einen "-Baum, weil die Aufmerksamkeit in Transformatoren funktioniert.Jede Schicht des Modells hat eine eigene "KSK3"-Matrizen

graph TB
    A[Input Tokens:<br/>'What is Docker?'] --> B[Layer 1 Attention]
    B --> C[Layer 1 KV Cache]

    B --> D[Layer 2 Attention]
    D --> E[Layer 2 KV Cache]

    D --> F[Layer 3 Attention]
    F --> G[Layer 3 KV Cache]

    F --> H[... up to Layer N]
    H --> I[Output: 'Docker is']

    C -.Key-Value pairs<br/>for all input tokens.-> C
    E -.Key-Value pairs<br/>for all input tokens.-> E
    G -.Key-Value pairs<br/>for all input tokens.-> G

    style C stroke:#bbf,stroke-width:2px
    style E stroke:#bbf,stroke-width:2px
    style G stroke:#bbf,stroke-width:2px

Jede Schicht speichert – :

  • SchlüsselDarstellungen verwendet, um Aufmerksamkeitspunkte zu berechnen
  • WerteDarstellungen, die auf der Grundlage von Aufmerksamkeit miteinander vermischt werden

Für ein Modell mit

  • Schichten
  • Versteckte Abmessungen
  • Aufmerksamkeitsköpfe
  • Kontextfenster von -8.K

Der KV-Cache für eine Sequenz ist:

2 (K and V) × 32 layers × 4096 dimensions × 8192 tokens × 2 bytes (FP16)
≈ 4.3 GB of VRAM!

Dies ist, warum lange Kontextfenster Speicher sind.

KV Cache in RAG-Systemen

RAG-Systeme können KV-Cache-Optimierung auf clevere Weise nutzen

Sofortiges Caching Unterstützt von einigen APIs wie Anthropoc Claude®):

public class CachedRAGService
{
    // System prompt and retrieved context can be cached!
    public async Task<string> GenerateWithCachedContextAsync(
        string systemPrompt,          // Cached
        List<SearchResult> context,   // Cached
        string userQuery)             // Not cached, changes each time
    {
        var contextText = FormatContext(context);

        // The KV cache for systemPrompt + contextText is reused across queries
        var prompt = $@"
{systemPrompt}

CONTEXT:
{contextText}

QUERY: {userQuery}

ANSWER:";

        return await _llm.GenerateAsync(prompt, useCaching: true);
    }
}

Warum dies mächtig ist

  • Erste Abfrage Berechnet KV-Cache für die Systemaufforderung
  • Nachfolgende Abfragen mit demselben Kontext: : Wiederverwendung von zwischengespeicherten KV-Daten schneller als !)
  • Nur der User Query-Teil benötigt eine neue Berechnung

Praktisches Beispiel

Query 1: "How do I use Docker?" → 2 seconds (no cache)
Query 2: "What are Docker benefits?" → 0.2 seconds (cache hit!)
Query 3: "Docker vs VMs?" → 0.2 seconds (cache hit!)

Alle drei Abfragen verwenden denselben abgerufenen Kontext, damit der KV-Cache für diesen Kontext wiederverwendet wird.

Token Limits und RAG-Strategie

Token und KV-Cache verstehen informiert Ihre RAG-Architekturentscheidungen

Größe des Krümels

Kleinere Brocken, genaueres Retrieval, aber mehr Overhead

// Option A: Small chunks (200 tokens each)
// Retrieve 20 chunks = 4,000 tokens
// Pro: Very precise, only relevant info
// Con: More KV cache entries, slower attention

// Option B: Larger chunks (500 tokens each)
// Retrieve 8 chunks = 4,000 tokens
// Pro: Better context coherence, fewer KV entries
// Con: More noise, less precise

public class AdaptiveChunker
{
    public int DetermineChunkSize(int contextWindowSize)
    {
        if (contextWindowSize <= 4096)
            return 200; // Small chunks for limited windows

        if (contextWindowSize <= 16384)
            return 500; // Medium chunks

        return 1000; // Large chunks for big windows
    }
}

Kontextfenster-Nutzung

Don-'-t max aus dem Kontextfenster lassen Raum für die Generation

public class SafeContextManager
{
    public int GetSafeContextLimit(int totalContextWindow)
    {
        // Use only 75% for input, reserve 25% for output
        return (int)(totalContextWindow * 0.75);
    }

    // Example: 4K model
    // Total: 4096 tokens
    // Safe input: 3072 tokens
    // Reserved for output: 1024 tokens
}

Mehrsprachige RAG-Gespräche

In chatbots.,, die Gesprächsgeschichte wächst mit jedem Dreh

Turn 1:
System + Context + Query1 = 3000 tokens
Response1 = 300 tokens
Total: 3300 tokens

Turn 2:
System + Context + Query1 + Response1 + Query2 = 3650 tokens
Response2 = 300 tokens
Total: 3950 tokens

Turn 3:
System + Context + Query1 + Response1 + Query2 + Response2 + Query3 = 4250 tokens
ERROR: Context window exceeded!

Lösung: Schiebefenster mit Re---Retrieval

public class ConversationalRAG
{
    private readonly int _maxHistoryTokens = 1000;

    public async Task<string> ChatAsync(
        List<ConversationTurn> history,
        string newQuery)
    {
        // Re-retrieve context based on current query
        var context = await RetrieveContextAsync(newQuery);

        // Keep only recent conversation history
        var relevantHistory = TrimHistory(history, _maxHistoryTokens);

        var prompt = BuildPrompt(context, relevantHistory, newQuery);

        return await _llm.GenerateAsync(prompt);
    }

    private List<ConversationTurn> TrimHistory(
        List<ConversationTurn> history,
        int maxTokens)
    {
        var trimmed = new List<ConversationTurn>();
        var currentTokens = 0;

        // Keep most recent turns
        foreach (var turn in history.Reverse())
        {
            var turnTokens = EstimateTokens(turn.Query) + EstimateTokens(turn.Response);

            if (currentTokens + turnTokens <= maxTokens)
            {
                trimmed.Insert(0, turn);
                currentTokens += turnTokens;
            }
            else
            {
                break;
            }
        }

        return trimmed;
    }
}

Optimierung der Tokenkosten

API.-. basierte LLMs Ladung pro Token. .. RAG kann Kosten explodieren, wenn nicht vorsichtig.

public class CostAwareRAG
{
    // OpenAI GPT-4 pricing (example):
    // Input: $0.03 per 1K tokens
    // Output: $0.06 per 1K tokens

    public decimal EstimateQueryCost(
        int systemPromptTokens,
        int retrievedContextTokens,
        int queryTokens,
        int expectedResponseTokens)
    {
        var inputTokens = systemPromptTokens + retrievedContextTokens + queryTokens;
        var outputTokens = expectedResponseTokens;

        var inputCost = (inputTokens / 1000m) * 0.03m;
        var outputCost = (outputTokens / 1000m) * 0.06m;

        return inputCost + outputCost;
    }

    // Example:
    // System: 300 tokens
    // Context: 3000 tokens (10 retrieved docs)
    // Query: 50 tokens
    // Response: 500 tokens
    //
    // Cost = ((300 + 3000 + 50) / 1000 * 0.03) + (500 / 1000 * 0.06)
    //      = (3350 / 1000 * 0.03) + (500 / 1000 * 0.06)
    //      = $0.1005 + $0.03
    //      = $0.1305 per query
    //
    // At 1000 queries/day = $130/day = $3,900/month!
}

Strategien zur Kostensenkung

  1. Weniger Dokumente abrufen
  2. Verwenden Sie promptes Caching für gecachete Tokens
  3. Verwenden Sie billigere Modelle für die letzte Generation
  4. Kontext komprimieren mit Zusammenfassung

Visualisierung des kompletten RAG Flows mit Token

Hier sind ', wie Tokens,, KV-Cache und RAG zusammenpassen.

flowchart TB
    A[User Query:<br/>'How does Docker work?'<br/>≈ 12 tokens] --> B[Generate Query Embedding]

    B --> C[Vector Search]
    C --> D[Retrieved Docs:<br/>5 docs × 500 tokens<br/>= 2,500 tokens]

    D --> E[Construct Prompt]
    A --> E

    E --> F["Complete Prompt:<br/>System: 300 tokens<br/>Context: 2,500 tokens<br/>Query: 12 tokens<br/>Total: 2,812 tokens"]

    F --> G[Tokenize Prompt]
    G --> H["Token IDs:<br/>[245, 1034, 8829, ...]<br/>2,812 token IDs"]

    H --> I[LLM Layer 1]
    I --> J[Compute K,V]
    J --> K[KV Cache Layer 1:<br/>2,812 K,V pairs]

    I --> L[LLM Layer 2]
    L --> M[Compute K,V]
    M --> N[KV Cache Layer 2:<br/>2,812 K,V pairs]

    L --> O[... Layers 3-32]
    O --> P[Generate Token 1: 'Docker']

    P --> Q[Add to KV Cache]
    Q --> R[Generate Token 2: 'is']
    R --> S[Add to KV Cache]
    S --> T[... until completion]

    T --> U["Response: 'Docker is a containerization platform...'<br/>≈ 400 tokens"]

    style K stroke:#f9f,stroke-width:2px
    style N stroke:#f9f,stroke-width:2px
    style Q stroke:#bbf,stroke-width:2px
    style S stroke:#bbf,stroke-width:2px

Wichtigste Erkenntnisse

  1. Eingabe-Token Einmal verarbeitet werden, um den ersten KV-Cache zu erstellen.
  2. Erzeugung passiert ein Token zu einem Zeitpunkt, um den KV-Cache wiederzuverwenden
  3. Jedes neue Token fügt dem KV-Cache für zukünftige Tokens hinzu
  4. VRAM insgesamt benötigte Modellgewichte für alle Tokens
  5. Längerer Kontext Größerer KV-Cache mehr VRAM

Praktische Implikationen für die RAG

Das Verstehen von Token und KV-Cache führt zu einem besseren RAG-Design

Gemeinsame Kontexte erstellen und zwischenspeichern

// Cache KV for frequently used system prompts + static context
var cachedSystemContext = await _llm.PrecomputeKVCache(systemPrompt + staticContext);

// Reuse for each query (much faster)
foreach (var query in userQueries)
{
    var response = await _llm.GenerateAsync(query, reuseKVCache: cachedSystemContext);
}

Optimieren Sie die Chunk-Grenzen

// Bad: Arbitrary 500-character chunks
var chunks = text.Chunk(500);

// Good: Chunk on sentence boundaries, measure in tokens
public List<string> ChunkByTokens(string text, int maxTokensPerChunk)
{
    var sentences = SplitIntoSentences(text);
    var chunks = new List<string>();
    var currentChunk = new StringBuilder();
    var currentTokens = 0;

    foreach (var sentence in sentences)
    {
        var sentenceTokens = EstimateTokens(sentence);

        if (currentTokens + sentenceTokens > maxTokensPerChunk && currentTokens > 0)
        {
            chunks.Add(currentChunk.ToString());
            currentChunk.Clear();
            currentTokens = 0;
        }

        currentChunk.Append(sentence).Append(" ");
        currentTokens += sentenceTokens;
    }

    if (currentTokens > 0)
        chunks.Add(currentChunk.ToString());

    return chunks;
}

Überwachen Sie die Verwendung von Token in der Produktion

public class RAGTelemetry
{
    public void LogRAGQuery(
        string query,
        List<SearchResult> retrievedDocs,
        string response)
    {
        var queryTokens = EstimateTokens(query);
        var contextTokens = retrievedDocs.Sum(d => EstimateTokens(d.Text));
        var responseTokens = EstimateTokens(response);
        var totalTokens = queryTokens + contextTokens + responseTokens;

        _logger.LogInformation(
            "RAG Query: {Query} | Context: {ContextTokens} tokens from {DocCount} docs | " +
            "Response: {ResponseTokens} tokens | Total: {TotalTokens} tokens",
            query, contextTokens, retrievedDocs.Count, responseTokens, totalTokens
        );

        // Alert if approaching context limit
        if (totalTokens > _maxTokens * 0.9)
        {
            _logger.LogWarning("Approaching token limit: {TotalTokens}/{MaxTokens}",
                totalTokens, _maxTokens);
        }
    }
}

Schlussfolgerung: Architekturbeherrschung

Wir haben die komplette technische Architektur der RAG-Systeme abgedeckt.

Phase der Indexierung

  • Textextraktion aus verschiedenen Quellen
  • Chunking-Strategien mit Überlappung von - auf Basis von, aus dem Satz- auf Grundlage von ,
  • Generierung von API-Diensten für die Embedding Generation
  • Vektorspeicher (-Qdrant,-pgvector,-Pinecone-)-

Phase-Übungsphase

  • Abfrage Einbettung des gleichen Modells als Indexierung
  • Ähnlichkeitssuche (-Cosin-,-euclidean-,-Punktprodukt
  • Optionales Reranking für bessere Präzision
  • Metadatenfilterung für verfeinerte Ergebnisse

Phase-Generierung

  • Prompt Konstruktion -(-Kontext-Anweisungen - +-Abfrage-)-
  • LLM-Inferenz (-Temperatur ,-Top---P.,-Max-Tokens )-
  • Nach der Verarbeitung von ( wird die Extraktion von , formatiert.

LLM-Interne

  • Die grundlegenden Einheiten
  • KV-Cache, :, warum die Generation schnell ist, nicht quadratisch.
  • Kontextfenster Bearbeiten von Token-Grenzen in RAG
  • Kostenoptimierung: :-Caching-,-Komprimierung: Smart Retrieval

Wichtige technische Erkenntnisse

  1. Gleiches Einbettungsmodell für Indexierung und Retrieval
  2. Es spielt mehr eine Rolle, als du denkst Bewahrt semantische Kohärenz
  3. Reranking verbessert die Präzision auf Kosten der Latenz
  4. Token-Management ist unerlässlich Schätzung vor Abfragen
  5. KV-Caching macht RAG möglich Berechnung der Eingabeaufforderung wiederverwenden
  6. Kontextfenster füllen schnell --Zeichnungszeichen

Weiter zum Teil RAG in der Praxis

Du verstehst jetzt Wie funktioniert RAG? auf der technischen Ebene - ., Aber die Theorie nur bekommt Sie so weit - . Wie können Sie tatsächlich bauen diese Systeme -?, Welche Herausforderungen werden Sie Gesicht -?, Welche fortschrittlichen Techniken können Sie verwenden

Im **RAG in der Praxis**Wir bewegen uns von der Architektur zur Implementierung

Reale Anwendungen der --Welt

  • Related posts Empfehlung auf diesem Blog
  • Semantische Blogsuche
  • Aufbau eines Schreibassistenten

Gemeinsame Herausforderungen und Lösungen

  • Chunking-Strategien, die den Kontext erhalten
  • Verbesserung der Einbettungsqualität für Ihre Domain
  • Kontextfenster dynamisch verwalten
  • Halluzination trotz Kontext verhindern
  • Halten Sie Ihren Index nach oben

Fortgeschrittene Techniken

  • Hypothetische Dokument-Embeddings
  • Selbstbezwingen mit LLM---geparsten Filtern
  • Mehrsprachige RAG für umfassende Ergebnisse
  • Kontextuelle Komprimierung zur Reduzierung der Token-Nutzung
  • Multi---Hop RAG für komplexe Abfragen
  • Langzeitgedächtnis des Gesprächs

Erste Schritte

  • Woche--von---Wochen-Umsetzungsplan
  • Beispiele für praktische Codes
  • Optimierungsstrategien
  • Wann darf ich RAG NICHT verwenden?

Weiter zum Teil RAG in der Praxis

Aufkommen

Grundlagenpapiere

Werkzeuge und Frameworks

Weiteres Lesen

Serien-Navigation-:-

Weiter zum Teil

logo

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