# DocSummarizer Part 3 - المبادئ المتقدمة: الـ"" لقد ذهبت بعيداً جداً ♫🤦" الغوص العميق

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

## المقدمة

هذا هو **الجزء 3** لسلسلة DocSummarizer:

1. **[الجزء 1: بناء مُلخص दस्तावेज مع RAG](/blog/building-a-document-summarizer-with-rag)** - الهندسة المعمارية ولماذا مقاربة الأنبوب تتفوق على مكالمات LLM الخاطئة
2. **[الجزء 2: باستخدام الأداة](/blog/docsummarizer-tool)** - مسرعة- دليل البداية لل CLI
3. **جزء 3: المفاهيم المتقدمة** (هذه المقالة) - لقد ذهبت بعيداً " غطس العميق التقني
4. **[جزء 4: بناء أنابيب RAG](/blog/docsummarizer-rag-pipeline)** - استخدم مكتبة NuGet لبناء تطبيقات RAG الخاصة بك

---


هذا جزء من "الزمن-أدوات المboxed "نهجيM SK3 أعطي نفسي نافذة ثابتة لبناء شيء عمليMSC4 إنه يفرض القرارات وينتج البرمجة العاملة بدلاً من التصميمات النظرية

بدأ DocSummarizer بعرض كيف يمكنك *يجب أن* بناء ملخصات दस्तावेज مع LLMs - مقاربة الخطوط العريضة التي شرحتها في الجزء 1. معظم الدروس تبين لك كيف تضع النص في LLM وتأمل في الأفضل. أردت أن أريكم المعمار المناسب

لكن كما أفعل دائماً, أصبحت مهتماً بمكان المشكلة. بعد أربعة أيامM SK2 حفرت في البحثMSC4 كيف تتعامل الأنظمة الإنتاجية--- مع التقليد في الواقع-- ? ما الذي يجعل عملية الاستخراج تعمل بشكل جيد؟

لقد قمت بتطبيق نسخ من هذه المقاربات. ما بدأ كـ " هنا ' هو النمط الصحيح " أصبح مداخلات ONNX تعمل محلياًM SK4 البحث الهجين يجمع بين BMMSC5 مع الاستخراج الكثيبMST6 الأهمية الحدية المحدودة للتعددMSP7 ودمج التصنيف المتقابل لدمج الإشاراتMSL8

**تحذير عادل**: هذا هو " لقد ذهبت بعيداً " الغوص العميق. إذا أردت فقط أن تستخدم الأداةM SK4 أقرأ الجزء 2. إذا كنت تريد أن تفهم *لماذا* إنها تعمل *كيف* تتناسب الأجزاء مع بعضها.

هذا المقال يغطي

