fix(173): Auto-Tag-Vorschläge via Embedding-Similarity #185

Merged
admin-mrrm merged 25 commits from fix-173-auto-suggest-trigger into main 2026-05-03 22:58:19 +02:00
Owner

Summary

  • Ersetzt @xenova/transformers v2 + Qwen2.5-0.5B (Text-Generation) durch paraphrase-multilingual-MiniLM-L12-v2 (Feature-Extraction + Cosine-Similarity)
  • ~45 MB Modell statt 100 MB — kein Browser-Tab-OOM-Crash auf Android
  • Kein Halluzinieren: wählt nur aus vordefinierten Kandidaten (Newsletter, Werbung, Bestellung, Rechnung, Spam, Termin, Reise, Finanzen, Paket, Arbeit, Privat)
  • Implizites Lernen: bestätigte User-Tags werden automatisch als Kandidaten hinzugefügt
  • useDeleteTag-Hook + UI: suggested Tags werden beim ✕ global gelöscht (kein Tag-Müll)
  • cleanSnippet(): bereinigt HTML-Entities, URLs und Zeilenumbrüche vor der Einbettung
  • Vitest-Suite: 27 Tests für parseTags, extractGeneratedText, cleanSnippet, DEFAULT_CATEGORIES
  • Eruda In-App-Konsole + window.__mailModelDebug für mobiles Debugging
  • Termux-Startskripte (dev/api.sh, dev/web.sh) für lokale Entwicklung

Bekannte Einschränkungen

  • Threshold 0.25 kann False-Positives nicht vollständig vermeiden (z.B. Bestellung bei Promo-Mails die das Wort enthalten)
  • Rejection-Learning (Ablehnungen beeinflussen zukünftige Scores) ist nicht implementiert — geplant für #176

Test plan

  • Mail öffnen → Modell lädt (~45 MB, einmalig)
  • Promo-Mail erhält Werbung
  • Bestellbestätigung erhält Bestellung
  • Falschen Tag mit ✕ löschen → Tag verschwindet global
  • pnpm --filter @mrrmlab/web test → 27 Tests grün

🤖 Generated with Claude Code

