Adrian RomoAdrian Romo
Alle Texte
Architektur-Notiz 3 Min. Lesezeit

Ein zweites Gehirn, das sich weigert zu raten

Das Beantworten einer operativen Frage kostete ein lokales Modell bisher einen Dump von 30–50K Tokens. Ich habe eine kompilierte Memory-Schicht gebaut, die das in 2–5K erledigt und sich selbst blockiert, wenn sie keine Quelle angeben kann.

Mein Homelab läuft auf 14 Hosts und etwa 110 Containern. Ich wollte ein lokales Modell auf meiner eigenen GPU, das Fragen dazu beantworten kann – „wovon hängt die DNS-Box ab“, „warum ist dieser Service so konfiguriert“ – ohne irgendetwas davon an eine gehostete API zu schicken.

Die naive Version funktionierte und war unbenutzbar. Plain RAG über meine Doku bedeutete, dass jede Frage 30.000 bis 50.000 Tokens an lose gerankten Chunks in den Kontext zog. Auf einer 24GB-Karte ist das fast dein gesamtes Budget, bevor das Modell überhaupt nachgedacht hat. Noch schlimmer: Die gefundenen Chunks hatten kein Zeitverständnis – ein Entscheidungsprotokoll von Juni und eine Laufzeitbeobachtung von heute Morgen kamen mit gleicher Autorität zurück.

Das eigentliche Problem

Es war nicht die Retrieval-Qualität. Es war, dass ich keine Repräsentation davon hatte, was jetzt gerade wahr ist, getrennt von was irgendwann mal aufgeschrieben wurde.

Drei konkrete Lücken:

  • Keine lexikalische Stufe. Embedding-Similarity allein ist schlecht bei exakten Bezeichnern. Fragst du nach einem bestimmten Servicenamen, bekommst du thematisch ähnliche Nachbarn statt der Zeile, die ihn tatsächlich benennt.
  • Kein Reranking. Die Top-k nach Kosinus-Distanz sind nicht die Top-k nach Nützlichkeit.
  • Keine Temporalität. Nichts unterschied „das ist der aktuelle Stand“ von „das war der Stand, als jemand das Dokument schrieb“.

Was ich gebaut habe

Eine kompilierte Memory-Layer. Das wichtige Wort ist kompiliert – es ist eine abgeleitete, löschbare, rekonstruierbare Sicht auf Quellen, die autoritativ bleiben. Es ist explizit keine weitere Datenbank, die synchron gehalten werden muss:

Git + Laufzeitbeobachtungen   <- autoritativ
Temporale Fakten (SQLite)     <- aktuell vs. historisch, close-nicht-overwrite
Kontext-Graph                 <- Beziehungen (940 Nodes / 1.449 Kanten)
Vektorindex                   <- semantische Suche
Hierarchische Kapseln         <- 400-1.200 Token Zusammenfassungen, generation-gestempelt
2-5K Kontextpaket             <- was das Modell tatsächlich sieht
r

Retrieval läuft gestuft und bricht früh ab: exakter Treffer, dann lexikalisch (SQLite FTS5/BM25 – stdlib, kein zusätzlicher Service), dann Kapsel, dann Graph, dann semantisch. Die meisten operativen Fragen erreichen nie die semantische Stufe.

Konfliktlösung ist eine feste Reihenfolge: Runtime schlägt Fakt schlägt Kapsel. Wenn der Generation-Stamp eines abgeleiteten Artefakts seiner Quelle hinterherhinkt, wird es als veraltet markiert und der Query-Pfad fällt auf die Rohdaten zurück, statt eine selbstbewusste Zusammenfassung einer Welt zu liefern, die sich längst weitergedreht hat.

Der Reranker und warum der Default langweilig ist

Drei Backends. Deterministisches Scoring nach Relevanz x Priorität x Frische ist der Default bei etwa einer halben Sekunde, komplett offline und reproduzierbar. Ein listweiser LLM-Pass kostet 2-5 Sekunden. Ein echter Cross-Encoder auf der 3090 braucht etwa 3 Sekunden warm, 11 Sekunden cold.

Die Einschränkung, die das shipping-sicher gemacht hat: Ein Reranker darf Kandidaten nur umsortieren. Er darf niemals einen hinzufügen oder entfernen. Das wird in Tests erzwungen. Jeder Backend-Failure degradiert auf die deterministische Reihenfolge statt einen Fehler zu werfen. Das bedeutet, dass der interessante-aber-flaky Pfad niemals den langweiligen Pfad lahmlegen kann.

Das Ergebnis

Ungefähr 99% weniger Tokens pro Query und Antworten mit Zitaten. Die Messung, die wichtiger war: Ich habe den kompilierten Pfad im Shadow-Mode gegen den alten RAG-Pfad laufen lassen, bevor ich ihn promoted habe, sodass „besser“ eine Zahl und kein Gefühl war.

Was ich anders machen würde

Ich habe den Faktenspeicher gebaut, bevor ich das Ding gebaut habe, das prüft, ob die Fakten noch stimmen. Die hätten zusammen ausgeliefert werden sollen – ein Memory ohne Self-Audit ist nur ein selbstbewusster Cache. Diese Korrektur wurde ihr eigenes Projekt und ihr eigener Post.

Geschrieben von

Adrian Romo

Senior Backend Engineer für skalierbare Python-APIs, AWS-Lambda-Architekturen, Voice-Systeme und Enterprise-Integrationen.

Weiter geht's

Wohin als Nächstes?

Stöbere durch weitere technische Texte, sieh dir die Engineering Case Studies an oder melde dich direkt.