# DocSummarizer-Teil-Dokument RAG Web-Anwendung

<!--category-- AI, LLM, RAG, C#, HTMX, GraphRAG, Semantic Search, DuckDB -->
<datetime class="hidden">2026-01-01T18:00</datetime>

Das ist **Teil** von der DocSummarizer Serie, und es ist auch der Höhepunkt von dem [SchaubildRAG-Reihe](/blog/graphrag-minimum-viable-implementation) und [Semantische Suche](/blog/semantic-search-with-onnx-and-qdrant)Wir kombinieren alles zu einer deployablen Web-Anwendung

> Der Kern ist aber gut gemacht und funktioniert gut.Erwarten Sie Updates in den nächsten Wochen, sobald ich unten aus dem Design.

> **Der ganze Punkt des Aufbaus der RAG-Infrastruktur ist es, sie für etwas reales zu verwenden.**

In den letzten Wochen haben wir'«ve gebaut.

- **DocSummarizer** Einbettungen von ONNX-Dokumenten
- **GraphRAG** Wissensgraphen zur Erkennung von Gemeinschaften
- **Semantische Suche** Hybride Suche mit RRF-Fusion

Jetzt verdrahten wir sie zusammen in **LucidRAG** Eine eigenständige Web-Anwendung für Multi--...Dokument Frage beantworten mit Wissensgraphenvisualisierung...

**Webseite** [lucidrag-.-com](https://lucidrag.com) | **Quelle: Eurostat.** [GitHub](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.RagDocuments)

[TOC]

## Was macht lucidRAG?

Hochladen von Dokumenten Fragen stellen – . Erhalten Sie Antworten mit Zitaten und einem Wissensgraphen, der zeigt, wie Konzepte verbinden –.

**Wesentliche Merkmale**

- **Mehrsprachiges Hochladen des Dokuments** mit Drag---und---Drop
- **Agenturische RAG** Suchdekomposition und Selbstkorrektur -Bereinigt,Begrenzte Deterministisch in der Struktur,Bereinigender Lebenszyklus von Einzelanfragen
- **Visualisierung von Wissensgraphen** Siehe Entity-Beziehungen
- **Beweisansicht** Quelle Zitate auf der Ebene von -
- **Standalone-Einsatz** Ausführbare Einzel- oder Docker

**Konstruktionsbeschränkungen**

- Keine Cloud-Abhängigkeit für Indexierung
- Deterministische Preprocessing-Vorbehandlung (-Cunking-,-Embedding MS K2-Entitätsextraktion )-Empfang
- Rebuildierbarer Vektorzustand aus Quelldokumenten
- Verwendete LLM *Nur* für Antwortsynthese über abgerufene Beweise

> An keinem Punkt sind LLMs für das Chunking verwendet ,, , Embedding , , , Entity-Extraktion , , oder Speicher , nur zur Synthese von Antworten über abgerufene ., zitieren , - , unterstützte Evidenz ,

## Warum Vektorsuche kombinieren?

Vektorsuche allein bricht für bestimmte Abfragetypen zusammen

Suchtyp des Vektor-Suchproblems
|------------|----------------------|----------------|
Wie bezieht sich X auf die Verbindung von Y-?" und | mit docs?
Was ist mit Docker, ?" und |? Graphenquersal aus der Entity?
Globale Zusammenfassungen - | - " - Hauptthemen -?" - | - Gemeinschaftsdetektion

lucidRAG verwendet beide :-Vektoren für Präzision, ,, Graphen für Kontext, ., Graphinabfragen sind tief, MSC3, begrenzt, MSV4,max, Hops, MKS6 und Scoped, um ungebundenes Traversal bei großen Corpora. zu verhindern.

## Architektur im Überblick

Die App schichtet drei Projekte aus, die wir bereits gebaut haben. **[StyloFlow](/blog/styloflow-signal-driven-workflows)** Eine signalgesteuerte Workflow-Engine (MSK)

```
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
```

**Warum StyloFlow-?** Anstelle von hardcodierten Pipelines ist jede Verarbeitungsstufe eine "-Wave, die Signale ausstrahlt, wenn ihre Triggerbedingungen mit , übereinstimmen und parallele Ausführung ermöglicht. [StyloFlow-:-Signal---Driven Workflow Orchestration](/blog/styloflow-signal-driven-workflows) für Details zur Implementierung .

## Die Verarbeitungspipeline

Wenn Sie ein Dokument hochladen, fließt es durch drei Stufen.

### Stage -1:-Upload und Warteschlange

Der Upload-Endpunkt validiert die Datei, berechnet einen Content Hash zur Deduplizierung und stellt sie für die Hintergrundbearbeitung bereit.

```csharp
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
```

Die Schlüssel-Insights... wir haben zuerst..., spare later... das verhindert, dass die Bearbeitungszeit bei doppelten Uploads verschwendet wird.

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

### Stage-Empfang und Einbettung

Der Hintergrund-Prozessor nimmt Schlange Dokumente und läuft sie durch DocSummarizer.

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

Diese einzelne Linie macht eine Menge Arbeit [DocSummarizer Teil](/blog/building-a-document-summarizer-with-rag)):

- Parsen Sie die Dokument-Struktur
- Aufgeteilt in semantische Stücke unter Beachtung von Überschriften
- ONNX-Embeddings für jeden Block generieren
- Vektoren in DuckDB mit HNSW-Indizierung speichern

### Stufe 3:-Entity Extraction

