Das Wissen eines Unternehmens mit mehreren Mandanten und Standorten lag über Jahre in SharePoint. Darin stand alles, was man über einen Standort wissen muss, von der Anreise bis zu den Check-in-Zeiten — gewachsen, nie aufgeräumt und über die Suche kaum zu finden. Benutzt haben es die Menschen, die täglich Auskunft geben müssen. Jetzt sollten zusätzlich zwei Maschinen daraus antworten.

Ein Importer hat den Bestand über die Graph-API aus SharePoint nach BookStack übertragen, und BookStack ist seitdem die einzige Quelle der Wahrheit. Direkt darauf antwortet ein interner Chat, rechte-genau für jeden angemeldeten Nutzer. Einmal pro Stunde geht ein ausdrücklich freigegebener Teil als Export nach Azure AI Search, je Mandant in eine eigene Knowledge Base. Aus ihr schöpfen ein Assistent auf der Website und ein Agent, der E-Mail-Antworten vorbereitet.

Ein Firmenwiki ist für Menschen gebaut, eine RAG-Wissensdatenbank für Maschinen. Beides aus derselben Quelle zu bedienen, ohne einen zweiten Bestand zu pflegen, ist machbar — und der Aufbau ist dabei nicht das Schwierige. Schwierig sind die Fehler, die Erfolg melden, während der Inhalt fehlt.

SharePointAltsystem
ImporterGraph-API, idempotent
BookStackQuelle der Wahrheit
MenschenBrowser, Rechte
Interner ChatLLM + Volltextindex
ExportEine Datei je Seite
Azure AI SearchKnowledge Base je Mandant

Drei Abnehmer derselben Quelle — kein zweiter Wissensbestand.Mandantentrennung durch getrennte Container, Indizes und Wissensbasen

Eine gepflegte Quelle, drei Abnehmer — und die Mandantentrennung entsteht erst hinter dem Export.

Überblick

Das Wichtigste in Kürze

  • Exportiert wird eine Datei pro Seite, kein Sammeldokument. Der Indexer zerlegt lange Dokumente ohne Rücksicht auf Seitengrenzen, und Quellenangaben zeigen dann ins Nichts.
  • Der Agent durchsucht nie das Wiki. Er fragt eine Knowledge Base, die auf dem Export beruht — der Unterschied entscheidet über Rechte, Mandantentrennung und Aktualität.
  • Die Löscherkennung muss vor dem allerersten Indexer-Lauf stehen. Danach hilft nur ein Neuaufbau, und das Portal legt sie nicht von allein an.
  • Eine Pipeline, die Erfolg meldet, hat damit nichts über ihren Inhalt gesagt. In einem Projekt waren gut drei Viertel des Korpus monatelang leer, während jeder Rückgabewert grün war.

Fehler

Der teuerste Fehler hat Erfolg gemeldet

Ich fange mit den Fehlern an, weil sie der nützlichste Teil sind. Der teuerste steckt nicht im Konzept, sondern in der Aufbereitung. In einem Projekt sollte ein Filter beim Export Bilder und Medien entfernen und griff dabei auch die umschließenden Elemente ab — und damit komplette Tabellen, in denen bei vielen Seiten der eigentliche Inhalt steckt. Gut drei Viertel des Korpus waren praktisch leer.

Dieser Fehler steckte von der allerersten Fassung des Exports an darin. Er lief monatelang in Produktion, stündlich, und die Protokolle waren durchweg zufrieden. Dateien wurden geschrieben, der Indexer meldete Dokumente, jeder Rückgabewert war grün. Gefehlt hat nur der Inhalt.

Entdeckt wurde er nicht durch eine Prüfung, sondern durch Zufall. Ein neuer Abnehmer kam hinzu, und im Pilotbetrieb stellte jemand die erste inhaltliche Stichprobenfrage — eine Frage, deren Antwort bekannt war. Sie wurde nicht gefunden.

