Diarisierung: Marker-Zwangsbedingungen absichern, Voiceprints als cannot-link; LLM: Sektions-Pfad ab 9k Zeichen
#79 verlor einen von vier Sprechern und lieferte eine 1082-Zeichen-Zusammenfassung. Diarisierung: ein zweiter, falsch verankerter Marker verschmolz Saschas Cluster in Merles (Kosinus -0,727). must-link prueft jetzt die akustische Aehnlichkeit, Anker werden nach Trefferqualitaet gewichtet, und enrolled Stimmen wirken als cannot-link -- damit ist der Box-Besitzer immer ein eigener Sprecher. LLM: der Whole-Pass komprimierte das erste Thema weg. Sektions-Pfad jetzt ab 9000 Zeichen, kleinere Chunks, woertlicher Kontext aus dem Vorgaenger-Chunk und ein gezielter Pruef-Aufruf gegen ueberdehnte Einzelaussagen. #79: 4 Sprecher, Zusammenfassung ~5300 Zeichen, 7 Themenbloecke. Regression: #70 -> 4, #68 -> 2, #74 -> 20 Sprecher unveraendert.
This commit is contained in:
+60
-6
@@ -19,7 +19,9 @@
|
||||
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 --> SPK{"labeled + ≥ 5 Sprecher?<br/>(MANY_SPEAKERS, countSpeakers)"}
|
||||
A -- nein --> LEN{"> 9 000 Zeichen?<br/>(SECTION_MIN_CHARS, seit 2026-07-29)"}
|
||||
LEN -- "ja (Meeting-Länge)" --> LONG
|
||||
LEN -- nein --> SPK{"labeled + ≥ 3 Sprecher?<br/>(MANY_SPEAKERS, countSpeakers)"}
|
||||
SPK -- "ja (Round-Robin-Meeting)" --> LONG
|
||||
SPK -- nein --> B{"≤ 32 000 Zeichen?"}
|
||||
B -- ja --> WHOLE["analyzeWhole:<br/>GANZES Gespräch in einem Analyse-Call<br/>(kohärente Summary, kleine JSON-Ausgabe)<br/>Instruktion VOR und NACH dem Transkript"]
|
||||
@@ -28,19 +30,71 @@ flowchart TB
|
||||
REPAIR --> WCHECK
|
||||
WCHECK -- "weiterhin leer/Junk" --> LONG
|
||||
WCHECK -- ja --> OUT
|
||||
B -- nein --> LONG["analyzeLong (Sektions-Konglomerat):<br/>Chunks à 4 200 Zeichen<br/>(bei 8k wird der Inhalt generisch/namenlos,<br/>bei 4k bleibt das Modell geerdet);<br/>chunkText splittet an Zeilen = Turn-Grenzen"]
|
||||
LONG --> MAP["MAP pro Chunk, sequenziell mit ROLLING CONTEXT<br/>(1-Satz-Kurzfassungen der vorherigen Abschnitte →<br/>Änderungen erkennbar: 'jetzt X, vorher Y'):<br/>Mini-JSON thema (Überschrift ≤ 6 Wörter) +<br/>zusammenfassung (JEDE berichtende Person, beruflich;<br/>Smalltalk max. Halbsatz) + eigennamen[] (EXTRAKTION)<br/>+ teilnehmer + todos (nur beruflich, Verantwortlicher<br/>in Klammern) — bewusst minimaler Prompt"]
|
||||
B -- nein --> LONG["analyzeLong (Sektions-Konglomerat):<br/>Chunks à 3 200 Zeichen (seit 2026-07-29;<br/>bei 8k wird der Inhalt generisch/namenlos,<br/>bei 4,2k konkurrieren zwei Themen pro Chunk);<br/>chunkText splittet an Zeilen = Turn-Grenzen"]
|
||||
LONG --> MAP["MAP pro Chunk, sequenziell mit ROLLING CONTEXT<br/>(1-Satz-Kurzfassungen der vorherigen Abschnitte →<br/>Änderungen erkennbar: 'jetzt X, vorher Y')<br/>+ TAIL: die letzten 700 Zeichen des VORIGEN Chunks<br/>WÖRTLICH, ausdrücklich 'nicht zusammenfassen'<br/>(Bezugswörter für Sätze am Chunk-Anfang):<br/>Mini-JSON thema (Überschrift ≤ 6 Wörter) +<br/>zusammenfassung (JEDE berichtende Person, beruflich;<br/>JEDES Thema mind. 1 Satz; nicht verallgemeinern;<br/>Smalltalk max. Halbsatz) + eigennamen[] (EXTRAKTION)<br/>+ teilnehmer + todos (nur beruflich, Verantwortlicher<br/>in Klammern)"]
|
||||
MAP --> SALV{"JSON parsebar +<br/>kein Feldbeschreibungs-Echo?"}
|
||||
SALV -- nein --> RETRY["bis 3 Versuche, Prompt VARIIERT<br/>(2.: ohne Kontext, 3.: ohne Hint —<br/>manche Inhalte echoen sonst JEDES Mal)<br/>dann Feld-Salvage per Regex<br/>(zusammenfassung/thema-Strings überleben fast immer),<br/>zuletzt Prosa-Rettung (≥ 60 Zeichen, sonst verwerfen)"]
|
||||
RETRY --> COLLECT
|
||||
SALV -- ja --> COLLECT["Sektionen einsammeln"]
|
||||
COLLECT --> TITLE["TITEL-Mikro-Call: Thema + 2-Satz-Kurzfassungen<br/>OHNE letzten Abschnitt (Schluss-Smalltalk-Filter)<br/>→ nur {titel} — kleines Input/Output = zuverlässig"]
|
||||
RETRY --> ABS
|
||||
SALV -- ja --> ABS{"Entwurf behauptet etwas Grundsätzliches?<br/>Skopus-Wort (nur/ausschließlich/statt/…)<br/>UND kollektives Subjekt (das Team/wir/künftig/…)"}
|
||||
ABS -- ja --> VERIFY["PRÜF-Aufruf NUR für diesen Abschnitt:<br/>Abschnitt wörtlich + Entwurf →<br/>'gilt das allgemein oder nur für EINEN Beitrag?'<br/>Antwort < 60 % der Entwurfslänge wird verworfen"]
|
||||
VERIFY --> COLLECT
|
||||
ABS -- nein --> COLLECT["Sektionen einsammeln"]
|
||||
COLLECT --> DROP{"letzter Abschnitt =<br/>nur Verabschiedung?<br/>(2 Floskeln ODER Überschrift 'Abschluss…')"}
|
||||
DROP -- ja --> DROPPED["Abschnitt verwerfen<br/>(nie ein innerer, nie der einzige)"]
|
||||
DROPPED --> TITLE
|
||||
DROP -- nein --> TITLE["TITEL-Mikro-Call: Thema + 2-Satz-Kurzfassungen<br/>OHNE letzten Abschnitt (Schluss-Smalltalk-Filter)<br/>→ nur {titel} — kleines Input/Output = zuverlässig"]
|
||||
TITLE --> OVERVIEW["ÜBERBLICK-Mikro-Call über dieselben Kurzfassungen<br/>→ {ueberblick} 2-3 Sätze, alle Abschnitte gleich gewichtet<br/>(Key-tolerant parsen: auch 'überblick' oder<br/>irgendein langer String-Value; leer = weglassen)"]
|
||||
OVERVIEW --> ASSEMBLE["KONGLOMERAT baut CODE (kein LLM-Reduce):<br/>Überblick-Absatz + pro Sektion 'Thema:\nSummary (Namen: …)'<br/>chronologisch + 'Genannte Namen: …' am Ende<br/>→ deterministisch, keine Fehlgewichtung möglich; Cap 16 000"]
|
||||
ASSEMBLE --> OUT["Ergebnis"]
|
||||
SINGLE --> OUT
|
||||
```
|
||||
|
||||
### Länge schlägt beides: Sektions-Pfad ab 9 000 Zeichen (2026-07-29)
|
||||
|
||||
Aufnahme #79 (23-min-Marketing-Jour-fixe, 3 diarisierte Sprecher, 21k Zeichen im gelabelten Transkript)
|
||||
erfüllte **keine** der beiden bisherigen Bedingungen — unter `WHOLE_MAX_CHARS` 32 000 und unter
|
||||
`MANY_SPEAKERS` 5 — und lief deshalb als Whole-Pass. Ergebnis: **1 082 Zeichen** Zusammenfassung, in der
|
||||
das erste Tagesordnungsthema (Timo/Sabbatical, die ersten 2,5 Minuten) **komplett fehlte**; MS Teams
|
||||
lieferte für dasselbe Meeting 5 Themenblöcke. Die Sprecherzahl war also nie der eigentliche Treiber:
|
||||
oberhalb von ~3k Token komprimiert das 4B den ANFANG des Prompts weg, egal wie viele Leute reden.
|
||||
|
||||
- Neu `SECTION_MIN_CHARS = 9 000` (`LLM_SECTION_MIN`): alles darüber geht in `analyzeLong`, das jeden
|
||||
Abschnitt bauartbedingt abdeckt. `MANY_SPEAKERS` zusätzlich 5 → 3.
|
||||
- Wirkung auf #79 (gleiches Transkript, gleiche Diarisierung): **1 082 → ~5 300 Zeichen**, 7 Themenblöcke,
|
||||
alle Teams-Themen enthalten, 15–18 ToDos mit Verantwortlichen.
|
||||
- Preis: der Sektions-Pfad braucht auf diesem Board ~20 statt ~8 Minuten.
|
||||
|
||||
### Zwei Fehlerbilder des Sektions-Pfads — und was wirklich half (2026-07-29)
|
||||
|
||||
Beide wurden über **zehn** Läufe derselben Aufnahme gemessen, nicht geraten.
|
||||
|
||||
**1. Verlorenes Thema am Chunk-Anfang.** Bei 4 200 Zeichen enthielt Chunk 1 von #79 zwei zusammenhanglose
|
||||
Themen (Timo-Gespräch, dann Video-Review) — das Modell fasste nur das längere zusammen, das Timo-Thema
|
||||
verschwand in 1 von 3 Läufen. Fix: `CHUNK_CHARS` 4 200 → **3 200** plus die Prompt-Zeile „Enthält der
|
||||
Abschnitt mehrere verschiedene Themen, behandle JEDES in mindestens einem Satz — auch ein kurzes Thema
|
||||
ganz am Anfang." Ein früherer Versuch mit dem Zusatz „…und lasse weg, was unklar bleibt" machte es
|
||||
**schlimmer** (das war die Lizenz, kurze Themen zu streichen) und wurde zurückgenommen.
|
||||
|
||||
**2. Überdehnte Einzelaussagen.** „Achso, ja, dann lassen wir LinkedIn raus, dann kommen wir es nur auf
|
||||
Instagram" — gesagt über EIN Ragebait-Video — wurde zu „das Team veröffentlicht nur noch auf Instagram",
|
||||
also der Umkehrung der echten Entscheidung (LinkedIn soll fachlicher werden). In 5 von 8 Läufen falsch,
|
||||
und **keine** Prompt-Formulierung hat das zuverlässig behoben (abstrakte Regel, dann Gegensatzpaar mit
|
||||
Beispiel — beide blieben drin, beide reichten nicht). Was gemessen wurde und half:
|
||||
|
||||
- Ein Skript, das `buildBasis` + `chunkText` nachbaut, lokalisierte die Auslöser: „lustigen Sachen nur bei
|
||||
Instagram" liegt **4 % in Chunk 6**, also ohne Bezugswort. → **`TAIL_CHARS` = 700**: jeder Chunk bekommt
|
||||
die letzten 700 Zeichen des vorigen wörtlich als ausdrücklich gekennzeichneten Kontextblock. Das behob
|
||||
genau diesen Abschnitt.
|
||||
- Der zweite Auslöser sitzt mitten in Chunk 5 (51 %), hat also Kontext und wird trotzdem überdehnt. →
|
||||
**gezielter Prüf-Aufruf**, wenn der Entwurf ein Skopus-Wort UND ein kollektives Subjekt enthält.
|
||||
Erste Fassung des Triggers suchte wörtlich nach „nur noch"/„statt auch" und feuerte deshalb NIE auf den
|
||||
echten Fehlsatz „Das Team entscheidet sich dafür, **nur** Instagram zu nutzen **statt** LinkedIn".
|
||||
|
||||
**Stand:** die harte Umkehrung („LinkedIn wird beendet") ist weg, der Rest-Effekt ist eine abgeschwächte
|
||||
Formulierung („verstärkter Fokus auf Instagram") in einem von sieben Abschnitten, während Überblick und
|
||||
LinkedIn-Abschnitt die Strategie korrekt wiedergeben. Ein vollständiger Verifikationsdurchlauf über ALLE
|
||||
Abschnitte würde die Analyse verdoppeln — dafür ist der Rest-Fehler zu klein.
|
||||
|
||||
### Round-Robin-Meetings: Sprecherzahl schlägt Länge (2026-07-17)
|
||||
|
||||
Aufnahme #74 (33-min-Weekly, 13 diarisierte Sprecher, 31k Zeichen) lag knapp UNTER `WHOLE_MAX_CHARS`
|
||||
|
||||
Reference in New Issue
Block a user