Phase 8: Lokale KI
Wiki mit semantischer Suche ueber pgvector, Diktat ueber faster-whisper, Berichtsassistent und Belegerfassung — alles gegen selbst betriebene Modelle. Dazu zwei Protokollansichten: was die Maschine getan hat und was Menschen getan haben. Nichts, was ein Modell erzeugt, gelangt ungeprueft in einen Nachweis. Jede Ausgabe ist zuerst ein eigener Datensatz, gekennzeichnet, mit zwei gleich grossen Knoepfen davor. Die Datenbank haelt gegen: Eingabe, Ausgabe und Modell sind unveraenderlich, geloescht wird nicht, entschieden wird einmal. Kein Rueckfall auf einen Anbieter: fehlen die Container, fehlt die Funktion. Dafuer bleibt alles andere ohne Modell vollstaendig benutzbar — geprueft von vierzehn E2E-Tests, die ohne laufendes Modell durchlaufen. Postgres bringt jetzt PostGIS und pgvector mit (eigenes Image), Ollama und faster-whisper stehen im Compose-Profil "ki". 73 neue Unit-Tests, 14 neue E2E-Tests, Version 0.9.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
12243f177d
commit
f7ccd9a908
@@ -31,7 +31,7 @@ Anmelden mit `SEED_ADMIN_EMAIL` und `SEED_ADMIN_PASSWORT` aus der `.env`
|
||||
| Phase 5 — Atemschutz (FwDV 7) | fertig |
|
||||
| Phase 6 — Kartenmodule | fertig |
|
||||
| Phase 7 — Benachrichtigungen und Termine | fertig |
|
||||
| Phase 8 — Lokale KI | offen |
|
||||
| Phase 8 — Lokale KI | fertig |
|
||||
| Phase 9 — Kassenverwaltung | offen |
|
||||
|
||||
Vollständige Planung mit Aufwänden: [docs/ROADMAP.md](docs/ROADMAP.md) und
|
||||
@@ -973,6 +973,188 @@ Meldungen bleiben einfach aus. Beide Schlüssel gehören deshalb ins Backup.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — Lokale KI
|
||||
|
||||
Ein Satz bestimmt alles, was hier gebaut wurde:
|
||||
|
||||
> **Nichts, was ein Modell erzeugt, gelangt ungeprüft in einen Nachweis.**
|
||||
|
||||
Daraus folgt der Aufbau: jede KI-Ausgabe ist zuerst ein eigener Datensatz und
|
||||
nicht ein Feld im Zieldokument. Ein Einsatzbericht bekommt seinen Text erst,
|
||||
wenn ein Mensch „übernehmen" gedrückt hat — bis dahin liegt der Entwurf daneben
|
||||
und ist als Entwurf erkennbar, mit gestricheltem Rahmen und dem Modellnamen
|
||||
darüber. Das ist der Unterschied zwischen einer Hilfe und einer Fälschung.
|
||||
|
||||
Der zweite Grundsatz: **es läuft alles im Haus.** Ollama und faster-whisper als
|
||||
Container daneben, kein Anbieter, kein Schlüssel, keine Zeile Text, die das
|
||||
Gerätehaus verlässt. Es gibt **keinen Rückfall** auf einen Cloud-Dienst: fehlen
|
||||
die Container, fehlt die Funktion. Das ist die einzige Bauart, bei der der Satz
|
||||
„die Daten bleiben im Haus" auch dann noch stimmt, wenn der eigene Server aus
|
||||
ist.
|
||||
|
||||
### Ohne Modelle geht alles andere weiter
|
||||
|
||||
Die wichtigste Eigenschaft der Phase, und die am meisten Arbeit gemacht hat:
|
||||
**FLORIAN bleibt ohne Sprachmodell vollständig benutzbar.** Wiki lesen und
|
||||
schreiben, Wortsuche, Belege ablegen und von Hand ausfüllen, beide Protokolle —
|
||||
alles läuft. Wo etwas fehlt, steht ein Satz, der sagt *was* fehlt und *wie es
|
||||
ohne geht*, statt eines kaputten Knopfes.
|
||||
|
||||
Das ist kein Zugeständnis, sondern der Normalfall: eine Wehr setzt FLORIAN auf,
|
||||
lange bevor sie über eine Grafikkarte nachdenkt.
|
||||
|
||||
### Das Wiki
|
||||
|
||||
Dienstvorschriften, Ausbildungsunterlagen, Merkblätter — das, was sonst als PDF
|
||||
im Ordner steht und im Einsatz niemand findet. Inhalt ist **Markdown**, weil es
|
||||
sich ohne Werkzeug lesen lässt, auch in zwanzig Jahren, auch ohne FLORIAN.
|
||||
|
||||
**Zwei Suchen nebeneinander**, und beide werden gebraucht:
|
||||
|
||||
| Suche | Findet | Braucht ein Modell |
|
||||
|---|---|---|
|
||||
| **Wortsuche** | was dasteht — „FwDV 7" findet die Seite mit dieser Zeichenfolge | nein |
|
||||
| **Frage** | was gemeint ist — „wie lange hält ein Schlauch" findet den Abschnitt über Ablegereife | ja |
|
||||
|
||||
Die Frage wird **ausschließlich aus den eigenen Seiten** beantwortet, mit
|
||||
Fundstellen darunter. Steht die Antwort nicht drin, sagt FLORIAN genau das. Ein
|
||||
Modell, das auf die Frage nach der Prüffrist von Steckleitern eine Zahl nennt,
|
||||
die nirgends steht, wäre gefährlicher als gar keine Antwort — deshalb wird ohne
|
||||
passende Auszüge gar nicht erst gefragt.
|
||||
|
||||
Zerlegt wird an den **Überschriften**, nicht nach Zeichenzahl: eine Fundstelle
|
||||
soll ein Kapitel sein, kein aus der Mitte gerissener Absatz. Und ein kurzer
|
||||
Abschnitt wird nie unter eine fremde Überschrift gehängt — sonst zitierte die
|
||||
Antwort eine Fundstelle, in der der Satz gar nicht steht.
|
||||
|
||||
Jede Änderung schreibt die vorherige Fassung weg. Eine Dienstvorschrift, die
|
||||
sich unbemerkt ändert, ist schlimmer als keine.
|
||||
|
||||
### Diktat
|
||||
|
||||
Der Weg, den es in jeder Wehr schon gibt: der Einsatzleiter spricht auf der
|
||||
Rückfahrt in sein Telefon, und daraus wird abends ein Bericht. Neu ist nur, dass
|
||||
abends schon ein Text dasteht.
|
||||
|
||||
Ein Knopf, keine Einstellungen. Die Aufnahme bleibt abgelegt, solange über den
|
||||
Text nicht entschieden ist — wer dem Transkript nicht traut, hört nach. Und das
|
||||
Modell darf **nichts verbessern**: „Hohlstrahlrohr" bleibt „Hohlstrahlrohr",
|
||||
auch wenn es das Wort nicht kennt.
|
||||
|
||||
### Berichtsassistent
|
||||
|
||||
Stichpunkte hinein, Entwurf heraus. Was das Modell **nicht** bekommt, ist ebenso
|
||||
wichtig: Zeitstempel und Einsatznummer stehen schon als Felder im Bericht und
|
||||
stünden sonst zweimal da, beim zweiten Mal womöglich falsch.
|
||||
|
||||
Der Entwurf landet nicht im Berichtsfeld. Er steht daneben, gekennzeichnet, mit
|
||||
zwei gleich großen Knöpfen — „Übernehmen" ist nicht der Standardweg und sieht
|
||||
auch nicht so aus.
|
||||
|
||||
### Belegerfassung
|
||||
|
||||
Quittung fotografieren, Angaben prüfen, gegenzeichnen. Alle Felder sind
|
||||
änderbar, und was das Modell nicht erkannt hat, bleibt **leer und gelb
|
||||
hinterlegt** statt geraten zu werden. Ein leeres Feld ist eine Aufforderung; ein
|
||||
geratener Betrag ist eine falsche Buchung, die niemand bemerkt.
|
||||
|
||||
Die Buchung daraus kommt mit der Kasse in Phase 9 — hier entsteht erst einmal
|
||||
ein sauber erfasster Beleg mit Foto.
|
||||
|
||||
### Zwei Protokolle nebeneinander
|
||||
|
||||
| Ansicht | Beantwortet |
|
||||
|---|---|
|
||||
| **KI-Protokoll** | Was hat die Maschine getan? Jede Befragung, jedes Modell, jede Entscheidung mit Namen und Zeitpunkt. |
|
||||
| **Protokoll** (Audit-Log) | Was haben Menschen getan? Wird seit Phase 0 geschrieben; die Ansicht dazu kam jetzt. |
|
||||
|
||||
Eingabe, Ausgabe, Modell und Art eines KI-Vorgangs sind nach dem Anlegen
|
||||
**unveränderlich** — ein Trigger hält dagegen. Löschen ist der Anwendungsrolle
|
||||
entzogen, und über einen Vorschlag wird genau einmal entschieden. Ohne das
|
||||
ließe sich in einem Jahr nicht mehr sagen, welcher Satz in einem Einsatzbericht
|
||||
von einem Menschen stammt.
|
||||
|
||||
### Einrichtung
|
||||
|
||||
Postgres braucht seit dieser Phase **pgvector** neben PostGIS. Es gibt kein
|
||||
offizielles Image mit beidem, deshalb baut FLORIAN eines
|
||||
(`docker/postgres/Dockerfile`):
|
||||
|
||||
```bash
|
||||
docker compose build postgres # bei einer bestehenden Installation
|
||||
docker compose up -d postgres # Datenverzeichnis bleibt unberührt
|
||||
npm run db:migrate
|
||||
```
|
||||
|
||||
Dann die Modelle:
|
||||
|
||||
```bash
|
||||
docker compose --profile ki up -d
|
||||
|
||||
docker compose exec ollama ollama pull qwen2.5:14b # Text
|
||||
docker compose exec ollama ollama pull nomic-embed-text # Einbettungen
|
||||
docker compose exec ollama ollama pull llama3.2-vision # Belege (optional)
|
||||
```
|
||||
|
||||
Auf einem Rechner ohne Grafikkarte läuft das Einbettungsmodell noch flott, das
|
||||
Textmodell zäh und das Vision-Modell praktisch nicht. Deshalb sind alle drei
|
||||
einzeln konfigurierbar: eine Wehr kann das Wiki durchsuchbar machen, ohne
|
||||
Berichte schreiben zu lassen.
|
||||
|
||||
Für eine Nvidia-Karte in die `docker-compose.override.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
ollama:
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
### Ein anderes Einbettungsmodell
|
||||
|
||||
Die Spalte `wikiabschnitt.einbettung` ist auf **768 Dimensionen** festgelegt —
|
||||
das liefert `nomic-embed-text`. Ein Modell mit anderer Länge braucht eine
|
||||
Migration:
|
||||
|
||||
```sql
|
||||
ALTER TABLE "wikiabschnitt" DROP COLUMN "einbettung";
|
||||
ALTER TABLE "wikiabschnitt" ADD COLUMN "einbettung" vector(1024);
|
||||
CREATE INDEX "wikiabschnitt_einbettung_idx"
|
||||
ON "wikiabschnitt" USING hnsw ("einbettung" vector_cosine_ops);
|
||||
```
|
||||
|
||||
Danach `LLM_EINBETTUNG_DIMENSIONEN` anpassen und alle Seiten neu einbetten. Der
|
||||
nächtliche Lauf holt das von selbst nach; wer nicht warten will, drückt auf
|
||||
jeder Seite „jetzt nachziehen".
|
||||
|
||||
Ohne Anpassung weist FLORIAN das Modell mit einer klaren Meldung ab, statt eine
|
||||
Fehlermeldung der Datenbank durchzureichen, die niemand versteht.
|
||||
|
||||
### Zusätzliche Umgebungsvariablen
|
||||
|
||||
| Variable | Vorgabe | Bedeutung |
|
||||
|---|---|---|
|
||||
| `LLM_ENABLED` | `false` | Schaltet alle Textfunktionen ein |
|
||||
| `LLM_BASE_URL` | — | OpenAI-kompatibler Endpunkt, z. B. `http://ollama:11434/v1` |
|
||||
| `LLM_MODELL` | — | Modell für Berichte und Antworten |
|
||||
| `LLM_TIMEOUT` | `120` | Zeitlimit in Sekunden |
|
||||
| `LLM_EINBETTUNG_MODELL` | `nomic-embed-text` | Modell für die semantische Suche |
|
||||
| `LLM_EINBETTUNG_DIMENSIONEN` | `768` | **Muss zur Spalte passen** |
|
||||
| `LLM_VISION_MODELL` | — | Modell für die Belegerfassung |
|
||||
| `WHISPER_ENABLED` | `false` | Schaltet das Diktat ein |
|
||||
| `WHISPER_URL` | — | z. B. `http://whisper:8000` |
|
||||
| `WHISPER_MODELL` | `base` | `tiny`, `base`, `small` |
|
||||
| `DIKTAT_MAX_SEKUNDEN` | `600` | Höchstlänge einer Aufnahme |
|
||||
| `OLLAMA_PORT` | `11435` | Port des Ollama-Containers (Profil `ki`) |
|
||||
| `WHISPER_PORT` | `8087` | Port des Whisper-Containers (Profil `ki`) |
|
||||
|
||||
---
|
||||
|
||||
## Befehle
|
||||
|
||||
```bash
|
||||
@@ -1003,6 +1185,9 @@ laufen kann. Alle Werte stehen in der `.env`.
|
||||
| MinIO Weboberfläche | http://localhost:9011 |
|
||||
| Gotenberg | http://localhost:3101 |
|
||||
| Mailpit (nur Entwicklung) | http://localhost:8125 |
|
||||
| ntfy (Profil `push`) | http://localhost:8086 |
|
||||
| Ollama (Profil `ki`) | http://localhost:11435 |
|
||||
| faster-whisper (Profil `ki`) | http://localhost:8087 |
|
||||
| Adminer (nur Entwicklung) | http://localhost:8081 |
|
||||
|
||||
## Produktionsbetrieb
|
||||
@@ -1025,9 +1210,15 @@ tut das bewusst nicht, damit nicht zwei Prozesse gleichzeitig migrieren.
|
||||
Schriften verwendet, die auf dem Gerät vorhanden sind.
|
||||
- Aufbewahrungsfristen konfigurierbar (`AUFBEWAHRUNG_JAHRE`,
|
||||
`AUDIT_AUFBEWAHRUNG_TAGE`, `PERSON_ANONYMISIERUNG_TAGE`).
|
||||
- Externe Dienste (Mail, Telegram, ntfy, Web-Push, LLM) sitzen hinter
|
||||
- Externe Dienste (Mail, Telegram, ntfy, Web-Push) sitzen hinter
|
||||
Schnittstellen in `src/lib/integrations/` und sind einzeln abschaltbar. Ohne
|
||||
Freischaltung wird nur ins Log geschrieben.
|
||||
- **Die KI-Funktionen laufen ausschließlich gegen selbst betriebene Modelle.**
|
||||
Es gibt keinen Rückfall auf einen Anbieter: fehlt der eigene Endpunkt, fehlt
|
||||
die Funktion. Kein Text verlässt das Gerätehaus.
|
||||
- Jede KI-Ausgabe ist gekennzeichnet, wird als eigener Datensatz festgehalten
|
||||
und muss von einem Menschen übernommen werden, bevor sie in einen Nachweis
|
||||
gelangt. Nachvollziehbar im KI-Protokoll.
|
||||
- Über Kanäle, die das Netz der Wehr verlassen (Telegram, Web-Push), gehen nur
|
||||
Kurzfassungen ohne Namen, Gerätebezeichnungen und Einsatzdetails.
|
||||
- Kein WhatsApp — die Begründung steht oben unter „Warum kein WhatsApp".
|
||||
|
||||
Reference in New Issue
Block a user