Das ist die eigentliche Lehre, und sie ist unbequemer als die technische. Es waren nicht ein paar Wochen bis zur Entdeckung. Es gab schlicht nie eine inhaltliche Prüfung, bis ein zweites Projekt eine brauchte. Seitdem prüfe ich das Endprodukt inhaltlich, indem ich der fertigen Wissensdatenbank eine Frage stelle, deren Antwort ich kenne — dieselbe Haltung, die auch hinter meinem Prüfskript für Onpage-Qualität steckt.

Ausgangslage

Wer etwas wusste, wusste vor allem, wen man fragen muss

Am Anfang steht fast immer dasselbe Bild. Das Wissen eines Betriebs liegt in einem historisch gewachsenen System, die Suche ist schwach, die Struktur unklar, und maschinell auswertbar ist nichts davon. Gefunden wird trotzdem etwas, nur eben über Menschen. Wer sich auskennt, kennt vor allem die Zuständigen.

Das funktioniert erstaunlich lange und bricht genau dann, wenn eine Maschine dieselbe Auskunft geben soll. Ein Mensch überliest eine unfertige Stelle, erkennt einen Widerspruch und weiß, welche von zwei Seiten die neuere ist. Eine maschinelle Antwort tut nichts davon. Sie gibt aus, was dasteht.

Ziele

Vier Nutzungswege aus einer Quelle

Der Anspruch war von Anfang an, keinen zweiten Bestand zu schaffen. Ein Wissensbestand, den nur eine Maschine benutzt, verwahrlost, weil niemand bemerkt, wenn er altert. Also bedient dieselbe Quelle vier Wege gleichzeitig.

  • Menschen lesen und suchen klassisch, über Hierarchie, Volltextsuche und Berechtigungen.
  • Ein interner Assistent beantwortet Fragen aus dem Live-Bestand, und zwar rechte-genau — jeder bekommt nur Antworten aus Inhalten, die er auch selbst öffnen dürfte.
  • Ein ausdrücklich freigegebener Teil speist die Automatisierung nach außen — einen Assistenten auf der Website und einen Agenten, der Korrespondenz vorbereitet —, streng getrennt nach Mandant und Produktlinie.
  • Eine strukturierte Extraktion erzeugt Datenblätter mit festen Feldkennungen für nachgelagerte Systeme, dazu einen automatischen Lückenbericht für die Redaktion.

Bauweise

Bewusst langweilige Software

Als Grundlage habe ich BookStack gewählt, selbst gehostet, PHP und Laravel. Eine feste Hierarchie, eine saubere REST-API, ein brauchbares Rechtesystem — und vor allem eine kleine Betriebsfläche. Was wenig kann, kann auch wenig kaputtgehen, und die Aufgabe liegt ohnehin nicht im Wiki, sondern in dem, was daraus entsteht.

Alle Anpassungen laufen über die offizielle Theme-Schicht von BookStack, also Übersetzungen, Layout, eigene Routen sowie eine eigene Skript- und Stilebene. Der Kern bleibt unberührt, ein Update bleibt damit ein Handgriff statt eines Projekts. Den Kern zu patchen wäre schneller gewesen und hätte mich bei jedem Release eingeholt.

Befüllt habe ich es aus SharePoint, ausgelesen über die Microsoft-Graph-API als App-only-Zugriff. Der Importer ist idempotent über eine Zuordnungstabelle alter und neuer Kennungen, gleicht per Änderungszeitstempel ab, schreibt Links auf die neuen Adressen um und klassifiziert für die neue Struktur. Geschrieben wird ausschließlich über die REST-API, niemals direkt in die Datenbank des Zielsystems.

Bausteine

Zwei eigene Ergänzungen

Die erste ist eine Anmerkungsebene. Man kann Textstellen markieren und mit Notizen versehen, gespeichert wird das aber in einer eigenen Tabelle und niemals im Seiteninhalt. Dadurch ist strukturell ausgeschlossen, dass interne Anmerkungen in einen Export geraten. Was nie im Inhalt steht, kann auch nicht mit ihm hinauswandern. Die Verankerung läuft über das zitierte Textstück samt Umgebung, und verliert eine Notiz ihren Anker, weil die Stelle verschwunden ist, fällt sie sichtbar in eine eigene Liste, statt still zu verschwinden.

