docs: komplette CallRec-Dokumentation mit Mermaid-Diagrammen
Architektur, Hardware/Thermik, PipeWire-Audio-Topologie, Aufnahme-Pipeline, Diarization (Anchor-Merge), LLM-Analyse (Gemma-3-4B/NPU), iPhone-BT-HFP, Betrieb/Troubleshooting und Changelog-Chronik. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# 05 — LLM-Analyse (Gemma-3-4B auf der NPU)
|
||||
|
||||
## Modell
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Modell | **Gemma-3-4B-it**, `gemma3-4b-w8a8g128-16k.rkllm` |
|
||||
| Runtime | rkllm **1.3.0** (Upgrade von 1.2.1 — API- und Chat-Template-Änderungen) |
|
||||
| Quantisierung | **grouped w8a8 (g128)** — plain w8a8 hat Gemma kaputt-quantisiert; w4a16 auf RK3588 nicht unterstützt |
|
||||
| Kontext | 16k konvertiert, **läuft mit 8192** (KV-Cache-RAM auf 8-GB-Board) ≈ 20 000 Zeichen Prompt-Budget |
|
||||
| Tempo | ~5 tok/s Decode → Kurzclip ~5 min, 71-min-Aufnahme ~20 min Analyse |
|
||||
| Historie | Vorher Qwen2.5-3B (fest 4096 Kontext). Gemma ≈ Gemini-Qualität, aber ~3× langsamer. gemma3n-E4B zu groß (10,4 GB). |
|
||||
|
||||
**Hart:** niemals Online-LLM — alles lokal auf der NPU.
|
||||
|
||||
## Pfadwahl nach Transkriptlänge
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
T["Transkript"] --> A{"≤ 4 800 Zeichen?"}
|
||||
A -- ja --> SINGLE["analyzeSingle:<br/>EIN Call = bereinigter Dialog + JSON-Analyse<br/>(Fallback: separater JSON-Call bei Echo/Parse-Fehler)"]
|
||||
A -- nein --> B{"≤ 20 000 Zeichen?"}
|
||||
B -- ja --> WHOLE["analyzeWhole:<br/>GANZES Gespräch in einem Analyse-Call<br/>(kohärente Summary, kleine JSON-Ausgabe)"]
|
||||
B -- nein --> LONG["analyzeLong (Map-Reduce):<br/>Chunks à 8 000 Zeichen"]
|
||||
LONG --> MAP["MAP: pro Chunk Mini-JSON<br/>(zusammenfassung, teilnehmer, todos)<br/>bewusst minimaler Prompt — lange Regelwände<br/>kippen Gemma in englische Prosa"]
|
||||
MAP --> SALV{"JSON parsebar?"}
|
||||
SALV -- ja --> COLLECT["einsammeln"]
|
||||
SALV -- nein --> PROSE["Prosa retten:<br/>Code-Fences UND rohe {...}-Blobs entfernen<br/>(JSON-Leak-Fix), dann als Teil-Summary nutzen"]
|
||||
PROSE --> COLLECT
|
||||
COLLECT --> REDUCE["REDUCE: Teil-Summaries (je ≤ 800 Zeichen gekappt)<br/>→ EIN JSON: titel, zusammenfassung,<br/>kernpunkte[], entscheidungen[]<br/>2 Parse-Versuche, Tail wird geloggt"]
|
||||
REDUCE --> OUT["Ergebnis"]
|
||||
SINGLE --> OUT
|
||||
WHOLE --> OUT
|
||||
```
|
||||
|
||||
## Strukturierte Zusammenfassung (seit 2026-07-15)
|
||||
|
||||
Das LLM liefert getrennte Felder; `composeSummary()` baut daraus den Summary-Text:
|
||||
|
||||
```
|
||||
<Überblick, 2–4 Sätze>
|
||||
|
||||
Kernpunkte:
|
||||
• <Punkt>
|
||||
• <Punkt>
|
||||
|
||||
Entscheidungen:
|
||||
• <Entscheidung>
|
||||
```
|
||||
|
||||
GUI rendert mit `white-space: pre-line` (style.css) → Bullets und Absätze sichtbar. Todos bleiben eigenes
|
||||
Feld/eigene Liste. **Teilnehmer kommen NICHT vom LLM**, sondern aus der Diarization (Kap. 04) — das LLM
|
||||
hatte alle im Gespräch *erwähnten* Namen als Teilnehmer gelistet (11 statt 3 bei Aufnahme #69).
|
||||
|
||||
## Robustheit gegen Gemma-Marotten (alle real aufgetreten)
|
||||
|
||||
| Marotte | Gegenmaßnahme |
|
||||
|---|---|
|
||||
| Antwortet Prosa statt JSON bei großen Inputs | kleine Chunks (8 000), minimaler Chunk-Prompt, Reduce-Input gekappt + Retry, Prosa-Salvage |
|
||||
| Kaputtes JSON leakt in die Summary | `{...}`-Blobs werden vor Salvage entfernt |
|
||||
| Todos als Objekte (`{"aufgabe": …}`) statt Strings | `cleanList()` flacht Objekt-Werte zu Strings ab |
|
||||
| `"null"` / `"[object Object]"` / lose `?` in Listen | `cleanList()` filtert/trimmt |
|
||||
| Echot Feldbeschreibungen statt Inhalt | `echoed()`-Guard → separater JSON-Call |
|
||||
| Echot konkrete Beispiele aus dem Prompt (Phantom-Todos, „Kompressor“-Summary) | Korrektur-Regeln bewusst OHNE konkrete Beispiele (`CORRECTION_HINT` abstrakt) |
|
||||
| Erfindet Fantasie-Komposita („Audio-Dualeintragung“) | `NO_INVENT`-Regel in allen Summary-Prompts |
|
||||
| Chunk-Parse-Fehler variieren pro Lauf (2–3 von 10) | nichtdeterministisch, wird per Salvage aufgefangen; bei schwachem Ergebnis einfach „Analyse neu“ |
|
||||
|
||||
## Eigennamen-Glossar (Whisper hört z. B. „Aime“ als „Amy“)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
G[("glossary-Tabelle<br/>GUI-Tab 'Glossar', /api/glossary")] --> Q{"Eintrag hat<br/>Soundalikes?"}
|
||||
Q -- ja --> DET["DETERMINISTISCH: Ganzwort-Ersetzung<br/>direkt auf rec.words VOR der Analyse<br/>(applyGlossaryText / glossaryMap)"]
|
||||
Q -- nein --> HINT["nur als Hinweis in den LLM-Prompt<br/>(glossaryHint)"]
|
||||
DET --> LLM2["LLM sieht schon den korrekten Begriff"]
|
||||
LLM2 --> BELT["Gürtel+Hosenträger: Ersetzung nochmal<br/>auf Titel/Summary/Todos des LLM-Outputs"]
|
||||
```
|
||||
|
||||
Wichtig: Begriffslisten **niemals** als Whisper-`--initial-prompt` (Echo-Loop, verifiziert) und dem 3–4B-Modell
|
||||
keine „ersetze X durch Y“-Anweisung anvertrauen (unzuverlässig) — deshalb die deterministische Text-Ersetzung.
|
||||
|
||||
## Zusammenspiel mit Whisper auf der NPU
|
||||
|
||||
rkllm (LLM) und Whisper-NPU-Encoder können nicht gleichzeitig resident sein (RAM-Contention, äußerte sich
|
||||
als `rc=-6`). Kurzclips (≤ 30 s) nutzen deshalb den CPU-Whisper-Daemon ohne rkllm-Swap; lange Aufnahmen
|
||||
swappen seriell unter dem NPU-Lock.
|
||||
Reference in New Issue
Block a user