# 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) + -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 current_date, channel, is_first_contact, address_mode=Du, ... ...Chunks aus der DB... ...oder "Kein Webkontext verfuegbar." ...Nutzerfrage... ``` - **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.