هذا هو الجزء 5 من سلسلة DocSummarizer , و هو ' أيضاً الذروة من سلسلة GraphRAG و سلسلة البحث الدلالي. نحن' ندمج كل شيء في تطبيق على الانترنت قابل للتطبيق
🚨🚨 أRTICLE PREVIEW 🚨🚨 مازلت أعمل على بعض اللاصق وإضافة الخصائص. لكن الجوهر قد تم عمله و يعمل بشكل جيدM SK3 نتوقع تحديثات خلال الأسابيع القليلة القادمةMSC4 ستكون في lucidRAGMST5comMst6 سأقوم بإضافة شاشات الشاشة هنا بمجرد أن أخرج التصميمM st8
الفكرة الأساسية لبناء بنية تحتية RAG هي استخدامها لشيء حقيقي
خلال الأسابيع القليلة الماضية
الآن نربطهم معاً في lucidRAG - تطبيق ويب مستقل لعدة -إجابات على أسئلة مستندة مع رسم بياني للمعرفة
الموقع الإلكتروني: lucidrag.com | المصدر: GitHub
تحمّل الملفات. أسأل الأسئلة. تحصل على الإجابات مع الاستشهادات ورسم بياني للمعرفة يوضح كيف ترتبط المفاهيمM SK2
الخصائص الرئيسية:
قيود التصميم:
في أي نقطة لا تستخدم الـ LLMs لتجزئة .
البحث الفيكتوري لوحده يكسر بعض أنواع الشؤال:
| نوع الសំណួរ | مشكلة البحث فيكتوري | حل الرسوم | |||
|---|---|---|---|---|---|
| متقاطعة-وثيقة | " كيف يرتبط X بالـ YM SK4 ♫ | ترابط الكيانات عبر الملاحظات | |||
| الكيان- مركزي | " ماذا عن الدوكرM SK4 ♫ | تمرير الرسم البياني من الكيان МSK6 | |||
| ملخصات عالمية | "المواضيع الرئيسية?" ♫ | الكشف المجتمعي МSK5 |
يستعمل lucidRAG كلاً من الـ: و , و الرسوم البيانية . و الأسئلة البيانية هي عمق- محدودة( اعظمي" ♫2" متداخلة) و مخصصة للقيام بالبحث في المستجدات لمنع التنقل دون حدود على المجسمات الكبيرة
طبقة التطبيق ثلاثة مشاريع قمنا ببنائها بالفعل تدفق Stylo - إشارة
lucidRAG
├── Controllers/Api/ # REST endpoints
├── Services/ # Business logic
│ ├── DocumentProcessingService # Wraps DocSummarizer
│ ├── EntityGraphService # Wraps GraphRAG
│ └── Background/ # Async queue processing (StyloFlow waves)
└── Views/ # HTMX + Alpine.js UI
لماذا StyloFlow? بدلاً من خطوط أنابيب موزعة بدقة , كل مرحلة المعالجة هي " موجه ," التي تبعث إشارات , . الموجات تعمل عندما تتطابق ظروف التنبيه ., تسمح بالتنفيذ المتوازن . StyloFlow:Signal-Driven Workflow Orchestration من أجل تفاصيل التنفيذ.
عندما تقوم بتحميل مستندة , تتدفق خلال ثلاث مراحل :
النقطة النهائية للتحميل تتحقق من الملف , تقوم بحساب هاش المحتوى لفك النسخ , وتصطفه لعمليات الخلفية
public async Task<Guid> QueueDocumentAsync(Stream fileStream, string fileName)
{
// Compute hash to detect duplicates
var contentHash = ComputeHash(fileStream);
var existing = await _db.Documents
.FirstOrDefaultAsync(d => d.ContentHash == contentHash);
if (existing != null)
return existing.Id; // Already processed
الفكرة الرئيسية: نخترق أولاً, نحفظ لاحقاً . هذا يمنع هدر وقت المعالجة عند تحميلات متكررةM SK3
// Save to disk, create DB record
var docId = Guid.NewGuid();
await SaveFileToDiskAsync(fileStream, docId, fileName);
// Queue for background processing
await _queue.EnqueueAsync(new DocumentProcessingJob(docId, filePath));
return docId;
}
المعالج الخلفي يلتقط الوثائق المصطفة ويشغلها من خلال DocSummarizer:
var result = await _summarizer.SummarizeFileAsync(job.FilePath, progressChannel);
هذه الخطة الواحدة تفعل الكثير من العمل (انظر أجزاء DocSummarizer 1):
بعد تجزئة , نستخرج الكائنات باستخدام GraphRAG ' المقاربة الهيuristics
var segments = await _vectorStore.GetDocumentSegmentsAsync(documentId);
var entityResult = await _entityGraph.ExtractAndStoreEntitiesAsync(documentId, segments);
هذا يستخدم تقييم IDF و الإشارات الهيكلية بدلاً من إتصالات -chunk LLM - أنظر GraphRAG Part 2 للتفصيل.
التنفيذ البديهي سوف يستخدم صفوف غير محدودة
private readonly Channel<DocumentProcessingJob> _queue =
Channel.CreateBounded<DocumentProcessingJob>(new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.Wait
});
عندما يملأ الصفوف, Wait يقف نظام الكتابة الجديدة حتى يتم فتح المساحة. نضيف وقتاً متأخراً بحيث يحصل المستخدم على خطأ واضح بدلاً من أن يعلق
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeoutCts.CancelAfter(TimeSpan.FromMinutes(5));
try {
await _queue.Writer.WriteAsync(job, timeoutCts.Token);
} catch (OperationCanceledException) when (!ct.IsCancellationRequested) {
throw new InvalidOperationException("Queue full. Try again later.");
}
الوثائق الكبيرة يمكن أن تستغرق دقائق من المعالجة.
while (!stoppingToken.IsCancellationRequested)
{
var job = await _queue.DequeueAsync(stoppingToken);
// 30-minute timeout per document
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
timeoutCts.CancelAfter(TimeSpan.FromMinutes(30));
try {
await ProcessDocumentAsync(job, timeoutCts.Token);
} catch (OperationCanceledException) when (!stoppingToken.IsCancellationRequested) {
await MarkDocumentFailedAsync(job.DocumentId, "Processing timed out");
}
}
الرموز المترابطة تضمن أننا لازلنا نحترم إيقاف تطبيقنا بينما نضيف الحد لكل - "document limit"
كل وثيقة معالجة تحصل على قناة تقدم ل تحديثات الSSE. لكن إذا أغلق أحد المستخدمين شاشتهم في منتصف الـ-upload, فإن ذلك المسار يصبح ناضجًا
private readonly ConcurrentDictionary<Guid, ProgressChannelEntry> _progressChannels = new();
public int CleanupAbandonedChannels()
{
var cutoff = DateTimeOffset.UtcNow - TimeSpan.FromHours(1);
var cleaned = 0;
foreach (var kvp in _progressChannels.Where(x => x.Value.CreatedAt < cutoff))
{
if (_progressChannels.TryRemove(kvp.Key, out var entry))
{
entry.Channel.Writer.TryComplete();
cleaned++;
}
}
return cleaned;
}
أ PeriodicTimer يطلب هذا كل 15 دقائق في المعالج الخلفي.
نستخدم قاعدة بيانات لأغراض مختلفة:
PostgreSQL/SQLite (EF CoreM SK2 يحفظ بيانات المجازة للوثيقة - ما هو موجود , حالة المعالجة, العلاقاتM SK3 هذه البيانات مستدامة وقابلة للبحث
DuckDB يحفظ الفيكتورات والرسم البياني للكائنات..
// Metadata in PostgreSQL
public class DocumentEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ContentHash { get; set; }
public DocumentStatus Status { get; set; }
}
// Vectors in DuckDB (managed by DocSummarizer)
// Entities in DuckDB (managed by GraphRAG)
تتدفق الأسئلة من خلال خط البحث الأجذري:
[HttpPost]
public async Task<IActionResult> ChatAsync([FromBody] ChatRequest request)
{
// 1. Get or create conversation for memory
var conversation = await GetOrCreateConversationAsync(request.ConversationId);
// 2. Search with hybrid retrieval
var searchResult = await _search.SearchAsync(request.Query, new SearchOptions
{
TopK = 10,
IncludeGraphData = request.IncludeGraphData
});
خدمة البحث تتعامل مع تحلل الطلب إذا كان ذلك ضروريا, ثم تقوم بتحليل الإجابة:
// 3. Generate answer with LLM
var answer = await _summarizer.SummarizeAsync(
request.Query,
searchResult.Segments,
new SummarizeOptions { IncludeCitations = true });
// 4. Save to conversation history
await SaveToConversationAsync(conversation.Id, request.Query, answer);
return Ok(new ChatResponse
{
Answer = answer.Text,
Sources = answer.Citations,
GraphData = searchResult.GraphData
});
}
الواجهة هي صفحة واحدة مع مستندات على اليسار
┌──────────────────┬─────────────────────────────────────┐
│ 📁 Documents │ 💬 Chat │
│ ───────────── │ [Answer] [Evidence] [Graph] │
│ [+ Upload] │ │
│ 📄 api-docs.pdf │ Q: How does auth work? │
│ 📝 readme.md │ A: JWT tokens stored... [1][2] │
│ ───────────── │ │
│ 🕸️ Graph: 168 │ ┌─────────────────────────────┐ │
│ │ │ Ask about your documents... │ │
└──────────────────┴──┴─────────────────────────────┴───┘
Alpine.js يدير الحالة; HTMX يعالج تحديثات قائمة الوثائقM SK2
function ragApp() {
return {
messages: [],
isTyping: false,
async sendMessage() {
const query = this.currentMessage.trim();
this.messages.push({ role: 'user', content: query });
this.isTyping = true;
const result = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ query })
}).then(r => r.json());
this.messages.push({
role: 'assistant',
content: result.answer,
sources: result.sources
});
this.isTyping = false;
}
};
}
بالنسبة للتطبيقات العامة مثل lucidrag.com, tryb العرض يحجب تحميلات ويستخدم ما سبقM SK2محتويات تم تحميلهاMSC3 نظام العرض موجود لجعل تطبيقات العامة آمنةMST4منطقيّة MST5 و رخيصة بدون خاصيةM ST6طريق رمزية صغيرةM st7
public class DemoModeConfig
{
public bool Enabled { get; set; } = false;
public string ContentPath { get; set; } = "./demo-content";
public string BannerMessage { get; set; } = "Demo Mode: Pre-loaded RAG articles";
}
أ DemoContentSeeder خدمة الخلفية تراقب مجلد المحتوى وتعالج أي فايل سقطة:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (!_config.DemoMode.Enabled) return;
await SeedExistingContentAsync();
StartFileWatcher(_config.DemoMode.ContentPath);
}
هذا يسمح لك بتحميل المحتوى التجريبي ببساطة عن طريق نسخ الملفات - لا حاجة لإعادة البداية
dotnet run --project Mostlylucid.RagDocuments -- --standalone
يستخدم SQLite + DuckDB محليا. Open http://localhost:5080.
services:
lucidrag:
build: .
ports: ["5080:8080"]
depends_on: [postgres, ollama]
| مكون | مصدر | هدف | ||||||
|---|---|---|---|---|---|---|---|---|
| تحليل الوثائق | DocSummarizer | PDFM SK3 DOCX, العلامة المنخفضة | ||||||
| مداخلات الـONNX | DocSummarizer | محلية, لا توجد مفاتيح API | ||||||
| إستخراج الكائنات | GraphRAG | IDF | + | الإشارات الهيكلية | ||||
| البحث الهجين | كلاهما S | BM25 + بريت مع RRF M | ||||||
| معالجة غير متجانسة | 新 | القنوات المحدودة | МSK3 | وقفات زمنية | ||||
| UI للويب | جديد | HTMX | + | Alpine | . | js |
صفر من تكاليف API من أجل التصنيف - المداخلات هي ONNX, الكائنات هيوريسيةM SK2 تدفع فقط لsynthesis LLM في وقت الاستطلاع , وهذا يعمل مع Ollama المحلية
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.