Wir extrahieren Entitäten mit GraphRAG-''s heuristischem Ansatz.

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

Dies verwendet IDF-Scoring und strukturelle Signale anstatt per---Cunk LLM-Aufrufe. [GraphRAG-Teil](/blog/graphrag-minimum-viable-implementation) für Details

## Gebundene Kanäle für Rückdruck

Eine naive Implementierung würde ungebundene Warteschlangen verwenden, die auslaufen könnten.-von -...Gedächtnisabstürzen während des Hochladens von Fluten.

```csharp
private readonly Channel<DocumentProcessingJob> _queue =
    Channel.CreateBounded<DocumentProcessingJob>(new BoundedChannelOptions(100)
    {
        FullMode = BoundedChannelFullMode.Wait
    });
```

Wenn die Warteschlange nach oben füllt `Wait` Modus blockiert neue Schreibvorgänge, bis der Raum öffnet... Wir fügen einen Timeout hinzu, damit Benutzer einen eindeutigen Fehler bekommen, anstatt zu hängen.

```csharp
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---Dokument Timeouts

Große Dokumente können Minuten dauern, um zu verarbeiten..-Aber ein feststeckendes Dokument sollte die gesamte Warteschlange blockieren.MSC2-Jedes Dokument erhält seinen eigenen Timeout.

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

Das verknüpfte Token stellt sicher, dass wir immer noch die Abschaltung der Anwendung respektieren, während das Hinzufügen des per---Dokuments Limits

## Fortschrittliche Kanalreinigung

Jedes Verarbeitungsdokument erhält einen Fortschrittskanal für SSE-Updates, aber wenn ein Benutzer seinen Browser schließt, wird ,, dass Kanal verwaist wird... Wir verfolgen Erstellungszeiten und reinigen regelmäßig

```csharp
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;
}
```

A `PeriodicTimer` ruft dies alle -15-Minuten im Hintergrundprozessor auf

## Lagerung: :, DuckDB, PostgreSQL, /, SQLite

Wir verwenden zwei Datenbanken für verschiedene Zwecke

**PostgreSQL-/-SQLite-Kern** speichert Dokument-Metadaten, was vorhanden ist, ,.Verarbeitungsstatus, ,. Beziehungen, MKK3. Diese Daten sind langlebig und abfragebar...

**EnteDB** speichert Vektoren und das Entity-Graph ,. , It , ' , ephemeral , - , Sie können es von Quelldokumenten wieder aufbauen Diese Trennung bedeutet Vektor speichern Korruption doesns , ' , t verlieren Ihr Dokument Inventar ,. ,

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

## Die Chat-API

Fragen fließen durch die agentische Suchpipeline.

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

Der Suchdienst behandelt Abfragedekomposition, wenn nötig, und synthetisiert dann eine Antwort.

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

## Die UI-:-HTMX-Studie Alpine-MSC2-Js

Die Benutzeroberfläche ist eine einzelne Seite mit Dokumenten auf der linken Seite.

```
┌──────────────────┬─────────────────────────────────────┐
│  📁 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 verwaltet State-;-HTMX behandelt Aktualisierungen der Dokumentenliste

```javascript
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;
        }
    };
}
```

## Demo-Modus

Für öffentliche Bereitstellungen wie lucidrag-.-com.,-Demomodus deaktiviert Uploads und verwendet vor dem Download-MSC2-loaded content.MSC3-Demos-Modus existiert, um öffentliche Deployments sicher zu machen, ,-deterministisch und billig ohne spezielle --case-Codepfade:

```csharp
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";
}
```

A `DemoContentSeeder` background service beobachtet das Content-Verzeichnis und verarbeitet alle heruntergelassenen Dateien

```csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    if (!_config.DemoMode.Enabled) return;

    await SeedExistingContentAsync();
    StartFileWatcher(_config.DemoMode.ContentPath);
}
```

Dies ermöglicht es Ihnen, Demo-Inhalte zu aktualisieren, indem Sie einfach Dateien kopieren.

## LucidRAG läuft

### Standalone Keine Abhängigkeiten)

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

Verwendet SQLite lokal Enten-DB `http://localhost:5080`.

### Docker

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

## Was eigentlich läuft

]|-Komponente - Quelle - Zweck - | -
|-----------|--------|---------|
Dokument-Parsing - DocSummarizer - PDF - , DOCX-, - Markdown - |
Einbetten von ONNX-Embeddings für |, DocSummarizer für lokale |, keine API-Tasten für |,
Entitätsextraktion - GraphRAG - IDF - Struktursignale
- Hybride Suche - beides - |BM25B+BERT mit RRF - |B
|-Async-Verarbeitung |-Neue gebundene Kanäle ,-Timeouts |-
Web-Benutzeroberfläche Neue Version von | HTMX Alpine Version von.

### Kosten

**Null API-Kosten** Für die Indexierung sind Embeddings ONNX.,. Entitäten heuristisch. Sie zahlen nur für LLM-Synthese zur Abfragezeit, und das funktioniert mit lokalen Ollama...

## Verwandte Artikel

- [DocSummarizer Teil Architektur](/blog/building-a-document-summarizer-with-rag)
- [DocSummarizer Teil 4-----RAG Pipelines](/blog/docsummarizer-rag-pipeline)
- [GraphRAG Teil Umsetzung](/blog/graphrag-minimum-viable-implementation)
- [Semantische Suche mit ONNX](/blog/semantic-search-with-onnx-and-qdrant)