# Architecture et architecture interne de RAG: Comment il fonctionne réellement

À l'intérieur [Partie 1](/blog/rag-primer), nous avons examiné les origines de RAG, ' , les principes fondamentaux, , et la raison pour laquelle cela est important,. Vous comprenez le concept d’un niveau élevé, MSC5 M SK6 saisir l’information pertinente, M SK7 puis l’utiliser pour générer des réponses, МSK8 Nous plongons maintenant profondément dans l’architecture technique.

<datetime class="hidden">2025-11-22T09:30</datetime>

<!-- category -- AI, RAG, Machine Learning, Semantic Search, LLM, AI-Article -->
# Introduction

**Navigation de la série 📖** Il s'agit d'une partie 2 de la série RAGM SK1

- [Partie 1: Origines et fondements](/blog/rag-primer) - Historique, motivationM SK2 et concepts de base
- **Partie 2: Architecture et intérieurs** (le présent articleM SK1 - Une profonde plongée technique sur la façon dont fonctionne le RAG
- [Partie 3: RAG dans la pratique](/blog/rag-practical-applications) - Établissement de systèmes réelsM SK1 défis , et techniques avancées
- [Partie 4a: ONNX & Mise en œuvre de la garantie](/blog/semantic-search-with-onnx-and-qdrant) - CPU- fondation de recherche sémantique conviviale
- [Partie 4b: Recherche sémantique en action](/blog/semantic-search-in-action) - Typeahead , recherche hybride, et UI
- [Pièce 5: Recherche hybride & AutoM SK2Indexage](/blog/rag-hybrid-search-and-indexing) - Modèles d'intégration de la production
- [Partie 6: GraphRAG](/blog/graphrag-knowledge-graphs-for-rag) - Graphiques de connaissances pour le corpus

Si vous n’avez pas lu la partie ', je recommande de commencer par comprendre celle-ci.

- Qu'est-ce que le RAG et pourquoi il est important
- L'histoire de la recherche par mots clés à la compréhension sémantique
- RAG vs fine-tuning et autres approches

Cet article suppose que vous comprenez ces principes fondamentaux et se concentre sur **architecture technique, détails de mise en œuvre, et internes LLM**.

[TOC]

# Comment fonctionne le RAG: Le tableau complet

Let's diviser exactement ce qui se passe dans un système RAG, du moment où vous ajoutez un document au moment où l’utilisateur reçoit une réponse

## Phase 1: Indexage (Préparation de la base de connaissances)

Avant que RAG puisse récupérer quelque chose, vous devez indexer votre base de connaissances.

```mermaid
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
```

### Étape 1: Extraction de texte

Extraire un texte simple de vos documents sources. Il pourrait s'agir de:

- Dossiers de marquage (comme mes articles sur mon blog)
- PDF (pour la documentation)
- HTML (for web scraping)
- Répertoires de bases de données
- Courriels, registres de chatM SK1 etc.

**Exemple de mon blog:**

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

### Étape 2: activités de fabrication

C'est là où la plupart des mises en œuvre RAG échouent. Vous pouvez' ne pas simplement diviser sur les limites du paragraphe - vous avez besoin de chunks sémantiquement cohérents

**Pourquoi la fragmentation est-elle importante:**

- Les LLM ont des limites de token ( fenêtres contextuelles)
- Parties plus petites = récupération plus précise
- Mais les chunks doivent contenir suffisamment de contexte pour être significatifs

**Faible fragmentation:**

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

**Bonne fractionnage:**

```
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"
```

**Exemple de ma mise en œuvre de la recherche sémantique:**

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

**Stratégies de regroupement communes:**

- **Fixe-dimension**: Simple mais rompt les limites sémantiques
- **Sentence- fondée**: Respecte la grammaire mais peut être trop petite
- **Paragraphe- fondé**: Taille naturelle mais variable
- **Section- fondée**: Suivant le contenu structuré (de mes préférencesM SK2
- **Vente à coucher avec chevauchement**: Assure qu'aucun contexte n'est perdu aux frontières

### Étape 3: Générer les embeddings

Les embeddings sont la magie qui rend possible une recherche sémantique.

**Concept clé:** Signes similaires → vecteurs semblables

```
"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]
```

Les deux premiers vecteurs seraient "closeM SK1 dans l'espace de vecteur (haute similitude cosine), alors que le troisième est loinMSC4

**Comment les embeddings sont générés:**
Les modèles d’embedding modernes sont des réseaux neuronaux formés sur de vastes ensembles de données textuelles pour apprendre les relations sémantiques.

- **all-MiniLMM SK1L 6-v2**: 384 dimensionsM SK2 rapides, bonne qualité ( ce que je vais utiliser sur ce blogMSC5
- **texte-embedding-3-small** (OpenAIM SK1 1536 dimensions, très haute qualité
- **BGE-base**: 768 dimensionsM SK2 état-deMSC4le\-art source libre

**Exemple de mon service d'embedding ONNX:**

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

**Pourquoi la normalisation est importante:** Après L2 normalisation , la similitude des cosine devient un simple produit de pointesM SK2 rendant la recherche beaucoup plus rapide.

### Étape 4: Entrer dans la base de données Vector

Les bases de données vectorielles sont optimisées pour stocker et rechercher des vecteurs hautement dimensionnels-Véctores dimensionnelles . Contrairement aux bases de datos traditionnelles qui utilisent les demandes SQLM SK2Les bases de base de données vectorales utilisent la recherche de similitude.

**Opérations clés:**

- **Ajouter**: Ajouter ou mettre à jour un vecteur avec des métadonnées
- **Rechercher**: Trouver des vecteurs K les plus similaires à un vecteur de demande
- **Filtre**: Combinez la recherche par vecteur avec les filtres de métadonnées

**Exemple de la mise en œuvre du Qdrant:**

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

**Bases de données populaires sur les vecteurs:**

- **Qdrant**: FastM SK1 self-hostable , excellent CMST4 support MST5my choiceM ST6
- **pgvector**: Extension PostgreSQL (excellent si vous utilisez déjà Postgres
- **Pinecone**: Service gÃ©nÃ©ral (prix mais bonM SK2
- **Favoriser**: Caractéristique
- **ChromaDB**: PythonM SK1focused, lightweight

Nous examinerons la mise en place de ces bases de données dans les prochains articles.

## Phase 2: Recouvrement (Trouver des renseignements pertinents)

Lorsque l’utilisateur pose une question, le système RAG doit trouver les informations les plus pertinentes de la base de connaissances.

```mermaid
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
```

### Étape 1: Générer l'intégration de la demande

La question de l’utilisateur' est convertie en vector à l’aide du **le même modèle d'implantation** utilisés pour indexer. Il s'agit d'un élément critique - les différents modèles produisent des vecteurs incompatiblesM SK2

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

### Étape 2: Recherche de similitude

La base de données des vecteurs calcule la similitude entre le vecteur de demande et tous les vecteurs stockés.

**Symétrie de cosine** (le plus populaire pour les vecteurs normalisésM SK1

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

Étendue: M SK1 à 1

**Distance Euclidienne** (pour les vecteurs non normalisésM SK1

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

Étendue: M SK1 à ∞ ≥( inférieure MSC4 plus similaireMSc5

**Produit de pointe** (lors que les vecteurs sont préétablisM SK1normalisés ):

```
similarity = A · B
```

Étendue: M SK1 à 1

**Exemple de mon service Qdrant:**

```csharp
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();
```

### Étape 3: Reclassement (Optionnelle mais recommandée)

La recherche initiale est rapide mais approximative. Le reranking utilise un modèle plus sophistiqué pour réévaluer les résultats de K supérieurs.

```mermaid
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
```

**Pourquoi le reclassement aide-t-il:**

- Les modèles d’embedding rapides optimisent la vitesse, en sacrifiant une certaine précision
- Les modèles de reclassement sont plus lentes mais plus précis
- Une approche à deux étapes de la - permet d’équilibrer la vitesse et la qualité

**Exemple de mise en œuvre de reclassement:**

```csharp
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 3: Génération ( Création de la réponseM SK2

Maintenant que nous disposons de l’information pertinente,, nous la transmettons au LLM en même temps que la question posée par l’utilisateur '

```mermaid
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
```

### Étape 1: Construction rapide

C'est là où le RAG devient un art. Vous devez structurer la prompte de façon à ce que le LLM

- Utilise le contexte fourni (pas ses connaissances internesM SK1
- Citer les sources lorsque possible
- Admet que le contexte ne contient pas de réponse
- Maintenir un ton cohérent/style

**Exemple de template d'appel du système GPT de mon avocat:**

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

### Étape 2: Inférence LLM

Le prompt construit va au LLM pour la génération. Il peut s'agir de:

- **L'API Cloud**: OpenAI, Anthropic ClaudeM SK2 Google PaLM
- **Modèle local**: Utilisation de llamaM SK1cpp , OnNX Runtime, ou TorchSharp

**Exemple en utilisant local LLM:**

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

**Paramètres clés expliqués:**

- **Température**: Contrôle de l'aléacité (0 = choisissez toujours la probabilité la plus élevée
- **Haut de la page**: Échantillonnage du noyau - considérer uniquement les tokens qui constituent la masse de probabilité supérieure P
- **Tokens maximaux**: Longueur de réponse limitée
- **Poursuivre les séquences**: Quand cesser de produire

### Étape 3: Post-Procédure

Après que le LLM génère une réponse,, nous devons souvent

- Extraire des citations et les convertir en liens
- Modifier les blocs de code
- Ajouter des métadonnées (sources,points de confianceM SK2
- Enregistrez l'interaction pour le débugging

**Exemple post-processing:**

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

# Comprendre les systèmes internes de LLM: Tokens, KV CacheM SK2 et Windows contextuels

Avant de passer aux applications pratiques, il est essentiel de comprendre comment les LLM fonctionnent à l’interne. Ce savoir vous aide à optimiser vos systèmes RAG et à éviter des pièges communs.

## Quels sont les tokens?

Les tokens sont les unités fondamentales que les LLM traitent.

**Exemple de tokenisation:**

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

Divers modèles utilisent différentes stratégies de tokenisation:

- **Modèles GPT**: Utilisez un ByteM SK1Encodage par pair (BPE) avec le vocabulaire de
- **Claude**: Approche BPE similaire
- **Modèles Llama**: Tokenisation de SentencePiece

**Pourquoi la tokenisation est importante pour RAG:**

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

**Limites de la fenêtre contextuelle:**

- Tokens GPT-3.5: M SK1K
- TÉU, TPAC, TPMD, TPG, TM, TMÉU, TC:
- Claude 3.5 SonnetM SK1 200K tokens
- Llama 3: 8K de cotes (qu'il est possible d'étendre les cotes)

Dans les systèmes RAG, vous devez s’adapter:

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

Si votre RAG reçoit 10 des documents de 500 tokens chacuneM SK2 que's 5,000 tokens seulement pour le contexte |- avant la demande et la réponse

**Gestion pratique des tokens RAG:**

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

## Le cache KV: LLM' est une arme secrete

Lorsqu’un LLM génère un texte, il ne reprocesse pas de tout à partir de zéro pour chaque token . Il utilise une **Key-Value M SK1KV) cache** pour se rappeler ce qu’il a déjà calculé.

### Comment fonctionnent les transformateurs (Simplifié)

Les transformateurs utilisent un mécanisme "attention" dans lequel chaque token "assiste à

```mermaid
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
```

**Sans cache KV:**

- Étape 1: Processus 1 token → OM SK3
- Étape 2: Processus 2 Tokens à partir de zéro
- Étape 3: Processus 3 Tokens à partir de zéro
- Total : OM SK1 + | | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

**Avec le cache KV:**

- Étape 1: Processus 1 token, cache KM SK3V → OMSC5
- Étape 2: Processus 1 nouveau token, réutilisation cached KM SK3V → OMSC5
- Étape 3: Processus 1 nouveau token, réutilisation cached KM SK3V → OMSC5
- Total: OM SK1N )

C'est ce qui fait la génération **de plus en plus rapides** - la différence entre les valeurs suivantes :

### Structure de l'arbre du cache KV

Le cache KV forme un "arbre" en raison de la façon dont l'attention fonctionne dans les transformateursM SK2 Chaque couche du modèle a ses propres matrices KMSC3VMNK4

```mermaid
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
```

**Chaque couche de stockage:**

- **clés (KM SK1**: Représentations utilisées pour calculer les points d'attention
- **Valeur (VM SK1**: Représentations qui se mélangent en fonction de l’attention

Pour un modèle avec:

- 32 couches
- 4096 dimensions cachées
- 32 têtes d'attention
- 8K fenêtre contextuelle

Le cache KV pour une séquence est:

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

C'est pourquoi les fenêtres contextuelles longues sont mémoire-intensive.

### Cache KV dans les systèmes RAG

Les systèmes RAG peuvent optimiser le cache KV de façon intelligente:

**Cache rapide** (appuyé par quelques API comme Anthropic Claude):

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

**Pourquoi cela est puissant:**

- Première demande: Compute le cache KV pour la prompte du système M SK1 contexte (slow)
- Demandes subséquentes avec le même contexte: Réutilise KV caché M SK1x plus rapides!)
- Seule la partie de demande de l'utilisateur a besoin d'un nouveau calcul

**Exemple pratique:**

```
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!)
```

Toutes les trois demandes utilisent le même contexte récupéré, de sorte que la cache KV pour ce contexte est utilisée à nouveau.

## Limites des tokens et stratégie RAG

La compréhension des tokens et du cache KV informe vos décisions d’architecture RAG:

### 1. Taille de fabrication

Parties plus petites = récupération plus précise, mais plus lourdeM SK2

```csharp
// 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
    }
}
```

### 2. Utilisation de la fenêtre contextuelle

Don't max out the context window - leave room for generationM SK2

```csharp
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
}
```

### 3. Multi-Retourner les conversations RAG

Dans les chatbots,, l’histoire de la conversation augmente à chaque tour.

```
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!
```

**Solution: fenêtre Sliding avec re-retrieval**

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

### 4. Optimisation des coûts du token

API-Les LLM basés sur la tarification par token . Le RAG peut exploser les coûts s’il n’est pas prudent.

```csharp
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!
}
```

**Stratégies de réduction des coûts:**

1. Récupérer moins de documents classés
2. Utiliser le cache rapide (Anthropic ClaudeM SK1 90% moins coûteux pour les tokens cachés)
3. Utiliser des modèles moins coûteux pour re-ranking, coûteuses pour la génération finale
4. Comprimer le contexte en utilisant la sommation

## Visualiser le flux RAG complet avec les tokens

Voici la façon dont les tokens, le cache KV, le RAG et le , passent ensemble.

```mermaid
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
```

**Principaux points de vue:**

1. **Tokens d'entrée** (2,812) sont traités une fois pour construire le cache initial KV
2. **Génération** se produit un token à la fois, en réutilisant le cache KV
3. **Chaque nouveau token** Ajout au cache KV pour les tokens futurs à assister
4. **Total VRAM** requis = Poids du modèle + Cache KV pour tous les tokens
5. **Contexte plus long** = Cache KV plus grande = VRAM plus grand

## Implications pratiques pour le RAG

La compréhension des tokens et du cache KV conduit à un meilleur design RAG:

**1. Pré-contextes communs de calcul et de cache :**

```csharp
// 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);
}
```

**2. Optimiser les limites des chunks:**

```csharp
// 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;
}
```

**3. Surveillance de l'utilisation des tokens dans la production**

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

# Conclusion: Maîtrise en architecture

Nous avons couvert l’architecture technique complète des systèmes RAG.

**Indexage de la phase 1:**

- Extraction de texte à partir de diverses sources
- Stratégies d’encouragement (section-basée sur la , sentenceM SK3basées sur la, avec chevauchement+)
- Génie d'embedding (ONNXM SK1 Services API)
- Entreposage de vecteurs (QdrantM SK1 pgvector, PineconeMSC3

**Récupération de phase 2:**

- Intégration de la question (le même modèle que l'indexation!)
- Recherche de similitude (cosine,euclideanMSC2 produit ponctuelM SK3
- Remplacement facultatif pour une plus grande précision
- Filtration des métadonnées pour les résultats raffinés

**Production de phase 3:**

- Construction rapide (contexte
- Inférence du LLM (températureM SK1 haut-p , tokens maximauxMSC4
- Posts-processage (extraction de citationsM SK2formattage )

**LLM Internals**

- Tokens: Les unités fondamentales (pas de caractèresM SK2
- KV Cache: Pourquoi la génération est rapide M SK1linear, non quadratiqueMSC3
- Contexte windows: Gestion des limites de cotes dans RAG
- Optimisation des coûts: Caching, compressionM SK2 recherche intelligente

**Principaux renseignements techniques:**

1. **Le même modèle d'implantation** pour l'indexation et la recherche (criticalM SK1
2. **Il est plus important que vous ne le croyez de s'entraîner** - préserve la cohérence sémantique
3. **Le reclassement améliore la précision** au prix de la latence
4. **La gestion des tokens est essentielle** - estimation avant la demande
5. **Le caching KV rend possible le RAG** - Renouvellement des calculs de prompt
6. **Les fenêtres de contexte remplissent rapidement** - 10 pièces justificatives

# Continuer à la partie 3: RAG dans la pratique

Vous comprenez maintenant **Comment fonctionne le RAG** à l’échelon technique. Mais la théorie ne vous mène que jusqu’à présent. Comment bâtir réellement ces systèmesM SK2 Quels sont les défis auxquels vous serez confrontés ? Quelles techniques avancées pouvez-vous appliquer?

À l'intérieur **[Partie 3: RAG dans la pratique](/blog/rag-practical-applications)**, nous passerons de l’architecture à la mise en œuvre

**Applications réelles-world applications:**

- Postes connexes Recommandation sur ce blog
- Recherche sur le blog sémantique
- Établissement d’un "avocat GPT" assistant à la rédaction

**Difficultés et solutions communes:**

- Stratégies de recyclage qui préservent le contexte
- Améliorer la qualité de l'implantation pour votre domaine
- Gestion dynamique des fenêtres contextuelles
- Prévenir les hallucinations malgré le contexte
- Maintenir votre index à jour-àM SK1date

**Techniques avancées:**

- Embeddings hypothétiques de documents (HyDE)
- Self-querying with LLM-parsed filters
- Multi-recherche RAG pour obtenir des résultats complets
- Compression contextuelle pour réduire l'utilisation des tokens
- Multi-hop RAG pour les demandes complexes
- Mémoire de conversation à long terme

**Pour commencer:**

- Plan de mise en œuvre hebdomadaire
- Exemples de codes pratiques
- Stratégies d'optimisation
- Quand ne pas utiliser RAG

**[Continuer à la partie 3: RAG dans la pratique →](/blog/rag-practical-applications)**

## Ressources

**Documents de base:**

- [Récupération-Generation améliorée des connaissances-Taxes importantes de PNL](https://arxiv.org/abs/2005.11401) - Le papier RAG original
- [Récupération de passages denses pour Open-Domain Question Answering](https://arxiv.org/abs/2004.04906) - Fondation du RMR
- [Attention est tout ce dont vous avez besoin](https://arxiv.org/abs/1706.03762) - Transformers

**Outils et cadres:**

- [Qdrant](https://qdrant.tech/) Base de données du vecteur -
- [Le temps de fonctionnement d'ONNX](https://onnxruntime.ai/) - Emplacements locaux
- [LLamaSharp](https://github.com/SciSharp/LLamaSharp) - Inférence locale LLM
- [Transformers de phrase](https://www.sbert.net/) - Modèles d'emplacement

**Lire plus:**

- [Anthropiques: Récupération contextuelle](https://www.anthropic.com/index/contextual-retrieval) - Techniques avancées

**Navigation de série:**

- [Partie 1: Origines et fondements](/blog/rag-primer) - Historique et motivation
- **Partie 2: Architecture et intérieurs** (le présent articleM SK1 - plongée technique dans les profondeurs
- [Partie 3: RAG dans la pratique](/blog/rag-practical-applications) - Établissement de systèmes réels

**[Continuer à la partie 3 →](/blog/rag-practical-applications)**