Back to "DocSummarizer Part 3 - Заawansовані концепціїM SK2 The МSK3 I Went Too Far МSK4 Deep Dive"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

.NET AI BERT C# Embeddings LLM ONNX RAG

DocSummarizer Part 3 - Заawansовані концепціїM SK2 The МSK3 I Went Too Far МSK4 Deep Dive

Sunday, 21 December 2025

Введение

Це Частина 3 з серии DocSummarizer:

  1. Частина 1: Збудування резюме документа з РАG - Архітектура і чому підхід трубопроводу перевершує наївні LLM виклики
  2. Частина 2: Використовуючи інструмент - ШвидкийM SK1початковий інструктор для CLI
  3. Частина 3: Заawansовані концепції ( цей artykułM SK1 - " МSK3 я зайшов надто далеко \ "" " технічне глибоке плавання
  4. Частина 4: Збудування трубопроводів РАG - Використовуйте библиотеку NuGet для створення власних програм RAG

Це частина мого підходу: "TimeM SK1Boxed Tools" give myself a fixed window to build something functional . It forces decisions and produces working code rather than theoretical designs

DocSummarizer розпочався як демонстрація того, повинна збудувати підсумки dokumentów за допомогою LLMs - підхід до pipeline, який я описав у частині 1. Більшість уроків показують вам, як втиснути текст в LLM і сподіватися на найкращу схему.

Але, як я завжди роблю, я зацікавився проблемним простіром.4 дні потому я почав досліджувати.4 - Як виробництво.5 - Оцінкові системи насправді справляються з сумірацією?6 - Що робить пошук ефективним?7 - Чому деякі вбудовані мають кращий результат, ніж інші?8 -

Я запровадив версії цих підходів. Те, що почалося як M SK1 тут ' є правильним шаблоном МSK3 стало ONNX вбудовами, що запускаються локально М SK4 гібридні пошуки, які об 'єднують BM M SK5 з щільним відтворенням , Максимальна маргінална важливість для різноманітності MSC7 і Реципрочний розрив рангу для об' єднання сигналів

Справедливе попередження: Це МSK1 Я зайшов надто далеко МSK2 глибоке плавання . Якщо ви просто хочете використати інструмент МСК4 прочитайте частину М СК5 Якщо вам захочеться зрозуміти чому він працює і як частинки підходять одна до одної, продовжувати читати

Цей artykuł стосується:

  • Вбудова речей: Як моделі трансформаторів (MiniLM/BGEM SK3GTEМSK4 перетворюють текст на вектори
  • Пропускний час на NX: запускML-моделей локально без Python чи хмарних API
  • RAG (Ретривал МSK1Поширене генерування МSK2: Виведення LLM для підземлення у матеріалі
  • Гібридний пошук: Об 'єднання семантичного і ліксичного відтворення з РРФ

З погляду архітектора

Перед тим, як зануритися у деталі, ось тут:

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

Усвідомлення вбудованих речей

Проблема: Як знайти важливу інформацію без ключевых словM SK1

Підсумовуючи інструкцію для сторінки 500-, вам потрібно знайти необхідні розділиM SK2 Традиційне пошук по ключевым словам не вдалось :

  • Пользователь запитує: " Як мені відновити пристрійM SK1
  • Руководство говорить: "Restore factory settingsM SK1
  • Keyword search misses it (no shared wordsM SK1

Вам потрібно пошук семантики - співпадіння по значущам

Що таке вбудований матеріал?

Втілення розв 'язують це, перетворюючи текст на щільні вектори ( сукупності чисел ) що захоплюють семантичні значення МSK2 Схожі значення

Тут – це інтуїція – МСК1. Уявіть собі простір, у якому кожен шматок тексту має певну позицію – МSK3. Тексти з схожими значеннями збираються разом.

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 Transformers (Not Raw BERTM SK1

Проблема: Мені потрібні були вбудовані додатки, які працювали для семантичної подібності . Raw BERT був розроблений для класифікаційних завдань МSK2 а не для пошуку подібічності

Рішення: Використовувати трансформатори речень Моделі - спеціально навчені на задачах схожості, використовуючи контрастне навчання . Вони МSK2 базуються на Архітектура BERT але добре-налаштований по-іншомуM SK1

Моделі, такі як all-MiniLM-L6-v2 і bge-small-en-v1.5 були треновані на мільярдах пар слів, таких як ":".

  • "Як відновити пристрійM SK1 МSK2 "Restore factory settings" | (similar | МSK6
  • "Як відрегулювати пристрійM SK1 МSK2 | | "Сpecifications of the product

Тренування вчить їх:: подібні значення M SK1 близькі вектори ( висока схожість косину

Подібне: Якщо ви хочете зрозуміти, як працюють моделі трансформаторів на глибшому рівні МSK1 включно з механізмами уваги МSK2 кодувальник - архітектура кодувальника \ , і чому вбудова працює Як працює нейронний машинний переклад. Це стосується тих самих концепцій трансформатора з точки зору перекладу

Втілення: Ми беремо модель ' і застосовуємо значне об 'єднання - середня кількість вбудованих символів для того, щоб отримати один вектор на весь текст

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

NX: Програмування моделей ML локально

Проблема: Python Dependency Hell

Я хотів, щоб вбудова у " просто працювала" коли хтось керує інструментомM SK2 Стандартний підхідMSC3

  1. Установити трансформатори Python + PyTorch +
  2. Скачаймо моделі вручну
  3. Конфликти версії надії не руйнують все

Це безглуздо. Пользовачі хочуть docsummarizer -f doc.pdf, не є МSK1пособним інструкцією для налагодженняM SK2

Чому ONNX?

ONNX (Open Neural Network Exchange) - це відкритий формат для моделей MLM SK2 Найголовніша характеристика : висновку про час запуску без Python.

Що я отримую з Runtime ONNX:

  1. Нуль зовнішніх відлежностей - Ніяких драйверів CUDA
  2. Автомодельи загрузки - Спочатку запускаємо завантаження з HuggingFace ~23-34MBM SK2 потім заキャッシュом
  3. Чистий .NET - працює будь-де. .NET працює.
  4. Дослідження procesorа - Ніяких графічних комп 'ютерів

Торгівля-offM SK1 Трохи повільніше, ніж GPU PyTorch, але набагато швидше, ніж просити користувачів заinstalувати PythonMSC3

Модель Rejestrу

DocSummarizer включає в себе декілька вбудованих моделей, кожен з різними товарамиM SK1offs:

Модель МSK1 Wymiary МSK2 Максимальні символи Розмір ХМSK4 Квантилізовані МСК5 МСк6 Корпус для використання MСК7 Виmaganі інструкції MСК8
AllMiniLmL6V2 МSK0 384 ♫ ♫ МSK2 ♫
BgeSmallEnV15 МSK0 384 ♫ ♫ МSK2 ♫
GteSmall МSK0 384 ♫ ♫ МSK2 ♫
MultiQaMiniLm МSK0 384 ♫ ♫ МSK2 ♫

Примітка: Всі записи реестру показують на WordPieceM SK1компанітивні експорти ONNX (застосування vocab.txt). BPE / Моделі Unigram ще не підтримують

Формат інструкцій BGE: Деякі моделі МSK1, як і BGEM SK2 потребують префиксів для оптимальної продуктивності . Точний формат залежить від моделі

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

Rejestr відстежує, які моделі потребують інструкцій через RequiresInstruction і QueryInstruction поля. Яка завжди якість відтворення Benchmark при роботі з інструкціями -побудовані моделі МSK2

Ось як працює модельний реєстр.

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 проти BPE

Різні моделі використовують різні токенізери. all-MiniLM-L6-v2 модель використовує символізацію WordPiece ( на зразок BERT),, яка розділяє невідомі слова на підсловні символиM SK2 інші моделі можуть використовувати BPE (ByteMSC4Pair EncodingMska5 або Unigram tokenizersM Ska6

Важливо: Модельні токенізери повинні підходити до тренувального токінізера . Наш реестр визначає, який токінізатор для кожного моделі необхідний Наразі впроваджується: WordPiece (via vocab.txt). BPE/Unigram підтримка через tokenizer.json заплановано, але ще не введено - дотримуватись моделей WordPiece в Rejestrі на даний момент

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

Наприклад, символізація:

Вхід Торки МSK2
"embedding" ["em", "##bed", "##ding"]
"DocSummarizer" ["doc", "##su", "##mm", "##ari", "##zer"]
"the quick brown" ["the", "quick", "brown"]

Об 'єднання з маскою уваги

Після того, як BERT обробляє символи, ми отримуємо прихований стан для кожного символу.

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

Полне введення трубопроводу

Тут' є повним потоком від тексту до введення :

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: ПеретягуванняM SK1Поширене генерування

Проблема: LLMs Can't Read M SK2Page Documents

Наївний підхід провалився:

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

Навіть з Windows контексту 128K1 ви не можете просто скинути величезні документи в МSK3

  • Трункація: Залишається ігноровано тільки те, що перші МSK1 сторінки відповідають
  • Галюцинація: LLM винаходить " факти МSK2, щоб заповнити прогалини
  • Ціна: Обробка
  • Якість: LLM заплутають з масивним контекстом (" втрачено всередині

Рішення: RAG M SK1Видобуток-Поширене генерування )

Замість того, щоб надсилати все, надсилайте тільки те, що є важливим

  1. Зйомка: розділити документ на сегменти
  2. Вбудова: Перетворити сегменти на вектори МSK1сенітичне представленняM SK2
  3. Видобуток: Найти найсучасніші сегменти для запиту
  4. Синтез: LLM підсумовує лише ті сегменти

Чому це працює: LLM бачить 10KB дуже релевантного контенту замість 2MB більш неважливого текстуM SK2

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

Strategiи документування

DocSummarizer підтримує багато стратегій відокремлення на основі структури dokumentu:

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

Видобуток сегментів для обробки Fine-Grained

Для довшего документу, DocSummarizer виділяє окремі сегменти M SK1 речення , пункти списку МSK3 кодові блоки M SK4 з відбитком salience

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, пошук для МSK1 Як працює キャッシュування? ?" повернуто

  1. " Перечень записівM SK1 МSK2 подібність)
  2. "Введення в кэшуванняМSK1 МSK2 подібністьM SK3
  3. " Що за Caching? МSK1 МSK2 подібність )
  4. "Details of Cache implementation" (0.85 SimilarityM SK3

Верхні результати 3 всі говорять одне й те ж: . I МSK2m марнування контексту на повторенні МSK3

Рішення: Максимальна маргинальна важливість (MMRM SK2

Баланси ММР важливість (подібність до запиту різноманітність (неподібність до попередньогоM SK1вибраних речей).

Формула: МSK0MMR = МSK2lambda СМСК3cdot СМСК4текстМСК5simММСК6sМПСК7 queryMСМСк8 ♫ ♫ ММСК9 ♫ MМСК10 ♫_МSK0с' МSK2в відібраномуM SK3 \ \текст{simMST6sMSC7 sMSL8

Що він робить: Викарбує кандидатів, схожих на вже наявні-вибрані сегментиM SK1 Це запобігає тому, що підсумок буде МSK2 версією одного й того ж абзацу

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

Формула:

МSK0MMR = МSK2lambda \cdot simM SK4s, centroid СМСК6 СМСК7 МСК8 МСК9 MSК10laMBda \ ) \ \ MSК12c dot \ ММСК13max_МSK0s' \в вибраних M SK3 simM SK4s+, s=')$$

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

Проблема: Семантичні пошуки не мають точного співпадіння

Я натрапив на це під час тестування:

Запитання: МSK1 Що МSK2 є кінцевим пунктом API для аутентифікації ?"

Семантичний пошук повернувся:

  1. "Собзор потоку входу користувачаM SK1 МSK2велика схожість)
  2. МSK0 Найкращі практики у галузі безпеки" M SK2велика подібність )
  3. "Управлення сидіаціямиM SK1 МSK2велика подібність)

Що він пропустив: Фактичний кінцевий пункт API закопаний в прикладах кодуM SK1 POST /api/v1/auth/login

Чому?: Моделі вбудованих модулів тренуються на природному мові POST /api/v1/auth/login не співпадає семантично з " кінцевим пунктом аутентифікації МSK2 МSK3 це ' є буквальною технічною референцією

Рішення: Гібридні пошуки M SK1Семантика МSK2 Лексика

Об 'єднати дві методи пошуку з докупними перевагами:

Тип пошуку МSK1 Сили МSK2 Загнічення
Dense (Embedding) Порозуміння семантикиM SK1 синоніми МSK2 Може пропустити точні співпадіння, рідкісні терміни |
Набір запасних частин (BMM SK1 Точне співпадіння ключевих слівM SK1 рідкісні терміни Без семантичного розуміння МSK3

Гібридний пошук об 'єднує обидві способи: Reciprocal Rank Fusion (RRFM SK1

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

Примітка: Оцінки РР показані є ілюстративнимиМSK1 константа k МSK2 - стандартна ; реальний рейтинг залежить від комплекту кандидатів

Implementation RRF

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: Працівник по відшуці запасних частинок

БММСК0 МSK1 Найкраще співпадіння 25) - це класичний алгоритм пошуку інформації МSK3 Він об 'єднує термінову частоту , зворотньу частоту документу M SK5 та стандартизацію довжини документу

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 для Центральності контенту

Проблема: Як відокремити ядро контенту від тривіа

Підсумовуючи роман, я отримав такі результати:

" Головний герой одягнув блакитний халат . Уотсон зауважив, що погода легка МSK2 У дослідженні було дерев 'яне меблі

Це точні виділення, але вони колір (сцена центральні точки.

Задача: Як ви розрізняєте

  • Головний зміст: Подібно у всьому документі
  • Дослідження: Виявляється в деяких частинах МSK1підстрої МSK2 пояснения )
  • Колір: РеальноM SK1 подібні деталі МSK2 те, що хтось носить МSK3 furniture descriptions )

Рішення: TFM SK1IDF для класифікації централізації

TFМSK0IDF (Термічна частота МSK2 Інверсивна частота документу ) оцінки наскільки центральним терміном є документ, не його значення

Логіка:

  • Високое DF (>50% частин МSK1МSK0 "ШерлокM SK2 ♫ ♫ МSK3 ♫Watson ♫ ", ♫
  • **Середній DF (20-50%)**МSK0 "Бакер-стритM SK2 | | МSK3 | розслідування " | → | Подходні деталі
  • **Низкий DF (<20%)**МСК0 "блакитний халатM SK2 | | " меблі з дубу" \ | МSK5 Інcidental colour

Це не про правду. ( повторюваний твердження може бути неправдою МSK1 рідкісний факт може бути правдою центральність до документа.

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

Повний Бертраг

Лінія виробництва DocSummarizer' M SK1BertRagSummarizer) об 'єднує всі ці поняття

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 і ви також зіткнетеся з ними):

  1. Незбіг токенізеру → нерозумні вбудовані: Завантаження Vocab WordPiece для BPE-викончений модель створює важливийM SK2глядливі, але семантично безглузді вектори . Знову перевіряйте, чи токенізер відповідає моделі

  2. Домінуючий-топічна упередженість в одній команді: Використовуючи один документ центраїд систематично вниз - оцінює меншітні теми МSK2 перешкоди МSK3 винятки

  3. BM25 перевершує щільні пошуки на рідкісних термінах: Якщо ваше запитання містить неправильний технічний жаргон або відповідний назви, - представлений у вбудованій моделі МSK2 тренувальні дані МSK3 lexikalне співпадіння

  4. ОCR сміття в сканованих PDF: Docling - хороший , але помилки OCR змішані МSK2 Якщо ви бачите безглуздо в підсумках M SK3 перевіряйте результати відмітки з Doclingа першими - підсумовувач може MSC5 не виправити вхідні дані з сміття MSSK6

  5. Низкий - підсумки об 'єму мають захищати мову: Якщо ви МSK1 бачите лише МSK2 dokumentu , такі фрази, як \ "\ врешті-решт \ МSK5\ або "\ в кінцевому підсумку

  6. Алюцинація цитацій: Невеликі LLM (1.5BM SK2B парами МSK3 іноді вигадують правдоподібні МSK4звучні цитати мSK5 Ми підтверджуємо, аналізуючи вихід [chunk-N], підтверджує, що N існує в вихідних шматочках , і позначає або виправляє вимоги, які стосуються бракуючих шматочків M SK2 Якщо ви бачите [chunk-999] для документу 10-chunk, ваш LLM бореться з завданням

Це не баги , а недоліки . МСК1 вони ' є вродженими напруженнями у просторі проектування. . Хороші системи виробництва визнають і зменшують їх.

Практичні погляди

Освіта та щирність зразків

Коли обробляємо дуже великі документи, DocSummarizer не намагається вміщувати все. Це означає, що підсумок базується на зразку

Система обробляє це прозоро:

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

Важливо: Це резюме отриманих доказівМСК0 не гарантує повного МSK1 поширення документу . Коли ми кажемо, МSK3 зразковували 3%", що мSK5 це точно те, що сталося M-, система побачила МСК7 документа і підсумувала, щоМСК8

Вибір не випадковий. - it 's семантичні. Ми використовуємо мультип 'єси МSK1 кластерування заchor, щоб гарантувати, що темами меншості не будуть виокремлені МSK2 excluded . Слуховий мSK4 може пропустити всі обмеження та крайні випадки M. Семантичний МСК6 намагається захопити один відображаючий сегмент з кожної головної теми МПС7 Він МОС8 все ще є частинним поширенням МОС9 але він МУС10 намірно різноманітним частиннім поширенням МБС11

Адаптивне зразок з багатьма тематичними анкерами: Давний МSK1флітер використовує численні анкери (kM SK3сформує стиль кластерування страйдизованого зразка МSK5 для того, щоб гарантувати, що меншітні теми не будуть систематично виокремлені MSC6. Це запобігає упередженості в темах, які домінують МСК8МСК9 там, де один центроід донизу М СК10вважається важливими M СК11но ж, М Ск12рідкісні обмеження, такі як обмеження MСК13 винятки М SК14 або висновки М СК15

Від SegmentExtractor.cs:

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

Це дослідження-інформоване M SK1вихилення одноразового - колапсування зворотнього зв 'язку запросу МSK3 але практичное МSK4 він запускається в секундах на процесорі

Чому б не просто вмонтувати все?

Для документу на сторінці 500- сегменти МSK1 ), вбудова все спрацює, але не є оптимальною

  • ЦінаМSK0 O(NM SK2 вбудова домінує в часі запуску МSK3 На сегментах MСК4 МСК5секМСК6 щоМ СК7s М СК8 секунд лише для введення перед тим, як ви навіть почнете відшукуватиM СК9
  • Якість: Вміщання всього збільшує шум у базі даних для отримання . Ви все ще потребуєте рейтингу МSK2 тож чому вміщувати сегменти, які ніколи не будуть найвищими
  • Практичність: Вказівки на пам 'ять та затримку мають значенняM SK1 Утримка векторівdim в пам' яті і обчисленнях 2,000 МSK3 співпадіння косину на консультацію марнує, коли вам потрібна лише верхня

Мультитаборні зразки -anchor дають вам найкращий результат з усіх двох : широкого спектру тематики з легкою комп 'ютеризацією МSK2

Перетягування в контексті: Чому відтворення не єM SK1 Просто пошук

Те, що я описав раніше - це не лише " відтворення, а й " МSK4. Я називаю це конкретним шаблоном Перетягування обмеженого неясного контексту (CFCD). Розуміння

Більшість підсумовувачів продовжує додавати контекст. DocSummarizer тягне вперед лише те, що виживає детерміністичне відбору, тоді дозволяє моделі плавно писати всередині цих кордонівM SK1

Ось як pipeline DocSummarizer об 'єднується з CFCD.

Концепція CFCD Втілення DocSummarizer МSK2
Розпізнавання язика (фузійнийM SK1 МSK2 Вмонтовані деталі , центроїдна подібність МSK4 TF
Детерміністичне просування MMRM SK1 BM25, РРФ синтезMSC3 верхні частини МSK4 вибір МSK5
Вказівник Згенерований сегмент, впорядкований з ідеями цитати МSK1
Стримана генерація Про prompt синтезу, обмежений отриманими доказами

Чому це важливо?: Модель не вирішує, що для цього потрібне ' МSK3 реакційна труба для отримання інформації. МSK4 Моделі генерують лише плавно в межах, які ми закріпили \ ' \ МSK6 \ Ось чому малі локальні моделі працюють. : \ Анкери роблять важке підтримування \

На практиці "anchor ledger" виглядає ось так.

{
  "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:

  • " Кожна заявка повинна містити включне ID блока "
  • "Якщо програма охоплює
  • "Використовуйте ці терміни послідовноM SK1

Ось чому

  • довші вікна у контексті - це червоний герінг МSK0 проблема в тому, що МSK1 не відповідає більшій кількості текстів, МSK2 це МSK3 вирішує, що заслуговує на виживання
  • Рекурсивне підсумування не працює - він сприймає проміжний вихід як текст , не обмежена структура
  • LLM може't M SK1forget" стимулювати обмеження МСК0 вони ' структурна , не промова, яка може рухатись від

CFCD - це той самий філософський поділ, що і Стримана неясність, Обмежений фузій МСМ, і Підсумовувач зображення - ймовірність запропонує

Кванталізація

Моделі ONNX можуть бути квантовані (знижена precyzія МSK1 для меншого розміру і швидшої гіпотези МSK2 Торгівля - знизується мінімальним втратою якості

Модель МSK1 Полна precyzія МSK2 Квантова величина Різниця у якості мSK4
МSK0 всіМСК1MiniLM-LM SK3vMSC4 ♫ ♫ МSK6МЗБ ♫
МSK0 bge-small-enM SK3vMSC4 MST5 \133MB 34MB

Обробка пакетів і конкуренція

Для великих Documents batch embedding є вирішальним для ефективності InferenceSession в цілому можна вільно ділитися потоками,, але продуктивність залежить від конфігурації сеансу

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 при створення сеансу:

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

Менеджмент пам 'яті для великих документів

Дуже великі документи (новелла, правні документиM SK2 потребують спеціального обробки, щоб уникнути

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

Характеристики ефективності

Реальний-виконка світу на типовій машині-розробнику M SK1Різен 5600X, МSK4ГБ оперативної пам 'яті МSK5 без ҐПУ

Операція МSK1 пропускна спроможність
Вбудова МSK0 ~150 сегментиM SK2сек розмір пакету МSK4 усі M SK5MiniLM-LMska7vMske8 кількісно мSK9
Dense retrieval МSK0 <10ms Схожість косину на сегментах МSK3
Результати: BM25 МSK0 <5ms В МSK3перевернутий індекс пам 'яті
РРФ синтез МSK0 <2ms МSK2 Об 'єднати СМС3 рейтинги СМС4
Закінчити-наM SK1закінчити (25-страница PDFMSC3 МSK1s МSK2 Включно з відрізкомM SK3 вбудоваю, пошукомMSC5 синтезом LLM

Випробовування середовищаМSK0 Різен 5600X МSK2 ядро ), мSK4 ГБ оперативної пам 'яті \ , без ҐПЗ \ МSK6 Усадка використовує все -MiniLM \ мSK8 \ L \ 6- \ v \ एमSK10 \ ( \ квантований | МSK12 \ {\displaystyle 256} \ } макс. символу \ МSK14 \ / 8- \ 'Parallel Batching " Штридовий пакетування (thread parallel batching) \

Головним драйвером є вбудова пропускної спроможності (вибір моделі + довжина токену МSK2 розмір партии МSK3 Перейняття та синтез є по суті безкоштовними мSK4 вони займають мілісекунди M. Це підкріплює принцип МСК6LLM останнійМСК7 МСК8 дешеві процесори працюють MSК9введенняМ СК10 перейняттяМ סК11 перший М СК12 дорогий ЛЛМ працює лише на відфільтрованому контенті МК13

Скалування: Ієрархічне вилучення упорядковує МSK1 документи сторінок МSK2 новелла , інструкції \ ) , обробляючи їх в партіях і зберігаючи лише верхню частину

Підсумок

DocSummarizer показує, що складні NLP можливості не потребують хмарних API чи віддаленості від Pythonу. За допомогою ONNX Runtime для вміщування та Ollama для генерації, ви можете побудувати повну RAG трубку, яка

  • Все працює локально
  • Produces traceable, cited summaries
  • Рукає документи будь-якого розміру
  • Працює без зв 'язку

Найголовніша ідея створення цього інструменту:

  1. Вбудова - це основа - Гарне відтворення залежить від хорошого вмонтування
  2. Гібридний пошук перевершує або самотужки - Поєднати семантику та ліксику для надійності
  3. ММР запобігає повторенню - Різниця така ж важлива, як і важливість
  4. Структурні питання - Увага до структури документів МSK1головкиМСК2 розділиМ СК3 покращує результати
  5. ЛЛМ повинні бути останніми - Спершу робіть дешеву CPU, дорогі LLM працюють лише на відфільтрованому контенті

Дальне читання

Документи та технічні натяки

Подібні глибокі занурення

Згортання серії

Це підсумовує серію DocSummarizer.

Частина 1 пояснює чому цей підхід перевершує наївні LLM виклики. Він охоплює архітектурні шаблони (chunkingM SK2 ієрархічне скороченняМSK3 цитування підтвердженьMSC4 що роблять будь-який документальний сумізер добре працювати

Частина 2 це ваш швидкий-початковий інструкторM SK1Installation,модиMSC3テンプレーтиМSK4звичайні випадки використанняMST5 Якщо ви просто хочете користуватися інструментомМСК6 щоMСК7 це все, що вам потрібно

Частина 3 ( цей artykuł ) - це глибоке занурення для тих, хто хоче зрозуміти як це дійсно працює: BERT проти речення трансформаторів , чому ONNX має значенняM SK2 готування символів МSK3 гібридна пошукова торгівля M SK4 відхилення , і що розбивається в процесі виробництваМSK6

Якщо ви – МСК0 – будуєте власний трубопровод, – МSK1 – читайте всі три частини, – . – якщо ви – MСК3 – користуєтесь лише інструментом, –МSK4 – читаєте частину 2 і, можливо, скиньте секцію про невдачі частини МSK6

Подібне

logo

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