197 lines
9.6 KiB
Markdown
197 lines
9.6 KiB
Markdown
# 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.57–0.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.
|