30+ Schritte von der Bürgerfrage bis zur fertigen Antwort
Bevor eine einzige Frage beantwortet werden kann, muss das System wissen was es weiß. KiStack bezieht sein Wissen aus zwei offiziellen Quellen der Stadt Brandenburg an der Havel: dem Stadtportal stadt-brandon.de (Verwaltung, Ämter, Dienstleistungen) und dem Tourismusportal erlebnis-brandon.de (Events, Unterkünfte, Sehenswürdigkeiten). Jede Nacht um 04:20 Uhr werden diese Quellen automatisch aktualisiert. Dienstags und freitags durchsucht ein automatischer Crawler (ein Programm, das Webseiten selbstständig ausliest) zusätzlich alle Unterseiten und PDF-Dokumente. Das gesamte Wissen — über 36.000 Textabschnitte — ist auf dem eigenen Server gespeichert, nicht in einer Cloud.
Lange Texte werden in handliche Abschnitte zerschnitten — ähnlich wie man ein Buch in Kapitel einteilt. Jeder Abschnitt bekommt seinen eigenen „Fingerabdruck" (Vektor), der seinen Inhalt mathematisch beschreibt. So kann das System später ähnliche Inhalte finden — auch wenn nicht dieselben Wörter verwendet werden. Dieser Schritt findet nur beim Einlesen neuer Daten statt (täglich 04:20 Uhr), nicht bei jeder Bürgerfrage.
Optional: Nach dem Chunking kann jeder Textabschnitt durch das Sprachmodell mit Alltagsbegriffen und einer Kurzbeschreibung angereichert werden. Damit schließt das System die semantische Lücke zwischen Verwaltungssprache und Bürgersprache bereits im Ingest-Schritt — noch bevor eine Bürgerfrage gestellt wird. Aktiviert via INGEST_ENRICH=1.
Synonyme aus Bürger-Sicht
z.B. "Gelbe Tonne", "Wertstoff", "Mülltrennung"→ Qdrant payload + Meili searchable
Kompakte Zusammenfassung im Verwaltungsstil
z.B. "Recycling-Abgabestellen in Brandenburg a. d. H. …"→ Qdrant payload + Meili searchable
Jeder Textabschnitt wird auf Adressen und Ortsnamen untersucht. Gefundene Adressen werden über den OpenStreetMap-Dienst Nominatim in Koordinaten (geo_lat/geo_lon) umgewandelt und im Qdrant-Payload gespeichert. Damit können Bürger standortbezogene Fragen stellen wie „Was ist in meiner Nähe?". Ein lokaler Cache beschleunigt wiederholte Abfragen.
Points of Interest werden wöchentlich aus OpenStreetMap synchronisiert. Die Overpass-API liefert Restaurants, Hotels, Museen, Parks, Kirchen, Supermärkte und weitere Orte im Stadtgebiet — als Node, Way und Relation mit Centroid-Koordinaten. 463 kuratierte POIs bilden die Collection city_pois mit 100% Geo-Abdeckung.
Personen der Stadtverwaltung werden aus dem Personalverzeichnis (PDF/HTML) extrahiert und als city_people Collection in Qdrant gespeichert. Manuelle Korrekturen über die Admin-Oberfläche (manual_overrides) überleben jeden Re-Ingest. Aktuell ~104 Personen mit Funktion, Abteilung, Kontaktdaten und Sprechzeiten.
Veraltete Datenpunkte, die in der Quell-XML nicht mehr vorhanden sind, werden nach 45 Tagen automatisch aus Qdrant und Meilisearch entfernt. Verhindert, dass das System Antworten auf Basis gelöschter oder veralteter Webseiten gibt. Protokollierung in den Ingest-Logs.
Nach jedem Qdrant-Ingest werden alle Dokumente automatisch in Meilisearch gespiegelt — die Stichwort-Suchmaschine (BM25). Titel, Textinhalt, Alltagsbegriffe, Tags und Geo-Koordinaten werden synchronisiert. 237 Synonym-Gruppen und Stoppwörter werden dabei angewendet, damit z.B. „Kita" auch „Kindertagesstätte" findet. Ohne diesen Schritt würde die Hybrid-Suche (Phase 2) nur Vektoren nutzen und keine Stichwort-Treffer liefern.
Zukünftig: Bürger können Fotos von Dokumenten, Briefen oder Ausweisen einscannen und als Eingabe verwenden. Das Modell extrahiert Text und Kontext direkt aus dem Bild.
Jede eingehende Anfrage wird zunächst auf Häufigkeit geprüft. Mehr als 30 Anfragen pro Minute von einer IP-Adresse werden mit HTTP 429 gedrosselt — Antwort als JSON (nicht HTML). Verhindert missbräuchliche Nutzung und schützt Server-Ressourcen.
Die Eingabe wird auf persönliche Daten (IBAN, Kreditkarte, Telefon, E-Mail, Ausweis, SV-Nummer) und auf Manipulationsversuche geprüft. Erkannte PII-Muster werden durch [Typ entfernt] maskiert, Injection-Versuche (6 Pattern-Gruppen: Rollenanweisungen, Jailbreak, DAN, Code-Injection, Datenexfiltration …) mit HTTP 400 abgewiesen. Output-Bereinigung entfernt IBAN und Kreditkartennummern aus LLM-Antworten.
Jede Browser-Sitzung erhält eine Session-ID (sessionStorage, Tab-basiert, kein Cookie). Die letzten 3 Fragen und Antworten werden als [Bisheriger Gesprächsverlauf]-Block dem Prompt vorangestellt. Bürger können Folgefragen stellen ohne die vorherige Frage zu wiederholen. TTL: 2 Stunden, Antworten auf 300 Zeichen gekürzt (Token-Effizienz).
Die Eingabe des Bürgers wird technisch vorbereitet: Groß- und Kleinschreibung wird vereinheitlicht, Satzzeichen werden entfernt, Umlaute in beide Schreibweisen umgewandelt — ä wird zu ae, aber auch ae wird zu ä. Dadurch findet das System „Straße" auch wenn „Strasse" eingegeben wurde, und umgekehrt.
Das System analysiert die Struktur der Frage und erkennt die Absicht des Bürgers. Fragt er nach einer Person (WHO), einem Ort (WHERE), einem Termin (WHEN), einer Aufzählung (LIST) oder einem Vorgang (HOW)? Diese Erkennung entscheidet welche Datenquellen bevorzugt werden und welche Antwortstruktur das Sprachmodell am Ende verwendet. Es gibt insgesamt 9 Absichtstypen.
Manchmal kennt das System die Antwort bereits — ohne die volle KI-Verarbeitung. Wenn eine Frage sehr genau mit einem gespeicherten FAQ-Eintrag übereinstimmt (Score ≥ 0.90), wird die Antwort direkt geliefert und alle nachfolgenden Schritte werden übersprungen. Bei einem Score ≥ 0.45 wird der FAQ-Treffer nur als Hinweis dem Kontext hinzugefügt, die volle Pipeline läuft weiterhin durch.
Das System erkennt besondere Anforderungen in der Frage: „Nenne mir 3 Restaurants" → es werden genau 3 Ergebnisse gesucht (COUNT). „Restaurants in der Nähe" → Ergebnisse werden nach Entfernung zum erkannten Standort sortiert (NEARBY). „Liste alle Hotels" → die Antwort wird als Aufzählung formatiert (LIST). Diese Constraints steuern sowohl Suche als auch Antwortstruktur des Sprachmodells.
Aus der vorbereiteten Frage werden inhaltlich tragende Wörter herausgelöst — die eigentlichen Fachbegriffe der Anfrage. Füllwörter wie „ist", „gibt", „kann", „sich" werden ignoriert (das System kennt 306 solcher Stoppwörter). Was übrig bleibt sind die Begriffe die die Suche steuern: z.B. „Touristinformation", „Öffnungszeiten", „Ordnungsamt". Je präziser diese Begriffe, desto besser das Suchergebnis.
Das System prüft ob die Frage einen geografischen Bezug enthält: Stadtteile (Neustadt, Altstadt, Görden …), Postleitzahlen (14770–14776) oder bekannte Orte (Bahnhof, Rathaus, Dom). Wird ein Ort erkannt, werden Suchergebnisse nach ihrer Entfernung zu diesem Punkt sortiert und die Antwort enthält Angaben wie „ca. 300 m entfernt".
Bevor die eigentliche Suche startet, reichert das System die Anfrage durch zwei parallele LLM-Aufrufe an — beide Schritte schließen die semantische Lücke zwischen Bürgersprache und Verwaltungsdokumenten. HyDE (Hypothetical Document Embeddings) lässt das Modell eine fiktive Verwaltungsantwort formulieren und embeddet diese statt der Originalfrage — der Vektor liegt viel näher an echten Dokumenten als der der Bürgerfrage. Query-Rewriting erzeugt 2–3 Umformulierungen für breiteren BM25-Abdeckung in Meilisearch.
Ollama → hypothetische Verwaltungsantwort (2–3 Sätze)
→ nomic-embed-text → 768-dim Vektor
→ Qdrant-Suche mit HyDE-Vektor statt Originalfragehardcoded aktiv
Ollama → normalisiert / Verwaltungssprache / Suchbegriff
→ BM25-Suche je Variante in Meilisearch
→ Treffer fließen in RRF-Merge (Phase 3) einENV: QUERY_REWRITE_ENABLED=1
Die ursprüngliche Frage wird mit den extrahierten Schlüsselbegriffen angereichert bevor sie in die Suche geht. Ein Duplikat-Schutz verhindert dass Begriffe doppelt erscheinen. Zusätzlich werden Umlaut-Varianten erzeugt (ä/ae, ö/oe, ü/ue), damit die Suche sowohl „Öffnungszeiten" als auch „Oeffnungszeiten" findet. Das Ergebnis ist eine optimierte Suchanfrage die mehr relevante Dokumente findet als die Originalfrage allein.
KiStack hat 5 Datenquellen (sogenannte Collections): Stadtverwaltung, Unterseiten, Dokumente, Tourismus/Erlebnis und Personen. Je nach erkannter Absicht werden diese unterschiedlich stark berücksichtigt: Bei einer Personenfrage werden die Personendaten bevorzugt, bei einer Veranstaltungsfrage die Tourismus-Daten mit dem 1,6-fachen Gewicht. Das Balkendiagramm zeigt welche Datenquelle für diese konkrete Frage welchen Verstärkungs-Faktor erhält.
Drei Suchpfade laufen gleichzeitig: (1) Dense-Suche mit dem HyDE-Vektor aus Phase 1b — findet semantisch ähnliche Dokumente im Bedeutungsraum der Verwaltungsdokumente. (2) Dense-Suche mit dem Embedding der erweiterten Originalfrage (Qdrant / nomic-embed-text). (3) BM25-Stichwortsuche für jede Query-Rewriting-Variante in Meilisearch — mit Synonymen und Stoppwörtern. Alle drei Ergebnislisten werden per RRF-Fusion zu einem gemeinsamen Ranking vereint.
Qdrant
nomic-embed-text
Hypothetisches Dokument-Embedding
Qdrant
nomic-embed-text
Erweitertes Originalfrage-Embedding
Meilisearch
2–3× je Variante
Synonyme + Stoppwörter
Die Ergebnisse beider Suchverfahren werden zusammengeführt und nach einem kombinierten Wert neu sortiert. Das Verfahren heißt RRF (Reciprocal Rank Fusion — gegenseitige Rangverstärkung): Ein Dokument das in beiden Suchlisten weit oben steht, erhält einen besonders hohen Gesamtwert. Zusätzlich misst ein Coverage-Score wie viele der Suchbegriffe tatsächlich im Dokument vorkommen. So profitiert jedes Ergebnis von beiden Suchverfahren gleichzeitig.
Nach dem RRF-Ranking werden die Top-20 Treffer durch das Modell BAAI/bge-reranker-v2-m3 (~1.1 GB, gecacht) neu bewertet. Es vergleicht Frage und Dokumentinhalt als Paar — präziser als reines Vektor-Ähnlichkeitsmaß. Smoke-Test bestanden: Score 0.9826 für relevanten Treffer.
Zwei Mechanismen verbessern die Qualität der Retrieval-Ergebnisse: (1) URL-Deduplication — bei mehreren Chunks derselben URL wird der längste/relevanteste bevorzugt statt alle anzuzeigen. (2) BIS-Artefakt-Cleaning — das Serviceportal (egov-bis-detail) erzeugt Template-Fragmente wie "nd:", "Textblöcke ein-/ausklappen" die vor der LLM-Übergabe entfernt werden. 654 Chunks (28%) waren betroffen.
Bei WHO-Anfragen (z.B. „Wer leitet das Amt im März 2026?") wird das Datum aus der Frage extrahiert — auch deutsche Monatsnamen. Personen-Treffer werden gegen ihre Amtszeiten geprüft: nur wer zum angefragten Zeitpunkt aktiv war, bleibt im Ergebnis. Bei NEARBY-Anfragen werden vergangene Veranstaltungen und abgelaufene Events herausgefiltert. Unterstützt DD.MM.YYYY, YYYY-MM-DD und „im April 2026"-Formate.
Zukünftig: Beziehungen zwischen Entitäten werden als Graph gespeichert. „Steffen Scheller → Oberbürgermeister → Rathaus → Öffnungszeiten" ermöglicht mehrstufige Antworten. Besonders wertvoll für Fragen wie „Welcher Dezernent ist für Stadtentwicklung zuständig und wo sitzt er?".
Nicht alle gefundenen Textabschnitte sind gleich gut. Das System prüft jeden Treffer nach mehreren Kriterien: Relevanz-Score (≥ 0.18 Minimum), Newsletter-Abwertung, Coverage-Score (wie viele Suchbegriffe im Dokument vorkommen), Freshness-Penalty (ältere Dokumente werden leicht abgewertet), Past-Event-Filter (vergangene Veranstaltungen werden entfernt), und bei NEARBY-Anfragen: rank_by_distance (Sortierung nach Geo-Distanz) plus Temporal-Filter (nur aktuelle Events und Seiten). Wenn zu wenige gute Quellen übrig bleiben, antwortet das System ehrlich: „Dazu habe ich leider keine Information."
Die Top-5 Dokumente werden als Textausschnitte zusammengestellt. Bei Standort-Fragen enthält jeder Ausschnitt die berechnete Entfernung zum gesuchten Ort. Dieses Kontext-Paket ist das einzige was das Sprachmodell zu sehen bekommt — es darf keine eigenen Kenntnisse oder Informationen aus dem Internet verwenden, sondern ausschließlich diese bereitgestellten, geprüften Quellen.
Das lokale Sprachmodell qwen3:14b — ein KI-Modell mit 14 Milliarden Parametern, vergleichbar mit einem sehr erfahrenen Textredakteur — läuft vollständig auf dem eigenen Server ohne Cloud-Anbindung. Es formuliert aus den bereitgestellten Quelltexten eine verständliche deutsche Antwort im Sie-Stil. 7 verschiedene Antwort-Vorlagen je nach erkannter Absicht steuern Ton und Struktur der Antwort.
Nach jeder generierten Antwort bewertet dasselbe Sprachmodell (qwen3:14b) die eigene Ausgabe in einer separaten, parallelen Anfrage — vollständig asynchron in einem Hintergrund-Thread, ohne die Antwortzeit zu beeinflussen. Das Modell übernimmt damit zwei Rollen gleichzeitig: Antwortgenerierung und anschließende Qualitätsprüfung. Bewertet werden vier Dimensionen auf einer Skala 1–5: Korrektheit (Inhalt ausschließlich aus den Quellen ableitbar?), Vollständigkeit (alle Aspekte der Frage beantwortet?), Ton (formelles Sie, bürgernah, verständlich?) und Quellennutzung (keine Halluzination?). Korrekte Ablehnungen ("Dazu liegen mir keine Informationen vor") werden automatisch übersprungen und zählen nicht als Fehler. Alle Scores werden in der Tabelle judge_scores in SQLite gespeichert und sind im Admin-Bereich unter /admin/settings auswertbar. Ermöglicht kontinuierliche Qualitätsmessung auch bei Fragen die noch nicht im Benchmark sind — ohne manuelle Musterlösungen.
Die rohe Antwort des Sprachmodells wird für die Anzeige aufbereitet: Formatierungszeichen wie ** oder ## die das Modell manchmal einfügt werden entfernt. Jede Quellenangabe wird als klickbarer Link zur Originalseite auf stadt-brandon.de oder erlebnis-brandon.de aufgebaut. Bei Spracheingabe wird die Antwort zusätzlich an den Vorlesedienst (Piper TTS — ein Sprachsyntheser, der lokal auf dem Server läuft) übergeben. Erst dann wird die fertige Antwort Wort für Wort (Streaming) an den Browser des Bürgers übertragen.
Vor der LLM-Generierung wird eine detaillierte Antwort-Instruktion erstellt: Formale Anrede („Sie"), Quellenpflicht, Länge (max. 4 Sätze), intent-spezifische Regeln (WHO → Name+Funktion+Kontakt, WHERE → Adresse+Öffnungszeiten, WHEN → Datum+Uhrzeit). Bei zu wenig Kontext (Score < 0.18) greift die Fallback-Logik: Das System antwortet ehrlich „Dazu habe ich leider keine verlässliche Information" statt zu halluzinieren.
Unter jeder Antwort erscheinen zwei Schaltflächen: Hilfreich / Nicht hilfreich. Das Feedback wird gespeichert und schließt den Qualitätskreislauf: Negative Bewertungen fließen automatisch als neue Testfälle in den Benchmark, positive Antworten können direkt als FAQ-Einträge übernommen werden.
Zeigt ein farbiges Balkendiagramm der gemessenen Laufzeiten aller Verarbeitungsschritte. Typisch: Sprachverarbeitung (NLP) ~15 ms, KI-Bedeutungssuche (Qdrant) ~150 ms, Stichwortsuche (Meilisearch) ~70 ms, Zusammenführung (RRF) ~10 ms, Sprachmodell (LLM) 3–8 Sekunden. Der mit Abstand größte Zeitblock ist immer die KI-Generierung der Antwort.
Dieser letzte Schritt ist kein technischer Verarbeitungsschritt, sondern ein bewusstes Bekenntnis: Die gesamte Verarbeitung — von der Frage des Bürgers bis zur fertigen Antwort — findet ausschließlich auf dem eigenen Server der STG Brandenburg an der Havel statt. Kein einziges Wort der Bürgerfrage verlässt den städtischen Server. Es wird kein Cloud-Dienst genutzt, keine externe KI-API aufgerufen (OpenAI, Google, Microsoft etc.), keine Nutzerdaten weitergegeben. Das Sprachmodell qwen3:14b, die Vektordatenbank Qdrant, die Suchmaschine Meilisearch und der Vorlesedienst Piper — alle laufen vollständig lokal auf der eigenen Hardware.
qwen3:14b wird ein zweites Mal aufgerufen — diesmal nicht als Chatbot, sondern als Judge. Er bewertet jede Antwort auf zwei Metriken: Faithfulness (Ist die Antwort durch die gefundenen Quellen belegt, oder halluziniert der LLM?) und Context Precision (Sind die retrieved Chunks überhaupt relevant für die Frage?). So lässt sich gezielt erkennen, ob ein Problem im Retrieval oder in der Generierung liegt.
688 Testfragen werden regelmäßig automatisch gegen die Pipeline getestet. Jede Antwort wird als OK oder Ablehner klassifiziert (ok_count Tracking). Ergebnisse fließen in die Lückenanalyse: Ablehner-Muster werden erkannt, fehlende Synonyme automatisch generiert und das System kontinuierlich verbessert.
Jeder Verarbeitungsschritt wird als Span mit Zeitstempel in SQLite gespeichert. 6 Spans: nlp_phase → embed → qdrant_search → meilisearch_search → rerank_merge → llm_generate. Bei Produktionsfehlern ist sofort sichtbar: In welchem Schritt ist die Pipeline abgewichen? Dashboard unter /admin/traces mit Span-Statistiken und Timeline-Visualisierung.
Jede Chat-Antwort wird in der Ergebnis-Datenbank gespeichert. Negative Bewertungen (👎) und Antworten mit niedrigem Score fließen automatisch als neue Testfälle in den Benchmark-Katalog. So wächst die Testabdeckung organisch mit der realen Nutzung. Positive Antworten können als FAQ-Einträge übernommen werden. Der Kreislauf schließt sich: Chat → Feedback → Benchmark → Verbesserung → bessere Antworten.
Neue Prompt-Versionen oder Modell-Upgrades werden automatisch gegen den Benchmark getestet bevor sie live gehen. Wenn eine neue Version die OK-Rate um mehr als 2% verschlechtert, wird das Deployment blockiert. Verhindert Regressions-Fehler bei Weiterentwicklungen.
Vollständig automatisierter Deployment-Prozess: Codeänderung → Git-Push → automatischer Benchmark-Run → nur bei bestandenem Test wird live deployed. Verhindert, dass manuelle Deployments unter Zeitdruck vergessene Tests verursachen.