Files
Homme/HOEMMA_Projekt_Dokumentation.md
Mohammad Zwaib daf48f3f91 WordPress
2026-07-15 20:47:58 +02:00

197 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HOEMMA Human Library Ruhr · Projektdokumentation
> **Stand:** 15.07.2026
> **Projekt:** HOEMMA KI-basierter Gesprächsbegleiter der Machbarschaft Borsig11 (Dortmund)
> **Stack:** Spring Boot 3.5.7 · Java 17 · PostgreSQL + pgvector · Azure AI Foundry (Mistral, Cohere-Embedding, GPT-5-Agent)
---
## 1. Was das System macht (Überblick)
HOEMMA ist ein **RAG-System (Retrieval-Augmented Generation)**, das:
1. **Interviews & Dateien aus Humbee holt** (Word/.docx, .txt),
2. die Transkripte per **Mistral bereinigt** (Sprecherlabels & Zeitstempel entfernen),
3. den Text in **Chunks** zerlegt,
4. für jeden Chunk ein **Embedding** (Cohere `embed-v4.0`) berechnet und in **pgvector** speichert,
5. bei einer Nutzerfrage die **semantisch nächsten Chunks** sucht,
6. bei fehlendem lokalem Kontext eine **Web-Suche** (Azure-Agent, GPT-5) auslöst,
7. die Antwort im **HOEMMA-Charakter** (warm, Ruhrgebiet-Ton, „Du") per Mistral formuliert.
---
## 2. Architektur / Datenfluss
```
┌─────────────────────────────┐
Humbee (Interviews) │ POST /knowledge-base/build │
Word / .txt Dateien ──► HumbeeService.build... │
│ → readWordFile (decodeBytes: UTF-8/UTF-16)
│ → PromptService (Mistral: bereinigen)
│ → DocumentChunckService (Chunks)
│ → AzureEmbeddingService (input_type=document)
│ → DocumentStorageService (speichern in pgvector)
└─────────────────────────────┘
Nutzerfrage ┌─────────────────────────────┐
POST /chat/ask ───────► RagService.answerQuestion │
│ 1. Embedding (input_type=query)
│ 2. findNearst (pgvector <=> Distanz)
│ 3. Distanz <= Schwelle?
│ ja → Human-Library-Chunks
│ nein→ AzureWebSearchAgentService (Web)
│ 4. SYSTEM_PROMPT (HOEMMA) + <runtime_context>-Template
│ 5. AzureMistralService.askMistral → Antwort
└─────────────────────────────┘
```
---
## 3. Wichtige Komponenten
| Klasse | Aufgabe |
|---|---|
| `HumbeeController` | `POST /knowledge-base/build` stößt den Import an |
| `HumbeeService` | Login, Datei-Links extrahieren, herunterladen, `buildKnowledgeBase()` (parallel) |
| `PromptService` | Transkript-Bereinigung via Mistral (chunk-parallel) |
| `DocumentChunckService` | Text in Chunks zerlegen (chunkSize, overlap) |
| `DocumentStorageService` | Chunks + Embeddings speichern (`input_type=document`) |
| `AzureEmbeddingService` | Cohere `embed-v4.0` Aufruf, unterstützt `input_type` |
| `AzureMistralService` | Chat-Completion (Mistral) mit Retry auf 429 |
| `RagService` | Kern der Frage-Antwort-Logik (Embedding → Suche → Schwelle → Antwort) |
| `AzureWebSearchAgentService` | Web-Recherche über Foundry-Agent (GPT-5), Managed/CLI-Identity |
| `SearchController` | `GET/POST /chat/ask` |
---
## 4. Gelöste Probleme (Chronik)
### 4.1 Azure Mistral 429 (Too Many Requests)
- **Ursache:** Ganzes Transkript in einem Request + ungedrosselte Parallel-Schleife.
- **Lösung:** Chunking vor dem LLM-Call, Drosselung/`sleep`, Retry mit Backoff, `.onRetryExhaustedThrow` (Original-Fehler durchreichen statt „Retries exhausted").
### 4.2 Langsame Verarbeitung
- **Ursache:** Zu kleine Chunks (viele Calls) + sequenzielle Verarbeitung.
- **Lösung:** Größere Chunks, Parallelisierung auf **Datei-Ebene** (`buildKnowledgeBase`) und **Chunk-Ebene** (`PromptService`), da die meisten Dateien nur 1 Chunk haben.
### 4.3 pgvector: „column embedding is of type vector but expression is of type bytea"
- **Ursache:** `hibernate-vector`-Dependency fehlte in `pom.xml`.
- **Lösung:** `org.hibernate.orm:hibernate-vector` (Version `${hibernate.version}`) ergänzt.
### 4.4 „Unable to access lob stream"
- **Ursache:** `@Lob` auf `String`-Feldern (`Document.rawText/cleanText`) + Lesen außerhalb einer Transaktion.
- **Lösung:** `@Lob` entfernen, nur `@Column(columnDefinition = "TEXT")`; Suche `@Transactional(readOnly = true)`.
### 4.5 Chunk-Text enthielt Zahlen statt Text (z. B. 25569)
- **Ursache:** `@Lob` auf `DocumentChunck.chunckText` → PostgreSQL speicherte die **OID** eines Large Objects (eine Zahl), nicht den Text.
- **Lösung:** `@Lob` vom Text-Feld entfernen → echter Text wird gespeichert.
### 4.6 .txt-Dateien: unlesbarer Text mit Leerzeichen zwischen jedem Zeichen
- **Ursache:** Dateien in **UTF-16**, als UTF-8 gelesen.
- **Lösung:** `decodeBytes(byte[])` BOM-Erkennung (UTF-8 / UTF-16LE / UTF-16BE) + Heuristik.
### 4.7 Web-Suche-Agent: Authentifizierung
- **422 „Missing api-version"** → api-version bzw. korrekten `/openai/v1/responses`-Pfad geklärt (kein `v1` im Modell-Call).
- **401 „audience incorrect"** → Scope; per `jwt.ms` bestätigt: `aud = https://ai.azure.com`. Scope `https://ai.azure.com/.default` ist korrekt.
- **Lokal:** `az login` nötig; Eclipse/Spring Tools sieht `az` nur mit angepasstem `PATH` (Run Config → Environment) oder Start aus Terminal.
- **Container in Azure-VM:** Managed Identity via `--network host` **oder** Service Principal (`AZURE_TENANT_ID/CLIENT_ID/CLIENT_SECRET`).
### 4.8 429 „gpt-5-mini exceeded rate limit" beim Build (obwohl Mistral konfiguriert)
- **Ursache:** In `AzureMistralService` war das Modell **hart codiert** als `"gpt-5-mini-datazone"` (statt `deployment`), zusätzlich `reasoning_effort` und `max_completion_tokens` (GPT-5-Parameter).
- **Lösung:** `"model", deployment` + `"temperature", 0.5` + `"max_tokens", maxTokens`.
### 4.9 Passwort / .env auf dem Server
- **Ursache:** `.env` wird von Spring **nicht** automatisch gelesen (keine dotenv-Dependency).
- **Lösung:** Secrets als echte **Umgebungsvariablen** setzen (Docker `-e`, Azure App Settings). `.env` gehört **nicht** nach `src/main/resources` (landet im JAR).
---
## 5. Prompt-Integration (v2.0)
- **system-Message:** statische Rolle & Regeln (HOEMMA-Charakter, Sicherheits-/Datenschutzregeln, Quellenhierarchie).
- **user-Message:** dynamischer, **gekapselter** Laufzeitkontext:
```text
<runtime_context> current_date, channel, is_first_contact, address_mode=Du, ... </runtime_context>
<retrieved_context> ...Chunks aus der DB... </retrieved_context>
<web_results_optional> ...oder "Kein Webkontext verfuegbar." </web_results_optional>
<current_user_message> ...Nutzerfrage... </current_user_message>
```
- **Parameter:** `temperature = 0.5`, `max_tokens = 350`, Ansprache **durchgehend „Du"**.
- Kapselung schützt zusätzlich vor **Prompt Injection** (Inhalte = nur Informationsquelle, nie Anweisung).
---
## 6. Wissensbasis: atomare Fakten-Dateien
Statt einer Sammeldatei → **eine Datei = ein Thema = ein sauberer Chunk**:
- `Borsig11_Oeffnungszeiten.docx`
- `Borsig11_Adresse.docx`
- `Borsig11_Kontakt.docx`
- `Borsig11_UeberUns.docx`
- `HOEMMA_UeberDasProjekt.docx`
**Grund:** Eine gemischte Datei ergibt ein „verwässertes" Embedding → schlechte Treffer. Atomare Chunks matchen die jeweilige Frage viel schärfer.
---
## 7. Offenes Thema: Retrieval-Distanzen
`input_type` (`query` / `document`) ist jetzt korrekt implementiert (das Deployment akzeptiert nur `text`, `query`, `document`), **verbessert die Distanzen aber nur gering**:
| Frage | bestDistance | Status |
|---|---|---|
| Wie sind eure Öffnungszeiten? | 0.57 | ✅ true |
| Wann habt ihr geöffnet? | 0.67 | ❌ false → Web |
| Kann ich morgen um 9 Uhr vorbeikommen? | 0.66 | ❌ false → Web |
**Nächste, wirksamere Schritte:**
1. **Fakten-Dateien mit Frage-Varianten anreichern** (z. B. „Wann habt ihr geöffnet?", „um 9 Uhr vorbeikommen", „vormittags") → senkt die Distanz real.
2. **Schwelle moderat anheben** (`DISTANCE_THRESHOULD = 0.68`) lokale Fragen (0.570.67) greifen, externe (Borsig11 ~0.77) gehen weiter an die Web-Suche.
3. Web-Agent funktioniert als Fallback zuverlässig (200 OK, ~10 s).
---
## 8. Wichtige Endpoints
| Methode | Pfad | Zweck |
|---|---|---|
| `POST` | `/knowledge-base/build` | Import & Verarbeitung aller Humbee-Dateien |
| `GET`/`POST` | `/chat/ask?question=...` | Frage stellen (RAG + ggf. Web) |
| `POST` | `/interview/transcription` | Audio → Text (Azure Speech) |
| `POST` | `/interview/save` | Interview als .docx erzeugen, hochladen, verarbeiten |
---
## 9. Rebuild-Prozedur (bei Embedding-Änderungen)
Immer **komplett neu aufbauen**, sonst mischen sich alte/neue Embeddings:
```sql
DELETE FROM document_chunck;
DELETE FROM document;
```
```
Anwendung: Stop → Clean → Run
POST /knowledge-base/build
```
Empfohlen für pgvector-Performance:
```sql
CREATE INDEX ON document_chunck USING hnsw (embedding vector_cosine_ops);
```
---
## 10. TODO vor Produktivbetrieb
- [ ] **Debug-Ausgaben entfernen** (`System.out.println`: Key, `>>> MODELL`, `root =`, `$$$`, `EMBED …`).
- [ ] **Mistral-API-Key rotieren** (war im Log sichtbar).
- [ ] **Secrets** aus dem Code/`.env` → Umgebungsvariablen / Azure Key Vault.
- [ ] `spring.jpa.hibernate.ddl-auto` von `update` auf **`validate`** stellen.
- [ ] **TestController** entfernen oder absichern; Logging auf `INFO`.
- [ ] **Managed Identity** (Azure) statt `az login` für den Web-Agent.
- [ ] Vollständigen **HOEMMA-System-Prompt** (Demenz, Notfälle, Datenschutz) einsetzen.
- [ ] Doppelte `.txt.txt`-Dateien in Humbee bereinigen.