# DocSummarizer Part 3 - Concepti avanzati: Il " Io sono andato troppo lontano 🤦" Immersione profonda

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

## Introduzione

Questo è **Parte 3** della serie DocSummarizer:

1. **[Parte 1: Costruire un Summarizer di Documento con RAG](/blog/building-a-document-summarizer-with-rag)** - L'architettura e il motivo per cui l'approccio del pipeline supera le chiamate naive di LLM
2. **[Parte 2: Utilizzando l'outil](/blog/docsummarizer-tool)** - Quick-Guide di partenza per il CLI
3. **Parte 3: Concepti avanzati** (Questo articoloM SK1 - Il SSK3 Sono andato troppo lontano" immergermi in profondità tecnica
4. **[Parte 4: Costruire i Pipeline RAG](/blog/docsummarizer-rag-pipeline)** - Usate la biblioteca NuGet per costruire le vostre app RAG

---


Questo fa parte del mio "Time-Tools boxedM SK2 approccio : darmi una finestra fissa per costruire qualcosa di funzionaleMSC4 forza le decisioni e produce il codice di lavoro piuttosto che i disegni teorici

DocSummarizer è iniziato come una dimostrazione di come si *Dovrebbe* costruire sommatori di documenti con LLM - l'approccio del pipeline che ho descritto nella parte 1. La maggior parte dei tutorial vi mostra come gettare il testo in un LLM e sperare nel meglio. Volevo mostrarvi la giusta architetturaM SK3 chunkingMSC4 embeddingsMST5 retrievalM ST6 citation validationMst7

Ma come faccio sempre, Mi sono interessato all'area del problema. Quattro giorni dopoM SK2 Io ' ho approfondito la ricercaMSC4 Come si fa a produrreMST5 i sistemi di classe realmente gestiscono la sommificazioneM ST6 Cosa fa funzionare bene il rilevamentoM st7 Perché alcune inserzioni superano le altreMst8

Ho implementato versioni di questi approcci. Quello che cominciò come "quell'oggi 'è il modello giustoM SK3 è diventato un embedding ONNX che funziona localmenteMSC4 la ricerca ibrida combinando BMMST5 con la ricerca densaMSSK6 la rilevanza marginale maximale per la diversitàMSL7 e la fusione dei gradi reciproci per combinare i segnaliMSR8

**Avvertimento equo**: Questo è il " Sono andato troppo lontano" immergermi in profonditàM SK3 Se volete solo usare l'outilamentoMSC4 leggere la parte 2. se volete capire *Perché?* Funziona. *Come?* i pezzi vanno insieme, continuate a leggere.

Questo articolo riguarda:

- **Inserzioni Sentenziali**: Come i modelli di trasformatori (MiniLM/BGEM SK3GTEMSC4 trasformano il testo in vettori
- **Il tempo di esecuzione di ONNX**: Running ML models locally without Python or cloud APIs
- **RAG (Retrival-Generazione aumentataM SK2**: Emissione di LLM in materiale di base
- **La ricerca ibrida**: La combinazione di ritrievo semantico e lexico con RRF

[TOC]

## L'architettura in un'occhiata

Prima di approfondire i dettagli, qui's come le parti si adattano insiemeM 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
```

## Capire l'incoraggiamento

### Il problema: Come si trova il contenuto rilevante senza parole chiave?

Quando si riassume un manuale di pagina 500-, bisogna trovare le sezioni rilevantiM SK2 La ricerca con la parola chiave tradizionale fallisce :

- L'utente chiede "Come ristabilire il dispositivo?"
- Il manuale dice "Rristinare le configurazioni fabbriche..."
- La ricerca Keyword la perde ( Nessuna parola condivisa)

Avete bisogno. **ricerca semantica** - corrispondenza per significato, non solo paroleM SK2

### Cosa sono gli imbeddings?

Gli inseritori lo risolvono trasformando il testo in vettori densi (arrays of numbers) that capture semantic meaningM SK2 Similar meanings = similar vectorsMNK4 regardless of exact wordingMSC5

Qui' è l'intuizioneM SK1 immaginate uno spazio 384-dimensionale in cui ogni pezzo di testo ha una posizione . Texte con significati simili si raggruppano insiemeMSC4

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

### Perché i Transformer di Sentenza (Non BERT Raw)?

**Il problema.**: Ho bisogno di inserzioni che funzionino per la semantice somiglianza. Il BERT grezzo è stato progettato per le compiti di classificazione , non per la ricerca di somiglianzeM SK3

**Soluzione**: Usare **Transformatori di frase** I modelli - sono specialmente addestrati a compiti di somiglianza usando l'apprendimento contrastivo. [L'architettura BERT](https://arxiv.org/abs/1810.04805) Ma bene-fondata diversamente.

modelli come questo. `all-MiniLM-L6-v2` e `bge-small-en-v1.5` Sono stati addestrati a miliardi di coppie di testo come :.

- "Come ristabilire l'apparecchioM SK1 ↔ \"Rristinare le configurazioni fabbriche"
- "Come resettare l'apparecchioM SK1 ↔ | | "Specificazioni del prodotto" \(dissimilariMSC6

La formazione li insegna significazioni simili = vettori vicini (similarità di cosine elevate

> **Related**: Se volete capire come funzionano i modelli di trasformatori a un livello più profondo - compresi i meccanismi dell'attenzione, encoderM SK3 architettura decoderiMSC4 e perché funzionano gli embeddings ♫- guarda il mio articolo su [Come funziona la traduzione della macchina neurale](/blog/how-neural-machine-translation-works). Riprende gli stessi concetti di trasformatori dal punto di vista della traduzione

**Implementazione**: Prendiamo la strata di output del modello ' e applichiamo. **pooling medio** - mediando l'inserzione di tutti i tocchi per ottenere un singolo vettore per tutto il testo

```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: Gestire modelli ML localmente

### Il problema: Python Dependency Hell

Volevo inserirmi in "poco funzionare" quando qualcuno gestisce l'outil . L'approccio standardM SK3

1. Installare i trasformatori Python + PyTorch +
2. scaricare i modelli manualmente
3. Spero che i conflitti tra le versioni non rovinino tutto.

Questo fa schifo. Gli utenti vogliono `docsummarizer -f doc.pdf`, non è un manuale di montaggio 30-

### Perché ONNX?

ONNX (Open Neural Network Exchange) è un formato aperto per i modelli ML . La funzione killerM SK3 **inferenza del runtime senza Python**.

**Cosa ottengo con ONNX Runtime:**

1. **Zero dipendenze esterne** - No PythonM SK1 PyTorch, I driver CUDA
2. **Auto-download models** - Fa partire prima i download da HuggingFace (~23-34MB), e poi viene archiviato.
3. **Pure .NET** - Funziona ovunque.
4. **L'inferenza CPU** - No GPU required, funziona su hardware economico

Commercio-offM SK1 leggermente più lento di GPU PyTorch, ma molto più veloce che chiedere agli utenti di installare PythonMSC3

### Il modello di registro

DocSummarizer include diversi modelli di inserzione, ciascuno con un commercio diversoM SK1offs:

| Modello | Dimensioni | | | Max Tokens || | Su misura | |  | Quantificato | МSK5 | Mistruazione | Use Case | Mezzo | Prevede istruzioni |
|-------|-----------|------------|------------------|----------|---------------------|
| `AllMiniLmL6V2` |
| `BgeSmallEnV15` |
| `GteSmall` |
| `MultiQaMiniLm` | 384 ≤| ≥512 ±| \~23MB | | | Optimizzato per Q&A || No |

**Nota.**: Tutte le registrazioni indicano WordPiece-export ONNX compatibili (uso `vocab.txt`). BPE/I modelli unigrammi non sono ancora supportati

**Formato dell'istruzione BGE**: Alcuni modelli ( come BGE) richiedono prefixi per una performance ottimaleM SK3 Il formato exacto dipende dal modelloMSC4

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

Il registro traccia quali modelli hanno bisogno di istruzioni tramite `RequiresInstruction` e `QueryInstruction` campi. Always benchmark retrieval quality when working with instruction

Ecco come funziona il registro del modello'

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

### Tokenizzazione: WordPiece vs BPE

Diversi modelli usano diversi tokenizzatori. `all-MiniLM-L6-v2` Il modello usa la tokenizzazione WordPiece (come BERT), che divide parole sconosciute in tokens di sotto parolaM SK2 Altri modelli possono usare BPE (ByteMSC4Pair EncodingMska5 o Unigram tokenizersMske6

**Importante**: I tokenizzatori del modello devono corrispondere al tokenizzatore di allenamentoM SK1 Il nostro registro traccia quale tokenizzazione sia necessaria per ogni modello. **: WordPiece** (via `vocab.txt`). BPE/Support per l'unigramma tramite `tokenizer.json` è pianificato ma non ancora implementato - rimanere con i modelli WordPiece nel registro per ora

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

Esempio di tokenizzazione:

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

### Mean Pooling con la maschera dell'attenzione

Dopo che BERT processa i tocchi, otteniamo uno stato nascosto per ogni tocco. Medie pooling medio di questiM SK2 ma solo su dei tocchi reali (no paddingMSC4

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

### Il Pipeline dell'Embedding Complete

Qui' è il flusso completo dal testo all'inserzione:

```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: Retrieval-Generazione aumentata

### Il problema: I LLM non sanno' leggere M SK2Documenti di pagina

**L'approccio naïvo fallisce:**

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

Anche con 128K context windows, non si possono trascinare enormi documenti in :.

- **Truncation**: Solo le prime pagine 100 vanno bene, restano ignorate.
- **Hallucinazione**: LLM inventa "facts" per riempire le lacune
- **Costo**: Traduzione 2MB di costi di testo $$$ per ogni domanda
- **Calità**: I LLM si confondono con un contesto massiccio ("loss in the middle

### La soluzione: RAG M SK1Retrieval-Augmented GenerationMSC3

Invece di mandare tutto, mandare solo quello che è rilevante'M SK2

1. **Laggiù.**: Dividere il documento in segmenti
2. **Incoraggiamento**: convertire segmenti in vettori (representazione semantica)
3. **Rilasciazione**: Trovare i segmenti più rilevanti per la domanda.
4. **Sintesi**: LLM riassume solo questi segmenti.

**Perché funziona:** Il LLM vede 10KB di contenuto altamente rilevante invece che 2MB di testo più o meno irrilevante.

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

### Strategie di documentazione

DocSummarizer supporta strategie di blocco multiple basate sulla struttura del 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;
    }
}
```

### Extrazione di segmenti per Fine-Grained Retrieval

Per i documenti più lunghi, DocSummarizer extrae segmenti individuali (sentenzeM SK2 elementi della listaMSC3 blocchi di codici ) con il punteggio di salienzaMNK5

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

### Il problema: La ricerca semantica riporta duplicati

**Senza MMR**, ricava per "Come funziona la cachingM SK2 ha risposto:

1. "Overview di archiviazioneM SK1 (0.95 somiglianza)
2. "Introduction to cachingM SK1 (0.94 similarity)
3. "Cosa è il cachingM SK1 (0.93 somiglianza)
4. "Details di implementazione del CacheM SK1 (0.85 somiglianza)

I risultati in alto 3 dicono tutti la stessa cosa. IM SK2m sprecare la finestra del contesto sulla ripetizioneMSC3

### La soluzione: Rilevantezza Marginale máxima (MMRM SK2

Balance del MMR **Rilevante** (similarità alla domanda) con **diversità.** (dissimilarità a quella già esistente-elementi selezionatiM SK2

**Formula:**
$$MMR = \lambda S\cdot SSK4textM SK5sim}(sMSC7questionoMNK8 \- |(1 | | - |_{sM SK1 \in seletto}

**Cosa fa:** Penalizza candidati simili a quelli già esistiti-sezioni selezionateM SK1 Questo impedisce che il sommesso sia 5 versione dello stesso paragrafo .

```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 formula:

$$MMR = \lambda S\cdot simM SK4s, centroideMSC6 \- | | (1 |_{sM SK1 \in seletto} simMSC4s+, s+')$$

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

## La ricerca ibrida con RRF

### Il problema: La ricerca semantica non ha corrispondenze precise

Mi sono imbattuto in questo test.:

**La domanda**: "Qual è il punto di fine dell'API per l'autentizzazione?"

**La ricerca semantica è tornata:**

1. "Overview del flusso di login degli utentiM SK1 (alta somiglianza)
2. "Le migliori pratiche di sicurezza" (alta somiglianzaM SK3
3. "Management delle sessioniM SK1 (alta somiglianza)

**Quello che manca.**: L'intero punto di fine dell'API nascosto in esempi di codice: `POST /api/v1/auth/login`

**Perché?**: I modelli di embedding sono addestrati sulla lingua naturale , non il codiceM SK2 le URL/ i termini precisiMSC4 Il punto finale `POST /api/v1/auth/login` non corrisponde semanticamente "estremità di autenticazione" - è una riferimento tecnica letteraleM SK5

### La soluzione: Ricerca ibrida M SK1Semantice + Lexica)

Combinare due metodi di ricerca con forze complementari:

| Tipo di ricerca | Le forze | Le debolezze |
|-------------|-----------|------------|
| **Denso (Embedding)** | comprensione semanticaM SK1 sinonimi | Può perdere le coincidenze precise, termini rari |
| **Sparse (BMM SK1** | Correspondenza accurata delle parole chiave, termini rari | Nessuna comprensione semantica MSC3

La ricerca ibrida combina l'uso di una combinazione reciproca di classifica (RRF):

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

**Nota.**: I punteggi RRF mostrati sono illustrativiM SK1 La costante k =60 è standard; il ranking effettivo dipende dal gruppo completo di candidatiMSC4

### Implementazione del 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: Il Cavallo di Ricerca di Sparse

BM25 M SK1Best Matching 25) è l'algoritmo classico di ricerca dell'informazione. Combina la frequenza del termineMSC4 la frequenza inversa del documentoMST5 e la normalizzazione della lunghezza del documentoMst6

```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 per la centralità del contenuto

### Il problema: Come distinguere il contenuto principale da Trivia?

Quando riassumo un romanzo, ho ottenuto risultati come:

> "Il protagonista indossava un cappotto blu . Watson ha notato che il tempo era leggero. L'esperimento aveva mobili di legno di cime

Queste sono estraziones accurate, ma quelle sono accurate. **Colore** (sceneM SK1detail di configurazione), non **punti di plot core**.

**La sfida.**: Come si fa a capire la differenza tra

- **Il contenuto principale**: appari nell'intero documento ( nomi di caratteriM SK2 temi principali, eventi chiaveMSC4
- **I dettagli di supporto**: Si presenta in alcune sezioni (subplots
- **Coloro**: RareM SK1 dettagli specifici ( cosa stava indossando qualcuno, descrizioni di mobiliMSC4

### La soluzione: TF-IDF per la classificazione della centralità

TF-IDF M SK1Frequenza di termine - Frequenza inversa del documento) stime **Quanto è centrale un termine per il documento?**, non è il suo valore realeM SK1

**Logica:**

- **DF alto (>50% di blocchi)**:  "Sherlock",
- **DF medio (20-50%)**:  "Baker Street",
- **Low DF (<20%)**: "coperta blu", | | "arredamento di cigliaM SK4 \→ colore casuale

Non si tratta di verità. (un'affermazione ripetuta può essere falsa, un fatto raro può essere veroM SK2 Si tratta di *La centralità del documento.*.

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

## L'intera BertRag Pipeline

DocSummarizer'prodotto di produzione M SK1`BertRagSummarizer`) combina tutti questi concetti

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

## Mode di fallimento comuni

Quando costruiamo e usiamo DocSummarizer, IoM SK1 ho avuto questi problemi (e anche voi lo avrete fatto ):

1. **Non corrisponde al tokenizzatore → inserzioni senza senso**: Lavorare un vocab WordPiece per un BPE-Il modello addestrato produce validiM SK2vectori che sembrano ma semanticamente senza significato .Verificare sempre se il tokenizzatore corrisponde al modelloMSC4il regime di allenamentoMNK5

2. **Pregiudizio dominante-topico nel punteggio di una sola personaM SK1centroide**: Utilizzando un documento centroide sistematicamente in bassoM SK1 classifica argomenti minoritari ( restrizioni M SK3 eccezioni, casi a bordoMSC5 MultiMST6 il riscontro con le ancorhe lo ripara ma aggiunge complessitàMSSK7

3. **BM25 batte la ricerca densa su termini rari**: Se la vostra domanda contiene un vocabolario tecnico o nomi connessi non bene-representato nel modello di inserzioneM SK2 i dati di allenamento , il corrispondenzamento lexico | ( | BM |25) | supererà la ricerca semantica | МSK6 | Questo è il motivo per cui la ricerca ibrida è importante

4. **La spazzatura in formato OCR in PDF scansionati**: Il Docling è buono, ma gli errori di OCR si compongonoM SK2 Se vedete il nonsenso nei sommimenti , controllate prima l'emissione di demarcazione dal Docling.

5. **Low-summari di copertura devono proteggere il linguaggio**: Se ' vedete solo 3% di un documento, frasi come M"ultimamenteM SK5 o m"in conclusioneMSC7 sono dishonesteMNK8 Il sistema deve dire R"in sezioni campionateMRK10 e evitare le fine definitiveMMK11

6. **Allucinazione di citazioni**: Small LLMs (1.5BM SK2B params) qualche volta inventare plausibiliMSC4sounding citationsMST5 Validiamo analizzando il risultato per `[chunk-N]`, la verifica dell'esistenza di N nelle particelle della fonte, e la marcazione o la riparazione delle affermazioni che citano particelli mancanteM SK2 Se vedete `[chunk-999]` per un documento 10-chunk, il vostro LLM sta lottando con la missione

Questi non sono errori, ma tensioni intrinsequenti nell'area di progettazione. I buoni sistemi di produzione li riconoscono e li attenuano.

## Considerazioni pratiche

### La copertura e l'onestà dei campioni

Quando si tratta di documenti molto grandi, DocSummarizer non prova a inserire tutto'non cerca di inserirlo M SK2 usa pre-semanticiMSC3filtrazione per selezionare segmenti rappresentativi . **Questo significa che il riassunto è basato su un campione, non sul documento completo.**

Il sistema lo gestisce in modo trasparente:

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

**Importante**: Questo è un *riassunto delle prove raccolte*, non è una garanzione della copertura completa del documento-When we say "sampled

**L'improduzione non è casuale.** - itM SK1s *semantico.*. Usiamo un multi-clustering -anchor clustering per assicurare che i temi minoritari non siano exclusiM SK2t excluded. Un caso casuale 3% potrebbe perdere tutte le restrizioni e i casi di bordoMSC5 Un semantico S3% cerca di catturare un segmento rappresentativo da ogni tema principaleMNK7 E' ancora una copertura parzialeMMK9 ma è una copertura intenzionalmente diversificata parzialmente.

**Esemplare adattabile con più ancor di tema.**: Il pre--filtro usa un'ancora multiple (k-meansM SK4classificazione stilistica di un campione stratificatoMSC5 per assicurare che i temi minoritari non siano exclusi sistematicamenteMNK6MMK7 Questo impedisce la "biezza dominante del temaMRK9 dove una singola centroide si abbassaMBK10sesponde a una classifica importanteMGK11maMDK12contento raro come restrizioniMKB13esposizioniMKK14 o conclusioniMZK15

Da. `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));
```

Questa è una ricerca-informata (avviare un singolo erroreM SK2rimorchiare il richiamo della domandaMSC3 ma pratica - funziona in secondi sul CPUMST5

**Perché non inserire tutto?**

Per un documento di pagina 500- segmenti (2,000+ M SK2 inserire tutto funzionerebbe, ma non è sufficiente.

- **Costo**: OM SK1N) l'inserzione domina il tempo di lavoro . In segmenti ~150 /sec, quelloMSC7s \13+ secondi solo per l'Inserzione prima ancora di iniziare la ricerca
- **Calità**: L'inserzione di tutto aumenta il rumore nel pool di ricavamentoM SK1 Ci vuole ancora un ranking, quindi perché inserire segmenti che non andranno mai classificati in alto?
- **Praticabilità**: Le restrizioni di memoria e di latenza sono importantiM SK1 Tenere 2,000 \384- vettori di dime nella memoria e nel calcolo |2,000 le somiglianze di cosine per ogni domanda è spreco se si ha bisogno solo del top.

La multi-sampling -anchor ti dà il meglio di entrambi gli ambiti : con un calcolo ridutabile

### Dragging Contexto: Perché la ricerca non è ' Non basta cercare

Quello che ho descritto prima è ' non è solo ", ma anche "retrieval", "M SK3", -, ma è un modello specifico che io chiamo. **Dragging Contexto Fuzzy Constrained** ([CFCD](/blog/constrained-fuzzy-context-dragging)). L'intuizione:

> La maggior parte dei somministratori continua a aggiungere un contesto. DocSummarizer *spinge avanti.* Solo quello che sopravvive alla selezione deterministica, poi permette al modello di scrivere fluidamente all'interno di quei confini.

Ecco come il pipeline DocSummarizer mappare al CFCD:

| Concepto di CFCD | Implementazione del DocSummarizer |
|--------------|------------------------------|
| **Detezione della saliva** (fuzzyM SK1 | Embeddings, Centroid similarityMska4 TFM Ska5IDF centrality Ska6
| **Promozione deterministica** | MMRM SK1 BM25, Fusione RRFMSC3 sopraMST4 Selezione K MST5
| **Anchor ledger** | Il segmento recuperato con ID di citazione |
| **Generazione limitata** | Prompto di sintesi limitato dalle prove recuperate |

**Perché questo è importante?**: Il modello non decide ' non decide cosa sia rilevante | ' | - | il tubo di ricava |. | Il modello genera fluidamente solo all'interno dei confini che abbiamo stabilito | L'ancora fa il sollevamento pesante | E questo è il motivo per cui i piccoli modelli locali funzionano | l'ancraggio fa la sollevazione pesante

In pratica, il ledger "anchor1 sembra questo.

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

Poi nella sequenza di sintesi :

- "Tutta la richiesta deve citare un ID di blocco incluso"
- "Se c'è copertura
- "Usi questi termini in modo consistenteM SK1

Ecco perché:

- **Le finestre del contesto più lunghe sono un cerotto rosso.** - il problema è ' non si adatta più a un testo , decide cosa merita di sopravvivere
- **La sommificazione recursiva fallisce.** - tratta il risultato intermedio come un testoM SK1 non struttura limitata
- **Il LLM può't "forgetM SK2 promuovere le restrizioni.** - loro ' la struttura, non è una prose che può scorrere da.

La CFCD è la stessa divisione filosofica di [Fuzzinesse limitata](/blog/constrained-fuzziness-pattern), [MoM Fuzzy Constretto](/blog/constrained-mom-mixture-of-models), e [Summarizatore dell'immagine](/blog/constrained-fuzzy-image-intelligence) - la probabilità proponeM SK1 il determinismo persiste - applicato lungo il tempo/ l'asse della memoriaMSC4

### Quantizzazione

I modelli ONNX possono essere quantificati ( precisione ridotta) per una dimensione più piccola e un'inferenza più veloceM SK2 Il commercio - è una perdita di qualità minimaMSC4

| Modello | Presizione completa | | | Quantizzato || | Differenza di qualità |
|-------|---------------|-----------|-------------------|
| tuttiM SK1MiniLM-LMST3vMSSK4 | \90MB | | |
| bge -smallM SK2en-vMSC4 | | | 133MB |

### Trattura in serie e competizione

Per i grandi documenti, l'inserzione di lotti è cruciale per la performance `InferenceSession` in genere può essere condiviso in sicurezza attraverso i thread, ma il risultato dipende dalla configurazione della sessione:

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

**Guidanza sulla performance**: Configure `SessionOptions` quando si crea la sessione:

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

### Gestione della memoria per grandi documenti

Documenti molto grandi (novels,documenti legaliM SK2 richiedono manodopera speciale per evitare l'uso OOM

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

## Carattéristiche di performance

Rendimento reale-world su una tipica macchina per sviluppatori M SK1Ryzen 5600X, 32GB RAMMSC5 nessun GPUMST6

| Operazione | Produtto | Note M|
|-----------|-----------|-------|
| **Incoraggiamento** | ~150 segmenti/sec | dimensione di lotto SSM4 tuttiM SK5MiniLMMST6LMSS7vMSSS8 quantificata SMSS9
| **Ricerca densa** |
| **BM25 punteggi** |
| **Fusione RRF** |
| **Conclusione-to-estremità (25-strona PDFM SK3** |

**L'ambiente di prova**: Ryzen 5600X (6-core), M32GB RAMM SK5 nessun GPUMSC6 L'Embedding usa tuttoMSSK7MiniLMMST8LMSV9vMS2MSL11quantificatoMSR12 MSR13 token maxMRS14MRSS15thread parallel batching\MRS16\Retrieval corpus\MSR17\MRR18\segmenti\MRRS19\ La vostra velocità varierà con i diversi modelli\MRSS20\hardware\MSSR21\ e complessità del documento\MRSA22\

**Il guidatore principale sta inserendo il throughput.** ( scelta di modello + lunghezza del tocco + dimensione del lotto ). La ricerca e la fusione sono sostanzialmente gratuite S- ci vogliono millisecondi MESK5 Questo rafforza il principio M"LLM ultima R" I: funzionano in CPU a basso costo P(inserzione A, ricerca E) prima U, costoso LLM funziona solo su contenuti filtrati H.

**Scalare**: L'estrazione gerarchica gestisce i documenti di pagina 500+(novelsM SK3struzioni manualiMSC4 processando in partite e mantenendo solo la parte superioreMska5K per partita nella memoriaMske6

## Summary

DocSummarizer dimostra che sofisticate capacità NLP non richiedono API cloud o dipendenze Python.

- Funziona interamente localmente
- Produce sommità trasparenti, citate
- Gestisce i documenti di qualsiasi dimensione.
- Funziona offline

Le idee chiave per costruire questo strumento:

1. **Gli inserimenti sono la base.** - Una buona ricerca dipende dalle buone inserzioni.
2. **La ricerca ibrida batte solo.** - Combinare semantica e lexica per la robustezza
3. **Il MMR impedisce la ripetizione** - La diversità è importante quanto la rilevanza
4. **Questioni di struttura** - Respecto alla struttura del documento (headingsM SK2 sections) migliora i risultati
5. **Le LLM dovrebbero essere l'ultima.** - Fate prima il lavoro del CPU economico, Il lavoro del LLM costoso solo sul contenuto filtrato.

## Seguire la lettura

### Documenti e riferimenti tecnici

- [BERT Paper](https://arxiv.org/abs/1810.04805) - Il codificatore originale del trasformatore
- [Sentenza-BERT](https://arxiv.org/abs/1908.10084) - BERT per inserzioni di frase
- [BM25 spiegato](https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables) - L'algoritmo classico di ricerca
- [Papiero RRF](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) - Fusione di posizione reciproca
- [Il tempo di esecuzione di ONNX](https://onnxruntime.ai/) - Cross-inferenza ML per la piattaforma

### Di immersioni profonde associate

- [Come funziona la traduzione della macchina neurale](/blog/how-neural-machine-translation-works) - copre l'architettura dei trasformatoriM SK1 i meccanismi di attenzione , e gli inserimenti dal punto di vista della traduzione.

## Costruire la serie

Questo conclude la serie DocSummarizer. Qui'è come le tre parti si adattano insiemeM SK2

**[Parte 1](/blog/building-a-document-summarizer-with-rag)** spiega. *Perché?* L'approccio del pipeline supera le chiamate naive di LLM. Copri gli schemi architettonici (chunkingM SK2 riduzione gerarchica , validazione delle citazioniMSC4 che fanno funzionare bene ogni sintetizzatore di documentiMSL5 L'outil è evoluto dal momento in cui la parte CMS6 fu scrittaMSM7 ma i principi rimangono validi

**[Parte 2](/blog/docsummarizer-tool)** è il vostro manuale di partenza.

**Parte 3** (Questo articolo) è la immersione profonda per chi vuole capire *Come?* Funziona davvero: BERT contro i trasformatori di frase, perché ONNX è importanteM SK2 tokenizzazione acquisita , il commercio di ricerca ibridaMSC4 offsMST5 e cosa si rompe nella produzioneMst6

Se 'ste costruendo la vostra pipeline , leggete tutti i tre. se ' usate solo l'outilettoM SK4 leggetevi la parte M2 e forse scacciate la parte

## Related

- [Analyse CSV con LLM locali](/blog/analysing-large-csv-files-with-local-llms)
- [Portare e analizzare il contenuto web con i LLM](/blog/fetching-and-analysing-web-content-with-llms)