RAG Support Desk — OZM Chat Hook

Ziel

Logfiles und Dokumente rein → Wissen aufbauen → Crew fragt → Antwort kommt.
Multi-Channel-Isolation, anonymisierte Log-Analyse, persistente Chat-History.

Stack

  • FastAPI (api.py) — REST-Backend, Uvicorn auf Port 8080
  • PostgreSQL 17 + pgvector — Vektoren (768-dim) + Chat-History + Channels
  • Ollama (nomic-embed-text) — Lokale Embeddings via Docker
  • Anthropic Claude Sonnet 4.6 — Reasoning via API
  • Vanilla HTML/JS (public/index.html) — IRC-Style SPA, kein Build-Schritt
  • Docker Compose — 3 Services: db, ollama, app

Schnellstart

docker compose up --build
# Browser: http://localhost:8080
# Ollama zieht nomic-embed-text beim ersten Start — kurz warten

Projektstruktur

api.py              ← FastAPI App — alle Endpoints, Routing
ingest.py           ← chunk_lines(), get_embedding(), ingest_text()
query.py            ← similarity_search(), ask(), analyze_log()
public/index.html   ← Frontend SPA (807 Zeilen, kein Framework)
schema.sql          ← PostgreSQL-Tabellen + pgvector Index
docker-compose.yml  ← db + ollama + app
Dockerfile          ← python:3.14-slim
.env                ← DB_URL, OLLAMA_URL, ANTHROPIC_API_KEY (nicht im Git)
.env.example        ← Vorlage
logs/               ← Testlogs (*.log in .gitignore, test.log nicht)
migrate_*.sql       ← DB-Migrationen (bei Schema-Änderungen ausfĂŒhren)

DB Schema

channels    (id TEXT PK,  name TEXT UNIQUE, category TEXT)
chunks      (id SERIAL,   source TEXT, content TEXT, embedding vector(768),
             channel_id TEXT DEFAULT 'general', created_at TIMESTAMPTZ)
chat_history(id SERIAL,   channel_id TEXT, question TEXT, answer TEXT,
             sources JSONB, created_at TIMESTAMPTZ)

IVFFlat-Index auf chunks.embedding (cosine distance, lists=1 fĂŒr kleine Datasets).

Key Endpoints

Methode Pfad Funktion
GET /health DB + Ollama + Claude Status
GET/POST /channels Channels abrufen / anlegen
POST /ingest-text Text direkt ingestieren
POST /ingest Datei ingestieren
POST /query RAG-Anfrage (embedden → search → Claude)
GET /history Chat-History eines Channels
POST /analyze-log Anonymisierte Log-Analyse (Rohdaten nie gespeichert)
POST /save-analysis Analyse in general Channel persistieren
POST /admin/reset Chunks löschen (channel-scoped oder global)

Architektur-Muster

Ingest-Pipeline: Text → chunk_lines(size=20) → Ollama Embedding → INSERT chunks
Query-Pipeline: Frage → Embedding → similarity_search(top_k=5) → Claude (max 1024 Token)
Channel-Scoping: Jede Query sucht im Ziel-Channel + immer auch in general
Log-Analyse: Anonymisierung durch System-Prompt — IPs/Hosts/User werden maskiert, Rohdaten werden nicht gespeichert

Ollama-Retry: 2 Versuche, 30s Timeout, 2s Pause — bei OllamaError Fehlermeldung zurĂŒckgeben.

HĂ€ufige Fallstricke

  • Schema-Änderungen brauchen eine migrate_*.sql — nicht direkt schema.sql Ă€ndern, wenn DB schon lĂ€uft
  • lists=1 im IVFFlat-Index ist fĂŒr kleine Datasets gesetzt — bei >10k chunks erhöhen
  • Ollama-Health-Check kann beim ersten Start lĂ€nger dauern (Modell-Download)
  • python-multipart muss in requirements.txt sein, sonst schlĂ€gt Form-Parsing fehl

URLs

  • App: http://localhost:8080
  • Health: http://localhost:8080/health