Die zweite sind Freigabe-Flags. Jede Ebene kann freigegeben werden, und die Freigabe vererbt sich nach unten, wobei die speziellere Ebene die allgemeinere schlägt. Voreingestellt ist nichts freigegeben. Das ist die entscheidende Richtung: Ein vergessenes Flag bedeutet zu wenig Wissen draußen, niemals zu viel. Andersherum wäre jede vergessene Einstellung ein Datenabfluss.

Assistent

Zwei Wege zur selben Seite

Der interne Chat startete nur mit der Auswahl durch ein Sprachmodell über das Inhaltsverzeichnis. Das scheiterte in der Praxis an Seiten, deren Titel nichts über den Inhalt verrät. Die Antwort auf eine konkrete Sachfrage stand auf einer Seite namens „Übersicht", und die Auswahl konnte das nicht wissen. In gewachsenen Beständen ist das nicht die Ausnahme, sondern der Normalfall.

Meine Reaktion war nicht, einen Vektorindex aufzubauen, sondern den Volltextindex anzubinden, den BookStack ohnehin pflegt. Er ist wortbasiert und IDF-gewichtet, seltene Wörter zählen also mehr als häufige, und Treffer in den Namen der übergeordneten Ebenen zählen mit, weil sie im Seitenindex gar nicht vorkommen.

Dafür gab es drei Gründe. Der Bestand ist live und ändert sich ständig — ein Embedding-Index bräuchte eine eigene, dauernd nachlaufende Infrastruktur, während sich der Volltextindex bei jedem Speichern von selbst aktualisiert. Die Rechte hängen am angemeldeten Nutzer und werden vor der Auswahl angewendet, und die eingebaute Suche ist von Haus aus rechte-gefiltert; ein externer Vektorindex müsste die Berechtigungen je Nutzer nachbilden, und das ist eine eigene Fehlerklasse. Drittens deckt die Kombination die Fälle ab, weil das Sprachmodell Synonyme, Kürzel und Umschreibungen versteht und der Volltextindex exakte Begriffe in Inhalten findet, deren Titel schweigen.

Die Antwort wird ausschließlich aus den Inhalten der gewählten Seiten gebildet und während der Entstehung ausgegeben. Die Rechtefilterung passiert vor der Auswahl und je angemeldetem Nutzer, nicht hinterher am Ergebnis. Eine Filterung am Ende wäre eine Einladung, sie irgendwann zu vergessen.

Eine Kleinigkeit mit großer Wirkung betrifft lange Seiten. Sie werden nicht stumpf nach vorn abgeschnitten, sondern als Anfang plus Textfenster um die Fundstellen der Fragewörter übergeben. Sonst fehlt regelmäßig genau die Passage am Seitenende, um die es ging.

Export

Eine Datei pro Seite

Für die maschinelle Nutzung wird der freigegebene Teil je Mandant als einzelne Dateien erzeugt, eine pro Seite. Die naheliegende Alternative, eine große Sammeldatei, ist verworfen worden, und zwar aus einem sehr konkreten Grund. Der Indexer zerlegt lange Dokumente in Abschnitte, und er tut das ohne Rücksicht auf Seitengrenzen. Ein Abschnitt enthält dann das Ende der einen und den Anfang der nächsten Seite, und Quellenangaben zeigen ins Nichts.

  • Der Dateiname stammt aus der stabilen Seitenkennung, nicht aus dem Titel. Sonst erzeugt jede Umbenennung ein Duplikat im Index statt einer Aktualisierung.
  • Die Metadaten stehen als Front Matter im Dokument selbst, nicht als Blob-Metadaten. Letztere vertragen keine Nicht-ASCII-Zeichen, und Umlaute sind im Bestand der Normalfall.
  • Die Zuordnung steht zusätzlich sichtbar im Inhalt, als Überschrift und Kontextzeile.
  • Abgeglichen werden nur Änderungen, einschließlich Löschungen und Umzügen zwischen Zuordnungen.
---
title: "Übersicht"
brand: "mandant-a"
book: "Mandant A"
chapter: "Standort Nord"
page_id: 2963
updated_at: "2026-09-11T14:37:26Z"
source: "https://wiki.example.internal/link/2963"
---

