Aufgabe
Jedes Projekt, das ein Sprachmodell braucht, schickt eine versionierte Aufgabe: „fasse dieses Transkript zusammen“, „bewerte diese Musiktitel“, „beantworte diese Frage nur aus diesen Passagen“. Die Schleuse wählt Anbieter und Modell, fällt bei Fehlern auf den nächsten Anbieter zurück, prüft das Budget, prüft die Region, anonymisiert wo gefordert und protokolliert jeden Versuch. Die Anwendung erfährt davon nichts, außer dem Ergebnis.
Technisch: PHP, eine SQLite-Datenbank, kein Framework. Ein Webprozess für synchrone Aufrufe, ein Hintergrundprozess für die Warteschlange. Angebunden sind ein deutscher Anbieter, zwei amerikanische, ein Modell auf eigener Maschine und der Anonymisierer als eigener Dienst.
Vorfrage
Die vier Bedingungen, angewandt auf das, was die Schleuse selbst tut.
- NEINWeg unbekannt? Nein. Prüfen, wählen, aufrufen, wiederholen, protokollieren: immer dieselbe Kette. Ein Modell entscheidet hier nichts über den Ablauf. Damit ist es ein Workflow, und die Prüfung endet.
Das ist der Punkt: die Teile, die einen Agenten im Betrieb halten, sind selbst keine Agenten. Sie sind Regeln, und Regeln sollen deterministisch laufen.
Bauform
Auslöser, Lauf, Rückgabe.
Die fünf Grenzen, die hier stecken
Das ist die Checkliste aus Abschnitt 05 des Grundlagendokuments, als Code.
Jedes Projekt hat ein Tagesbudget in Token, jeder Kunde eines dazu. Die kleinere Grenze gilt. Geprüft wird vor dem Aufruf, gegen das Maximum, das die Aufgabe anmeldet, nicht hinterher gegen das, was sie gebraucht hat. Ist das Budget erschöpft, antwortet die Schleuse mit „später“ und einer Uhrzeit, eingereihte Aufträge warten bis zum nächsten Tag, ohne einen Versuch zu verbrauchen. Wie sich das anfühlt, wenn es zuschlägt, steht bei der Musikbibliothek.
Eine Aufgabe darf deklarieren, in welcher Region ihre Daten verarbeitet werden dürfen. Wo ein Anbieter verarbeitet, steht in der Umgebung der Maschine, nicht im Code, weil dasselbe lokale Modell in Frankfurt ein EU-Verarbeiter ist und anderswo nicht. Geprüft wird, bevor die Anbieterkette abgearbeitet wird. Ein Anbieter außerhalb wird nie erreicht, egal wie die Kette sortiert ist. Ein Anbieter ohne bekannte Region gilt als unbekannt und ist draußen. Bleibt keiner übrig, heißt der Fehler „kein zulässiger Anbieter“ und nicht „vorübergehend nicht verfügbar“, weil eine Wiederholung hier nie gelingen kann.
Ein Projekt oder sein Kunde kann fordern, dass vor bestimmten Anbietern anonymisiert wird. Beide dürfen fordern, beide Forderungen gelten: Vorgaben verschärfen, sie weichen nicht auf. Entschieden wird je Anbieter, nicht je Anfrage. Eine Ersatzroute erbt die Einstellung nicht, sondern wird neu geprüft, sonst hinge der Schutz davon ab, wer gerade ausgefallen ist. Der Anbieter bekommt Platzhalter, die Anwendung bekommt Klartext zurück. Die Zuordnungstabelle dazwischen ist der Schlüssel zur Re-Identifizierung. Sie lebt nur im Speicher des einen Aufrufs: nicht im Protokoll, nicht in der Datenbank, nicht in der Antwort. Nachgeprüft: nach mehreren Läufen mit Klarnamen steht in keiner Tabelle einer davon.
Antwortet der Anonymisierer nicht, bricht der Aufruf ab: „Anonymisierung war gefordert, der Dienst hat nicht geantwortet, es wurde nichts gesendet.“ Kein Durchreichen, kein Ausweichen auf den nächsten Anbieter. Das Gegenteil, ein Schutz, der bei Ausfall den Originaltext weitergibt, sieht im Betrieb aus wie ein Schutz, der funktioniert. Genau das tut ein älterer Prototyp von mir bis heute.
Jeder Anbieter-Versuch ist eine Zeile: Projekt, Kunde, Auftrag, Aufgabe, Anbieter, Modell, Status, Latenz, vier Token-Arten, Abbruchgrund, Ersatzroute, Fehler, ob anonymisiert wurde. Was nicht darin steht: der Wortlaut des Prompts und der Antwort. Ein Protokoll, das Inhalte dupliziert, ist ein zweiter Datenspeicher mit den Rechten eines Logs. Ein übersprungener Anbieter steht mit Begründung drin, ob wegen der Region oder wegen einer fehlenden Freigabe, damit ein Ausschluss nie wie ein Fehler aussieht.
Ein Durchstich: der Recall
Die Aufgabe, die am häufigsten mit „dafür braucht es eine Vektor-Datenbank“ beantwortet wird, gebaut ohne eine.
Ich arbeite seit Monaten mit einem Programmierassistenten, und jede Sitzung hinterlässt ein Transkript. Die Frage „wann habe ich das schon einmal gelöst, und wie“ war lange nur mit Suchen im Dateisystem zu beantworten. Der Recall beantwortet sie in zwei Schritten:
- 01Volltextsuche. Alle Transkripte liegen in einem Volltextindex auf dem eigenen Server. Die Frage liefert die zehn passendsten Passagen, nummeriert, mit Projekt, Sitzung und Datum. Kein Modell beteiligt, das Ergebnis ist bei gleicher Frage gleich.
- 02Ein Modellaufruf. Frage und Passagen gehen als Aufgabe durch die Schleuse, Region EU, weil Transkripte echte Namen enthalten. Der Auftrag an das Modell: antworte ausschließlich aus den Passagen, belege jede Aussage mit der Passagennummer, und wenn die Antwort nicht darin steht, sag wörtlich „In den Passagen nicht enthalten“. Keine Pfade, keine Befehle, keine Namen erfinden, auch keine plausiblen.
Das Ergebnis ist eine Antwort mit Quellen, erreichbar aus dem Assistenten selbst, aus einer kleinen Webseite, aus der Kommandozeile und aus dem Chat-Dienst des Anbieters. Der Recall hat einen eigenen Schlüssel mit eigenem Tagesbudget, damit seine Kosten von denen der Musikbibliothek getrennt bleiben. Und er war der Fall, an dem sich zeigte, dass die Geduldslogik nur der Stapel braucht: kleine Einzelanfragen liefen selbst während der Lastwellen des Anbieters in etwa zwei Sekunden.
Messlatte
Für die Schleuse selbst gibt es drei Prüfläufe, die nach jeder Änderung laufen: Verträge und Validierung, Nebenläufigkeit der Warteschlange, und die Verwaltung über HTTP gegen eine Wegwerf-Datenbank. Sie kosten nichts, weil sie keinen Anbieter erreichen. Was sie nicht messen: ob ein Anbieter gut urteilt. Das habe ich getrennt gemessen, an über tausend Musiktiteln, und die Kurzfassung steht hier.
Das Urteil des deutschen Anbieters liegt näher am Konsens der Spitzenmodelle als das kleine amerikanische Modell. Bezahlt wird also Verfügbarkeit, nicht Urteilskraft. Der ganze Lauf mit Grafik: Hetzner AI Experiments im Praxistest.
Was schiefging
Datiert, weil ein Fehler ohne Datum eine Anekdote ist.
Ein Parameter, den der Anbieter aus seiner Schnittstelle entfernt hatte, stand bei mir als gleichwertige Einstellung zur Wahl. Erst die Temperatur, dann das Denkbudget. Beides führte zu abgewiesenen Anfragen. Seitdem nimmt jeder Anbieter den Transport als austauschbare Schicht, und jede Einstellung hat einen Test, der die gesendete Nutzlast prüft. Ein Review ersetzt den Test nicht: beide Male war der Code gelesen worden.
Denkende Modelle liefern eine leere Antwort, solange sie denken. Die Schleuse hielt das für eine kaputte Konfiguration und stoppte die Kette, statt weiterzufallen. Regel seitdem: endgültig sind nur Zugangsdaten und abgewiesene Anfragen. Alles andere ist vorübergehend.
Lange Stapelanfragen an denkende Modelle brachen ab, und die Schleuse sah einen Transportfehler. Ursache war nicht der Anbieter, sondern der eigene Reverse-Proxy. Stapel laufen seitdem direkt gegen den Dienst, nicht durch den Proxy.
Beim Anlegen der Umgebungsdatei über die Shell wurde der Variablenname wörtlich in die Datei geschrieben. Aufgefallen ist es nicht, weil der Testaufruf denselben Fehler machte und beide zusammenpassten. Eine Prüfung, die den kaputten Stand als Referenz nimmt, beweist nichts.
Derselbe Text ergab bei einem Spitzenmodell „Zahlungserinnerung“, bei einem kleinen „bill payment reminder“, bei einem dritten „negativ“. Ohne feste Labelmenge ist die Aufgabe nachgelagert nicht auswertbar. Fällig vor dem ersten echten Verbraucher, danach wäre die Labelmenge ein öffentlicher Vertrag.
Was absichtlich fehlt, und was noch fehlt
Kein Prompt-Inhalt im Protokoll
Absichtlich. Das Protokoll ist für Kosten und Fehlersuche, nicht für Inhalte.
Keine Modellwahl durch die Anwendung
Absichtlich. Anbieter und Modell sind Konfiguration, nie Teil einer Anfrage. Ein Betreiber, der Kandidaten vergleicht, bekommt einen eigenen Eingang.
Keine harte Mandantentrennung
Noch nicht. Der Kunde ist Zurechnung und Begrenzung, keine Zugriffsgrenze. Wer sie braucht, braucht zusätzlich eine Zuordnung, welches Projekt für welchen Kunden arbeiten darf.
Kein Produktivziel
Noch nicht. Die Schleuse läuft auf der Testinstanz. Der Weg auf einen Produktivserver ist eine Entscheidung, keine technische Frage.
Quellen
ai-core/AGENTS.md. Betrieb und Schnittstelle: ai-core/README.md. Aufgabenverträge: ai-core/config/tasks.php.
Vier Befundberichte in der Reihenfolge ihrer Entstehung: BEFUND-review.md, BEFUND-nachpruefung.md, BEFUND-nachpruefung-2.md, BEFUND-probelauf.md. Messung der Anbieter: BENCHMARK-hetzner-experiments.md.
Recall: Repo transkript-recall, dort PLAN.md; Aufgabe recall.v1 in den Aufgabenverträgen.
Werkstattbericht mit sechs Messprotokollen im Wortlaut: Wie ich arbeite.