# DocSummarizer Part 5 - lucidRAG: MultiM SK3Document RAG Web Application

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

Tämä on **Osa 5** DocSummarizerin sarjasta , ja se ' on myös huippukokonaisuus [GraphRAG-sarja](/blog/graphrag-minimum-viable-implementation) ja [Semanttinen hakusarja](/blog/semantic-search-with-onnx-and-qdrant). Me' yhdistämme kaikki yhteen sovellettavissa olevaan web-sovelluseen

> 🚨🚨 Tarkistus ARTICLE 🚨🚨 Työskentelemme vielä parissa kinkissä ja lisäämme ominaisuuksiaM SK2 Mutta ydin on tehty ja se toimii hyvin. Odotamme seuraavien viikkojen aikana saatavia päivityksiäMSC4 Se tulee lucidRAG-sivuilleMST5comMst6 MinäM st7 Lisään täällä screenshoteja, kun saan mallin valmiiksiMSt8

> **RAG-infrastruktuurin rakentamisen tarkoituksena on käyttää sitä todelliseen toimintaan.**

Viime viikkoina olemme rakentaneet

- **DocSummarizer** - Asiakirjojen parsiminen, semanttisten chunkkien tekeminenM SK2 ONNX-embeddings
- **GraphRAG** -yksiköiden ekstrahointi,tietämyksen grafiikatM SK2yhteisön havaitseminen
- **Semanttinen haku** - BMM SK1 + BERT hybriditutkimus RRF fuusiolla

Nyt yhdistämme ne yhteen **lucidRAG** - yksittäinen web-sovellus monien asiakirjojen kysymyksiin vastaamiseen tietografiikan visualisoinnin avulla