## Summary - Ersetzt `@xenova/transformers` v2 + Qwen2.5-0.5B (Text-Generation) durch `paraphrase-multilingual-MiniLM-L12-v2` (Feature-Extraction + Cosine-Similarity) - ~45 MB Modell statt 100 MB — kein Browser-Tab-OOM-Crash auf Android - Kein Halluzinieren: wählt nur aus vordefinierten Kandidaten (`Newsletter`, `Werbung`, `Bestellung`, `Rechnung`, `Spam`, `Termin`, `Reise`, `Finanzen`, `Paket`, `Arbeit`, `Privat`) - Implizites Lernen: bestätigte User-Tags werden automatisch als Kandidaten hinzugefügt - `useDeleteTag`-Hook + UI: suggested Tags werden beim ✕ global gelöscht (kein Tag-Müll) - `cleanSnippet()`: bereinigt HTML-Entities, URLs und Zeilenumbrüche vor der Einbettung - Vitest-Suite: 27 Tests für `parseTags`, `extractGeneratedText`, `cleanSnippet`, `DEFAULT_CATEGORIES` - Eruda In-App-Konsole + `window.__mailModelDebug` für mobiles Debugging - Termux-Startskripte (`dev/api.sh`, `dev/web.sh`) für lokale Entwicklung ## Bekannte Einschränkungen - Threshold 0.25 kann False-Positives nicht vollständig vermeiden (z.B. Bestellung bei Promo-Mails die das Wort enthalten) - Rejection-Learning (Ablehnungen beeinflussen zukünftige Scores) ist nicht implementiert — geplant für #176 ## Test plan - [ ] Mail öffnen → Modell lädt (~45 MB, einmalig) - [ ] Promo-Mail erhält `Werbung` - [ ] Bestellbestätigung erhält `Bestellung` - [ ] Falschen Tag mit ✕ löschen → Tag verschwindet global - [ ] `pnpm --filter @mrrmlab/web test` → 27 Tests grün 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore(web): Eruda In-App-Konsole für mobile Debugging
All checks were successful
continuous-integration/drone/push Build is passing
aaeb826a3d
Nur in DEV-Mode aktiv — floating Button öffnet vollständige
Konsole mit Logs, Netzwerk und Storage (kein PC nötig).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(web): import-Reihenfolge in main.tsx korrigieren (Eruda nach App-Import)
All checks were successful
continuous-integration/drone/push Build is passing
2a1a98b26b
chore: Termux-Startskripte + shared-types ESM-Fix
All checks were successful
continuous-integration/drone/push Build is passing
5eb4fa2cc4
dev/api.sh: PostgreSQL init/start + DB-Setup + Migrationen + API
dev/web.sh: .env.local auf localhost setzen + Vite-Cache leeren + start
shared-types: main/exports auf ./src/index.ts (ESM für Vite)
index.html: Boot-Error-Handler für mobiles Debugging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(dev): PostgreSQL-Superuser auf 'postgres' setzen
All checks were successful
continuous-integration/drone/push Build is passing
7a2f6daad6
fix(shared-types): browser→src/index.ts, default→dist/index.js
All checks were successful
continuous-integration/drone/push Build is passing
ed78977e24
fix(web): onnxruntime-web aus Vite-optimizeDeps ausschließen + ENCRYPTION_SECRET in envSchema
All checks were successful
continuous-integration/drone/push Build is passing
c16606f083
fix(web): onnxruntime WASM-Pfad auf CDN setzen (Vite dev WASM-Auflösung)
All checks were successful
continuous-integration/drone/push Build is passing
ec94fb4254
fix(web): @xenova/transformers → @huggingface/transformers v4 (ESM-natives ONNX)
All checks were successful
continuous-integration/drone/push Build is passing
3bffb13eec
Xenova/Qwen2.5-0.5B-Instruct liefert HTTP 401 seit dem Gating.
onnx-community/Qwen2.5-0.5B-Instruct ist das identische Modell ohne Auth-Anforderung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0.5B-Modelle ignorieren komplexe System-Prompts und halluzinieren.
Ein konkretes Beispiel im Prompt zeigt das erwartete Format, greedy
decoding (do_sample:false) verhindert zufällige Ausgaben.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Raw bodyText enthält &zwnj;-Entities und lange URLs die die 300-Zeichen-
Budget für sinnvollen Text verbrauchen und das Modell verwirren.
cleanSnippet() entfernt beides vor dem Pipeline-Aufruf.
Few-shot-Beispiel auf Reise/Buchung geändert um Bestellungs-Bias zu vermeiden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Das Modell ist zu klein um Beispielformat von Aufgabenkontext zu trennen.
Es gibt das few-shot Beispiel (Reise, Buchung) für jede E-Mail aus.
Ohne Beispiel + greedy decoding produziert es eigenständige Kategorien.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0.5B folgt Instruktionen zu schlecht; 1.5B ist das kleinste Modell das
zuverlässig instruction-following beherrscht (~1.4 GB Download, einmalig).
window.__mailModelDebug speichert letzten Output für nachträgliche
Inspektion in Eruda.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fp16 belegt ~3 GB im Tab-Speicher → Browser-Crash auf Mobile.
4-bit Quantisierung reduziert auf ~400 MB ohne merklichen Qualitätsverlust.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1.5B benötigt ~800 MB runtime-Speicher, Chrome Android Tab-Limit ~512 MB.
0.5B q4 passt mit ~100 MB ins Tab. Qualität wird über Prompting verbessert.
Für die native App (kein Tab-Limit) kann später 1.5B+ verwendet werden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
slice(0,500) vor dem Bereinigen lieferte nur ~70 sinnvolle Zeichen weil
der E-Mail-Body mit &zwnj;-Entities und URLs beginnt. Jetzt 3000 Zeichen
roh übergeben, cleanSnippet destilliert daraus 300 sinnvolle Zeichen.
Unvollständige Entity am String-Ende (&z) wird nun ebenfalls entfernt.
Existing-Tags-Hint als Warnung statt Auswahlliste formuliert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Suggested Tags werden beim ✕ global gelöscht (DELETE /mail/tags/:tagId),
da ein abgewiesener KI-Vorschlag die globale Tag-Liste nicht verschmutzen
soll. Confirmed Tags werden weiterhin nur von der Mail entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0.5B halluziniert Variationen der Hint-Liste statt den E-Mail-Inhalt zu
analysieren (z.B. "Rechenkarte" statt "Rechnung"). Hint komplett raus;
die Signatur bleibt für größere Modelle erhalten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ohne Prefill generiert das Modell einen strukturierten Freitext statt
kommagetrennte Kategorien. "Kategorien: " als Seed zwingt das Modell,
direkt mit den Kategorien fortzufahren.
parseTags filtert jetzt auch Tags mit Zeilenumbrüchen oder Doppelpunkten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0.5B extrahiert Preise und Zahlen als Kategorien (z.B. "14 €").
Kategorien dürfen keine Währungssymbole oder Ziffern enthalten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ersetzt Qwen2.5-0.5B (Text-Generation) durch paraphrase-multilingual-
MiniLM-L12-v2 (Feature-Extraction + Cosine-Similarity):
- Kein Halluzinieren möglich — wählt nur aus Kandidaten-Liste
- 45 MB statt 100 MB (q8), kein Browser-Tab-Crash
- Multilingual (DE/EN/...)
- Implizites Lernen: bestätigte User-Tags werden automatisch Kandidaten
DEFAULT_CATEGORIES deckt Newsletter/Werbung/Bestellung/Rechnung/Spam/...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Einzelne Wörter als Labels liefern Scores ~0.15-0.23, zu nah beieinander
für sinnvolle Unterscheidung. Erweiterte Beschreibungen (z.B. "Werbung
Angebot Rabatt Gutschein Aktion...") verbessern die Discrimination deutlich.
Threshold von 0.35 auf 0.20 gesenkt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(173): Threshold 0.25 + Bestellung-Beschreibung präzisieren
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
50a1417911
Rechnung (0.215) war knapp über dem alten Threshold aber klar falsch.
Bestellung-Beschreibung auf Auftragsbestätigungen fokussiert um False-
Positives bei Promo-Mails die das Wort 'Bestellung' im Fließtext nennen
zu reduzieren.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge branch 'main' into fix-173-auto-suggest-trigger
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
6b3c08bb9d
Merge branch 'main' into fix-173-auto-suggest-trigger
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
3bca0eb3fd
admin-mrrm deleted branch fix-173-auto-suggest-trigger 2026-05-03 22:58:20 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
admin-mrrm/mrrmlabapp!185
No description provided.