- **إضافات الجملة**: كيف تتحول نماذج المحولات (MiniLM/BGEM SK3GTEMSC4 تحول النص إلى محركات
- **أونكس وقت التشغيل**: تشغيل نماذج ML محلياً بدون استخدام برمجيات Python أو السحاب
- **RAG (Retrieval-Augmented GenerationM SK2**: خروجية LLM الأرضية في المواد المصدرية
- **البحث الهجين**: الجمع بين البحث الحرفي والكتابي مع RRF

[TOC]

## العمارة على وجه التحديد

قبل الغوص في التفاصيل, هنا' كيف تتناسب الأجزاء مع بعضها البعض

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

## فهم المداخلات

### المشكلة: كيف تجد المحتوى المناسب بدون كلمات مرور?

عند تلخيص 500- دليل للصفحة, تحتاج إلى العثور على الفصول ذات الصلةM SK2 إختلال البحث عن كلمات رئيسية تقليديةMSC3

- يسأل المستخدم "كيف أقوم بتثبيت الجهاز?"
- المعدات تقول " لاستعادة nastavení المصانع..."
- لقد ضيع البحث Keyword (لا كلمات مشتركة)

أنت بحاجة **البحث الدلالي** - تطابق مع المعنى, ليس مجرد كلمات

### ما هي الملحقات?

المداخلات تحل هذا عن طريق تحويل النص إلى محركات كثيفة (سلسلة من الأرقام) التي تلتقط المعنى الدلاليM SK2 المعاني المتقاربة = محركات متقاربةMSC4 على الرغم من الكلمات الدقيقة

هنا ' هو الحدس : تخيل مساحة 384- بعدية حيث كل قطعة من النص لديها موقع . Textes with similar meanings cluster together .

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

### لماذا تحويل الم Sentence (Not Raw BERT)?

**المشكلة**: أحتاج إلى مداخلات تعمل على شبيهة الدلالية. تم تصميم بيرت الخام لواجبات التصنيفM SK2 ليس بحث شبيهية .

**الحل**: استخدام **محولات الجملة** نماذج - تم تدريبها بشكل خاص على مهام شبيهة باستخدام التعلم المتناقض . [معمارية بيرت](https://arxiv.org/abs/1810.04805) لكن جيد-مضبط بشكل مختلف.

نماذج مثل `all-MiniLM-L6-v2` و `bge-small-en-v1.5` تم تدريبها على مليارات زوجات نصية مثل :

- "كيف تعيد تشغيل الجهاز" ↔ |"إعادة ضبط настройки المصانعM SK4 ♫(متشابهةMSC6
- "كيف تعيد تشغيل الجهاز" ↔ | | "仕様 المنتجM SK4 ♫( متشابهة جداً |)

الدرس يعلمهم : المعاني مماثلة = векторات قريبة | ( | شبيهة بكزيونية عالية |).

> **مرتبطة**: إذا كنت تريد أن تفهم كيف تعمل نماذج المحولات على مستوى أعمق - بما في ذلك آليات الانتباه, البرمجةM SK3 العمارة البرمجيةMSC4 ولماذا تعمل التداخلات - شاهد مقالي على [كيف تعمل ترجمة الآلة العصبية](/blog/how-neural-machine-translation-works). إنها تغطي نفس مبادئ المحول من وجهة نظر الترجمة

**التنفيذ**: نأخذ طبقة خروجية النموذج' ونطبقها **الجمع المتوسط** - متوسط تداخل كل الرموز للحصول على вектор واحد لكل النص

```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: تشغيل نماذج ML محليا

### المشكلة: جهنم dependency Python

أردت دمجات إلى "عمل فقط" عندما يقوم شخص ما بتشغيل الأداةM SK2 المقاربة القياسية :

1. ដំឡើង محولات Python + PyTorch +
2. تحمّل النماذج لاسيًا
3. اشتباكات النسخة الأملية لا تكسر كل شيء

هذا سيء. يريد المستخدمون `docsummarizer -f doc.pdf`, ليس دليل لإعداد خطوة 30-

### لماذا ONNX?

أونكس ( تبادل شبكات عصبية مفتوحة ) هو نموذج مفتوح للنماذج الـ ML **إستنتاج وقت الجري بدون Python**.

**ما أحصل عليه مع ONNX Runtime:**

1. **صفر من الإعتمادات الخارجية** - لا بولسات Python, PyTorchM SK2 مشغلات CUDA
2. **اتومات-تحميص نماذج** - أبدأ بتشغيل تحميلات من HuggingFace (~23-34MB), ثم تم تخزينها
3. **نقي .NET** - يعمل في أي مكان .NET يشتغل
4. **إستنتاج CPU** - لا يحتاج إلى GPU , يعمل على الأجهزة الرخيصة

التجارة-off: أبطأ قليلاً من GPU PyTorchM SK2 ولكن أسرع بكثير من طلب المستخدمين أن يشتروا Python .

### مسجل النماذج

DocSummarizer يحتوي على العديد من نماذج الدمج, كل منها مع تجارة مختلفة-offsM SK2

| النماذج | الأبعاد | | | اعظمي الرموز || | الحجم | ( | كميّة |) | | موضوعة الاستخدام | مSK7 | تتطلب التعليمات | المSK8
|-------|-----------|------------|------------------|----------|---------------------|
| `AllMiniLmL6V2` | 384 ≥| \256 ♪| |~23 |MB | | |លំនាំដើម |MSC7 |سرعة | MSC8 | جودة جيدة | msc9 | لا |msc10
| `BgeSmallEnV15` |  384
| `GteSmall` | 384 ≥| ≤512 | ~34 MB | الغرض العام مسك7 لا مسك8
| `MultiQaMiniLm` | 384 ≥| \512 | | | ♪~23 |MB || optimized for Q

**ملاحظة**: جميع مدخلات السجل تشير إلى WordPiece-مصدرات ONNX المتوافقة (استعمال `vocab.txt`). BPE/نماذج الـ"Unigram" لم يتم تشغيلها بعد

**صيغة التعليمات BGE**: بعض النماذج ( مثل BGE) تتطلب مسبقات لأداء مثاليM SK3 الصيغة الدقيقة تعتمد على النموذج

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

يتتبع السجل أي نماذج تحتاج إلى التعليمات من خلال `RequiresInstruction` و `QueryInstruction` مجالات. دائماً تقييم جودة الترقب عند العمل مع التعليمات -نماذج مبنية على .

هنا ' كيف يعمل مسجل النموذج :

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

### الرمزية: WordPiece vs BPE

نماذج مختلفة تستخدم توكنيزات مختلفة `all-MiniLM-L6-v2` النموذج يستخدم توكنالوجيا WordPiece (مثل BERT), الذي يقسم الكلمات غير المعروفة إلى توكنات كلمات فرعيةM SK2 قد تستخدم النماذج الأخرى BPE

**مهم**: توكيزة النموذج يجب أن تتطابق مع توكيزر التدريب . سجلنا يتبع أي توكيسور يحتاجه كل نموذج **يتم تنفيذها حاليا: WordPiece** (via `vocab.txt`). BPE/دعم وحدة الرسم عن طريق `tokenizer.json` planned but not yet implemented - stick to the WordPiece models in the registry for now

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

مثال توكيزة:

| الدخل | الرموز |
|-------|--------|
| `"embedding"` | `["em", "##bed", "##ding"]` |
| `"DocSummarizer"` | `["doc", "##su", "##mm", "##ari", "##zer"]` |
| `"the quick brown"` | `["the", "quick", "brown"]` |

### الجمع المتوسط مع قناع الانتباه

بعد أن يقوم بريت بتحليل الرموز، نحصل على حالة مخفية لكل رموز.

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

### أنابيب الدمج الكاملة

هنا'التدفق الكامل من النص إلى الدمج:

```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: استرجاع- الجيل المتزايد

### المشكلة : لا يمكن لـ LLMs ' قراءة 500- مخطوطات الصفحة

**المقاربة البديهية فشلت**

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

حتى مع 128K النوافذ السياقية, يمكنك ' عدم رمي الوثائق الهائلة في

- **التقطيع**: فقط الصفحات الأولى 100 تطابق
- **الهلوسة**: شركة LLM اخترعت "facts" لملأ الفجوات
- **التكلفة**: معالجة 2MB من تكاليف النص $$$ لكل سؤال
- **الجودة**: يخلط الـ LLM مع السياق الهائل

### الحل: RAG (Retrieval -Augmented GenerationM SK3

بدلاً من إرسال كل شيء، أرسل فقط ما هو ملائم

1. **تشيلينغ**: تجزئ الملف إلى أجزاء
2. **الدمج**: حول Segments to vectors (semantic representation)
3. **الجمع**: أبحث عن الأجزاء الأكثر أهمية 10-20 للبحث
4. **التركيب**: LLM يخص فقط تلك القطع

**لماذا يعمل هذا** نرى في LLM 10KB من المحتويات ذات الصلة العالية بدلاً من 2MB من النص الأكثر أهمية.

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

### استراتيجيات تصنيع الوثائق

DocSummarizer يدعم استراتيجيات تجزئة متعددة بناء على بنية الوثائق:

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

### إستخراج Segment ل Fine-Grained Retrieve

للوثائق الطويلة, DocSummarizer يستخرج أجزاء فردية M SK1جمل,मदات القائمةMSC3كتلوف الرمزMska4 مع درجات الجدوىMske5

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

### المشكلة : البحث الدلالي يعيد النسخ

**بدون MMR**, البحث عن " كيف يعمل ال caching?" أعاد

1. " نظرة ال cachingM SK1 (0.95 شبيهة)
2. "بداية ال cachingM SK1 (0.94 شبيهة)
3. " ما هو ال cachingM SK1 (0.93 شبيهة)
4. "Details on implementation of the cacheM SK1 (0.85 similarity)

النتائج الأعلى 3 جميعها تقول نفس الشئ

### الحل: الجدوى المحدودة الأعلى (MMRM SK2

توازن MMR **الأهمية** (similarity to query) with **التنوع** (ماثل جداً بما هو موجود بالفعل-أشياء تم اختيارها

**الصيغة:**
$$MMR = \lambda S\cdot \textM SK5sim}(sMSC7 queryMNK8 | | - \ (1 |- ♫\laMBda \) ♪\\ cdot \\\max_{sM SK1 \in Selected}

**ما الذي تفعله** penalizes candidates similar to already-selected segments. This prevents the summary from being 5 versions of the same paragraphM SK3

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

الصيغة:

$$MMR = \lambda SSK3cdot simM SK4s, centroidMSC6 | | - \ |(1 |_{sM SK1 \in Selected} 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;
}
```

## البحث الهجين مع RRF

### المشكلة: البحث الدلالي يفتقد التطابق بالضبط

صادفت هذا عندما كنت أختبر :

**السؤال**: "ماذا'هي نقطة نهاية الAPI للاتصالM SK3

**استعادت البحث الدلالي:**

1. "üleview user login flowM SK1 (high similarity)
2. "أحسن الممارسات في مجال الأمنM SK1 (تشابه كبير)
3. "إدارة الجهد" (متشابهة عاليةM SK3

**ما تفتقده**: نقطة النهاية الفعلية للAPI مدفونة في أمثلة البرمجة: `POST /api/v1/auth/login`

**لماذا**: تم تدريب النماذج المدمجة على اللغة الطبيعية , ليس الرمز/URLsM SK3مصطلحات دقيقةMSC4 النقطة النهائية `POST /api/v1/auth/login` لا يتوافق 'من الناحية الدلالية "نقطة النهاية للتحقق من صحته " - إنه S'مرجع تقني حرفي SSK5

### الحل: البحث الهجين ( السماتي + اللغويM SK3

دمج طريقتين للبحث مع نقاط متماثلة

| نوع البحث | نقاط قوته | | | نقاط ضعفه ♫ | |
|-------------|-----------|------------|
| **كثافة (** | الفهم الدلالي, المصطلحات المتماثلة | يمكن أن تضيع تطابق تامةM SK3 مصطلحات نادرة |
| **المعزوفة (BM25)** | تطابق مفاتيح الكلمة بالضبط, عبارات نادرة | لا فهم لغوي |

البحث الهجين يجمع كلاهما باستخدام دمج الرتبة المتقابلة (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
```

**ملاحظة**: درجات الـ RRF المعروضة موضحة . المستقيم kM SK2 هو معياري; التصنيف الفعلي يعتمد على المجموعة الكاملة للمرشحين

### تطبيق 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: عمال البحث عن المزود

BM25 M SK1Best Matching 25) is the classic information retrieval algorithmMSC3 It combines term frequency, inverse document frequenceMska5 and document length normalizationM Ska6

```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 لتركيز المحتوى

### المشكلة: كيف تتميز المحتوى الأساسي من الأمور المثيرة للاهتمام?

عند تلخيص رواية، حصلت على نتائج مثل

> "الشخصي يرتدي قبعة أزرقة . ولاحظ واتسون أن الطقس كان معتدلاً

هذه استخراجات دقيقة، ولكنها **اللون** (sceneM SK1setting details), not **نقاط الرسم الأساسي**.

**التحدي**: كيف تحدد الفرق بين

- **المحتوى الأساسي**: يظهر في كل dokument ( أسماء الخصائص, الموضوعات الرئيسيةM SK3 الأحداث الرئيسية)
- **دعم التفاصيل**: يظهر في بعض الفصول (مخططات فرعية, تفسيراتM SK3
- **اللون**: نادرةM SK1 التفاصيل المحددة ( ماذا كان شخص ما يرتدي, وصفات الأثاث

### الحل: TF-IDF لتصنيف مركزية

TF-IDF **كيف أن المصطلح مركزي للوثيقة**, ليس قيمة الحقيقة

**منطقي:**

- **DF عالي (>50% من الأجزاء)**: "Sherlock", ≥"WatsonM SK4 ≤"murderMSC6 → المحتوى الأساسي
- **متوسط DF (20-50%)**: "Baker Street", | | "investigation |
- **DF المنخفض (<20%)**: "السترة الزرقاء", | | "اثاثاث الخشبM SK4 ♫→ لون مصادف

هذا لا يتعلق بالحقيقة (إدعاءات متكررة يمكن أن تكون خاطئة,حقيقة نادرة يمكنها أن تكون صحيحة *مركزية للوثيقة*.

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

## أنابيب BertRag الكاملة

أنابيب الإنتاج DocSummarizer'`BertRagSummarizer`) يجمع كل هذه المفاهيم

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

## طرق الفشل الشائعة

عند بناء و استخدام DocSummarizer, أنا' واجهت هذه المشاكل M SK2 وسوف تفعل أيضاًMSC3

1. **خطأ توكينر → مداخلات غير معقولة**: تحميل مصطلح WordPiece vocab لBPE-نموذج تدريبي ينتج векторات صالحةM SK2 تبدو ولكنها غير ذات معنى فيزيائياً . دائماً تتأكد من تطابق الرمز مع النموذج

2. **التعصب الرئيسي -الموضوعي في واحد -درجات مركزية**: بإستخدام مستند واحد مركزي بشكل منهجي إلى الأسفل- يصنف المواضيع الأقلية ( القيود M SK3 استثناءات , حالات الحافة MSC5 متعددةM SK6 تصحيح هذا ولكن تضيف تعقيداً

3. **BM25 يتغلب على البحث الكثيب على عبارات نادرة**: إذا كان سؤالك يحتوي على المصطلحات التقنية أو الأسماء الصحيحة ليس جيداً- ممثلة في نموذج الدمجM SK2 بيانات التدريب , تطابق اللغوي MSC4BMMSC5 سيتفوق البحث الدلاليMSc6 لهذا السبب يهم البحث الهجين MSc7

4. **القمامة المطبوعة بصيغة أو سي آر في ملفات بي دي اس مسحت**: التدوين جيد, لكن أخطاء الرسم البياني المركبة . إذا رأيت فوضى في الإقتباساتM SK3 قم بالتحقق من إخراج العلامات المنخفضة من التدوير أولا | - | يمكن للملخص |' | لا إصلاح مدخل القمامة |

5. **منخفض - توصيل الغطاء يجب أن يضمن اللغة**: إذا كنتم ' تشاهدون فقط 3% من الوثائق , عبارة مثل "M SK4" في نهاية المطاف "Mska5" أو "Mske6" في الختام "Msko7" غير صادقة "Msek8" يجب أن تقول النظام " في مقاطع العينة " ويتجنبون نهاية نهائية "SSK11"

6. **هلوس الاقتباس**: Small LLMs (1.5BM SK2B params) sometimes invent plausibleMST4sounding citationsM ST5 We validate by parsing the output for `[chunk-N]`, التحقق من وجود N في الجسيمات المصدريّة, وتشير أو تصليح الادعاءات التي تشير إلى الجسيمين المفقودةM SK2 إذا رأيتم `[chunk-999]` من أجل 10-documentchunk, يصارع LLM مع المهمة

هذه ليست "مسك0" bugs مسك1" they'"re intrinsic tensions in the design space. Good production systems acknowledge and mitigate them"

## وجهات النظر العملية

### التغطية والحقيقة في أخذ العينات

عند معالجة مستندات كبيرة جدا, DocSummarizer لا يحاول ' أن يدمج كل شيء - إنه يستخدم الدلالية قبلM SK3 التصفية لتحديد الأجزاء reprezentativeMSC4 **هذا يعني أن الخلاصة تعتمد على عينة**

النظام يتعامل مع هذا بوضوح:

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

**مهم**: هذا هو *ملخص الأدلة التي تم الحصول عليها*, ليست ضماناً للغطية الكاملة - للوثيقة

**العينة ليست عشوائية** - it 's *الدلالية*. نستخدم مجموعة متعددة - لنتأكد من أن المواضيع الأقلية ليست excluded' excluedM SK3 يمكن أن يضيع عشوائيًا | 3% | جميع القيود و حالات الحافة |. | سيميّن ـ 3% | يحاول إلتقاط جزء واحد reprezentative من كل موضوع رئيسي ٬. | إنه ٬ ' ٬ لا يزال تغطية جزئية ,, ٬ ولكنه , ' ٬ هو تغطية متنوعة بشكل مقصود .

**القياسات الملائمة مع مزجات موضوعية متعددة**: في الـ"pre"ـ--المرشح يستخدم العديد من القناصات |(ـk"ـ -- يعنى -- التجمع على شكل عينة مقسمة "M SK5"- للتأكد من أن المواضيع الأقلية "MSC6"ـ لا يتم استبعادها بطريقة منهجية .- هذا يمنع ـ"ـ تحيز موضوعي رئيسي "Mska9"ـ حيث ينزل مركز واحد "Msek10"ـ إلى مستوى مهم "Msec11"ـ لكن "M Sec12"ـ محتويات نادرة مثل القيود "Msc13"ـ إستثناءات "M sec14"ـ أو إفتراضات "م Sec15"

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

هذا بحث - مُعلم ( يتجنب الإنهيار الفردي | - | تخريب استرجاع الطلب |) | لكن عمليا ♪ - | يعمل في ثوان على ال CPU ♪

**لماذا لا ندمج كل شيء**

بالنسبة لوثيقة 500-صفحة (2,000+segments), تداخل كل شيء سيعمل ولكن ليس كافياً

- **التكلفة**: OM SK1N) تسيطر الدمجات على وقت التشغيلMSC3 في أجزاء ~150 ,/ ,sec , , , ذلك . .' , . 13+ , ثوان فقط للدمج قبل أن تبدأ حتى بالبحث .
- **الجودة**: تدمج كل شيء يزيد من الضجيج في حوض البحث. مازلت تحتاج إلى التصنيفM SK2 إذن لماذا تدمج أجزاء لن تصنف أبداً عالياً
- **الواقعية**: الحواسيب وقيود ال latency مهمة. الحفاظ على 2,000 | | 384- вектор Dim في الحوسبة وحساب |2,000 شباهة الجسيمات لكل سؤال مضيعة عندما تحتاج فقط إلى أعلى |

العينات متعددة -anchor تعطيك أفضل من كلاهما : تغطية موضوعية واسعة مع حوسبة قابلة للتعامل

### الت Dragging : لماذا السحب ليس ' فقط البحث

ما وصفته سابقاً هو "ماسك 1" ليس مجرد "ماسک 2" والحصول على "ماكس 3" و "ماك 4" بل هو "مسك 5" وهو نمط معين أسميه **سحب السياق الخافت المقيد** ([CFCD](/blog/constrained-fuzzy-context-dragging)). الإدراك

> معظم المختصين يواصلون إضافة السياق. DocSummarizer *يسحب إلى الأمام* فقط ما يحافظ على الانتقاء المعرفي, عندها يسمح للنموذج بكتابة fluently داخل تلك الحدود.

هنا ' كيف يرسم خط أنابيب DocSummarizer إلى CFCD

| مبدأ CFCD | تطبيق DocSummarizer
|--------------|------------------------------|
| **الكشف عن اللعاب** (fuzzyM SK1 | ملحقات, شبيهة مركزيةMSC4 TFMske5 مركزية IDF |
| **التعزيز المعرفي** | MMRM SK1 BM25, الإنصهار RRFMST3 أعلىMSC4الانتخاب |
| **دليل القنابل** | الجزء الذي تم الحصول عليه مكون من أرقام الاقتباس |
| **الجيل القياسي** | عملية التمثيل محصورة من خلال الأدلة المستخرجة |

**لماذا هذا مهم**: النموذج لا يقرر ' ما هو ذا صلة |' | - | خط الإستخراج |. | النموذج ينتج بسلاسة فقط ضمن الحدود التي قمنا بوضعها | ' | و هذا هو السبب في عمل نماذج محلية صغيرة | : | المثقوب تقوم بالرفع الثقيل |.

في الواقع، "كتابة القنابل" تبدو مثل هذا (من الناحية النظريةM SK3

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

ثم في عملية التحليل prompt:

- "يتوجب على كل ادعا أن يشير إلى رقم جزء مضمن "
- " إذا كانت تغطية < 5%, لغة الحماية"
- "استخدموا هذه المصطلحات بشكل مستمر"

لهذا السبب

- **النوافذ السياقية الطويلة هي شرارة حمراء** - المشكلة هي ' لا تلائم المزيد من النص
- **فشل الجمع المتكرر** - يعامل المخرج الوسطى كنص , ليس هيكل مقيد
- **يمكن لـ LLM 't "forget" تعزيز القيود** - إنهم' هيكله , لا يمكنه أن ينزلق من

CFCD هو نفس الانقسام الفيزيائي كما [الغموض المقيد](/blog/constrained-fuzziness-pattern), [MoM مقيدة](/blog/constrained-mom-mixture-of-models), و [توصيل الصورة](/blog/constrained-fuzzy-image-intelligence) - تقترح إحتمالية, يستمر التفاضل والتكامل - يتم تطبيقه على طول الزمنM SK3 محور الذاكرةMSC4

### ال Quantization

نماذج ONNX يمكن قياسها (دقة أقل) لصغر الحجم وأكثر استنتاجًا سريعًاM SK2 التجارة - تعني خسارة نوعية بسيطةMSC4

| النماذج | الدقة الكاملة | الكمية | الاختلافات في الجودة S|
|-------|---------------|-----------|-------------------|
|كل-MiniLMM SK2LMST3vMSC4 | | | 90MB || \23MB
| bge-small -enM SK3vMSC4 | | | 133MB || \34 | MB |

### معالجة القطعة والتنافس

بالنسبة للوثائق الكبيرة , تجميع الحزم مهم جداً لأداء `InferenceSession` يمكن أن يتشارك بشكل عام عبر السطور بشكل آمن, لكن الأداء يعتمد على إعداد الإجتماع:

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

**نصيحة الأداء**: កំណត់ រចនាសម្ព័ន្ធ `SessionOptions` عند إنشاء المحادثة:

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

### إدارة الذاكرة للوثائق الكبيرة

الوثائق الكبيرة جداً (novels, الوثائق القانونيةM SK2 تحتاج إلى معالجة خاصة للتجنب

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

## خصائص الأداء

أداء حقيقي-عالمي على آلة مطورة تقليدية (ريزن 5600XM SK3 32GB RAMMスク5 لا GPUMSC6

| عمليات |吞吐ة | | | ملاحظات ||
|-----------|-----------|-------|
| **الدمج** | ~150 أجزاء/sec | حجم المجموعة 64, كلها -MiniLM MSc6L MScs7v MSC8 مقدرة msc9
| **إعادة البحث الكثيفة** | <10ms | شبيهة الجسيمات على أجزاء | | 500 |
| **درجات BM25** | <5ms | في-مؤشر الذاكرة المعكسة |
| **الإنصهار RRF** | <2ms S| تجميع 3 التصنيفات M|
| **النهاية-toM SK1end (25-page PDFMSC3** includes chunking, embedding, retrieval , LLM synthesis M SK6

**بيئة الاختبار**: Ryzen 5600X (6-core), 32GB RAMM SK5 no GPUMSC6 Embedding uses allMST7MiniLMMSP8LMSSK9vMS2 ♫(quantizedMSR12 |MSR13 token maxMSL14 ♪MSR15thread parallel batchingMSF16 Retrieval corpusMSV17 .MSF18 segmentsMSS19 your mileage will vary with different modelsMSSR20 hardwareMSSA21 and document complexityMSA22

**محرك رئيسي هو دمج الإنتاجية** (إختيار النماذج | + | طول الرموز |+ | حجم العينة | МSK3 | إستخراج و الإنصهار في الأساس مجانية ♫- | تستغرق الميلي ثانية | . | هذا يعزز | | الـ " | LLM الأخير | مSK7 | المبدأ | ماSK8 | عمل المعالجات المركزية الرخيصة | من ثمة ( | التداخل | بِ , | الاستخراج | و ) | أولاً | إلى , | العمل الـ LLM الثمني فقط على المحتوى المجهول |

**التدرج**: يعالج الاستخراج الهرمي 500+ مخطوطات الصفحات | ( | نوبل |, | دليل | МSK4 | من خلال المعالجة في مجموعات وحفظها فقط في الأعلى | - | K لكل مجموعة في الذاكرة |

## الخلاصة

DocSummarizer يوضح أن إمكانية NLP المتطورة لا تحتاج إلى APIs أو إعتمادات Python.

- يشتغل محلياً بالكامل
- ينتج إحصائيات قابلة للتتبع,
- يتعامل مع وثائق بأي حجم
- يعمل خارج نطاق الإنترنت

الملاحظات الرئيسية من بناء هذا الأداة:

1. **المداخلات هي الأساس** - البحث الجيد يعتمد على المداخلات الجيدة
2. **البحث الهجين يضرب إما وحيداً** - تدمج الدلالية والكتابة لتكون صلبة
3. **يمنع MMR التكرار** - التنوع مهم بقدر أهمية الجدوى
4. **المسائل الهيكلية** - Respecting document structure (headingsM SK2 sections) improves results
5. **يجب أن تأتي الـ LLMs الأخيرة** - أعمل ب CPU رخيص أولا, تعمل LLM مكلفة فقط على المحتوى المصف

## قراءة أخرى

### الأبحاث والمراجعات التقنية

- [ورق بيرت](https://arxiv.org/abs/1810.04805) - مُفكّر المحول الأصلي
- [الجملة-BERT](https://arxiv.org/abs/1908.10084) - BERT للتعبيرات الجملة
- [BM25 شرح](https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables) - الخوارزمية الكلاسيكية للبحث
- [ورق RRF](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) - توصيل الرتبة المتقابلة
- [أونكس وقت التشغيل](https://onnxruntime.ai/) - Cross-platform ML inference

### الغوصات العميقة المرتبطة

- [كيف تعمل ترجمة الآلة العصبية](/blog/how-neural-machine-translation-works) - تغطي هيكلة المحولين , آليات الانتباه | , | وضمنات من وجهة نظر الترجمة

## كتابة سلسلة

هذا يخلص لسلسلة DocSummarizer

**[الجزء 1](/blog/building-a-document-summarizer-with-rag)** يشرح *لماذا* نهج خط الأنابيب يتغلب على مكالمات LLM ساذجة. إنه يغطي أنماط الهندسة المعمارية (تشنجM SK2 التقليل الهرمي , تصديق الإقتباسات ) التي تجعل أي summarizer يعمل جيداًMSC5 الأداة قد تطورت منذ أن أُكتبت الجزء | 1 |, | لكن المبادئ مازالت صالحة

**[الجزء 2](/blog/docsummarizer-tool)** هو مؤشرك السريع-إرشاد البداية .التركيب,مداراتM SK3نماذج templatesMSC4 حالات الاستخدام الشائعةـ. إذا أردت فقط أن تستخدم الأداةMST6 التيـMST7 هي كل ما تحتاجه

**الجزء 3** (this article) is the deep dive for people who want to understand *كيف* إنه بالفعل يعمل: بيرت مقابل محولات الجملة, لماذا يهم ONNX , توكنالوجيا جوتchasM SK3 تجارة البحث الهجينةMSC4 الإختلافاتMNK5 وما ينكسر في الإنتاجMRK6

إذا كنت ستقوم ببناء خط أنابيب الخاص بك، ستقرأ كل الثلاثة، ستقراء ثلاثة، إذا كنت تستخدم فقط الأداة، ستقر الجزء، ستفهم الجزء، و ربما تتصفح الجزء،

## مرتبطة

- [تحليل CSV مع LLMs المحلية](/blog/analysing-large-csv-files-with-local-llms)
- [جلب وتحليل المحتوى على شبكة الإنترنت مع LLMs](/blog/fetching-and-analysing-web-content-with-llms)