# Übersicht — Standort Nord
Kontext: Mandant A, Standort Nord
WikiFreigegebener Teil
Mandant A
Blob-Container
Index
Knowledge Base
Mandant B
Blob-Container
Index
Knowledge Base
Mandant C
Blob-Container
Index
Knowledge Base
Je Mandant ein eigener Container, ein eigener Index, eine eigene Knowledge Base. Zwischen den Spuren führt kein einziger Pfeil.

Azure

Knowledge Source, Knowledge Base, agentisches Retrieval

Der Export landet per rsync über SSH auf einem klassischen Apache-Host für die Datei-Konsumenten und parallel in Azure Blob Storage, je Mandant ein eigener Container. Der SSH-Zugang ist mit restrict und rrsync auf Schreiben in genau einen Pfad begrenzt und an eine IP gebunden.

Darüber liegen eine Knowledge Source und eine Knowledge Base je Mandant in Azure AI Search im Basic-Tier. Das Chunking übernimmt der Dienst, die Embeddings kommen von text-embedding-3-large, und für Query-Planung und Antwort-Synthese arbeitet ein kompaktes Modell der GPT-Familie — das ist gemeint, wenn von agentischem Retrieval die Rede ist. Alle Deployments laufen als DataZoneStandard in der EU-Data-Zone. Einen semantischen Ranker habe ich bewusst nicht gebucht, und eine eigene Vektordatenbank betreibe ich nicht. Die laufenden Kosten des gesamten Aufbaus liegen damit im niedrigen zweistelligen Bereich pro Monat, Stand September 2026.

Als zweite Quelle hängt an jeder Knowledge Base eine Websuche über Grounding with Bing, begrenzt auf die eigene Domain des Mandanten. Das ist die ehrlichste Stelle des Aufbaus und zugleich die unangenehmste: Der Dienst läuft als First-Party-Angebot außerhalb der Compliance-Grenze des übrigen Aufbaus. Wer das einsetzt, sollte es wissen und nicht in einem Architekturbild verschwinden lassen.

Die Trennung zwischen Mandanten entsteht durch Konstruktion, nicht durch Filter. Jeder bekommt einen eigenen Container, einen eigenen Index und eine eigene Knowledge Base. Ein gemeinsamer Bestand mit Filtern wäre billiger gewesen und hätte die Trennung von der Disziplin jeder einzelnen Abfrage abhängig gemacht. So kann ein Mandant nicht auf fremde Inhalte zugreifen, weil in seinem Bestand schlicht keine liegen — dasselbe Prinzip, nach dem auch meine 24 Websites auf einer Codebasis getrennte Datenbanken statt gemeinsamer Tabellen benutzen.

Löscherkennung

Die Einbahnstraße vor dem ersten Indexer-Lauf

Und hier liegt der Fehler, dessen Vermeidung anderen am meisten Zeit spart. Azure AI Search erkennt gelöschte Blobs nur, wenn an der Datenquelle eine Deletion-Detection-Policy hängt. Für Blob Storage mit Soft Delete ist das die NativeBlobSoftDeleteDeletionDetectionPolicy. Das Portal legt sie nicht automatisch an, und sie lässt sich nur per REST nachziehen.

PUT {search-endpoint}/datasources/{name}?api-version=2025-08-01-preview
{
  "dataDeletionDetectionPolicy": {
    "@odata.type": "#Microsoft.Azure.Search.NativeBlobSoftDeleteDeletionDetectionPolicy"
  }
}

Die Versionsangabe im Aufruf ist eine Vorschau-Fassung und damit ein wanderndes Ziel — der gezeigte Stand ist September 2026. Wer das nachbaut, prüft die aktuelle Fassung, bevor er sich auf die Zeichenkette verlässt.

Takt

Wie aktuell das Ganze wirklich ist

