Back to "DocSummarizer 部件 : @ 5 @ @ MPK1 @ 清晰的RAG}% MS K2 多-Document RAG Web 应用程序"

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

AI C# DuckDB GraphRAG HTMX LLM RAG Semantic Search

DocSummarizer 部件 : @ 5 @ @ MPK1 @ 清晰的RAG}% MS K2 多-Document RAG Web 应用程序

Thursday, 01 January 2026

这是 部分#5 和它也是世界顶峰 图图RAG系列 和 语义搜索系列@._We'}将所有内容合并成可部署的网络应用程序@.#

“.”将会在LawryraGQ.com .I''在这里添加截图,

建立RAG基础设施的目的就是要用它来做一些真实的东西

在过去几周内,我们建造了:

  • DocSummamer 缩写器 -文档解析@,语义填充 @,ONNX嵌入
  • 图图RAG “-实体提取”“,知识图 ,社区探测”
  • 语义搜索 -BMZK1+BERT混合搜索与RRF聚变

现在我们把他们联合起来 更清晰 - @ a 独立的网络应用程序, 用于多@ MS K1_ document problem question 回答知识图解可视化的答题@.}

网站#: 清晰度@ . @ com | 资料来源:: 吉特胡布

何为清晰的RAG

上传文档@.问问题 @.以引用获得答案,并有一个显示概念如何连接.的知识图表。

密钥特性 @:

  • 多@ - @ document 上传 拖动@ - @ 和 @ MS K1}% 跳
  • RAG 代理RAG -查询分解和自定义=-校正=@(,,在结构上的确定性单要求生命周期_)
  • 知识图图可视化 - 见实体关系
  • 证据观点 @-_ 句子-}来源引用
  • 独立部署 - 單一執行檔或 docker

设计限制 :

  • 索引编制不依赖云
  • 磁性预处理@(_chunking @,}嵌入,}实体提取=)}
  • 可重建的矢量状态
  • 使用的LLM 仅 供回答合成检索过的证据

在任何关卡中都从未使用 LLMs 来填块@, @ 嵌入\ ,} 实体提取, @ 或存储 *-{ 仅用于对已取回的回答进行合成=%,# 引用_ MS K5 @ 支持的证据=.}

为何合并矢量搜索@+知识图表 @?#

某些查询类型的矢量单搜索分类@: @%

查询类型 @ 矢量搜索问题@ 图表解答 *
@"#X 和Y?"}{ _BARBAR_Cross@-}文件= Q X有什么关系?
“ ”实体“-”核心“MS K2” ,“"” 那么“Docker @?"” 来自实体“ ”的“I ”图表翻转情况如何?
全球摘要 @ #_"}主題 @MS K3> @ 社区探测@ #

清晰的RAG 使用两个“:”矢量来精确@,}图表用于上下文 @.}图表查询是深度-}<(>max {2}{Hops)},并用于检索文档的范围,以防止大型子公司无限制地穿行*.}

建筑结构概览

应用三层三项目 we'_#已经建立 ,}{通过 斯泰罗花 - 信号- 驱动的工作流程引擎:

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

为什么斯泰洛花 ? 而不是硬编码的输油管,}每个处理阶段都是一个Q"}波浪•"}当触发条件与允许平行执行的“,”相匹配时,发出信号{.〞波跑 StyloFlow :信号-Driven工作流管弦化 执行细节@. @%

处理管道

当您上传文档时 ,}它会流过三个阶段\ OMK1

阶段 @ 1: 上传和排队

上传端点验证文件@ , @ 计算一个内容散列, 用于解码\ MS K1} @ 并排队用于背景处理@ MPK2

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

关键洞察@: @ we hash first @ , @ save later\ .} 这防止在重复上传中浪费处理时间 @ MPK3 @

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

此单行可做很多工作 @ (_ See DocSummarizer 部件 *\ 1 {):

  • 分析文档结构 *{(_PDF,} DOCX} , Markdown@)
  • 按标题划分成语义块
  • 生成每个块块的ONNX嵌入
  • 使用 HNSW 索引化将矢量存储在 DuckDB 中

实体采掘阶段

在块积后, 我们使用 GraphraG' 来提取实体@ : @

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

这使用了以色列国防军的评分和结构信号,而不是使用任何-chunk LLM电话@-见。 图表( G) 部分 @ 2 @% 详细描述@. @%

后压带宽通道

一个天真的执行将会使用无约束的队列@ , @ 冒着在上传洪涝期间发生崩溃的风险\ MS K1_ @ - @ momory crash

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

当队列填满时 @ , @ Wait 新区块模式新写到打开空间@. @ we 添加一个超时, 这样用户就有一个清晰的错误, 而不是挂上@ :} @

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

佩尔-文件超时

大文档处理@ . @ but a stuck 文档应该'}%t 屏蔽整个队列\ MS K2每份文件都有其自身的超时=:}

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

链接的标记确保了我们仍然尊重应用程序关闭, 同时添加 per-\ document 限制@ .

进度频道清理

每个处理文档都有一个 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;
}

AA PeriodicTimer 在背景处理器中每分钟 15 @ minutes

存储@: @ duckDB @ + @ postgreSQL @ /_ SQLite

我们为不同目的使用两个数据库 :

PostgreSQL/SQ莱特 @(EF 核心@) 文档存储元数据 @ -}\ MS K1\ 处理状态@ ,\ 关系 @ I. 此数据是耐久和可查询的@ MPK4}

鸭子数据库 存储矢量和实体图形@. @ it'_%s epheperal *-}您可以从源文档中重建它 < . > 此分隔表示矢量仓库腐败}{'不会丢失您的文件清册>.}

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

搜索服务在需要时处理查询分解@,}然后合成一个回答:

    // 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 管理 stateMS K1HTMX 处理文档列表更新@:

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

演示模式

对于诸如 Llearrag.com,示范模式等公共部署, 禁用上传并使用 pre@ -loaded 内容@.Demo 模式使公共部署安全@ MPK4 确定性 @,, 没有特殊@ I-case 代码路径@MS K7}

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

AA DemoContentSeeder 背景服务 监视内容目录并处理任何已丢弃的文件@: @%

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

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

这样您就可以仅通过复制文件 {-} 来更新演示内容。 没有需要的重新启动@.}fn微软雅黑

运行清晰

无依赖性 **

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

使用 SQLite @ + @ duckDB 本地端@ MS K1 打开 http://localhost:5080.

嵌入器

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

实际运行的

目的 @
文档解析 @ DocSummarizer @MS K2 PDF, DOCX}, 标记@
ONNX 嵌入@ DocSummarizer @ 本地},没有 API 键 *
I 实体提取 G MS K2IFD+结构信号MSKO4
“ 混合搜索”“ ”都“MS K2B25++BERT与RF ”
Async 处理 @ 新的# Bounded croute+,超时@
@ @WebUI# @New _HTMX@+}Alpine @.js @MS K5 @

费用成本

零APPI费用 您在查询时间只支付LLM合成费@,}和本地Ollama. 的工作单位。

相关条款

logo

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