From 35857ce91bbccc25a21dcba9dc9d50fbf48007d8 Mon Sep 17 00:00:00 2001 From: Marcus Nimtz Date: Wed, 5 Aug 2026 08:46:01 +0200 Subject: [PATCH] =?UTF-8?q?v1.11.82=20=E2=80=94=20KI-Indexierung:=20OOM-Cr?= =?UTF-8?q?ash-Schutz=20+=20Modell-Mismatch=20sichtbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vorfall 2026-08-05: Reindex ließ den Server abrauchen (OOM) und der Chat gab 0 Treffer trotz vorhandener Embeddings. - ExtractTextAsync: 15-MB-Größen-Cap (ProbeAsync) — große Dateien werden nie komplett in den RAM geladen (MemoryStream + ToArray + Base64-OCR = bis 3 Kopien). Verhindert den OOM beim (Re-)Indexieren. - AiPostProcessor: Parallelität 2 → 1 — halbiert Speicher-Peak und SQLite-Writer-Contention (busy_timeout blockiert sonst Threads → Hänger). - RetrieveHitsAsync: 0-Treffer-trotz-Embeddings loggt jetzt query- vs. gespeichertes Modell-Label sichtbar, statt stiller Leere — Grundlage für gezieltes Relabel/Re-Embed statt riskantem Blind-Reindex. Lokal mit .NET 8 gebaut: 0 Fehler. VERSION 1.11.81 → 1.11.82. Co-Authored-By: Claude Opus 4.8 --- VERSION | 2 +- src/NimShare.Api/Controllers/AiController.cs | 11 +++++++++++ src/NimShare.Api/Services/AiPostProcessor.cs | 6 +++++- src/NimShare.Api/Services/AiProvider.cs | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 9920539..89ce98e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.11.81 +1.11.82 diff --git a/src/NimShare.Api/Controllers/AiController.cs b/src/NimShare.Api/Controllers/AiController.cs index 09976f8..f39a1f0 100644 --- a/src/NimShare.Api/Controllers/AiController.cs +++ b/src/NimShare.Api/Controllers/AiController.cs @@ -762,6 +762,17 @@ public async Task Search([FromBody] SearchReq req, // Embeddings. Marcus's Bug-Report: Chat/Suche antworten fast nie. var embsAll = await _db.FileEmbeddings.Where(e => fileIds.Contains(e.FileId)).ToListAsync(ct); var embs = embsAll.Where(e => e.Model == queryModel).ToList(); + // v1.11.82: 0 Treffer trotz vorhandener Embeddings = Modell-Label- + // Mismatch (Index mit anderem Modell als die aktuelle Query). War bislang + // still ("Keine passenden Dateien") — jetzt sichtbar im Log, damit ein + // gezieltes Relabel/Re-Embed entschieden werden kann statt blind (und + // riskant) zu reindexieren. Siehe Vorfall 2026-08-05. + if (embs.Count == 0 && embsAll.Count > 0) + { + var storedModels = string.Join(", ", embsAll.Select(e => e.Model).Distinct()); + Console.Error.WriteLine( + $"[Retrieve] Embedding-Modell-Mismatch: query='{queryModel}', gespeichert=[{storedModels}] über {embsAll.Count} Embeddings. Relabel oder gezieltes Re-Embed nötig."); + } var scored = new List<(Guid Id, double Score)>(); foreach (var e in embs) { diff --git a/src/NimShare.Api/Services/AiPostProcessor.cs b/src/NimShare.Api/Services/AiPostProcessor.cs index 3299cd1..61699c3 100644 --- a/src/NimShare.Api/Services/AiPostProcessor.cs +++ b/src/NimShare.Api/Services/AiPostProcessor.cs @@ -25,7 +25,11 @@ public class AiPostProcessor : IAiPostProcessor // Jetzt: max 2 gleichzeitige AI-Runs, alle weiteren warten in Reihe. // Semaphore statt Channel gewählt weil kein Fairness-Requirement und // fire-and-forget Semantik erhalten bleibt. - private static readonly SemaphoreSlim _concurrency = new(2, 2); + // v1.11.82: nach dem OOM-Vorfall (2026-08-05) von 2 auf 1 gesenkt. Zusammen + // mit dem Größen-Cap in ExtractTextAsync bleibt der Speicher-Peak beim + // (Re-)Indexieren bei EINER Datei — kein paralleles Vielfaches mehr, und + // weniger SQLite-Writer-Contention (busy_timeout blockiert sonst Threads). + private static readonly SemaphoreSlim _concurrency = new(1, 1); public AiPostProcessor(IServiceScopeFactory scopes, ILogger log) { diff --git a/src/NimShare.Api/Services/AiProvider.cs b/src/NimShare.Api/Services/AiProvider.cs index 956154b..54cb64d 100644 --- a/src/NimShare.Api/Services/AiProvider.cs +++ b/src/NimShare.Api/Services/AiProvider.cs @@ -1027,6 +1027,22 @@ public async Task CreateProviderAsync(CancellationToken ct = defaul { try { + // v1.11.82: Speicher-Schutz gegen OOM beim (Re-)Indexieren. Die + // Extraktion lädt die Datei komplett in einen MemoryStream (+ToArray + // +Base64 für OCR = bis zu 3 Kopien im RAM); mehrere große Dateien + // parallel killten die Azure-Instanz (Vorfall 2026-08-05). Über der + // Grenze überspringen — die Suche fällt dann auf Dateiname/AiSummary + // zurück, statt den Prozess zu riskieren. + const long MaxExtractBytes = 15L * 1024 * 1024; + var probe = await blobs.ProbeAsync(blobPath, ct); + if (probe.Exists && probe.SizeBytes > MaxExtractBytes) + { + _log.LogInformation( + "Text extraction skipped for {Path}: {Size} bytes exceeds {Cap} byte cap.", + blobPath, probe.SizeBytes, MaxExtractBytes); + return null; + } + using var ms = new MemoryStream(); await blobs.DownloadToAsync(blobPath, ms, ct); ms.Position = 0;