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
Tuesday, 30 December 2025
هذا هو الجزء 4 لسلسلة DocSummarizer. أنظر الجزء 1 للعمارة, الجزء 2 لأداة CLI, أو الجزء 3 للغوص العميق على المركبات.
الجزء الصلب من RAG هو ' هو "LLM" . هو كل شيء قبل "LLS"
ربما رأيتم النمط
ذلك ' هو الكثير من البنية التحتية قبل أن تكتب سطر واحد من شفرة التطبيق .
DocSummarizer.Core يعالج كل ذلك في حزمة واحدة - متاحة لكلا .NET و العقدةM SK2js.
DocSummarizer.Core هو أساسا طبقة ذكاء الوثائق: الهيكلات الإفتراضية أولاً, الاستخراج الإحتمالي ثانياً . يحل مشكلة المعلوماتM SK3الهندسة بحيث يمكنك التركيز على مشكلة التفسير
هنا ' ما يفعله DocSummarizer - و بشكل حرج لا '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
ما يفعله DocSummarizer (صندوق أخضر):
ما تفعله (صندوق أزرق):
ما الذي يحدث في وقت الاستفسار (صندوق أزرق):
رؤية أساسية: LLM (حدود حمراء) يتدخل فقط في وقت الاستفسارM SK2 التدخين هو محدد تماماً - نفس الملف دائماً ينتج نفس الأجزاءMSC4 هذا ما يجعل RAG قابلة للتكرار وقابلة للحل
بونس التكاثر: الإمتصاص المعرفي يعني أنه يمكنك إعادة - "index" , " diff" , "and debug your RAG pipeline like any other build artifact" "No prompt variance" "no model temperature" "only pure"
من أجل RAG, تريد جمل حقيقية من مستنداتكم - ليس LLM- الاقتباسات المولدةM SK2 عندما يسأل أحد المستخدمين " ماذا يقول العقد عن التخلي عن العقدMSC4 تحتاج إلى الحصول على النص الحقيقي للتعاقد
يأتي الـ LLM لاحقاً ــ , ــ في وقت البحث ــ, ــ لتحليل الإجابة من القطعة التي تم استخلاصها ـــ . ــ لكن القطعة نفسها يجب أن تكون مصدراً حرفياً ـــ. ــ ذلك ــ МSK4 ــ هو ما يجعل الإقتباسات ذات معنى
DocSummarizer's ExtractSegmentsAsync يعطيك هذا بالضبط: أجزاء النص الأصلية مع المداخلات, جاهزة للإستخراجM SK2 لا يوجد LLM مشارك في الإمتصاص
ها هو ما تحصل عليه مع واحدة dotnet add package:
dotnet add package Mostlylucid.DocSummarizer
لا برمجة Python. لا واجهات API خارجية. لا تركيب معقدM SK2 يعمل على أرض الواقع بعد تحميل النموذج الأول .
أبسط حالة إستخدام - استخرج Segments with embeddings ready for your vector store
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)]}...");
}
ال sortie:
[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...
ذلك 'ه هو . لا ترسيم ,, لا إشارات , , لا رأي .- فقط أجزاء مع المداخلات و provenance .
كل جزء 's Id تم بناءه من ID للوثيقة بالإضافة إلى النوع والمؤشر: {docId}_{type}_{index}.
يمكنك أن تقدم ID الخاص بك للوثيقة , أو تسمح DocSummarizer بحساب واحد من المحتوى 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)
لماذا هذا يهم RAG:
[s42] دائماً يحل إلى نفس النص المصدركل جزء تم استخراجه يحتوي على كل ما تحتاجه لـ 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.
}
الـ Id هو المفتاح لمتابعة الاستشهادات. عندما يصدر LLM [s42], يمكنك حلها مرة أخرى إلى موقع المصدر بالضبط باستخدام StartChar/EndChar.
بونس: الـ ExtractionResult يحتوي على طرق مساعدة لحل الإقتباس
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 يعطيك المداخلات. استخدم أي قاعدة بيانات فيكتورية تحبها.
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)}]"
});
}
لا تريد إدارة قاعدة بيانات مستقلة
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;
});
معظم أنظمة RAG تفشل ليس لأن التداخلات سيئة, ولكن لأن كل الجسيمات تعتبر مهمة بنفس القدر.
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
الجملة التجريدية هي أكثر أهمية من الجملة المضافة.
// Get the top 20% most salient segments
var topSegments = extraction.Segments
.OrderByDescending(s => s.SalienceScore)
.Take((int)(extraction.Segments.Count * 0.2));
عوامل ال Salience:
| عامل | تأثير | |||
|---|---|---|---|---|
| وظيفة | مقدمة | / | جمل الإختبار تعطي علامة أعلى | |
| قرب العنوان | أول جملة بعد العناوين هي جملة موضوعية | |||
| طول | أجزاء قصيرة جدًا (< 80 تُعاقب اللوحات S) | |||
| نوع الفصول | خلاصة / تحسن في التقديم , مرجعية /تقلص في الملحق | |||
| نوع المحتوى | بلاك الرمز, اقتباساتM SK3 القوائم الموزعة بشكل مختلف |
هذا يعني أن إستخراجك يمكنه أن يزن (similarity * salience) بدلا من التشابه
DocSummarizer automatic-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
التصنيف يؤثر على البحث: مقاييس عمق البحث مع إنتروبيا الوثائق , ليس رمزاً صلباً TopK | . | Documents Narrative |( | Fiction | , | stories | МSK5 | get a 1.5 | boost to retrieval count because they need more context
نظرة الهيوريستيك على :
إذا كانت الهرسية غير مؤكدة , DocSummarizer يمكن أن يرجع بشكل اختياري إلى ترتيب سريع لـ LLM باستخدام "sentinel" نموذج .. هذا يتطلب من أولاما العمل محليا مع نموذج صغير مثل tinyllama. أمكنه من خلال الإعداد
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);
بالنسبة للكثير من الوثائق , هلuristics وحدها هي دقيقة بما فيه الكفاية - هناك تراجع LLM لقضايا الحواف
هنا "MSC0" هو المؤشر الكامل "MSc1" و "MScs2" خط أنابيب الطلب "MSec3" نحن "MSCS4" سنبنيه بثلاثة خطوات
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();
في الإنتاج، ستستخدمون ' قاعدة بيانات векторية حقيقية (QdrantM SK2 pgvector, وهلم جراMSC4 لكن هذا يظهر النمط الأساسيMNK5
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));
}
}
ملاحظة: لا وجود لـ LLM . نحن ' نقوم بحفظ حقيقة نص документа, لا خلاصات.
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
}
المحتوى الذي أردناه يحتوي على حقيقية نص وثائقي مع إختبارات القطع. قد تبدو سؤال LLM الخاص بك كالتالي
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 يحتوي على VectorMath.CosineSimilarity() إذا كنت لا تريد أن تكتب هذا بنفسك
DocSummarizer يكشف أيضاً IEmbeddingService مباشرة إذا كنت بحاجة إلى دمج الطلبات منفصلا عن خط التحليل الكامل.
الលំនាំដើម هو AllMiniLmL6V2 -سرعةM SK1 صغيرة, جودة جيدة . إختيار بناء على إحتياجاتكMSC4
services.AddDocSummarizer(options =>
{
options.Onnx.EmbeddingModel = OnnxEmbeddingModel.BgeBaseEnV15;
});
| النماذج | يضيئ | إرقام اعظمية | الحجم | ملاحظات | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
AllMiniLmL6V2 |
384 ≥ | ≤256 | ~23 MB | លំនាំដើម MSC7 سرعة MSC8 | |||||||
BgeSmallEnV15 |
384 ≥ | \512 | ♪~34 | MB | أفضل نوعية ♪ / | نسبة الحجم ♪ | |||||
BgeBaseEnV15 |
768 | ||||||||||
JinaEmbeddingsV2BaseEn |
768 ≥ | \8192 | ♪~137 | MB | سندات طويلة |
نماذج اتومات-download from HuggingFace on first useM SK1 Subsequent runs load from disk.
مراقبة أنابيب RAG في الإنتاج:
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());
القياسات الرئيسية:
docsummarizer.summarizations - حساب الطلباتdocsummarizer.summarization.duration - وقت المعالجة في msdocsummarizer.document.size - الحجم للوثائقdocsummarizer.ollama.embed.requests - إدخال مكالمات APIDocSummarizer يتعامل مع العديد من صيغ الوثائق بكشف و معالجة ذكية.
هذه الصيغ تُعالج بفطرة - لا حاجة للدواسة أو الخدمات الأخرى :
| الصيغة | الإختصار | معالجة | |||
|---|---|---|---|---|---|
| العلامة المنخفضة | .md, .markdown |
تمت تحليلها مع ماركديج, تم الحفاظ على الهيكل | |||
| النص البسيط | .txt, .text |
تقسم بال odstavات ( خطوط جديدة مضاعفة) | |||
| HTML | .html, .htm |
مصحوبة, حولت إلى Markdown | |||
| آرشيف ZIP | .zip |
يستخرج فايلات نصية, تلقائياM SK2 يكتشف شكل غوتينبيرغ |
نص بسيط يحصل على معالجة ذكية: عندما لا توجد مخطوطات تشير إلى النزول, يتحول المجزأ إلى الفقرة - تم تقسيمها على أساسه M SK3 يكشف عن هيكل الوثائق بشكل عام - إذا كان نصك يحتوي على وقفات فقرية واضحة MSC5 تلك تصبح حدود المجزء
// 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, DOCX, PPTXM SK2 XLSXMSC3 وصور (OCRMska5 Mske6 أضف الملاحظات
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");
تحتفظ الملاحظات بتركيب الوثائق - العلاماتM SK1 الجداول , قائمة تأتي من خلال علامة مناسبة. هذا يعني أن أفضل تكديس من إستخراج النص الخام
| الصيغة | الإختصار | ملاحظات | |||
|---|---|---|---|---|---|
.pdf |
النص + تم الحفاظ علىレイアウトه, حولت الجداول | ||||
| كلمة | .docx |
الصيغة الكاملة, العلاماتM SK2 القوائم | |||
| PowerPoint | .pptx |
تتحول الشرائح إلى أجزاء | |||
| Excel | .xlsx |
الجداول المستخرجة | |||
| الصور | .png, .jpg, .tiff |
رسم بياني عبر الطباعة |
أنظروا الجزء 1 لمعرفة المزيد عن دمج دوكلينج, أو العديد من -تحويل الصيغة للឯកសារ للغوص العميق.
| مشكلة | حل DocSummarizer |
|---|---|
| تدوير في الحدود الدلالية | تقسيمات على الأشرطة, مجموعة المحتوى المرتبطة |
| توكيزة لكل نموذج | يستخدم توكيزا صحيحة لكل جيل ONNX |
| تداخل الخزبة | حجم الخزعة قابلة للضبط, الذاكرةM SK3 فعال |
| تتبع الاقتباس | كل جزء يحصل على فريد من نوعه SegmentId |
| تحويل الصيغ | العلامة المنخفضة , HTMLM SK3 PDF, DOCX من خلال API واحد |
| تحميل النموذج/ تخزين المخازن | أوتوماتMSC3downloadsM SK4 مخازن في ~/.docsummarizer |
أنابيب RAG تحتاج إلى البنية التحتية قبل الجزء المثير
dotnet add package Mostlylucid.DocSummarizer
تم التوصيل بالماء . بناء تطبيق RAG الخاص بك .
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.