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;