Der vollständige Export über alle Mandanten dauert typischerweise drei bis vier Minuten, inklusive Bereinigung, Platzhalter-Prüfung und Abgleich. Ändert sich nichts, entscheidet ein Hash-Vergleich in Sekunden. Der Indexer läuft im Stundentakt hinter dem Export her und braucht dann einstellige Minuten, weil nur Geändertes verarbeitet wird. Die Erst-Indexierung lag beim größten Mandanten unter zehn Minuten, beim kleinsten unter einer.

Eine einzelne Anfrage an die Knowledge Base braucht Ende zu Ende einstellige Sekunden, wobei die Query-Planung in der Regel mehrere Suchvarianten je Quelle stellt. Die Datenblatt-Extraktion läuft täglich, aber nur für Standorte mit geänderten Quellseiten; ein geänderter Standort kostet etwa eine Minute. Die Zahl, die im Alltag wirklich zählt, ist aber eine andere — das Wissen ist im Stundentakt aktuell, ohne dass es jemand merkt.

Datenblätter

Ein Feldkatalog als Vertrag

Für nachgelagerte Systeme entsteht täglich eine strukturierte Auswertung gegen einen festen Feldkatalog im dreistelligen Bereich. Die Extraktion übernimmt ein Claude-Modell über einen Azure-Endpunkt, ebenfalls als DataZoneStandard. Die Kennungen sind ein Vertrag und werden nie geändert, nur ergänzt.

{
  "version": 1,
  "hinweis": "IDs nie ändern, nur ergänzen",
  "kategorien": [
    { "titel": "Kontakt", "felder": [
      { "id": "kontakt.tel_empfang",  "label": "Telefon Empfang" },
      { "id": "kontakt.email_service", "label": "E-Mail Service" }
    ] }
  ]
}

// Ergebnis je Feld — Wert mit Beleg oder ausdrücklich null
{ "wert": "…", "quelle_seite": 2963, "quelle_url": "…" }

Dieses ausdrückliche Nichts ist der eigentliche Gewinn. Eine freie Extraktion liefert immer irgendetwas, und eine Lücke sieht dann aus wie eine Auskunft. So dagegen entsteht aus den leeren Feldern automatisch ein Bericht für die Redaktion, sortiert nach Handlungsbedarf. Die Pflege wird dadurch nicht kleiner, aber sie wird gerichtet.

Getaggte Wiki-SeitenJe Standort
ExtraktionGegen den Feldkatalog
  • kontakt.tel_empfangWert + Beleg
  • kontakt.email_serviceWert + Beleg
  • anreise.parkenWert + Beleg
  • check_in.zeitWert + Beleg
  • ausstattung.saunanull
  • gebuehren.haustiernull
LückenberichtNur die leeren Felder

Jeder Wert trägt seinen Beleg — oder steht ausdrücklich auf null.

Jedes Feld trägt seinen Beleg — oder ausdrücklich nichts. Nur die leeren laufen weiter in den Lückenbericht.

Irrtümer

Sechs weitere Fallen, mit denen kaum jemand rechnet

Tabellen als rohes Auszeichnungsformat zu übergeben, ruiniert die Auffindbarkeit. Das Rauschen aus Formatierungsangaben dominiert den Abschnitt, und die eine relevante Zelle geht in der Bewertung unter. Tabellen werden deshalb vor der Indexierung zu schlichten Textzeilen umgewandelt.

Gleichnamige Seiten über mehrere Standorte hinweg haben zu den unangenehmsten Fehlern geführt. Viele Seiten heißen gleich, und die Zuordnung stand nur in Metadaten, die tiefere Abschnitte nicht mehr mittragen. Die Antwort mischte daraufhin Standorte und gab die Regel des einen als Auskunft für den anderen aus. Geholfen hat, den Kontext sichtbar in den Inhalt jeder Datei zu schreiben und ausdrücklich anzuweisen, dass Angaben je Standort gelten.

Zweimal hat dieselbe Lehre zugeschlagen. Eine Antwort, die während der Entstehung ausgegeben werden sollte, kam beim Empfänger als ein einziger Block an, weil eine Pufferschwelle im Webserver kleine Häppchen zurückhielt. Und später liefen Uploads wochenlang erfolgreich in ein Verzeichnis, das längst niemand mehr auslieferte. Beide Male war das eigene Protokoll zufrieden. Geprüft werden muss am ausgelieferten Ergebnis, nicht am eigenen Sendeprotokoll.

