DocSummarizer Part 5 - lucidRAG: MultiM SK3Document RAG Web Application (Українська (Ukrainian))

DocSummarizer Part 5 - lucidRAG: MultiM SK3Document RAG Web Application

Thursday, 01 January 2026

//

8 minute read

Це Частина 5 з серії DocSummarizer – ,, а це – МSK1. Це також вершина Серії GraphRAG і Серія Semantic Search. Ми ' поєднаємо все в веб-приложений, який можна застосувати

🚨🚨 АКТУАЛЬНЫЙ АRTICLE МSK1 Ми все ще працюємо над деякими кінками та додаємо додаткиM SK2 Але ядро зроблено і працює добре. Сподіваюсь на поновлення протягом наступних кількох тижнівMSC4 Це буде на lucidRAGMST5comMst6 ЯM stsк7 Я додам скриншоти тут, як тільки я вичерплю дизайнMSt8

Весь сенс створення інфраструктури РАG - використати її для чогось реального.

За останні кілька тижнів ми створили

  • DocSummarizer - Дослідження документівM SK1 семантичне відокремлення, Вбудова на NX
  • GraphRAG - Extraction Entity , knowledge graphsM SK2 community detection
  • Семантичні пошуки - BMM SK1 + Гібридний пошук BERT з синтезом РРФ

Тепер ми підключимо їх до lucidRAG - автономна веб-аplikaція для багатостороннього -розпитів на питання з допомогою візуалізації графів знань

Вебсайт: lucidrag.com | Источник: GitHub

Що робить lucidRAG

завантажити документи. Задавати питанняM SK1 отримати відповіді з цитатами та графом знань, що показує, як концепти пов 'язані

Головні характеристики:

  • Завантаження документу Multi- з перетягуванням
  • Агентська РАГ - Декомпозиція та самовідтворення запиту МSK1 Koreкція МSK2 обмежені , детерміністична структура
  • Візуалізація графів знань - Погляньте на взаємозв 'язки podmiotів
  • Погляньмо на докази - реченняM SK1за citations на рівні
  • Саморозгортання - Одноразовий інтерактивний або Докер