**verkkosivusto:** [lucidrag.com](https://lucidrag.com) | **Lähde:** [GitHub](https://github.com/scottgal/mostlylucidweb/tree/main/Mostlylucid.RagDocuments)

[TOC]

## Mitä lucidRAG tekee

Laajentaa asiakirjoja. Pitä kysymyksiäM SK1 Saa vastauksia viittauksineen ja tietotaulukkoon, jossa kuvataan, miten käsitteet liittyvät toisiinsa

**Tärkeimmät ominaisuudet:**

- **Monien -asiakirjojen laatiminen** liukulla
- **Agentti RAG** - Kysymyksen hajoaminen ja itsenäinen rakenteellisuus
- **Tietografiikan visualisointi** - Katso yksiköiden suhteet
- **Todisteiden näkökanta** - SanontaM SK1tasoa koskevat lähdeviittaukset
- **Yksittäinen käyttöönotto** - Yksittäinen täytäntöönpanokelpoinen tai Docker

**Suunnitelmarajoitukset:**

- Ei pilviriippuvuutta indeksointia varten
- Deterministinen ennakkokäsittely (
- Alkuperäisistä asiakirjoista muutettava vektoritila
- Käytetyt LLM:t *vain* vastauksen synteesiä kerättyjen todisteiden perusteella

> Mitään LLM:itä ei missään vaiheessa käytetä chunkkiin, sisällytykseen , entity extraction, tai varastointiin M SK3 ainoastaan löydettyjen vastausten synteesimiseen

## Miksi yhdistetään vektorin haku + osaamispiirteetM SK1

Ainoastaan vektorinen haku katkeaa tietyille kysymyksentyypeille:

| Kysymyksen tyyppi | Vektorin hakuongelma | | | Graph-ratkaisu ||
|------------|----------------------|----------------|
| Yhdyskunta-asiakirjat, -asiakirja, SSK2 " Miten X liittyy Y:hen?
| Yhteinen osapuoliM SK1keskinen | SSK3Entä Docker, ?" \| Yhdestä osapuolesta peräisin oleva Graph traversal
| Maailmanlaajuiset yhteenvedot

lucidRAG käyttää sekä: vektoreita täsmällisyyden varmistamiseksiM SK1 grafiikkoja kontekstin varmistamiseen . Graph-kyselyt ovat pitkiä-rajoitettuja MSC4max SSK5 hyökkäyksiäMST6 ja laajennettuja löydettyihin asiakirjoihin, jotta estettäisiin rajaton läpikulku suurten yritysten osaltaMSL7

## Luettelo arkkitehtuurista

App-layerissä on kolme hanketta, jotka olemme jo rakentaneet: ' ja ,. **[StyloFlow](/blog/styloflow-signal-driven-workflows)** - signaali

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

**Miksi StyloFlow?** Kukin käsittelyvaihe on ennakolta "aalto, ", joka antaa signaalejaM SK3aallot toimivat, kun niiden lähtö- ja lähtöolosuhteet vastaavat toisiaan,, mikä mahdollistaa rinnakkaisen täytäntöönpanon. [StyloFlow:Signaali](/blog/styloflow-signal-driven-workflows) täytäntöönpanon yksityiskohdat.

## Jalostusputki

Kun laaditte asiakirjan, se kulkee kolmeen vaiheeseen:

### Taakko 1: Upload ja queue

Laadinnan loppupiste vahvistaa tiedoston, laskee sisällöllisen hassin päällekkäisyydeksiM SK1 ja järjestelee sen taustakäsittelyyn :

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

Tärkein näkemys: haaskaamme ensin, säästämme myöhemminM SK2 Näin vältetään päällekkäisissä laatimissaan tiedostoihin liittyvän käsittelyajan tuhlaaminenMSC3

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

### Taajuus 2: Ruuhkaminen ja sisällyttäminen

Taustakäsittely ottavat esille asiakirjat, jotka ovat joukossa ja hallinnoivat niitä DocSummarizerin kautta:

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

Tämä yksisuuntainen linja tekee paljon työtä (katso [DocSummarizer -osuus 1](/blog/building-a-document-summarizer-with-rag)):

- Käsitellään asiakirjarakennetta (PDF , DOCXM SK2 Markdown)
- Jakataan otsikoita koskeviin semanttisiin chunkkiin
- Generaa ONNX-embeddings kutakin chunkia varten
- varastoida vektorit DuckDB:ssä HNSW-indeksoinnin avulla

### Tason 3:-yksikön poisto

Yhdenmukaistamisen jälkeen otamme GraphRAG:n heuristisen lähestymistavan mukaiset entiteet

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

Siinä käytetään IDF:n tulosta ja rakenteellisia signaaleja sen sijaan, että per -chunk LLM-puheluja - katso [GraphRAG-osuus 2](/blog/graphrag-minimum-viable-implementation) Lisätietoja.

## Rajoitettuja kanavia takapaineelle

Naiivin täytäntöönpanossa käytettäisiin rajattomia queueja, vaarantaminen--muistien romahdus Uploadin tulvien aikanaM SK3Me käytämme rajattuja kanavia, joilla on selvät kapasiteetin rajoitukset

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

Kun queue täyttyy, `Wait` mode estää uusia kirjoituksia siihen asti, kunnes avaruus avautuu. lisätään tauko, jotta käyttäjät saavat selvän virheen sen sijaan, että häntelevät

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

Suurten asiakirjojen käsittely voi kestää minuutteja.

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

Liitetyllä tokenilla varmistetaan, että noudatamme edelleen sovelluksen sulkemista lisättäessä per-asiakirjan rajaaM SK1

## Progress-kanavan puhdistaminen

Jokainen käsittelyasiakirja saa etenemiskanavan SSE-tarkistuksia varten. Mutta jos käyttäjä sulkee browserinsa keskiössäM SK1upload, se kanaali jää orphanediksi . Seuraamme luomisen aikoja ja puhdistetaan säännöllisestiMSC4

```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` kutsuu tämän joka 15 minuutti taustaprosessissa .

## Storage: DuckDB + PostgreSQLM SK2SQLite

Käytämme kahta tietokantaa eri tarkoituksiin:

**PostgreSQL/SQLite (EF CoreM SK2** tallennetaan asiakirjan metadataa - mikä on olemassa , käsittelytilanneM SK2 suhteet. Nämä tiedot ovat kestäviä ja kysyttävät

**DuckDB-ohjelma** varastoi vektorit ja entiteetin grafiikka. Se' on väliaikainen M SK2 voitte rakentaa sen uudelleen lähteistä asiakirjoista . Tämä ero merkitsee, että vector store korruption ei tapahdu

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

## Chat API

Kysymykset etenevät agentisen etsinnän kautta:

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

hakupalvelu käsittää kysymyksen hajoamisen tarpeen mukaan, ja tuo sen jälkeen yhteen vastauksen

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

## UI: HTMX + AlpineM SK2js

UI on yksi sivu, jossa asiakirjat ovat vasemmalla, chat oikealla:

```
┌──────────────────┬─────────────────────────────────────┐
│  📁 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 hallinnoi tilannettaM SK1 HTMX käsittelee asiakirjaluettelon päivityksiä:

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

Lucidragin kaltaisten julkisten käyttöönottojen osalta demo-modus estää laadinnon ja käyttää ennalta laadittua sisältöä.

```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` taustapalvelu seuraa sisällön luetteloa ja käsittelee kaikki pudotetut asiakirjat:

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

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

Tämä antaa mahdollisuuden päivittää demo-sisältöä yksinkertaisesti kopioimalla asiakirjoja.

## Running lucidRAG

### Yksinäinen (Ei riippuvuuksiaM SK1

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

Käytetään SQLitea + DuckDB paikallisesti. Avataan `http://localhost:5080`.

### Docker

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

## Mitä itse asiassa käy

| Osuus | Lähde | Tavoite SSK3
|-----------|--------|---------|
| Asiakirjojen parsiminen | DocSummarizer
| ONNX-embeddings | | | DocSummarizer || | Local
| Entiteettilähetys | GraphRAG | | | IDF |+ | rakenteelliset signaalit
| Yhdistetty haku | | | Molemmat || | BM\25 |
| Asynkkikäsittely | | | Uudet || | Rajoitettuja kanavia |
| Web- UI | Uusi | HTMX M+ AlpineM SK4js MPSK5

### Kustannus

**Nolla API-kustannuksia** indeksointia varten - sisällytykset ovat ONNXM SK1 entiteet onuristisia . maksatte LLM-synteesistä vain kysymyksen aikana, ja se toimii paikallisen Ollaman kanssa

## Related Articles

- [DocSummarizer-osa 1  -arkkitehtuuri](/blog/building-a-document-summarizer-with-rag)
- [DocSummarizerin osa 4 - RAG-putket](/blog/docsummarizer-rag-pipeline)
- [GraphRAG osa 2 - täytäntöönpano](/blog/graphrag-minimum-viable-implementation)
- [Semanttinen haku NX-järjestelmällä](/blog/semantic-search-with-onnx-and-qdrant)