DocSummarizer Part 4 - Byggnader RAG Pipelines
Det här är Del 4 av DocSummarizer-serien. Se Del 1 för arkitekturen, Del 2 för CLI-verktyget, eller Del 3 för djupdykning på inbäddar.
Den hårda delen av RAG är
Du har antagligen sett mönstret.
- Dokuments解析are för varje format
- Skrivningslogik som respekterar semantiska gränser
- Tokenisering som matchar din inbäddade modell
- Batchning för effektiv embedding generering
- Salience scoring så att inte alla bitar behandlas som lika
- Citationsspårning så att du vet var svar kom från
Det är en massa infrastruktur innan du skriver en enda rad applikationskod.
DocSummarizer.Core hanterar allt det i ett enda paket - tillgängliga för både .NET och Node
DocSummarizer.Core är i grund och botten dokumentinformation-lagret: deterministisk struktur först , sannolikhetslärmningslärmning sedan M SK2 Det löser informationen - ingenjörsproblemet så att man kan fokusera på resonemanget
Intagningskedjan
Här är vad DocSummarizer gör. doesn'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
Vad gör DocSummarizer?
- analyserar dokument som bevarar strukturen
- Splitter i semantiska segment (niet godtyckliga chunks)
- Genererar inbäddar lokalt med ONNX
- Beräknar saliensscore
- Spegla citationsidentifikationer med teckenpositioner
Vad gör du? (bla låda):
- Speichera segment i din vektordatabas
- Att bygga din söklogi
Vad händer vid frågatid (purple box):
- Inbädda frågan ( Samma modell)
- Ta upp liknande segment
- Senda till LLM för syntetisering
Nyckelinsikten: LLM: n ( röda gränsen ) är inblandad endast vid querytimet M SK2 Ingestion är helt deterministisk - samma dokument producerar alltid samma segmenter MSC4 Det är vad som gör RAG återskapbar och lösbar
Fortplantningsbonus: Deterministisk ingestion innebär att du kan göra om dig i RAG-röret som något annat byggande artefakt.
Varför ingen LLM för ingestion
För RAG, vill du ha faktiska meningar ur dina dokument. - inte LLM, utan -, skapade parafraser.
LLM kommer senare, ,, vid sökningens gång, ,, för att syntetisera ett svar från uppfunna chunks. Men de enda chunken borde vara bokstavligt källmaterial. ., det är det som gör citat meningsfulla.
DocSummarizer's ExtractSegmentsAsync ger dig exakt detta: de ursprungliga textsegmenten med inbäddar , redo för att få tag på. Ingen LLM involverad i ingestionenM SK3
Världsförståndet
Här är vad du får med en dotnet add package:
dotnet add package Mostlylucid.DocSummarizer
- Smart segmentering - Splitter på opskrifte , respekterar semantiska gränser
- Inbäddar för ONNX - Lokalt , inga API-klyter, modeller automatisktM SK3download
- Saliens scoring - Innan -beräkningsbarhet för varje segment
- Citationsspårning - Varje segment har en unik ID
- Flera format - Markdown, ,, HTML, ,, PDF, M SK3, DOCX, MST4, med dokumentation, Mst5
- Vektorlagningsoptioner - InMemory
Inget Python. Inga externa APIs. Ingen komplicerad konfigurationM SK2 Fungerar offline efter den första modelldownloaden .
Extrahera segmenter: Core-API
Det enklaste sättet att använda fallet - extrahera segment med inbäddar redo för din vektoraffär
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)]}...");
}
utgång:
[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...
Det där' det är det . Inget orkestering , inga instruktioner, , inga åsikter, M SK4 bara segment med embeddings och provenance, . Klara för din vektordatabas
Dokument IDs
Varje segment Id är konstruerad från ett dokument ID plus typ och index: {docId}_{type}_{index}.
Du kan ge din egen dokument ID, eller låta DocSummarizer bearbeta en från innehållet hash:
// 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)
Varför är detta viktigt för RAG:
- Custom IDs låter dig korelera segment med ditt dokumenteringssystem
- innehåll-hash IDs försäkra att re-indexering av samma dokument ger identiska segment IDs
- Båda tillvägagångssätten stödjer stabila citationer -
[s42]löser alltid till samma källtext
Vad är ' i en segment?
Varje extraherat segment innehåller allt du behöver för RAG:
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.
}
Den Id är nyckeln för citationsspårning. När din LLM utgår [s42], kan du lösa det tillbaka till den exakta källplatsen genom att StartChar/EndChar.
Bonus: Den ExtractionResult inkluderar hjälpmedel för citationslösning:
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()); // **...**
Att pumpa in i vektorbutiker
DocSummarizer ger dig inbäddar. Använd vilken vektordatabas som helst
Qdrant
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);
PostgreSQL + pgvector
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)}]"
});
}
Eller använda Built-in Stores .
Don' vill inte sköta en separat databas ? DocSummarizer innehåller tre baksidor
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;
});
Varför är ödmjukhet viktigt
De flesta RAG-system misslyckas inte för att inbäddar är dåliga, utan för att alla chunks behandlas som lika viktiga.
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
En mening i abstraktionen är viktigare än en i appendixen.
// Get the top 20% most salient segments
var topSegments = extraction.Segments
.OrderByDescending(s => s.SalienceScore)
.Take((int)(extraction.Segments.Count * 0.2));
Salensfaktorer:
| Faktor | |
|---|---|
| Pozition | IntroM SK2 slutsatsen får högre poäng |
| Rubriksnärahet | De första meningarna efter rubrikerna är ämnena meningar |
| Längslängd | Väldigt korta segment ♫ ♫ (< ♫ |
| Sektionstyp | Syntaxen/Introduction upphöjtM SK3 Referenser |
| innehållstyp |
Det betyder att din extraheration (similarity * salience) istället för enkel likhet
Dokument klassificering
DocSummarizer automatiskt- upptäcker dokumenttyp med hjälp av helistik på innehållet
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
Klassifiering påverkar sökningen: Uppfinningsdjuur skalor med dokumententropin , inte en hårdkoderad TopK. Berättande dokument M SK3 fiktion , berättelser MSC5 får en 1.5x ökning till uppfinningstalet eftersom de behöver mer kontext
Heuristiken ser ut på
- Kodblockfrekvens (technischtM SK1
- Rubriksstruktur ( akademisk , tekniskM SK2
- Dialoogmönster (berättelse)
- Juridisk terminology (kontrakter)
- Densitet i listan (dokumentation
Om hanuristiken är osäkerhetsbar, kan DocSummarizer optionellt gå tillbaka till en snabb LLM-klassificering med hjälp av "sentinel tinyllama. Aktivera det via konfiguration
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);
För de flesta dokumenten , är enbart heuristik tillräckligt noggrann | - | LLM fallback finns där för randskador
Ett fullt RAG-pipeline exempel
Här är en komplett index.
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
steg 1: Datastrukturerna
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();
I produktionen använder du en verklig vektordatabas.
steg 2: Indekseringsdokument
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));
}
}
Nota : inga LLM inblandade. Vi verkliga dokument text , inte sammanfattningar.
steg 3: Fråga
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
}
Den återlämnade kontexten innehåller verkliga dokumenttext med segment IDs. Din LLM prompt kan se ut som
Answer the question based on the following context.
Cite sources using the [chunk-N] markers.
Context:
{context}
Question: {question}
Math (Standardkosine likhet)
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 inkluderar VectorMath.CosineSimilarity() om du inte vill skriva det här själv
DocSummarizer visar också IEmbeddingService direkt om du behöver lägga in sökningar separat från hela sammanfattningskedjan.
Embedding modeller
Standard är AllMiniLmL6V2 - snabbt , litet, bra kvalitetM SK3 Välj utifrån dina behov
services.AddDocSummarizer(options =>
{
options.Onnx.EmbeddingModel = OnnxEmbeddingModel.BgeBaseEnV15;
});
| Modell | Dimm | МSK2 | Max tokens | Storlek | ||||
|---|---|---|---|---|---|---|---|---|
AllMiniLmL6V2 |
384 | |||||||
BgeSmallEnV15 |
||||||||
BgeBaseEnV15 |
||||||||
JinaEmbeddingsV2BaseEn |
768 |
Modeller automatiskt -download från HuggingFace på första användning. Efteråt kör laddad data från disken.
OpenTelemetry
Att övervaka din RAG-torn i produktion:
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());
Nyckelmåttet:
docsummarizer.summarizations- Request countsdocsummarizer.summarization.duration- Processeringstid i msdocsummarizer.document.size- Dokumentens storlekardocsummarizer.ollama.embed.requests- Embedding av API-sändningar
Format Support
DocSummarizer hanterar flera dokumentformat med smart detektering och bearbetande.
Direkt bearbetning ( Inga yttre beroenden
De här formaten bearbetas på ett inhemskt sätt - inga dokumentering eller andra tjänster behövs :
| Format | Omlängning | Bearbetande | ||
|---|---|---|---|---|
| Markdown | .md, .markdown |
analyserad med Markdig, struktur bevarad | ||
| Enkel text | .txt, .text |
Splittring efter punkter (dubbla nya linjer ) | ||
| HTML | .html, .htm |
Saniterad, omvandlad till Markdown | ||
| ZIP-Archiv | .zip |
Extraherar textfiler , automatiskt - upptäcker Gutenbergformatet |
Enkel text får smart handling: När det inte finns markeringsrubriker , byter chunker till paragrafen M SK2 baserat splittring . Det detekterar dokumentstrukturen heuristiskt. ♫ - om din text har tydliga paragrafbrytningar ♫, ♫ så blir de chunkgränserna ♫
// Plain text works the same way
var plainText = File.ReadAllText("notes.txt");
var extraction = await summarizer.ExtractSegmentsAsync(plainText);
// Chunks split by paragraphs, embeddings generated
Rika dokument med dokumentering
För PDF , DOCX, PPTX, XLSXM SK3 och bilder Mska4OCRMske5 Mska6 kan man lägga till dokumentation
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");
Dokling bevarar dokumentstrukturen. - opskrifte , tabeller , listor går igenom som korrekta markering.
| Format | Omlängning | Notar | ||
|---|---|---|---|---|
.pdf |
Text + layout bevarad, omvandlade tabeller | |||
| Orde | .docx |
Den fulla formateringen | ||
| PowerPoint | .pptx |
Slider blir sektioner | ||
| Excel | .xlsx |
Talar extraherade | ||
| Bilder | .png, .jpg, .tiff |
OCR via dokumentation |
Se Del 1 för mer information om Docling integration, eller Multi-Format Dokument omvandling för en djupdyk.
De hårda delar du hoppar över
| Problem | DocSummarizer lösning | ||
|---|---|---|---|
| Spelning vid semantiska gränser | |||
| Tokenisering per modell | Använd rätt tokenisering för varje ONNX-modell | ||
| Embedding batching | Konfigurerad batch-storlek, minneM SK3effektivt | ||
| Citationsspårning | Varje segment blir unik SegmentId |
||
| Konvertering av format ♫ | ♫ Markdown ♫ , ♫ HTML ♫, ♫ PDF ♫ , ♫ DOCX via en enda API ♫ | ||
| ModelldownloadM SK1 kassering | Auto-downloads ~/.docsummarizer |
sammanfattning
RAG-rör behöver infrastruktur innan den intressanta delen kommer.
- Segment med inbäddar - Klar för vilken vektorbutik som helst
- Salience: s poäng - Alla chunks är inte lika
- Citationsspårning - Vet var svar kommer ifrån
- Lokala-first - Inga API-keys , kör offline
- Tillverkningstelemetri - OpenTelemetry inbyggd
dotnet add package Mostlylucid.DocSummarizer
Sampling är gjort. Att bygga din RAG app.
Related Articles
DocSummarizer-serien
- Del 1 - Arkitektur | & | Patterns - Varför rörsledningsmetoden fungerar
- Del 2 - Genom att använda verktyget - CLI installering , moder M SK2 templates
- Del 3 - avancerade koncept - BERT-integreringar , ONNX, hybridsökning
- Del 5 - DoomSummarizer: djup forskning - Multi
RAG Deep Dives
- RAG Primer - Grundslagna koncept för att få fram data - ökad generation
- RAG arkitektur - Systemdesignsmönster för RAG
- Hybrid sökande och indexering - Kombinerar tät och sparse extraktion
- RAG praktiska tillämpningar - Verkliga-world användarfall
GraphRAG
- GraphRAG: kunskapsgrafer för RAG - När vektorsökningen inte är sufficient
- GraphRAG Minimum Viable Implementation - Att bygga ett fungerande GraphRAG-system
Related Tools
- Semantiska sökningar med ONNX och Qdrant - byggstenarna
- Self-Hosted Vector Databases: Qdrant - Running Qdrant lokalt
- Att analysera stora CSV-Dater med lokala LLM - Samma mönster , olika domain
- Att hämta och analysera webb innehåll med LLMs - Web-skrapping för RAG
- Multi-Format Document Conversion Service - Docling djupdykning