Zwei kleinere Fallen zum Schluss. Freigabe-Flags wandern mit, wenn Inhalte ins Archiv verschoben werden, und kippen dann veraltetes Material nach außen — der Bestand braucht also eine regelmäßige Durchsicht, nicht nur eine beim Aufbau. Und Platzhalter wie eine offengelassene Zahl oder ein „folgt noch" landen wörtlich in maschinellen Antworten. Ein einfacher Mustertest in der Aufbereitung fängt das ab, mit Ausnahmen für Schreibweisen, die legitim so aussehen.

Eine Verarbeitung, die Erfolg meldet, hat damit nichts über ihren Inhalt gesagt.

Fazit

Der Aufbau ist der einfache Teil

Wer so etwas baut, unterschätzt zuverlässig dasselbe. Die Dienste zusammenzusetzen kostet Tage, nicht Wochen — BookStack steht an einem Nachmittag, der Export ist ein Skript, und Azure AI Search nimmt einem Chunking, Embeddings und Antwort-Synthese ab. Was wirklich Zeit kostet, ist die Gewissheit, dass am Ende der Kette noch Inhalt ankommt.

Die einzige Regel, die ich aus diesem Aufbau tatsächlich mitgenommen habe, ist deshalb keine technische. Jede Stufe meldet Erfolg über das, was sie selbst getan hat — Dateien geschrieben, Dokumente indexiert, Antwort erzeugt. Keine einzige meldet, ob das Ergebnis stimmt. Diese Prüfung muss man sich selbst bauen, und sie besteht im Kern aus einer einzigen Frage, deren Antwort man vorher kennt.

Fragen

Häufige Fragen

Warum kein eigener Wissensbestand nur für die Maschine?

Weil ihn niemand pflegen würde. Ein Bestand, in den täglich Menschen schauen, bleibt aktuell, weil Fehler auffallen. Ein Bestand, den nur eine Maschine liest, altert unbemerkt und liefert irgendwann zuverlässig falsche Auskünfte.

Ist eine Freigabe je Seite nicht zu aufwendig?

Der Aufwand entsteht einmal und verteilt sich, weil die Freigabe sich nach unten vererbt. Wichtiger ist die Richtung des Fehlers. Bei Opt-in fehlt im schlimmsten Fall Wissen, bei Opt-out steht es an der falschen Stelle.

Warum getrennte Bestände statt Filter?

Weil ein Filter jedes Mal richtig gesetzt sein muss und ein getrennter Bestand kein einziges Mal. Die Trennung soll eine Eigenschaft des Aufbaus sein und nicht eine Eigenschaft der Sorgfalt.

Was tun mit Widersprüchen im Bestand?

Sie sind das eigentliche Problem, weil eine maschinelle Antwort sie nicht bemerkt und einfach eine Variante wählt. Helfen kann nur die Redaktion. Technisch lässt sich das Aufspüren unterstützen, die Entscheidung nicht.

Durchsucht der Assistent das Wiki direkt?

Nein, und das ist ein wichtiger Unterschied. Er fragt die Knowledge Base, die auf dem Export beruht. Dadurch verlassen nur ausdrücklich freigegebene Inhalte das Haus, und die Mandantentrennung hängt nicht an einer richtig formulierten Abfrage. Der Preis ist eine Verzögerung zwischen Pflege und Wirksamkeit.

Warum Azure AI Search und keine eigene Vektordatenbank?

Weil der Betrieb einer eigenen Lösung Arbeit macht, die nichts zum Ergebnis beiträgt. Chunking, Indexpflege und Synthese sind im Dienst enthalten. Was ich dafür aufgebe, ist Kontrolle über das Chunking — und genau die habe ich mir über eine Datei pro Seite zurückgeholt. Auf der Leseseite wirkt sich das vor allem darauf aus, wie zuverlässig ein Agent seine Quellen benennen kann.

Über ein Projekt sprechen?

Ob KI-Anwendung, Automation oder Mediaeinkauf — schreib mir gern.

Kontakt aufnehmen