fix(brain): Memory-Suche filtert jetzt Rauschen — score_threshold + kleineres k

Bug: bei kleiner DB (31 Eintraege) lieferte die Suche fuer JEDES Wort
fast alles als Treffer zurueck — k=20 Top-N ohne Threshold sorgte
dafuer dass auch "banane" zehn vermeintliche Treffer mit Scores
0.09-0.22 (= Rauschen) zurueckgab.

Fix:
- vector_store.search() bekommt optional score_threshold (an Qdrant
  durchgereicht, das nimmt's nativ)
- /memory/search endpoint hat score_threshold-Query-Param (default 0.30)
- Diagnostic schickt k=10 + score_threshold=0.30 statt k=20 ohne Threshold
- "Keine Treffer"-Info-Box wenn alle Treffer < Threshold

MiniLM-multilingual liefert typischerweise:
  >0.50 → starker Treffer
  0.30-0.50 → relevant
  0.20-0.30 → grenzwertig
  <0.20 → Rauschen

Mit score_threshold=0 (oder None) bleibt die alte Top-N-Semantik
fuer Aufrufer die Rauschen explizit wollen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 15:59:05 +02:00
parent 051d629cb3
commit daf0d44dd7
3 changed files with 34 additions and 7 deletions
+13 -4
View File
@@ -3410,7 +3410,10 @@
return;
}
const typeFilter = document.getElementById('brain-filter-type').value;
const params = new URLSearchParams({ q, k: '20', include_pinned: 'true' });
// k=10 + Score-Threshold im Backend (0.30) → nur relevante Treffer.
// Frueher k=20 ohne Threshold: bei kleiner DB landete fast alles
// als "Treffer", egal wie unaehnlich.
const params = new URLSearchParams({ q, k: '10', include_pinned: 'true', score_threshold: '0.30' });
if (typeFilter) params.set('type', typeFilter);
try {
const r = await fetch('/api/brain/memory/search?' + params.toString());
@@ -3420,9 +3423,15 @@
brainSearchIds = hits.map(m => m.id);
if (info) {
info.style.display = 'block';
info.innerHTML = `🔍 ${hits.length} Treffer für "${escapeHtml(q)}"` +
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') +
` · sortiert nach Aehnlichkeit`;
if (hits.length === 0) {
info.innerHTML = `🔍 Keine relevanten Treffer für "${escapeHtml(q)}"` +
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') +
` (Score < 0.30). Versuche andere Begriffe oder klicke das ✕ rechts um die Suche zu schliessen.`;
} else {
info.innerHTML = `🔍 ${hits.length} Treffer für "${escapeHtml(q)}"` +
(typeFilter ? ` · Typ=${escapeHtml(typeFilter)}` : '') +
` · sortiert nach Aehnlichkeit (Score &ge; 0.30)`;
}
}
renderBrainList(hits, true);
} catch (e) {