# DocSummarizer Part 3 - Concepts avancésM SK2 Le MSC3 Je suis allé trop loin SMC4 plongée profonde

<!--category-- AI, LLM, RAG, BERT, ONNX, C#, .NET, Embeddings -->
<datetime class="hidden">2025-12-21T12:00</datetime>

## Introduction

Il s'agit de **Partie 3** de la série DocSummarizer:

1. **[Partie 1: Établissement d'un résumé de document avec RAG](/blog/building-a-document-summarizer-with-rag)** - L’architecture et la raison pour laquelle l’approche du pipeline bat les appels naïfs de LLM
2. **[Partie 2: Utilisation de l'outil](/blog/docsummarizer-tool)** - Quick-guide de démarrage du CLI
3. **Partie 3: Concepts avancés** (le présent articleM SK1 - Le SSK3Je suis allé trop loin" plongée technique de profondeur
4. **[Partie 4: Établissement de pipelines RAG](/blog/docsummarizer-rag-pipeline)** - Utilisez la bibliothèque NuGet pour construire vos propres applications RAG

---


C’est une partie de mon "TimeM SK1Outils en boîte "approche: donner à moi-même une fenêtre fixe pour construire quelque chose fonctionnelMSC4 Il forcère les décisions et produit le code de travail plutôt que des conceptions théoriques.

DocSummarizer a commencé comme une démonstration de *Il faudrait* Build document summarizers with LLMs - l'approche de pipeline que j'ai exposée dans la partie 1. La plupart des tutorials vous montrent comment insérer le texte dans un LLM et espérer la meilleure.

Mais comme je le fais toujours, J’ai commencé à me pencher sur l’espace du problèmeM SK1 Quatre jours plus tard, JeMSC3 j’ai approfondi la recherche . Comment fonctionnent les systèmes de classement de la production?

J’ai mis en œuvre des versions de ces approches. Ce qui commençait à "oùMSC2 est le bon modèleM SK3 est devenu un emplacement ONNX fonctionnant localement , recherche hybride combinant BMMST5 avec une extraction dense MST6 Relevance marginale maximale pour la diversitéMst7 et Fusion de rang réciproque pour combiner les signauxM st8

**Faire mise en garde**: Il s’agit du " J’ai allé trop loin" plongée profondeM SK3 Si vous voulez simplement utiliser l’outilMSC4 lire la partie 2. si vous voulez comprendre *pourquoi* il fonctionne et *comment* les pièces conviennent ensemble, conserver la lectureM SK1

Cet article porte sur:

- **Embeddings de la Sentence**: Comment les modèles de transformateurs (MiniLM/BGEM SK3GTEMSC4 transforment le texte en vecteurs
- **Le temps de fonctionnement d'ONNX**: Running ML models locally without Python or cloud APIs
- **RAG (Retraite-Generation amélioréeM SK2**: Résultat d'un LLM de base dans le matériau source
- **Recherche hybride**: Combinaison de la recherche sémantique et lexique avec le RRF

[TOC]

## L'architecture à un coup d'œil

Avant de vous plonger dans les détails, ici' c'est comment les pièces se passent ensembleM SK2

```mermaid
flowchart TB
    subgraph Input["Document Input"]
        DOC[/"Document<br/>(PDF, MD, URL)"/]
    end

    subgraph Parse["Parsing Layer"]
        DOCLING["Docling<br/>(PDF/DOCX)"]
        MARKDIG["Markdig<br/>(Markdown)"]
    end

    subgraph Extract["Extraction Layer"]
        CHUNK["Document Chunker"]
        SEGMENT["Segment Extractor"]
    end

    subgraph Embed["Embedding Layer"]
        ONNX["ONNX Runtime<br/>(Sentence Transformers)"]
        OLLAMA_EMB["Ollama<br/>(Optional)"]
    end

    subgraph Store["Vector Storage"]
        QDRANT["Qdrant<br/>(Vector DB)"]
        MEMORY["In-Memory<br/>(Small Docs)"]
    end

    subgraph Retrieve["Retrieval Layer"]
        DENSE["Dense Search<br/>(Semantic)"]
        BM25["BM25<br/>(Lexical)"]
        RRF["RRF Fusion"]
    end

    subgraph Synthesize["Synthesis Layer"]
        OLLAMA["Ollama LLM<br/>(Local)"]
        TEMPLATES["Summary Templates"]
    end

    subgraph Output["Output"]
        SUMMARY[/"Summary with<br/>Citations [chunk-N]"/]
    end

    DOC --> DOCLING & MARKDIG
    DOCLING & MARKDIG --> CHUNK & SEGMENT
    CHUNK --> ONNX & OLLAMA_EMB
    SEGMENT --> ONNX
    ONNX & OLLAMA_EMB --> QDRANT & MEMORY
    QDRANT & MEMORY --> DENSE
    CHUNK --> BM25
    DENSE & BM25 --> RRF
    RRF --> OLLAMA
    OLLAMA --> TEMPLATES
    TEMPLATES --> SUMMARY
```

## Comprendre les aménagements

### Le problème: Comment trouver le contenu pertinent sans mots clés?

Lorsque vous résumez un manuel de page 500-,, vous devez trouver les sections pertinentes.

- Utilisateur demande "Comment réinstaller le périphérique?"
- Le manuel indique "Retablir les paramètres de l'usine..."
- La recherche de mots clés manque (pas de mots partagés)

Vous avez besoin **recherche sémantique** - correspondance par signification

### Qu'est-ce que les embeddings?

Les embeddings résolvent ce problème en transformant le texte en vecteurs denses.

Voici ' est l’intuition : imaginez un espace 384- dimensionnel où chaque morceau de texte a une position . Les textes ayant des sens similaires se regroupent ensemble M.

```mermaid
graph LR
    subgraph "Embedding Space (simplified to 2D)"
        A["🚗 car"]
        B["🚙 automobile"]
        C["🏎️ vehicle"]
        D["🍎 apple"]
        E["🍊 orange"]
        F["🍌 fruit"]
    end
    
    A -.->|"close"| B
    B -.->|"close"| C
    A -.->|"close"| C
    
    D -.->|"close"| E
    E -.->|"close"| F
    D -.->|"close"| F
    
    A -.-|"far"| D
```

### Pourquoi les transformateurs de Sentence (Non BERT bruts)?

**Problème**: J’avais besoin d’embeddings qui fonctionnent en fonction de la similitude sémantique . Le BERT brut a été conçu pour les tâches de classification

**Solution**: Usage **transformateurs de sentences** Les modèles - ont été spécialement formés sur des tâches de similitude en utilisant l’apprentissage contrastif. [Architecture BERT](https://arxiv.org/abs/1810.04805) mais fine-ajusté différemmentM SK1

Modèles comme `all-MiniLM-L6-v2` et `bge-small-en-v1.5` ont été formés sur des milliards de paires de texte comme

- "Comment rétablir le périphériqueM SK1 ↔ \"Retablir les paramètres de l'usine" |( similaireMSC6
- "Comment rétablir le périphériqueM SK1 ↔ | | "Spécifications du produit

La formation les enseigne: significations similaires M SK1 vecteurs proches ( similitude cosine élevée).

> **Connexion**: Si vous voulez comprendre comment fonctionnent les modèles de transformateurs à un niveau plus profond - incluant des mécanismes d'attentionM SK2 codeur- architecture de décoderMSC4 et pourquoi les embeddings fonctionnent - voir mon article sur [Comment fonctionne la traduction en machine neuronale](/blog/how-neural-machine-translation-works). Il couvre les mêmes concepts de transformateurs du point de vue de la traduction

**Mise en œuvre**: Nous prenons la couche de sortie du modèle ' et appliquons **Tableau 1** - moyenne des emplacements de tous les tokens pour obtenir un seul vecteur pour l'ensemble du texte

```mermaid
flowchart LR
    subgraph Input
        TEXT["The quick brown fox"]
    end
    
    subgraph Tokenization
        CLS["[CLS]"]
        T1["the"]
        T2["quick"]
        T3["brown"]
        T4["fox"]
        SEP["[SEP]"]
    end
    
    subgraph "BERT Encoder"
        direction TB
        L1["Layer 1: Self-Attention"]
        L2["Layer 2: Self-Attention"]
        L3["..."]
        L6["Layer 6: Self-Attention"]
    end
    
    subgraph Output
        E1["E[CLS]"]
        E2["E[the]"]
        E3["E[quick]"]
        E4["E[brown]"]
        E5["E[fox]"]
        E6["E[SEP]"]
    end
    
    subgraph Pooling
        MEAN["Mean Pool<br/>(with attention mask)"]
        VEC["384-dim Vector"]
    end
    
    TEXT --> CLS & T1 & T2 & T3 & T4 & SEP
    CLS & T1 & T2 & T3 & T4 & SEP --> L1
    L1 --> L2 --> L3 --> L6
    L6 --> E1 & E2 & E3 & E4 & E5 & E6
    E1 & E2 & E3 & E4 & E5 & E6 --> MEAN
    MEAN --> VEC
```

## ONNX: Modèles ML exploités localement

### Le problème: Python Dependency Hell

J'ai voulu des embeddings à "just work" quand quelqu'un exploite l'outilM SK2 L'approche standardMSC3

1. Installer les transformateurs Python + PyTorch +
2. Télécharger les modèles manuellement
3. Hope version conflicts don't break everything

Ce n'est pas bon. Les utilisateurs veulent `docsummarizer -f doc.pdf`, n’est pas un guide de mise en place des étapes 30-

### Pourquoi ONNX?

ONNX (Open Neural Network Exchange) est un format ouvert pour les modèles M.L. **inference du temps d'exécution sans Python**.

**Ce que je reçois avec ONNX Runtime:**

1. **Nulle dépendance extérieure** - Pas de pilotes PythonM SK1 PyTorch, CUDA
2. **Modèles de téléchargement automatiques-** - Démarrer les téléchargements à partir de HuggingFace (~23-34MB), puis cacher
3. **Pure .NET** - Fonctionne partout .NET fonctionne
4. **Inférence du CPU** - Aucun GPU n'est requis, fonctionne sur du matériel peu coûteux

Commercialisation-offM SK1 légèrement plus lente que GPU PyTorch, mais beaucoup plus rapide qu'en demandant aux utilisateurs d'installer Python

### Le registre type

DocSummarizer comprend plusieurs modèles embedding, chacun avec un commerce différentM SK1offs:

| Modèle MSC1 Dimensions SMC2 Tokens maximaux MSC3 Taille SMC4 Quantité MSC5 SMC6 Case d'utilisation SMC7 Besoins d'instruction ESC8
|-------|-----------|------------|------------------|----------|---------------------|
| `AllMiniLmL6V2` | 384 ≥| ≤256 | ~23 MB MSC6 Défault MSC7 Fast msc8 Bonne qualité msc9 Non SMC10
| `BgeSmallEnV15` | 384 ≥| ≤512 | ~34 MB MSC6 Besoins pour les documents techniques MSC7 Oui SMC8
| `GteSmall` | 384 ≥| ≤512 | ~34 MB MSC6 Objectif général MSC7 N°
| `MultiQaMiniLm` | 384 ≥| ≤512 | ~23 MB MSC6 Optimisé pour Q MSC7 A SMC8 Non SMC9

**Remarque**: Toutes les données du registre indiquent WordPiece-exportations compatibles d'ONNX (utilisation `vocab.txt`). BPE/Les modèles unigrammes n'ont pas encore été supportés

**Format d'instruction BGE**: Certains modèles, tels que le BGE, requièrent des préfixes pour obtenir une performance optimaleM SK3 Le format exact dépend du modèle

```csharp
// Query embedding (what the user asks)
var queryText = "Represent this sentence for searching relevant passages: " + userQuery;
var queryEmbedding = await EmbedAsync(queryText);

// Passage embedding (document chunks)
// Some BGE variants prefix passages, others don't - check model documentation
var passageEmbedding = await EmbedAsync(chunkText);
```

Le registre suit les modèles qui ont besoin d'instructions par `RequiresInstruction` et `QueryInstruction` champs. Always benchmark retrieval quality when working with instruction

Voici comment fonctionne le registre du modèle'

```csharp
public static class OnnxModelRegistry
{
    public static EmbeddingModelInfo GetEmbeddingModel(OnnxEmbeddingModel model, bool quantized = true)
    {
        return model switch
        {
            OnnxEmbeddingModel.AllMiniLmL6V2 => new EmbeddingModelInfo
            {
                Name = "all-MiniLM-L6-v2",
                HuggingFaceRepo = "Xenova/all-MiniLM-L6-v2",
                ModelFile = quantized ? "onnx/model_quantized.onnx" : "onnx/model.onnx",
                VocabFile = "vocab.txt",
                EmbeddingDimension = 384,
                MaxSequenceLength = 256,
                SizeBytes = quantized ? 23_000_000 : 90_000_000,
                RequiresInstruction = false
            },
            // ... other models
        };
    }
}
```

### Tokenisation: WordPiece vs BPE

Différents modèles utilisent différents tokenizers. Le `all-MiniLM-L6-v2` Le modèle utilise la tokenisation WordPiece (comme BERTM SK1 qui sépare les mots inconnus en sous- mots de tokens. D'autres modèles peuvent utiliser BPE (ByteMSC4Pair EncodingMska5 ou Unigram tokenizersM Ska6

**Importance**: Les témoins de modèle doivent correspondre au témoin de formation. Notre registre indique quels témoines chaque modèle doit utiliserM SK2 **Mise en œuvre à l'heure actuelle: WordPiece** (via `vocab.txt`). BPE/Support à l'unigramme via `tokenizer.json` est prévue mais n’est pas encore mise en œuvre - rester aux modèles WordPiece dans le registre pour l’instant

```csharp
public class BertTokenizer
{
    private readonly Dictionary<string, int> _vocab;
    private const int ClsTokenId = 101;  // [CLS] - start of sequence
    private const int SepTokenId = 102;  // [SEP] - end of sequence
    private const int PadTokenId = 0;    // [PAD] - padding
    private const int UnkTokenId = 100;  // [UNK] - unknown token

    public BertTokenizer(string vocabPath)
    {
        // Load vocabulary: word -> token ID
        _vocab = File.ReadAllLines(vocabPath)
            .Select((word, index) => (word, index))
            .ToDictionary(x => x.word, x => x.index);
    }

    public (long[] InputIds, long[] AttentionMask, long[] TokenTypeIds) 
        Encode(string text, int maxLength)
    {
        // Split text into words, then apply WordPiece to each word
        var words = text.ToLowerInvariant()
            .Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
        var tokens = words.SelectMany(WordPieceTokenize).ToList();
        
        // Truncate to fit [CLS] and [SEP] tokens
        if (tokens.Count > maxLength - 2)
            tokens = tokens.Take(maxLength - 2).ToList();

        // Build input: [CLS] + tokens + [SEP] + [PAD]...
        var inputIds = new List<long> { ClsTokenId };
        inputIds.AddRange(tokens.Select(t => (long)GetTokenId(t)));
        inputIds.Add(SepTokenId);

        // Pad to maxLength
        var padCount = maxLength - inputIds.Count;
        inputIds.AddRange(Enumerable.Repeat((long)PadTokenId, padCount));

        // Attention mask: 1 for real tokens, 0 for padding
        var attentionMask = inputIds.Select(id => id != PadTokenId ? 1L : 0L).ToArray();
        
        // Token type IDs: all zeros for single sentence
        var tokenTypeIds = new long[maxLength];

        return (inputIds.ToArray(), attentionMask, tokenTypeIds);
    }

    private IEnumerable<string> WordPieceTokenize(string word)
    {
        // If the whole word is in vocabulary, return it
        if (_vocab.ContainsKey(word))
        {
            yield return word;
            yield break;
        }

        // Otherwise, split into subwords with "##" prefix
        int start = 0;
        while (start < word.Length)
        {
            int end = word.Length;
            string? curSubstr = null;

            while (start < end)
            {
                var substr = word[start..end];
                if (start > 0) substr = "##" + substr;  // Continuation marker

                if (_vocab.ContainsKey(substr))
                {
                    curSubstr = substr;
                    break;
                }
                end--;
            }

            if (curSubstr == null)
            {
                yield return "[UNK]";
                yield break;
            }

            yield return curSubstr;
            start = end;
        }
    }
}
```

Exemple de tokenisation:

| Input | Tokens |
|-------|--------|
| `"embedding"` | `["em", "##bed", "##ding"]` |
| `"DocSummarizer"` | `["doc", "##su", "##mm", "##ari", "##zer"]` |
| `"the quick brown"` | `["the", "quick", "brown"]` |

### Répartition moyenne avec masque d'attention

Après que BERT traite les tokens, on obtient un état caché pour chaque token.

```csharp
private static float[] MeanPool(Tensor<float> hiddenStates, long[] attentionMask, int hiddenSize)
{
    // Assumes last_hidden_state shape: [batch=1, seq_len, hidden_size]
    // Note: Many sentence-transformer models export a pooled output directly,
    // but we use mean pooling for consistency across all ONNX exports.
    var result = new float[hiddenSize];
    var dims = hiddenStates.Dimensions.ToArray();
    var seqLen = (int)dims[1];
    
    // Count real tokens (not padding)
    float maskSum = attentionMask.Count(x => x == 1);
    if (maskSum == 0) maskSum = 1; // Avoid division by zero

    // Average each dimension, weighted by attention mask
    for (int h = 0; h < hiddenSize; h++)
    {
        float sum = 0;
        for (int s = 0; s < seqLen; s++)
        {
            if (attentionMask[s] == 1)
                sum += hiddenStates[0, s, h];
        }
        result[h] = sum / maskSum;
    }

    // L2 normalize for cosine similarity
    float norm = MathF.Sqrt(result.Sum(x => x * x));
    if (norm > 0)
    {
        for (int i = 0; i < result.Length; i++)
            result[i] /= norm;
    }

    return result;
}
```

### La conduite d'intégration complète

Voici'le flux complet du texte à l’incorporation:

```csharp
public class OnnxEmbeddingService : IEmbeddingService, IDisposable
{
    private InferenceSession? _session;
    private BertTokenizer? _tokenizer;

    public async Task<float[]> EmbedAsync(string text, CancellationToken ct = default)
    {
        await InitializeAsync(ct);  // Downloads model if needed
        
        // Prepend instruction for models that need it (like BGE)
        if (_modelInfo.RequiresInstruction)
            text = _modelInfo.QueryInstruction + text;

        // Tokenize
        var (inputIds, attentionMask, tokenTypeIds) = 
            _tokenizer.Encode(text, _maxSequenceLength);

        // Create ONNX tensors
        var inputIdsTensor = new DenseTensor<long>(inputIds, new[] { 1, inputIds.Length });
        var attentionMaskTensor = new DenseTensor<long>(attentionMask, new[] { 1, attentionMask.Length });
        var tokenTypeIdsTensor = new DenseTensor<long>(tokenTypeIds, new[] { 1, tokenTypeIds.Length });

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

        // Run inference
        using var results = _session.Run(inputs);
        
        // Get hidden states output
        var output = results.First(r => r.Name == "last_hidden_state");
        var outputTensor = output.AsTensor<float>();

        // Mean pooling with attention mask
        return MeanPool(outputTensor, attentionMask, _modelInfo.EmbeddingDimension);
    }
}
```

## RAG: Récupération-Generation augmentée

### Le problème: Les LLMs ne peuvent't lire les documents de page M SK2

**L’approche naïve échoue:**

```csharp
var text = File.ReadAllText("500-page-manual.txt"); // 2MB of text
var summary = await llm.GenerateAsync($"Summarize: {text}"); // ❌ Doesn't fit in context
```

Même avec les fenêtres contextuelles de 128K ,, vous ne pouvez pas simplement jeter des documents énormes dans l’interface.

- **Rétroaction**: Seules les premières pages 100 correspondent à la taille de la page
- **La hallucination**: LLM invente "factsM SK2 pour combler les lacunes
- **Coût**: Traitement 2MB des coûts de texte $$$ par demande
- **Qualité**: Les LLM sont confondus avec un contexte massif.

### La solution: RAG (RetrievalM SK2Augmented Generation)

Au lieu de tout envoyer, envoyer uniquement ce qui est pertinent.

1. **Amélioration de la qualité des services**: Diviser le document en segments
2. **Intégration**: Convertir les segments en vecteurs (représentation sémantiqueM SK2
3. **Recouvrement**: Trouver les segments 10-20 les plus pertinents pour la demande
4. **Synthèse**: LLM résume seulement ces segments

**Pourquoi cela fonctionne:** Le LLM voit 10KB de contenu hautement pertinent au lieu de 2MB de texte presque irrélevé.

```mermaid
flowchart LR
    subgraph "Without RAG"
        DOC1[/"500-page PDF"/]
        LLM1["LLM<br/>(32K context)"]
        OUT1["❌ Truncated or<br/>Hallucinated"]
    end
    
    subgraph "With RAG"
        DOC2[/"500-page PDF"/]
        CHUNKS["100 Chunks"]
        VDB["Vector DB"]
        QUERY["Query"]
        TOP["Top 10 Chunks"]
        LLM2["LLM"]
        OUT2["✅ Grounded<br/>Summary"]
    end
    
    DOC1 --> LLM1 --> OUT1
    
    DOC2 --> CHUNKS --> VDB
    QUERY --> VDB --> TOP --> LLM2 --> OUT2
```

### Stratégies d'élaboration de documents

DocSummarizer appuie plusieurs stratégies de regroupement fondées sur la structure du document:

```csharp
public class DocumentChunker
{
    public List<DocumentChunk> ChunkByHeadings(string markdown, int maxHeadingLevel = 2)
    {
        var chunks = new List<DocumentChunk>();
        var lines = markdown.Split('\n');
        var currentChunk = new StringBuilder();
        var currentHeading = "";
        var headingLevel = 0;
        var order = 0;

        foreach (var line in lines)
        {
            // Detect heading (# to ######)
            var headingMatch = Regex.Match(line, @"^(#{1,6})\s+(.+)$");
            
            if (headingMatch.Success && 
                headingMatch.Groups[1].Length <= maxHeadingLevel)
            {
                // Flush current chunk
                if (currentChunk.Length > 0)
                {
                    chunks.Add(new DocumentChunk(
                        Order: order++,
                        Heading: currentHeading,
                        HeadingLevel: headingLevel,
                        Content: currentChunk.ToString().Trim(),
                        Hash: ComputeHash(currentChunk.ToString())
                    ));
                }
                
                // Start new chunk
                currentHeading = headingMatch.Groups[2].Value;
                headingLevel = headingMatch.Groups[1].Length;
                currentChunk.Clear();
            }
            else
            {
                currentChunk.AppendLine(line);
            }
        }
        
        // Don't forget the last chunk
        if (currentChunk.Length > 0)
        {
            chunks.Add(new DocumentChunk(
                Order: order,
                Heading: currentHeading,
                HeadingLevel: headingLevel,
                Content: currentChunk.ToString().Trim(),
                Hash: ComputeHash(currentChunk.ToString())
            ));
        }
        
        return chunks;
    }
}
```

### Extraction de segments pour la recherche fine-Grained

Pour les documents plus longs, DocSummarizer extrait des segments individuels (énoncésM SK2 éléments de listeMSC3 blocs de code ) avec cote de salienceMNK5

```csharp
public class SegmentExtractor
{
    public async Task<ExtractionResult> ExtractAsync(string docId, string markdown)
    {
        // 1. Parse into typed segments
        var segments = ParseToSegments(docId, markdown);
        
        // 2. Generate embeddings
        await GenerateEmbeddingsAsync(segments);
        
        // 3. Calculate document centroid (average embedding)
        var centroid = CalculateCentroid(segments);
        
        // 4. Score by salience using MMR (Maximal Marginal Relevance)
        ComputeSalienceScores(segments, centroid);
        
        return new ExtractionResult
        {
            AllSegments = segments,
            TopBySalience = segments.OrderByDescending(s => s.SalienceScore).Take(50).ToList(),
            Centroid = centroid
        };
    }
}
```

### Le problème: Les répliques de la recherche sémantique

**Sans MMR**, récupération pour " Comment fonctionne le cachement?

1. " Vue d'ensemble de la saisie de donnéesM SK1 (0.95 similitude)
2. "Introduction au caching" (0.94 similitudeM SK3
3. "Qu'est-ce qu'on cache?
4. "Details sur la mise en œuvre du cacheM SK1 (0.85 similitude)

Les résultats les plus élevés 3 disent tous la même chose. IM SK2m jeter de l’écran contextuel sur la répétitionMSC3

### La solution: Importance marginale maximale M SK1MMR)

Soldes de MMR **la pertinence** (similarité à la demande) avec **diversité** (dissimilarité par rapport à ce qui est déjà faitM SK1articles sélectionnés ).

**Formulaire:**
$$MMR = \lambda \cdot SMK4texteM SK5sim}(sMSC7query) ≥- \(1 | | 11 | 12 | 14 | 15 | max_{sM SK1 \dans le cas choisi} ≥\texteMska5simM Ska6s~Mska7 sMka8

**Ce qu'il fait:** Sanctionner les candidats semblables à ceux qui ont déjà fait l’objet d’une pénalité-segments choisisM SK1 Cela empêche que le résumé ne soit une version de ce même paragraphe 5

```mermaid
flowchart TB
    subgraph "MMR Selection"
        S1["Segment 1<br/>Score: 0.95"]
        S2["Segment 2<br/>Score: 0.90"]
        S3["Segment 3<br/>Score: 0.88"]
        S4["Segment 4<br/>Score: 0.85"]
    end
    
    subgraph "Selected"
        SEL1["✓ Seg 1<br/>(highest)"]
        SEL2["✓ Seg 3<br/>(most diverse)"]
        SEL3["✓ Seg 4"]
    end
    
    S1 -->|"Select"| SEL1
    S2 -->|"Skip - too similar to Seg 1"| X["❌"]
    S3 -->|"Select"| SEL2
    S4 -->|"Select"| SEL3
```

La formule:

$$MMR = \lambda \cdot sim (s, centroid MSк6 ♫ ♫ - | ♫ (1 \ - | \Lambda\ ) ♫_{sM SK1 \dans le cas choisi} simMSC4s≥, sMST6

```csharp
private List<Segment> SelectSentencesMMR(
    List<Segment> segments,
    float[] centroid,
    int targetCount)
{
    var selected = new List<Segment>();
    var candidates = new HashSet<Segment>(segments.Where(s => s.Embedding != null));
    
    // Pre-calculate centroid similarities
    foreach (var segment in candidates)
    {
        segment.Score = CosineSimilarity(segment.Embedding!, centroid) 
                      * segment.PositionWeight;
    }
    
    while (selected.Count < targetCount && candidates.Count > 0)
    {
        Segment? best = null;
        double bestScore = double.MinValue;
        
        foreach (var candidate in candidates)
        {
            // Relevance: similarity to centroid
            var relevance = candidate.Score;
            
            // Diversity: max similarity to already selected
            double maxSimToSelected = 0;
            foreach (var sel in selected)
            {
                var sim = CosineSimilarity(candidate.Embedding!, sel.Embedding!);
                maxSimToSelected = Math.Max(maxSimToSelected, sim);
            }
            
            // MMR score: balance relevance and diversity
            var mmrScore = _config.Lambda * relevance 
                         - (1 - _config.Lambda) * maxSimToSelected;
            
            if (mmrScore > bestScore)
            {
                bestScore = mmrScore;
                best = candidate;
            }
        }
        
        if (best != null)
        {
            selected.Add(best);
            candidates.Remove(best);
        }
    }
    
    return selected;
}
```

## Recherche hybride avec RRF

### Le problème: La recherche sémantique manque des matches exacts

J’ai rencontré ce problème lors de l’essai:

**Demande de renseignements**: "Qu'est le point final de l'API pour l'authentification?

**La recherche sémantique est retournée:**

1. "Aperçu du flux d'inscription des utilisateurs" (haute similitudeM SK3
2. "Pratiques exemplaires en matière de sécuritéM SK1 (haute similitude)
3. " Gestion des crisesM SK1 (haute similitude)

**Ce qu'il a manqué**: Le point final réel de l'API emprisonné dans des exemples de code `POST /api/v1/auth/login`

**Pourquoi?**: Les modèles d'embedding sont formés sur la langue naturelle , non code/URLsM SK3expressions exactesMSC4 Point final `POST /api/v1/auth/login` n’apparaît pas semantiquement "point final d’authentification"

### La solution: Recherche hybride M SK1Sémantique + Lexique)

Combiner deux méthodes de recherche avec des forces complémentaires:

| Type de recherche
|-------------|-----------|------------|
| **Densité (Embedding)** | Compréhension sémantiqueM SK1 Synonymes | Peut manquer des parallèles exacts
| **Pièces de rechange (BMM SK1** | Correspondance exacte des mots clés, termes rares | Aucune compréhension sémantique |

La recherche hybride combine les deux à l'aide de la réciproque fusion des rangs (RRFM SK1

```mermaid
flowchart TB
    QUERY["Query: 'authentication security'"]
    
    subgraph Dense["Dense Search (Semantic)"]
        D1["1. OAuth 2.0 implementation"]
        D2["2. User login flow"]
        D3["3. Password hashing"]
    end
    
    subgraph Sparse["BM25 Search (Lexical)"]
        S1["1. Authentication middleware"]
        S2["2. Security headers"]
        S3["3. OAuth 2.0 implementation"]
    end
    
    subgraph RRF["RRF Fusion (Illustrative)"]
        R1["OAuth 2.0 implementation<br/>RRF = 1/(60+1) + 1/(60+3) ≈ 0.032"]
        R2["Authentication middleware<br/>RRF = (not in dense) + 1/(60+1) ≈ 0.016"]
        R3["User login flow<br/>RRF = 1/(60+2) + (not in BM25) ≈ 0.016"]
    end
    
    QUERY --> Dense & Sparse
    Dense --> RRF
    Sparse --> RRF
```

**Remarque**: Les cotes de RRF indiquées sont illustrables . La constante kM SK2 est standard; le classement réel dépend du nombre total de candidatsMSC4

### Mise en œuvre du RRF

```csharp
public static class HybridRRF
{
    /// <summary>
    /// Reciprocal Rank Fusion: combine multiple rankings into one.
    /// 
    /// Formula: RRF(d) = Σ 1/(k + rank_i(d))
    /// 
    /// Where k = 60 (standard constant to prevent division by small numbers)
    /// </summary>

    public static List<Segment> Fuse(
        List<Segment> segments,
        string query,
        BM25Scorer bm25,
        int k = 60,
        int topK = 20)
    {
        // Rank by dense similarity
        var byDense = segments
            .Where(s => s.Embedding != null)
            .OrderByDescending(s => s.QuerySimilarity)
            .ToList();
        
        // Rank by BM25 (scorer is built over the same ordered segment list)
        var bm25Scores = segments
            .Select((s, i) => (segment: s, score: bm25.Score(i, query)))
            .OrderByDescending(x => x.score)
            .Select(x => x.segment)
            .ToList();
        
        // Rank by salience (pre-computed importance)
        var bySalience = segments
            .OrderByDescending(s => s.SalienceScore)
            .ToList();
        
        // Compute RRF scores
        var rrfScores = new Dictionary<Segment, double>();
        
        void AddRRFScore(List<Segment> ranking)
        {
            for (int i = 0; i < ranking.Count; i++)
            {
                var segment = ranking[i];
                var rrfContribution = 1.0 / (k + i + 1);  // 1-based rank
                
                if (!rrfScores.TryAdd(segment, rrfContribution))
                    rrfScores[segment] += rrfContribution;
            }
        }
        
        AddRRFScore(byDense);
        AddRRFScore(bm25Scores);
        AddRRFScore(bySalience);
        
        // Return top-K by fused score
        return rrfScores
            .OrderByDescending(kv => kv.Value)
            .Take(topK)
            .Select(kv => kv.Key)
            .ToList();
    }
}
```

### BM25: La main-d'oeuvre de récupération des pièces détachées

BM25 M SK1Meilleure correspondance 25) est l’algorithme classique de recherche d’information. Il combine la fréquence des termesMSC4 la fréquence inverse du documentMST5 et la normalisation de la longueur du document

```csharp
public class BM25Scorer
{
    private const double K1 = 1.5;  // Term frequency saturation
    private const double B = 0.75;  // Length normalization factor
    
    public double Score(int docIndex, string query)
    {
        var queryTerms = Tokenize(query);
        var docTermFreq = _docTermFreqs[docIndex];
        var docLength = _docLengths[docIndex];
        
        double score = 0;
        
        foreach (var term in queryTerms.Distinct())
        {
            if (!docTermFreq.TryGetValue(term, out var tf)) continue;
            if (!_docFreqs.TryGetValue(term, out var df)) continue;
            
            // IDF with smoothing
            var idf = Math.Log((_corpusSize - df + 0.5) / (df + 0.5) + 1);
            
            // BM25 TF component with length normalization
            var tfNorm = (tf * (K1 + 1)) / 
                (tf + K1 * (1 - B + B * docLength / _avgDocLength));
            
            score += idf * tfNorm;
        }
        
        return score;
    }
}
```

## TF-IDF pour la centralité du contenu

### Le problème: Comment différencier le contenu de base de Trivia?

Lorsque je résume un roman, j’ai obtenu des résultats comme :

> "Le protagoniste portait un manteau bleu . Watson a noté que le temps était léger M SK2 L’étude comportait des meubles de chênes ..."

Ces extraits sont exacts, mais ils **coloration** (sceneM SK1détails de configuration), non **points de diagramme de base**.

**Défi**: Comment établir la différence entre

- **Contenu de base**: apparaît tout au long du document
- **Détails de soutien**: apparaît dans certaines sections
- **Couleur**: RareM SK1 détails spécifiques ( ce que quelqu'un portait, description des meublesMSC4

### La solution: TF-IDF pour la classification de la centralité

TF-IDF M SK1Fréquence du terme -Frequence inverse des documents)estimations **le rôle central d'un terme dans un document**, n'est pas sa valeur réelleM SK1

**Logique:**

- **DF élevé (>50% de blocs)**: "Sherlock", ≥"WatsonM SK4 ≤"murderMSC6 → Contenu de base
- **DF moyen (20-50%)**: "Baker StreetM SK2 ≥"investigation" ≤→ Détails de soutien
- **DF faible (<20%)**: "vêtement bleu", ≥"ameublement en acierM SK4 ≤→ couleur incidentale

Il ne s’agit pas de la vérité (une revendication répétée peut être fausse , un fait rare peut être vrai *centralité du document*.

```mermaid
flowchart LR
    subgraph "TF-IDF Classification"
        CLAIM["Claim text"]
        TERMS["Extract terms"]
        TFIDF["Compute TF-IDF"]
        CLASS["Classify"]
    end
    
    subgraph "Term Types"
        COMMON["High DF (>50%)<br/>→ Core content"]
        MODERATE["Medium DF (20-50%)<br/>→ Supporting detail"]
        RARE["Low DF (<20%)<br/>→ Incidental colour"]
    end
    
    CLAIM --> TERMS --> TFIDF --> CLASS
    CLASS --> COMMON & MODERATE & RARE
```

```csharp
public class TextAnalysisService
{
    private readonly Dictionary<string, int> _documentFrequency = new();
    private int _totalDocuments;

    public void BuildTfIdfIndex(IEnumerable<string> documents)
    {
        _documentFrequency.Clear();
        _totalDocuments = 0;
        
        foreach (var doc in documents)
        {
            _totalDocuments++;
            var terms = Tokenize(doc).Distinct();
            
            foreach (var term in terms)
            {
                _documentFrequency.TryGetValue(term, out var count);
                _documentFrequency[term] = count + 1;
            }
        }
    }

    /// <summary>
    /// Classify term centrality (not epistemic truth):
    /// - High DF (>50%): appears across most chunks = core content
    /// - Medium DF (20-50%): supporting detail
    /// - Low DF (<20%): rare = likely incidental ("colour")
    /// 
    /// Note: This estimates centrality, not factuality. A repeated 
    /// claim can be false; a rare fact can be true.
    /// </summary>

    public ClaimType ClassifyTermImportance(string term)
    {
        var df = _documentFrequency.GetValueOrDefault(term.ToLowerInvariant(), 0);
        
        if (_totalDocuments == 0 || df == 0)
            return ClaimType.Colour;
        
        var documentRatio = (double)df / _totalDocuments;
        
        // High centrality = appears widely
        if (documentRatio > 0.5)
            return ClaimType.Core;
        
        // Medium centrality = supporting themes
        if (documentRatio > 0.2)
            return ClaimType.Supporting;
        
        // Low centrality = incidental detail
        return ClaimType.Colour;
    }
}
```

## Le pipeline BertRag complet

DocSummarizer's pipeline de production M SK1`BertRagSummarizer`) combine tous ces concepts

```csharp
public class BertRagSummarizer
{
    /// <summary>
    /// Full pipeline: Extract → Retrieve → Synthesize
    /// 
    /// Key properties:
    /// - LLM only at synthesis (no LLM-in-the-loop evaluation)
    /// - Deterministic extraction (reproducible, debuggable)
    /// - Validated citations (every claim traceable to source segment)
    /// - Scales to any document size
    /// - Cost-optimal (cheap CPU work first, expensive LLM last)
    /// </summary>

    public async Task<DocumentSummary> SummarizeAsync(
        string docId,
        string markdown,
        string? focusQuery = null)
    {
        // === Phase 1: Extract ===
        // Parse document → segments with embeddings + salience scores
        var extraction = await _extractor.ExtractAsync(docId, markdown);
        
        // === Phase 2: Retrieve ===
        // Hybrid search: Dense + BM25 + Salience via RRF
        var retrieved = await RetrieveAsync(extraction, focusQuery);
        
        // === Phase 3: Synthesize ===
        // LLM generates fluent summary from retrieved segments
        var summary = await SynthesizeAsync(docId, retrieved, extraction, focusQuery);
        
        return summary;
    }
}
```

## Modes d'échec communs

Lors de la construction et de l’utilisation du DocSummarizer, IM SK1 j’ai relevé ces problèmes (et vous le ferez aussi):

1. **Incompatibilité de l'identificateur → embeddings nonsens**: Déchargement d'un vocab WordPiece pour un BPE-le modèle entraîné produit des vecteurs valides -apparaissants mais semantically sans significationM SK3 Vérifier toujours si le tokenizer correspond au modèle

2. **Tendance dominante-biais topique dans le scoring de la zone centralisée**: Utiliser un document centralisé de façon systématique pour descendre - classer les sujets minoritaires

3. **BM25 remporte la recherche dense en termes rares**: Si votre demande contient un jargon technique ou des noms de famille appropriés qui ne sont pas bien représentés dans le modèle d'incorporation ' les données sur la formation M SK3 l'appariement lexique

4. **Déchets OCR dans les fichiers PDF scannés**: Docling est bon , mais les erreurs d'OCR sont combinéesM SK2 Si vous voyez des nonsens dans les résumés, vérifiez la sortie de marquage à partir du Docling d'abord.

5. **Les sommaires de couverture faibles- doivent tenir compte du langage**: Si vous ' ne voyez que les phrases suivantes dans un document :

6. **Hallucinations de citation**: Small LLMs (1.5BM SK2B params) inventer parfois des citations plausiblesMSC4sounding citationsMST5 Nous validons en analysant le résultat pour `[chunk-N]`, la vérification de l’existence d’un N dans les tronçons de source , et le marquage ou la réparation des revendications qui citent les troncs manquants . Si vous voyez `[chunk-999]` pour un document 10-chunk, votre LLM se heurte à la tâche

Il ne s’agit pas de bugs, mais de tensions inhérentes à l’espace de conception. Les bons systèmes de production les reconnaissent et les atténuent.

## Considérations pratiques

### Respect de la couverture et des échantillonnages

Lors du traitement de documents très grands, DocSummarizer n’essaie pas' d’incorporer tout - il utilise la sémantique préM SK3 le filtrage pour sélectionner les segments représentatifsMSC4 **Cela signifie que le résumé est basé sur un échantillon, et non sur le document complet.**

Le système le gère de façon transparente:

```csharp
// If coverage is low (<5%), prepend disclaimer and use cautious language
if (coverage < 0.05)
{
    var disclaimer = $"WARNING: Summary (sampled ~{coverage:P1} of document)";
    summary = $"{disclaimer}\n\n{CleanAndHedge(summary)}";
}

// Append coverage footer to every summary
var footer = $"\n\n---\nCoverage: {coverage:P1} ({scope})\nConfidence: {confidence}";
```

**Importance**: C'est un *Résumé des éléments de preuve obtenus*, n’est pas une garantie de la couverture complète des documents - Lorsque nous disons :

**L’échantillonnage n’est pas random** - it 's *sémantique*. Nous utilisons un regroupement multilatéral de points d’ancrage pour veiller à ce que les thèmes des minorités ne soient pas exclus '. Une combinaison aléatoire de points de constraint et de cases étroites pourrait manquer.

**Échantillonnage adaptatif avec plusieurs ancres thématiques**: Le filtre pré-- utilise des ancrages multiples (kM SK3 signifie-groupement de style d'un échantillon stratifiéMSC5 pour s'assurer que les thèmes minoritaires ne sont pas systématiquement exclusMNK6MMK7 Cela évite le biais du thème dominantMRK9 où un seul centroid descendMBK10se situe dans une rangée importanteM BK11maisM GK12contenu rare comme des contraintesMSBK13exceptions MSBK14 ou conclusionsSBK15

À partir de `SegmentExtractor.cs`:

```csharp
// Multi-anchor approach prevents single-centroid bias
var topicAnchors = ComputeTopicAnchors(embeddedSample, k: 5);

// Score by max similarity to ANY anchor (catches minority topics)
var score = topicAnchors.Max(anchor => CosineSimilarity(segment.Embedding, anchor));
```

Il s’agit d’une recherche -informée ( évitant l’effondrement des rappels de demande uniques- mais pratique - il fonctionne en secondes sur le CPUM SK5

**Pourquoi ne pas intégrer tout simplement?**

Pour un document de page 500- segments (2,000+ embedding everything would work but isnM SK3t optimal:

- **Coût**Les embeddings dominent le temps de fonctionnement.
- **Qualité**: Embedding everything increases noise in the retrieval pool . Vous avez encore besoin de classementM SK2 pourquoi intégrer des segments qui ne seront jamais classés hauts?
- **Réalisabilité**: Les contraintes en matière de mémoire et de latence sont importantesM SK1 Maintenir les vecteursdim dans la mémoire et le calcul 2,000 des similitudes cosine par demande est irréaliste lorsque vous n'avez besoin que du haut de la liste

L’échantillonnage multi-anchor vous permet d’obtenir le meilleur de la couverture thématique large de : et . avec une compétence facile à manipuler.

### Traçage du contexte: Pourquoi la recherche n'est-elle pas de nature à

Ce que j’ai décrit ci-dessus n’est pas seulement ', mais aussi ", la récupération, " et -, c’est un modèle spécifique que je appele **Traçage de contexte fuzzy restreint** ([CFCD](/blog/constrained-fuzzy-context-dragging)). L’incertitudeM SK1

> La plupart des résumés continuent d’ajouter le contexte. DocSummarizer *Ã©coule vers l'avant* seulement ce qui survivt à la sélection déterministe, puis permet au modèle d'écrire fréquemment à l'intérieur de ces limites.

Voici' comment le pipeline DocSummarizer se déplace vers CFCD:

| Concept de CFCD | Mise en œuvre du DocSummarizer
|--------------|------------------------------|
| **Détection de la salience** (fuzzyM SK1 | Embeddings, similarity centroidMska4 TFM Ska5IDF centrality mska6
| **Promotion déterministe** | MMRM SK1 BM25, Fusion RRFMSC3 haut de la pageMNK4Sélection MNK5
| **Anchor ledger (en anglais seulement)** | Le segment récupéré est constitué d'identifiants de citation |
| **Génération restreinte** | Prompt de synthèse limité par les preuves récupérées |

**Pourquoi cela est important?**: Le modèle ne détermine pas ce que le pipeline de récupération fait ' ce qui est pertinent

Dans la pratique, le "annuaire d'ancrage" ressemble à ce qui suit :

```json
{
  "coverage": "3.2% semantic sample",
  "anchors": [
    { "id": "chunk-12", "text": "Reset requires holding button 10s", "salience": 0.92 },
    { "id": "chunk-45", "text": "Factory reset clears all settings", "salience": 0.88 }
  ],
  "constraints": {
    "terms": { "factory reset": "restore factory settings" },
    "hedging": "sampled 3% - avoid definitive conclusions"
  }
}
```

Ensuite, dans le prompt de synthèse:

- "Toutes les revendications doivent indiquer une pièce d’identité incluse.
- "Si la couverture est assurée < 5%, langue de couvertureM SK3
- "Usez ces termes de façon cohérente

C’est pourquoi:

- **Les fenêtres de contexte plus longues sont un herring rouge** - le problème n’est pas 't fitting more text, itM SK3s deciding what deserves to survive
- **Résumé récursif :** - il traite la sortie intermédiaire comme un texte
- **Le LLM peut't "forgetM SK2 promouvoir les contraintes** - ils 're structure, non prose il peut déborder de

La CFCD est la même division philosophique que [Fuzziness restreinte](/blog/constrained-fuzziness-pattern), [MoM Fuzzy restreint](/blog/constrained-mom-mixture-of-models), et [Sommaire de l'image](/blog/constrained-fuzzy-image-intelligence) - la probabilité proposeM SK1 le déterminisme persiste - appliqué tout au long du temps

### Quantisation

Les modèles ONNX peuvent être quantifiés (précision réduite ) pour une taille plus petite et une inference plus rapideM SK2 Le commerce-off est une perte minimale de qualitéMSC4

| Modèle | PrÃ©cision totale | QuantitÃ© | Différence en matiÃ ̈re de qualitÃ©
|-------|---------------|-----------|-------------------|
|tous-MiniLMM SK2LMST3vMSC4 | ≥90MB | ≤23MB
| bge -smallM SK2en-vMSC4 | | | 133MB |

### Traitement par lots et compétition

Pour les grands documents, l'incorporation de lots est cruciale pour la performance `InferenceSession` peuvent généralement être partagés en toute sécurité entre les threads, mais le rendement dépend de la configuration de session:

```csharp
public async Task<float[][]> EmbedBatchAsync(IEnumerable<string> texts, CancellationToken ct)
{
    var textList = texts.ToList();
    var results = new float[textList.Count][];
    
    // InferenceSession is safe to share for inference in most cases
    // Tune SessionOptions.IntraOpNumThreads and InterOpNumThreads for your workload
    var maxParallel = Math.Min(Environment.ProcessorCount, 8);
    
    await Parallel.ForEachAsync(
        textList.Select((text, index) => (text, index)),
        new ParallelOptions { MaxDegreeOfParallelism = maxParallel },
        async (item, token) =>
        {
            results[item.index] = await EmbedSingleAsync(item.text, token);
        });
    
    return results;
}
```

**Conseils sur la performance**: Configurer `SessionOptions` lors de la création de la session:

```csharp
var sessionOptions = new SessionOptions
{
    IntraOpNumThreads = 4,  // Threads within a single operation
    InterOpNumThreads = 2   // Threads across operations
};
var session = new InferenceSession(modelPath, sessionOptions);
```

### Gestion de la mémoire pour les grands documents

Documents très volumineux (novels,documents juridiquesM SK2 nécessitent une manipulation spéciale pour éviter les erreurs de saisie.

```csharp
// For documents > MaxSegmentsToEmbed, use hierarchical extraction
if (segments.Count > _config.MaxSegmentsToEmbed)
{
    // Process in batches, keeping only top-K per batch
    // Then re-rank globally
    return await ExtractHierarchicalAsync(segments);
}
```

## Caractéristiques du rendement

Rendement réel-world sur une machine de développement typique M SK1Ryzen 5600X, MSC4GB RAMMSC5 aucun GPUMST6

| Opération | Débit d'alimentation MSC2 Notes MSC3
|-----------|-----------|-------|
| **Intégration** | ~150 segmentsM SK2sec
| **Récupération dense** | <10ms | La similitude des cosine sur les segments ≥500
| **BM25 cote** Indice inversé de mémoire |
| **Fusion RRF** | <2ms MSC2 Combiner SMC3 rangées SMC4
| **Page PDF )** | ~15-20s | Comprend le chunking, l'embeddingM SK4 la récupérationMSC5 la synthèse de LLM

**Environnement d'essai**: Ryzen 5600X (6-core), M32GB RAMM SK5 pas GPUMSC6 Embedding utilise tous les types de supports ≥-MiniLMMska8LMka9vM Ska10 |Mska11quantiséM ska12 \Mska13 token maxMSKA14 ♫Mska15thread parallel batchingMskar16 Retrieval corpusMkar17 Mskar18 segmentsMKar19 Votre distance varie selon les différents modèlesMsar20 matérielM Kar21 et complexité des documentsMzar22

**Le conducteur principal intègre le débit** (choix de modèle +longueur du token + taille du lot ). La récupération et la fusion sont essentiellement gratuites SMC4 elles prennent des millisecondes SMC5 Ce qui renforce le principe S"LLM dernier S MC7 MSC8 fonctionnent sur un CPU bon marché ESC9embeddingS MC10 recupérationS EC11 première ESC12 les LLM coûteux ne fonctionnent que sur le contenu filtré S EC13

**Étalonnage**: L'extraction hiérarchique gère les documents de page 500+

## Résumé

DocSummarizer démontre que les capacités de NLP sophistiquées ne nécessitent pas des API cloud ou des dépendances Python. Avec ONNX Runtime pour les embeddings et Ollama pour la génération, vous pouvez construire un pipeline RAG complet qui

- Fonctionne entièrement localement
- Produit des résumés cités, traçables
- Appuyer les documents de toute taille
- Travaille en mode offline

Les principales conclusions tirées de l’élaboration de cet outil:

1. **Les emplacements sont la base** - Une bonne récupération dépend de bonnes embeddings
2. **La recherche hybride bat soit seule** - Combiner sémantique et lexique pour la robustesse
3. **MMR empêche la répétition** - La diversité est aussi importante que la pertinence
4. **Questions relatives à la structure** - Compte tenu de la structure du document  positions
5. **Les LLM devraient être le dernier.** - Travaillez d’abord sur un CPU bon marché, Les LLM coûteux ne fonctionnent que sur des contenus filtrés

## Autres lectures

### Documents et références techniques

- [Document BERT](https://arxiv.org/abs/1810.04805) - Encoder original du transformateur
- [Sentence-BERT](https://arxiv.org/abs/1908.10084) - BERT pour les emplacements de phrases
- [BM25 Expliqué](https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables) - Algorithme de recherche classique
- [Papier RRF](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) - Fusion de rang réciproque
- [Le temps de fonctionnement d'ONNX](https://onnxruntime.ai/) - Inférence ML de la plate-forme

### Diversifications profondes connexes

- [Comment fonctionne la traduction en machine neuronale](/blog/how-neural-machine-translation-works) - Couvre l’architecture des transformateursM SK1 les mécanismes d’attention , et les embeddings du point de vue de la traduction

## Établissement de la série

Ceci conclut la série DocSummarizer. VoiciM SK1 comment les trois parties s’intègrent ensemble :

**[Partie 1](/blog/building-a-document-summarizer-with-rag)** Il y a lieu de souligner que, dans le cadre du processus d’élaboration et de mise en oeuvre des programmes, il est important de veiller à ce qu’ils soient conformes aux normes établies par les États membres. *pourquoi* L’approche du pipeline remporte les appels naïfs de l’ALM.

**[Partie 2](/blog/docsummarizer-tool)** est votre guide de démarrage rapide- . Installation, modesM SK3 modèlesMSC4 cas d'utilisation courantesMST5 Si vous voulez simplement utiliser l'outilMSL6 queMSR7 c'est tout ce dont vous avez besoinM SR8

**Partie 3** (le présent article ) est la plongée profonde pour les gens qui veulent comprendre *comment* il fonctionne en fait: BERT vs transformateurs de sentences , pourquoi ONNX est important, tokenisation gotchasM SK3 commerce de recherche hybrideMSC4offsMST5 et ce qui se débrouille dans la productionMSP6

Si vous construisez votre propre pipeline, lisez les trois sections suivantes :

## Connexion

- [Partie 5 - DoomSummarizer: Recherche approfondie](/blog/doomsummarizer-deep-research) - Faire avancer ces concepts: 6-Vecteurs de profil d'entités avec RRF adaptatif du signalM SK3vectores de profil des entités avec découverte HNSWMSC4 Compression de TextRankMST5 et génération parallèle longue constrÃ©mentÃ©eMSP6form
- [Analyse CSV avec les LLM locaux](/blog/analysing-large-csv-files-with-local-llms)
- [Extraire et analyser le contenu Web avec les LLM](/blog/fetching-and-analysing-web-content-with-llms)