9.6 KiB
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:
- Interviews & Dateien aus Humbee holt (Word/.docx, .txt),
- die Transkripte per Mistral bereinigt (Sprecherlabels & Zeitstempel entfernen),
- den Text in Chunks zerlegt,
- für jeden Chunk ein Embedding (Cohere
embed-v4.0) berechnet und in pgvector speichert, - bei einer Nutzerfrage die semantisch nächsten Chunks sucht,
- bei fehlendem lokalem Kontext eine Web-Suche (Azure-Agent, GPT-5) auslöst,
- 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 inpom.xml. - Lösung:
org.hibernate.orm:hibernate-vector(Version${hibernate.version}) ergänzt.
4.4 „Unable to access lob stream"
- Ursache:
@LobaufString-Feldern (Document.rawText/cleanText) + Lesen außerhalb einer Transaktion. - Lösung:
@Lobentfernen, nur@Column(columnDefinition = "TEXT"); Suche@Transactional(readOnly = true).
4.5 Chunk-Text enthielt Zahlen statt Text (z. B. 25569)
- Ursache:
@LobaufDocumentChunck.chunckText→ PostgreSQL speicherte die OID eines Large Objects (eine Zahl), nicht den Text. - Lösung:
@Lobvom 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 (keinv1im Modell-Call). - 401 „audience incorrect" → Scope; per
jwt.msbestätigt:aud = https://ai.azure.com. Scopehttps://ai.azure.com/.defaultist korrekt. - Lokal:
az loginnötig; Eclipse/Spring Tools siehtaznur mit angepasstemPATH(Run Config → Environment) oder Start aus Terminal. - Container in Azure-VM: Managed Identity via
--network hostoder 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
AzureMistralServicewar das Modell hart codiert als"gpt-5-mini-datazone"(stattdeployment), zusätzlichreasoning_effortundmax_completion_tokens(GPT-5-Parameter). - Lösung:
"model", deployment+"temperature", 0.5+"max_tokens", maxTokens.
4.9 Passwort / .env auf dem Server
- Ursache:
.envwird von Spring nicht automatisch gelesen (keine dotenv-Dependency). - Lösung: Secrets als echte Umgebungsvariablen setzen (Docker
-e, Azure App Settings)..envgehört nicht nachsrc/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:
<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.docxBorsig11_Adresse.docxBorsig11_Kontakt.docxBorsig11_UeberUns.docxHOEMMA_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:
- Fakten-Dateien mit Frage-Varianten anreichern (z. B. „Wann habt ihr geöffnet?", „um 9 Uhr vorbeikommen", „vormittags") → senkt die Distanz real.
- Schwelle moderat anheben (
DISTANCE_THRESHOULD = 0.68) – lokale Fragen (0.57–0.67) greifen, externe (Borsig11 ~0.77) gehen weiter an die Web-Suche. - 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:
DELETE FROM document_chunck;
DELETE FROM document;
Anwendung: Stop → Clean → Run
POST /knowledge-base/build
Empfohlen für pgvector-Performance:
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-autovonupdateaufvalidatestellen.- TestController entfernen oder absichern; Logging auf
INFO. - Managed Identity (Azure) statt
az loginfür den Web-Agent. - Vollständigen HOEMMA-System-Prompt (Demenz, Notfälle, Datenschutz) einsetzen.
- Doppelte
.txt.txt-Dateien in Humbee bereinigen.