Дизайнові обмеження:

  • Немає залежності від хмар для індексування
  • Детерміністичне попереднє оброблення ( підштовхування МSK1 введення МSK2 видобуток об 'єктів )
  • Перетворити векторний стан з вихідних документів
  • Використововані LLM лише для синтезу відповідей за отримані докази

В жодному випадку LLM не використовується для відокремлення, введення, вилучення об 'єктівM SK2 або зберігання MSC3 лише для синтезу відповідей над отриманимиMSL4 цитатаMKL5 підтверджений доказMCL6

Чому об 'єднати пошук векторів + Графи знаньM SK1

Вektorний пошук розривається лише для певних типів запитів:

Тип запиту МSK1 Проблема з пошуком векторів МSK2 Вигадка графа
КрестніM SK1документ МSK2 МSK3Як X пов 'язано з Y?" | Об' єднання об 'єктів між Docs
Об 'єктM SK1центричний МSK3 Як щодо Докера?" | Перехід графу від об' єкта МSK6
Глобальні підсумки МSK1 МSK2Главні темиM SK3 ♫ Детективність в суспільстві ♫ МSK5 ♫

lucidRAG використовує як вектори : для точності, так і графи , для контексту, МSK2 Попити на графи мають глибину

Уявлення про архітектуру

Ця програма поєднує три проекти, які ми вже збудували: StyloFlow - сигнал - двигун робочого потоку МSK2

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? Замість закодованих трубок , кожен етап обробки - це МSK1 хвиля МSK2, яка випромінює сигнали . хвиле запускаються тоді, коли умови їх триггерування співпадають | МSK4 | що дає можливість паралельного виконання | ( | вмонтування |+ | видобуток об 'єктів StyloFlow: Сигнель-Організація потоку роботи для подібних деталей.

Переробка трубопроводу

Коли ви завантажуєте документ, він проходить через три етапи:

Стадія 1: Завантаження і черга

Даний кінцевий пункт завантаження підтверджує файл, обчислює хаш контенту для відокремленняM SK1 і ставить його в чергах для обробки фона:

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

Найголовніша ідея: ми хашуємо першими , заощаджуємо пізніше МSK2 Це запобігає марнуванню часу на обробку подібних завантажень

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

Стадія 2: Заряди та вбудова

Пізніше обробник підбирає заліковані документи і запускає їх через DocSummarizer:

var result = await _summarizer.SummarizeFileAsync(job.FilePath, progressChannel);

Ця одна лінія робить багато роботи (гляньте DocSummarizer Part 1):

  • Розшифруйте структуру dokumentu (PDF, DOCXM SK2 Markdown )
  • Розділити на семантичні шматочки стосовно заголовків
  • Створити вбудовані додатки для кожного шматка
  • Поміщайте вектори в DuckDB з індексуванням HNSW

Стадія Extraction Entity 3:

Після відокремлення, ми виділяємо об 'єкти, використовуючи геріатичний підхід GraphRAG '

var segments = await _vectorStore.GetDocumentSegmentsAsync(documentId);
var entityResult = await _entityGraph.ExtractAndStoreEntitiesAsync(documentId, segments);

Це використовує IDF оцінку та структурні сигнали, а не за один -chunk LLM дзвінок - patrz GraphRAG - частина 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.");
}

Per-Timeouts документу

Великі документи можуть займати хвилини, щоб їх обробити. Але застряглий документ має не блокувати всю чергу ' each document gets its own timeout МSK3

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

Поєднаний символ гарантує, що ми все ще дотримуємося замкнутої програми, додаючи limit для документу per-document limitM SK1

Процедура очистки каналів

Кожен документ, що обробляється, отримує канал прогресу для оновлення SSE. Але якщо пользователь закриває свій браузер всерединіMSC1upload, цей канал стає сиротоюM SK3 Ми відстежуємо час створення і регулярно очищаємоМSK4

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 хвилини в фоновому процесорі

Storage: DuckDB + PostgreSQLM SK2SQLite

Ми використовуємо дві бази даних для різних цілей:

PostgreSQL/SQLite (EF CoreM SK2 зберігає метадані dokumentu - те, що існує , статус обробки МSK2 відносини

Дук-ДБ зберігає вектори і граф об 'єкту. Це ' ефемерний M SK2 ви можете побудувати його з джерельних документів. Це відокремлення означає, що уektorи зберігають коррупцію не

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

API чату

Питання протікають через канал агентичного пошуку:

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

Поиск регулює декомпозицію запиту, якщо необхідно, потім синтезує відповідьM SK1

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

UI: HTMX + AlpineM SK2js

В інтерфейсі є одна сторінка з документами ліворуч, чат праворучМSK1

┌──────────────────┬─────────────────────────────────────┐
│  📁 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.comM SK1 режим демонстрації блокує завантаження та використовує до-завантаження контентуMSC3 tryb демонстрації існує для того, щоб зробити громадські застосування безпечнимиMST4 детерміністичнимиM ST5 і дешевими без спеціальногоM st6 кодових шляхівMst7

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

Це дає вам змогу оновити зміст демонстрації, просто копіюючи файли - не потрібно перезапускатиM SK1

Працюючий lucidRAG

Самотній (Не має жодних залежностіM SK1

dotnet run --project Mostlylucid.RagDocuments -- --standalone

Використовуючи SQLite + DuckDB локально. Open http://localhost:5080.

Докер

services:
  lucidrag:
    build: .
    ports: ["5080:8080"]
    depends_on: [postgres, ollama]

Що насправді відбувається

МSK0 Komponenт Źródło МSK2 Задача
Дослідження Dokumentів МSK1 DocSummarizer PDFM SK3 DOCX, Замітка МSK5
вбудова на NX DocSummarizer МSK2 локальний
Видобуток об 'єктів
Гібридні пошуки МSK1 Обидві МSK2 BMM SK3 + BERT з РРФ мSK5
Асинхове оброблення МSK1 Нові МSK2 Замкнуті канали , відхилення часу
Веб інтерфейс МSK1 Новое МSK2 HTMX + Альпіjski MSК4 js МСК5

Ціна

Нуль هزینه API для індексування - вбудова - ONNX , об 'єкти - эвристичні МSK2 Ви платите тільки за синтез LLM в часі запиту МSK3 і це працює з локальною Олламі

Подібні статті

Finding related posts...
logo

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