Tämä on Osa 4 DocSummarizer -sarjasta. Katso Osa 1 arkkitehtuuria varten, Osa 2 CLI-välineelle, tai Osa 3 syvennykseen sisällytteisiin.
RAG:n kova osa on ', ei "LLM" vaan .. Se on kaikki ennen LLM:tä.
Olette luultavasti nähneet mallin.
Se' on paljon infrastruktuuria ennen kuin kirjoitatte yhden ainoan soveltamiskoodin rivin
DocSummarizer.Core käsittelee kaikkea yhdessä paketissa - on saatavilla sekä .NET- että nodeksi
DocSummarizer.Kore on pohjimmiltaan asiakirjan tiedustelutaso: määritelmällinen rakenne ensinM SK1 todennäköinen jäljittäminen toiseksi . Se ratkaisee tiedonlähestymisongelman, niin että voitte keskittyä johdonmukaiseen ongelmaan
Tässä on se, mitä DocSummarizer tekee: - ja kriittisesti , ei't Do:
flowchart TB
subgraph INPUT["Input (Your Document)"]
DOC[/"PDF / DOCX / Markdown / HTML / URL"/]
end
subgraph DOCSUMMARIZER["DocSummarizer.Core (Deterministic)"]
direction TB
PARSE["Parse & Structure"]
SEGMENT["Segment by Semantics"]
EMBED["Generate Embeddings<br/>(ONNX - Local)"]
SCORE["Compute Salience"]
CITE["Assign Citation IDs"]
PARSE --> SEGMENT
SEGMENT --> EMBED
EMBED --> SCORE
SCORE --> CITE
end
subgraph OUTPUT["Output (ExtractionResult)"]
SEGMENTS[/"Segments[]<br/>• Original text (verbatim)<br/>• float[384] embedding<br/>• Salience score<br/>• StartChar / EndChar<br/>• Section context"/]
end
subgraph YOURS["Your Code"]
STORE[("Vector Store<br/>(Qdrant / pgvector / etc)")]
end
subgraph QUERY["Query Time (Later)"]
Q["User Question"]
RETRIEVE["Retrieve Top-K"]
LLM["LLM Synthesis"]
ANS["Answer + Citations"]
Q --> RETRIEVE
RETRIEVE --> LLM
LLM --> ANS
end
DOC --> PARSE
CITE --> SEGMENTS
SEGMENTS --> STORE
STORE --> RETRIEVE
style DOCSUMMARIZER stroke:#27ae60,stroke-width:3px
style YOURS stroke:#3498db,stroke-width:2px
style QUERY stroke:#9b59b6,stroke-width:2px
style LLM stroke:#e74c3c,stroke-width:2px
Mitä DocSummarizer tekee?
Mitä teette (blue boxM SK1
Mitä tapahtuu kysymyksen aikana (purple boxM SK1
Keskeinen näkemys: LLM ( punainen raja) osallistuu vain kysymyksen aikanaM SK2 syöttyminen on täysin deterministista - sama asiakirja tuottaa aina samoja segmenttejäMSC4 Tämä tekee RAG:sta toistuvaa ja parannuskeinoista riippumatonta
Reprodussibility bonus: Deterministinen siirto merkitsee sitä, että voitte tarkistaa RAG-putkensa uudelleen ja debugoida sen kuten minkä tahansa muun rakentavan artifactin tavoin.
RAG,:n osalta haluatte todelliset lauseet asiakirjoistanne - ei LLM-generoituja johdanto-osan kappaleitaM SK2 Kun käyttäjä kysyy " mitä sopimuksessa sanotaan lopettamisestaMSC4 teidän on saatava takaisin todellinen sopimustekstiMST5 ei sen yhteenvetoMSV6
LLM tulee myöhemmin, kysymyksen aikana, kootakseen vastauksen löydetyistä chunkista . Mutta itse chunkilla pitäisi olla sananmukainen lähdeaineistoMSC3 SeM SK4, mikä tekee viittauksista merkityksellisiä
DocSummarizer's ExtractSegmentsAsync antaa teille täsmälleen tämän: alkuperäisten tekstisegmenttien sisällyttämisen kanssa
Tässä on se, mitä saatte yhdestä dotnet add package:
dotnet add package Mostlylucid.DocSummarizer
Ei Pythonia. Ei ulkoisia API:itaM SK1 Ei monimutkaista järjestelyä . Ongelma on poissa ensimmäisen mallin laadinnon jälkeen
Yksinkertaisin käyttötapa - ekstrahoida segmentit sisällyttämällä ne valmiiksi vektoritehtaalle
using Microsoft.Extensions.DependencyInjection;
using Mostlylucid.DocSummarizer;
// Setup DI
var services = new ServiceCollection();
services.AddDocSummarizer();
var provider = services.BuildServiceProvider();
var summarizer = provider.GetRequiredService<IDocumentSummarizer>();
// Extract segments with embeddings
string markdown = File.ReadAllText("document.md");
var extraction = await summarizer.ExtractSegmentsAsync(markdown);
foreach (var segment in extraction.AllSegments)
{
Console.WriteLine($"[{segment.Type}] {segment.SectionTitle}");
Console.WriteLine($" ID: {segment.Id}");
Console.WriteLine($" Salience: {segment.SalienceScore:F2}");
Console.WriteLine($" Embedding: float[{segment.Embedding?.Length}]");
Console.WriteLine($" Text: {segment.Text[..Math.Min(80, segment.Text.Length)]}...");
}
Tuotanto:
[Heading] Introduction
ID: a1b2c3d4e5f6g7h8_h_0
Salience: 0.85
Embedding: float[384]
Text: This document describes the architecture of our new microservices platform...
[Sentence] Introduction
ID: a1b2c3d4e5f6g7h8_s_1
Salience: 0.72
Embedding: float[384]
Text: The system is designed to handle 10,000 requests per second with sub-100ms...
Se's itM SK1 Ei järjestelyä, ei viivytyksiä , ei mielipiteitäMSC4 vain segmentit, joihin sisällytetään lisäyksiä ja alkuperää
Jokainen segmentti's Id rakennetaan asiakirjan ID:stä sekä tyyppistä ja indeksistä: {docId}_{type}_{index}.
Voitte antaa oman asiakirjanne ID:n, tai antaa DocSummarizerin laskea yhden sisällöstä.
// Option 1: Provide your own ID (useful for tracking documents in your system)
var extraction = await summarizer.ExtractSegmentsAsync(markdown, documentId: "contract-2024-001");
// Segments get IDs like: contract_2024_001_s_0, contract_2024_001_h_1, ...
// Option 2: Auto-generated from content hash (default)
var extraction = await summarizer.ExtractSegmentsAsync(markdown);
// Segments get IDs like: a1b2c3d4e5f6g7h8_s_0, a1b2c3d4e5f6g7h8_h_1, ...
// Same document = same hash = same IDs (deterministic)
Miksi tämä on tärkeää RAG:
[s42] aina samaan alkuperäiseen tekstiinJokainen ekstrahoitu segmentti sisältää kaiken, mitä RAG: tarvitsee
public class Segment
{
string Id; // Unique ID: "mydoc_s_42" (for citations)
string Text; // The actual content
SegmentType Type; // Sentence, Heading, ListItem, CodeBlock, Quote, TableRow
int Index; // 0-based order in document
// Source location tracking
int StartChar; // Character offset where segment starts
int EndChar; // Character offset where segment ends
int? PageNumber; // Page number (for PDFs)
int? LineNumber; // Line number (for text/markdown)
// Section context
string SectionTitle; // "Introduction" - immediate heading
string HeadingPath; // "Chapter 1 > Introduction > Overview"
int HeadingLevel; // 1-6 (heading depth)
// Computed during extraction
float[] Embedding; // 384-dim vector (default model)
double SalienceScore; // 0-1 importance score
string ContentHash; // Stable hash for citation tracking across re-indexing
// For retrieval (set during query)
double QuerySimilarity; // Similarity to the query
double RetrievalScore; // Combined score: similarity + salience
string Citation { get; } // Auto-generated: "[s42]", "[h3]", etc.
}
Euroopan unionin Id on avain viittausten seurantaan. Kun LLM-tulokset [s42], voit ratkaista sen takaisin täsmälliseen alkuperäpaikkaan käyttäen StartChar/EndChar.
Bonus: Euroopan unionin ExtractionResult mukaan lukien viittauspäätöstä koskevat avustajamenetelmät:
var extraction = await summarizer.ExtractSegmentsAsync(markdown);
// Fast O(1) lookups
var segment = extraction.GetSegment("mydoc_s_42");
var segmentByIdx = extraction.GetSegmentByIndex(42);
// Find segment at a character position
var segmentAtPos = extraction.GetSegmentAtPosition(5432);
// Get all segments on page 5 (for PDFs)
var pageSegments = extraction.GetSegmentsOnPage(5);
// Get source location for highlighting
var location = extraction.GetSourceLocation("mydoc_s_42");
// Returns: StartChar, EndChar, LineNumber, PageNumber, SectionTitle, HeadingPath
// Extract highlighted text with context
var highlight = extraction.GetHighlightedText(originalMarkdown, "mydoc_s_42", contextChars: 50);
Console.WriteLine(highlight.ToHtml()); // <span class="highlight">...</span>
Console.WriteLine(highlight.ToMarkdown()); // **...**
DocSummarizer antaa teille sisällytykset. Käytä mitä tahansa vector-tietokantaa, jota haluatteM SK1
var points = extraction.AllSegments.Select((s, i) => new PointStruct
{
Id = (ulong)i,
Vectors = s.Embedding,
Payload =
{
["text"] = s.Text,
["section"] = s.SectionTitle,
["salience"] = s.SalienceScore,
["segment_id"] = s.Id,
["start_char"] = s.StartChar,
["end_char"] = s.EndChar
}
}).ToList();
await qdrantClient.UpsertAsync("documents", points);
foreach (var segment in extraction.Segments)
{
await connection.ExecuteAsync(
@"INSERT INTO documents (segment_id, text, heading, salience, embedding)
VALUES (@id, @text, @heading, @salience, @embedding::vector)",
new {
id = segment.Id,
text = segment.Text,
heading = segment.SectionTitle,
salience = segment.SalienceScore,
// NOTE: String interpolation is for demo simplicity only.
// For production, use NpgsqlParameter with Vector type for better
// performance and to avoid culture-dependent decimal separators.
embedding = $"[{string.Join(",", segment.Embedding)}]"
});
}
Ette halua hallinnoida erillistä tietokantaa? DocSummarizer sisältää kolme backend-järjestelmää
services.AddDocSummarizer(options =>
{
// In-memory (fastest, no persistence)
options.BertRag.VectorStore = VectorStoreBackend.InMemory;
// DuckDB (embedded file-based, default)
options.BertRag.VectorStore = VectorStoreBackend.DuckDB;
// Qdrant (external server)
options.BertRag.VectorStore = VectorStoreBackend.Qdrant;
options.Qdrant.Host = "localhost";
options.Qdrant.Port = 6334;
});
Useimmat RAG-järjestelmät epäonnistuvat ei siksi, että sisällytykset ovat huonoja, vaan koska kaikkia chunkia pidetään yhtä tärkeinä.
flowchart LR
subgraph DOC["Document"]
H1["# Title"]
P1["First paragraph<br/>(intro)"]
H2["## Methods"]
P2["Technical details..."]
P3["More details..."]
H3["## Results"]
P4["Key findings here"]
H4["## Appendix"]
P5["Reference data..."]
end
subgraph SCORES["Salience Scores"]
S1["0.95"]
S2["0.85"]
S3["0.70"]
S4["0.65"]
S5["0.60"]
S6["0.80"]
S7["0.30"]
end
H1 --> S1
P1 --> S2
H2 --> S3
P2 --> S4
P3 --> S5
P4 --> S6
P5 --> S7
style S1 stroke:#27ae60,stroke-width:3px
style S2 stroke:#27ae60,stroke-width:2px
style S6 stroke:#27ae60,stroke-width:2px
style S7 stroke:#e74c3c,stroke-width:2px
Lyhyesti sanottu lause on tärkeämpi kuin liitteessä oleva.
// Get the top 20% most salient segments
var topSegments = extraction.Segments
.OrderByDescending(s => s.SalienceScore)
.Take((int)(extraction.Segments.Count * 0.2));
Salienssitekijät:
| tekijöistä | vaikutuksesta | ||||||
|---|---|---|---|---|---|---|---|
| Pysymys | AlkuvaiheM SK2päätelmäsanat ovat parempia SSK3 | ||||||
| otsikon läheisyys | Ensimmäiset lauseet otsikoiden jälkeen ovat aiheesimerkit SSK2 | ||||||
| Pituus | hyvin lyhyeet osat | (< | 3 | pisteitä | ) | rangaistaan | |
| Pääluokkatyyppi | Lyhyesti ottaenM SK2Täytäntöönpanoa lisättiin, ViittauksetMSC4Lisäys vähennetty SSK5 | ||||||
| Luettelo sisällöstä |
Tämä merkitsee, että pyynti voi painottaa (similarity * salience) päinvastoin kuin vain samankaltaisuus.
DocSummarizer auto-detects document type using heuristics on the content
var extraction = await summarizer.ExtractSegmentsAsync(markdown);
// Document type detected from content
Console.WriteLine($"Type: {extraction.DocumentType}"); // Technical, Narrative, Legal, etc.
Console.WriteLine($"Confidence: {extraction.Confidence}"); // High, Medium, Low
Luokittelu vaikuttaa hakuun: Dokumenttien entropin mukaiset jäljittämislähestysskaalitM SK1 ei ole hardcoded TopK. Narratiiviset asiakirjat ( kuvitteelliset jäljitetyt jäljitestävät jäljittämättömät jäljitysluvut, koska niille tarvitaan enemmän kontekstiaMSC7 Selkeän rakenteen tekninen asiakirja tarvitsee vähemmän
Heuristiikka tarkastelee::
Jos heuristiikka on epävarmaa, DocSummarizer voi vapaaehtoisesti palata nopeaan LLM-luokitteluun käyttäen M SK1sentinel" malliaMSC3 Tämä edellyttää, että Ollama toimii paikallisesti pienellä mallilla, kuten tinyllama. Sovittelun avulla
services.AddDocSummarizer(options =>
{
options.Ollama.BaseUrl = "http://localhost:11434";
options.Ollama.Model = "tinyllama";
});
// Then use with LLM fallback enabled
var extraction = await summarizer.ExtractSegmentsAsync(markdown, useLlmFallback: true);
Useimmissa asiakirjoissa , yksinomaan heuristiikka on riittävän tarkkaa - LLM-valvomus on olemassa reunatapauksissa
Tässä on täydellinen indeksi: - ja -, kysyntäputki, ., rakennetaan kolmessa vaiheessa
sequenceDiagram
participant User
participant App as Your App
participant DS as DocSummarizer
participant VS as Vector Store
participant LLM
Note over DS: INGESTION (No LLM)
App->>DS: ExtractSegmentsAsync(markdown)
DS->>DS: Parse structure
DS->>DS: Split into segments
DS->>DS: Generate embeddings (ONNX)
DS->>DS: Compute salience
DS-->>App: ExtractionResult
App->>VS: Store segments + vectors
Note over LLM: QUERY TIME (LLM involved)
User->>App: "What about X?"
App->>DS: EmbedAsync(question)
DS-->>App: float[384]
App->>VS: Search(vector, topK=5)
VS-->>App: Top segments
App->>LLM: Question + Context
LLM-->>App: Answer with [citations]
App-->>User: Answer
public class SimpleRagService
{
private readonly IDocumentSummarizer _summarizer;
// In-memory segment store - maps "docId:segmentId" to the full segment
private readonly Dictionary<string, ExtractedSegment> _segments = new();
// In-memory vector index - pairs of (id, embedding vector)
private readonly List<(string Id, float[] Vector)> _index = new();
Tuotteessa olette käyttänyt todellista vektoritietokantaa: (Qdrant, pgvector, jne.),, mutta tässä esitetään ydinmuodon
public async Task IndexAsync(string markdown, string docId)
{
// Extract segments with embeddings - this is where DocSummarizer does the work
var extraction = await _summarizer.ExtractSegmentsAsync(markdown);
// Store each segment and its vector
foreach (var segment in extraction.Segments)
{
// Composite key: document + segment for citation tracking
var id = $"{docId}:{segment.SegmentId}";
// Keep the full segment for retrieval
_segments[id] = segment;
// Add to vector index for similarity search
_index.Add((id, segment.Embedding));
}
}
Huomautus: ei liity LLM-järjestelmäänM SK1 me 'varmistamme tosiasiallinen asiakirjan teksti, ei yhteenvedotM SK1
public async Task<string> QueryAsync(string question, int topK = 5)
{
// Embed the question using the same model as documents
// This ensures vectors are in the same space
var embedding = await _summarizer.EmbedAsync(question);
// Find top-K most similar segments
var results = _index
.Select(x => (x.Id, Similarity: CosineSimilarity(embedding, x.Vector)))
.OrderByDescending(x => x.Similarity)
.Take(topK)
.Select(x => _segments[x.Id])
.ToList();
// Build context with citation markers
// The LLM can reference [chunk-3] and we can trace it back
var context = string.Join("\n\n", results.Select(s =>
$"[{s.SegmentId}] {s.Text}"));
return context; // Send this + the question to your LLM
}
Palautettu konteksti sisältää todelliset asiakirjan teksti segmenttitietoja sisältävillä merkinnöillä. LLM-kysymys voi näyttää siltä
Answer the question based on the following context.
Cite sources using the [chunk-N] markers.
Context:
{context}
Question: {question}
private static float CosineSimilarity(float[] a, float[] b)
{
float dot = 0, normA = 0, normB = 0;
for (int i = 0; i < a.Length; i++)
{
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (MathF.Sqrt(normA) * MathF.Sqrt(normB));
}
}
DocSummarizer sisältää VectorMath.CosineSimilarity() jos ette halua kirjoittaa tätä itse'
DocSummarizer paljastaa myös IEmbeddingService suoraan, jos on tarpeen sisällyttää kysymykset erikseen koko yhteenvetomenettelyyn.
Ennakolta on AllMiniLmL6V2 - nopea , pieni, hyvä laatuM SK3 Omien tarpeidenne mukaan valitse
services.AddDocSummarizer(options =>
{
options.Onnx.EmbeddingModel = OnnxEmbeddingModel.BgeBaseEnV15;
});
| Malli | Dims | Max Tokens M | Suuruus H | Huomautukset K |
|---|---|---|---|---|
AllMiniLmL6V2 |
||||
BgeSmallEnV15 Paras laatu/kokosuhteet M SK8 |
||||
BgeBaseEnV15 |
768 | |||
JinaEmbeddingsV2BaseEn |
768 |
Mallit auto-download from HuggingFace on first use. Subsequent runs load from diskM SK2
Valvotte RAG-putkenne tuotantoa:
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("Mostlylucid.DocSummarizer")
.AddSource("Mostlylucid.DocSummarizer.Ollama")
.AddSource("Mostlylucid.DocSummarizer.WebFetcher")
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddMeter("Mostlylucid.DocSummarizer")
.AddMeter("Mostlylucid.DocSummarizer.Ollama")
.AddMeter("Mostlylucid.DocSummarizer.WebFetcher")
.AddPrometheusExporter());
Keskeiset mittarit:
docsummarizer.summarizations - Pyynnöt lasketaandocsummarizer.summarization.duration - Prosessiaika msdocsummarizer.document.size - Asiakirjakokodocsummarizer.ollama.embed.requests - API-puhelujen sisällyttäminenDocSummarizer käsittelee useita asiakirjamuotoja älykkään havaitsemisen ja käsittelyn avulla.
Nämä muodot käsitellään alkuperäisesti - ei tarvita asiakirjojen laatimista tai muita palveluja
| Formatti | Laajentaminen | Jalostus | ||||
|---|---|---|---|---|---|---|
Markdown .md, .markdown |
Parsoitu Markdigin kanssaM SK1 rakenne säilyy | |||||
| Selkeä teksti | .txt, .text |
Siirretään kohdalta (double newlinesM SK2 | ||||
HTML .html, .htm |
SanitoituM SK1 muutettu merkitseväksi | |||||
ZIP-arkistot .zip |
Extracts text files, autoM SK2detects Gutenberg-format |
Selkeä teksti saa älykkään käsittelyn: Jos ei ole merkitseviä otsikoita, chunker siirtyy kohdaksi -perustainen jakaminen M SK3 Se tunnistaa asiakirjan rakenteen heuristisesti.
// Plain text works the same way
var plainText = File.ReadAllText("notes.txt");
var extraction = await summarizer.ExtractSegmentsAsync(plainText);
// Chunks split by paragraphs, embeddings generated
PDF:n osalta , DOCXM SK1 PPTX, XLSXMSC3 ja kuvat (OCRMST5 - liittää asiakirjat SSK7
docker run -d -p 5001:5001 quay.io/docling-project/docling-serve
services.AddDocSummarizer(options =>
{
options.Docling.BaseUrl = "http://localhost:5001";
});
// PDF, DOCX, PPTX, images all work
var pdfBytes = await File.ReadAllBytesAsync("document.pdf");
var extraction = await summarizer.ExtractSegmentsAsync(pdfBytes, "document.pdf");
Laatiminen säilyttää asiakirjan rakenteen - otsikotM SK1 taulukot , luettelot tulevat läpi asianmukaisena merkitsemisenä. Tämä tarkoittaa parempaa chunkointia kuin raakatekstien extracointiaMSC4
| Formatti | Laajentaminen | Huomautukset | |||||
|---|---|---|---|---|---|---|---|
.pdf |
Teksti + järjestys säilytetäänM SK2 taulukoita muutetaan SSK3 | ||||||
| Word | .docx |
Täysin muotoiltavaM SK1 otsikot, luettelot | |||||
PowerPoint .pptx |
Slides become sections | ||||||
| Excel | .xlsx |
Esitettyjä taulukoita | |||||
Kuvat .png, .jpg, .tiff |
OCR Doclingin välityksellä |
Ks. Osa 1 lisätietoja asiakirjan laatimiseen liittyvästä integroimisesta, tai Multi-Format-asiakirjan kääntäminen syveen uimiseen.
| Ongelma | DocSummarizerin ratkaisu |
|---|---|
| Semanttinen rajan ylitys | Luettelot otsikoistaM SK2 ryhmiin liittyvä sisältö SSK3 |
| Tokenointi kutakin mallia varten | |
| Sisällyttäminen pakettiin | Asennettavissa oleva pakettikokoM SK2 muisti-tehokas |
| Viittausseuranta | Jokainen segmentti on ainutlaatuinen SegmentId |
| Formattimuutos | Markdown, HTMLMSC3 PDFM SK4 DOCX yhden API:n välityksellä |
| mallin laatiminenM SK1caching | Auto ~/.docsummarizer |
RAG-putket tarvitsevat infrastruktuuria ennen mielenkiintoista osaa. DocSummarizerM SK1Core antaa teille:
dotnet add package Mostlylucid.DocSummarizer
Vetytys on tehty. Rakennetaan RAG-sovellusta.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.