Ein Portfolio-Chatbot, der „Ich weiß es nicht“ sagt, statt zu raten
Wie ein Portfolio-Chatbot ehrlich bleibt: ein gesperrter Ehrlichkeitsregler, fünf handgeschriebene Notizen im Prompt statt RAG und ein fester Ablehnungssatz, der zugleich als Lückendetektor dient.
Geben Sie irgendeinem Sprachmodell den Namen einer Person, von der es nie gehört hat, und es beschreibt sie trotzdem, selbstbewusst und detailreich: ein Werdegang, ein Kompetenzprofil, sogar Einschätzungen ihrer Stärken. Nichts davon existiert. „Halluzination“ ist ein sanftes Wort dafür. Das Modell liest nichts falsch. Es erschafft einen Menschen aus dem Nichts, flüssig und ohne einen Hauch von Zweifel.
Für einen Chatbot auf einer Portfolio-Website ist genau das das ganze Problem. Jedes erfundene Detail ist ein Satz, den eine Recruiterin am Montag lesen und am Donnerstag im Vorstellungsgespräch wiederholen könnte – und der Seitenbetreiber muss dann erklären, dass seine eigene Website sich etwas über ihn ausgedacht hat. Für dieses Gespräch gibt es keinen guten Weg.
Also dreht sich die Designfrage um. Ein Modell dazu zu bringen, flüssig über jemanden zu reden, dauert einen Nachmittag. Es dazu zu bringen, auf konkrete und zählbare Weise zu sagen, dass es etwas nicht weiß, dauert viel länger – und genau dort liegt die interessante Arbeit.
Der Bot in diesem Fall heißt PATS. Er sitzt in der Ecke der Website, antwortet nur aus handgeschriebenen Notizen und gibt, wenn eine Frage darüber hinausgeht, einen festen Satz aus und verweist die Besucher an die E-Mail-Adresse des Betreibers. Dieser feste Satz wurde zum nützlichsten Teil des Projekts. Die fehlende Vektordatenbank war der zweitnützlichste.
Kein RAG, keine Vektordatenbank: rund viertausend Wörter handgeschriebene Notizen plus eine Regel, nämlich dass der Bot nicht improvisieren darf. Die folgenden Zahlen wurden am 13. September 2026 im Live-Betrieb gemessen.
Ein Regler, der sich nicht dreht
Beginnen wir mit dem Teil, den Besucher anfassen können. PATS hat drei Einstellungen: Ehrlichkeit, Humor und Knappheit. Zwei funktionieren. Humor reicht von klinisch bis zu einer Stufe namens „max sarcasm“, und der Ton ändert sich komplett. Knappheit reicht von einer ausschweifenden Erzählung, die mehr bietet, als man gefragt hat, bis zu einzeiligen Antworten.
Ehrlichkeit steht fest auf 100. Sie hat dieselben fünf Stufen wie die beiden anderen Regler, in derselben Reihe und Form, sodass sie wie ein echtes Bedienelement aussieht und nicht wie eine Beschriftung. Die vier Stufen unter 100 sind ausgegraut, und wo die anderen Zeilen ihren aktuellen Wert zeigen, steht hier „gesperrt“. Keine Stufe reagiert.
Im Code ist es eine Konstante mit einem Kommentar, der die Entscheidung erklärt:
/**
* Not a parameter. It is displayed as a percentage because that is the joke,
* and it is a `const` because a lowerable honesty setting would contradict
* the one rule the whole site is built on.
*/
export const HONESTY = 100;
Sie wird nie an das Modell geschickt. Sie steht überhaupt nicht im Request-Body, also kann nicht einmal eine von Hand gebaute Anfrage sie herunterdrehen.
Die Idee stammt aus Interstellar, wo die Persönlichkeit eines Roboters einstellbar ist und die Crew das Einstellen übernimmt. Cooper setzt den Humor von TARS auf 75 Prozent. Die Ehrlichkeit steht auf 90, weil TARS erklärt, dass absolute Ehrlichkeit nicht immer die diplomatischste oder sicherste Art ist, mit emotionalen Wesen zu kommunizieren.
Der Humorregler blieb. Die Ehrlichkeit ging auf 100, weil TARS’ Grund für das Zurückhalten der letzten zehn Prozent hier nicht greift. TARS kümmert sich im tiefen All um die Moral einer Crew, während PATS Fragen zum Lebenslauf beantwortet, bei denen kaum etwas emotional aufgeladen ist. Zehn Prozent weniger Ehrlichkeit würden hier kein Taktgefühl erkaufen, sondern nur Genauigkeit kosten.
Eine Website, die argumentiert, dass ein lautes Scheitern besser ist als eine plausible falsche Antwort, sollte genau eine Einstellung haben, die sich nie bewegt, und das ist die Ehrlichkeit. Ein Bedienelement, nach dem eine Recruiterin greifen kann und das sich als verschweißt herausstellt, belegt die These besser als jeder Absatz, der sie behauptet. Es folgt die Mechanik hinter dieser Behauptung.
Warum es keine Vektordatenbank gibt
Alles, was PATS aussagen kann, steht in fünf Markdown-Dateien: ein Profil, Projekte, Arbeitsstil, Interessen und ein FAQ, insgesamt etwa viertausend Wörter. Beim Laden des Moduls werden sie zu einem einzigen System-Prompt zusammengefügt, und dieser komplette Prompt geht mit jeder Nachricht raus. Es gibt kein Retrieval, keine Embeddings, keinen Vector Store, kein Chunking und keine Ähnlichkeitssuche.
Danach fragen die Leute zuerst, meist mit hochgezogener Augenbraue, also hier die Begründung.
Ein großer Teil der Argumente für RAG dreht sich um Kosten. Ein ganzes Korpus mit jeder Anfrage zu schicken, ist unbezahlbar, also holt man die Handvoll Chunks, die am relevantesten aussehen. Prompt-Caching hat diese Rechnung bei dieser Größe verändert. Der komplette Prompt umfasst 10.870 Tokens und wird nach der ersten Anfrage für etwa ein Zehntel des normalen Input-Preises aus dem Cache bedient. Heraus kommt weniger als ein Cent pro Nachricht, selbst bei der teuersten Einstellung. Gegen eine solche Zahl rechnet sich keine Retrieval-Architektur, und jede Variante hätte einen weiteren Dienst, ein weiteres Schema und eine weitere Fehlerquelle bedeutet.
Der zweite Grund gab den Ausschlag. Ein Retriever, der danebenliegt, schadet mehr als gar keiner. Kommt der richtige Chunk nicht zurück, trifft die Frage trotzdem ein, und das Modell muss mit leeren Händen antworten – genau die Bedingung, die selbstbewusste Erfindungen hervorbringt. Das ist der Fehler, den das Projekt verhindern soll, also hätte RAG einen stillen Fehlermodus in ein System geschmuggelt, das gebaut wurde, um keinen zu haben. Der gesperrte Ehrlichkeitsregler und die fehlende Vektordatenbank sind dieselbe Entscheidung, zweimal getroffen.
Man sollte diese Aussage aber nicht überdehnen. Sie gilt für etwa zehntausend Tokens Notizen, die nie über das hinauswachsen werden, was eine Person selbst schreibt. Sie ist kein Urteil, dass RAG tot oder Vektordatenbanken sinnlos seien. Bei zehnfacher Größe kippen die Zahlen, und eine Retrieval-Schicht verdient sich ihren Platz; dasselbe gilt, wenn der Bot aus echten Codebasen antworten soll statt aus Notizen darüber. Keine der beiden Bedingungen trifft heute zu, und Retrieval nur einzuführen, weil alle es erwarten, wäre der eigentliche Fehler.
Der Loader meldet Lücken absichtlich laut. Fehlt eine der fünf Dateien oder ist sie leer, schlägt der Build fehl, sodass ein Bot mit einer Wissenslücke nie ausgeliefert wird, über die dann Besucher stolpern könnten.
Der Ablehnungssatz als Messinstrument
Wann immer die Notizen die Antwort nicht enthalten, beendet PATS seine Antwort mit einem einzigen Satz, der sich um kein Zeichen ändert:
That's not in my notes. You can ask the owner directly at their email address.
(„Das steht nicht in meinen Notizen. Sie können den Betreiber direkt per E-Mail fragen.“ Der echte Satz nennt den Namen des Betreibers und die tatsächliche Adresse.) Der Satz ist nicht aus Höflichkeit da. Er ist ein Instrument.
Eine private Admin-Seite analysiert die protokollierten Unterhaltungen und findet die Fragen, die PATS nicht beantworten konnte, indem sie genau diesen String abgleicht. Sie lässt kein Modell jede Antwort klassifizieren. Sie vergleicht Strings, im Code:
if (row.role !== "assistant" || !row.content.includes(FALLBACK_LINE)) continue;
// The visitor turn immediately before it, in the same session.
for (let j = i - 1; j >= 0; j--) {
if (rows[j].session_id !== row.session_id) break;
if (rows[j].role === "user") {
gaps.push({ question: rows[j].content, at: rows[j].created_at });
break;
}
}
Die Alternative wäre ein zweiter Klassifizierungsaufruf pro Nachricht gewesen, der jede Antwort als beantwortet oder unbeantwortet markiert. Das hätte Kosten und Latenz im Hot Path verdoppelt, nur um zu erfahren, was ein Stringvergleich ohnehin weiß, und die Lückenliste läge in einer Form vor, die ein Modell wegparaphrasieren könnte. Mit einem festen String gibt es einen Modellaufruf pro Runde, und die Lückenerkennung gibt es gratis dazu.
Jede Frage, die PATS nicht beantworten kann, wird zu einer konkreten Zeile auf einer Liste noch ausstehender Notizen. Das Nichtwissen des Bots ist das Feature – und der einzige Teil des Systems, der sagt, was als Nächstes gebaut werden soll.
Der Preis: Der Satz ist jetzt eingefroren. Ihn umzuformulieren, würde jede bereits protokollierte Lücke stillschweigend verwaisen lassen, deshalb gilt eine Änderung als Spezifikationsänderung, nicht als Textkorrektur, und wird auch so festgehalten.
Eine Feinheit hält das Ganze am Laufen. PATS lehnt auch Fragen ab, die es beantworten könnte, etwa zu Gehalt, Einwanderungsdetails und Familie. Darauf folgt eine kurze, freundliche Absage, die den Ablehnungssatz bewusst nicht verwendet. Täte sie es, füllte sich die Lückenliste mit Fragen, die niemand beantworten will, und das Instrument würde nicht mehr messen, wofür es da ist. Eine bewusste Grenze ist keine Lücke.
Die Talent-Scout-Hälfte
PATS steht für Portfolio Assistant and Talent Scout, und die zweite Hälfte wird meist falsch herum verstanden. Er sucht keine Talente für die Besucher. Er hält Ausschau nach Chancen für den Betreiber.
Wer eine E-Mail-Adresse, eine LinkedIn-URL oder einen Terminlink in den Chat schreibt oder erwähnt, dass er Leute einstellt, löst eine E-Mail an den Betreiber aus. Das erledigt ein regulärer Ausdruck, kein Modellaufruf. Drei Details im Detektor haben länger gedauert als das Feature selbst.
Erstens ist der Empfänger eine fest codierte Konstante und wird nie aus etwas abgeleitet, das im Chat eingegeben wurde. Was auch immer Besucher eintippen, die Mail landet im Postfach des Betreibers oder nirgends. Das ist das gesamte Missbrauchsschutzkonzept, und es ist weit kürzer als die Alternativen.
Zweitens verlangt das Muster für Telefonnummern auf beiden Seiten ein Nicht-Ziffern-Zeichen. Die Chats hier sind voller Zahlen wie 10,870 tokens und 4,325 rows, und ein Detektor, der eine Token-Zahl für eine anrufbare Nummer hält, schlägt ständig falschen Alarm.
Drittens dürfen die eigenen Prompt-Vorschläge des Widgets ihn nie auslösen. „Was sucht der Betreiber?“ ist ein klickbarer Chip, weshalb Vokabular rund ums Einstellen allein kein Auslöser sein darf. Einem Detektor, der über die eigenen Vorschläge stolpert, vertraut bald niemand mehr.
Der Bug war interessanter als das Feature. Sessions wurden als benachrichtigt markiert, bevor der Versand überhaupt versucht wurde, und zwar in derselben Spalte, in der der Auslösegrund gespeichert war. Schlug der Versand dann fehl, etwa weil der Mail-API-Key fehlte oder das Tageslimit erreicht war, blieb die Session dauerhaft als nicht mailbar markiert, selbst nachdem die Ursache behoben war. Gewollt war eine E-Mail pro Session, doch die Prüfung hing an einer Spalte, die geschrieben wurde, bevor es überhaupt eine E-Mail gab. Jetzt prüft sie einen Zeitstempel, der erst gesetzt wird, nachdem ein 2xx zurückkommt.
Ausprobieren
PATS sitzt unten rechts im Portfolio, hinter einem Chat-Button auf der Startseite. Fragen Sie ihn etwas, das er unmöglich wissen kann, und sehen Sie zu, wie er sich weigert zu raten. Der Stack: Next.js App Router, TypeScript, Supabase Postgres und zod.