Freorit

Open Source & Security Practitioner
RAG Oracle 23ai Ollama Self-Hosted ca. 8 min Lesedauer Fortgeschritten Stand: 2026

Wie mein KI-Coding-Assistent tatsächlich meine Codebase kennt

Eine selbst gehostete Retrieval-Pipeline, die einem KI-Coding-Assistenten automatisch, immer aktuellen Projekt-Kontext liefert — ohne API-Kosten, ohne Vendor-Lock-in.

Das Problem: KI-Coding-Assistenten vergessen alles

Wenn du täglich einen KI-Coding-Assistenten wie Claude Code nutzt, kennst du das Ritual: neue Session öffnen, drei Dateien für Kontext reinkopieren, die Projektstruktur nochmal erklären — dann erst die eigentliche Frage stellen. Jede Session startet bei null. Der Assistent ist clever, kennt aber deine Codebase nicht — nur das, woran du gedacht hast, es reinzukopieren.

Für ein einzelnes Skript ist das kein Problem. Bei einem echten Projekt mit Dutzenden Repos, Monaten an Entscheidungen und Docs, die erklären warum etwas so gebaut wurde, wie es gebaut wurde, funktioniert das nicht mehr — dieser Kontext lebt in deinem Kopf, nicht in der Datei, die gerade offen ist.

Also habe ich ein kleines System gebaut, das das löst: Jeder Prompt bekommt automatisch die paar Code-Ausschnitte und Doku-Schnipsel, die am besten zur Frage passen, direkt aus einem Index meiner eigenen Repos — kein Copy-Paste, kein manuelles Kontext-Management, keine Abhängigkeit von einer kostenpflichtigen Embeddings-API.

Die Idee: Retrieval statt Auswendiglernen

Das Muster nennt sich RAG — Retrieval-Augmented Generation. Statt zu versuchen, eine ganze Codebase in das Kontextfenster eines Modells zu quetschen (oder darauf zu hoffen, dass es sich „einfach merkt"), durchsucht ein separates, schlankes System einen Index nach den relevantesten Text-Ausschnitten und gibt nur die an den Assistenten weiter. Klein, gezielt, aktuell.

Das Leitprinzip, an das ich mich durchgehend gehalten habe: deterministisch vor clever. Git-Historie, Dateipfade und Vektor-Ähnlichkeitssuche erledigen fast die gesamte Arbeit. Es gibt kein zusätzliches LLM in der Kette, das rät, was ich „eigentlich gemeint" habe, indem es meine Anfrage umschreibt — diese Schicht habe ich bewusst weggelassen, weil sie Latenz und einen komplett neuen Fehlermodus hinzufügt, für einen Nutzen, den ich noch gar nicht gemessen hatte. Erst einfache, beobachtbare Bausteine; Cleverness erst dazu, wenn sie sich beweisen kann.

Die Architektur: drei Rollen, jede mit genau einer Aufgabe

Git-Host (klein, stromsparend)
   -- Push-Webhook -->
Cloud-Worker (Embeddings + Vector Search)
   -- Retrieval-Anfrage -->
Dev-Rechner (KI-Coding-Assistent)
   -- Prompt --> zurück zum Git-Host

Kurz erklärt, was „Embedding" eigentlich bedeutet, für alle, die neu dabei sind: Ein Embedding-Modell verwandelt einen Textausschnitt in eine Liste von Zahlen, die die Bedeutung dieses Texts repräsentiert — nicht den Text selbst. Zwei Textausschnitte mit ähnlicher Bedeutung landen bei ähnlichen Zahlen-Listen. Genau das macht Vektor-Suche möglich — statt nach exakten Wörtern zu suchen, sucht man nach „Nähe" im Bedeutungsraum. Ollama übernimmt diese Umwandlung lokal auf dem Cloud-Worker; Oracle 23ais nativer Vektor-Datentyp ist das, was der Datenbank erlaubt, diesen Bedeutungsraum direkt zu durchsuchen.

Nichts davon braucht einen kostenpflichtigen API-Key außer dem Coding-Assistenten selbst. Cloud-Compute im Free-Tier, ein offenes Embedding-Modell, eine Datenbank mit nativer Vektorsuche, und Infrastruktur, die ohnehin schon aus anderen Gründen lief.

Retrieval-Qualität messen statt raten

Es ist einfach, ein RAG-System per Augenschein zu bewerten, ein paar Fragen zu stellen, „sieht gut aus" zu denken und weiterzumachen. Darauf wollte ich mich nicht verlassen. Bevor ich die Retrieval-Qualität als verlässlich genug eingestuft habe, habe ich einen kleinen Satz realistischer Testanfragen gebaut, jede gepaart mit dem Dokument, das als Top-Ergebnis zurückkommen sollte — ein „Golden-Query"-Set — plus ein Skript, das alle automatisch durchläuft und Recall@1, Recall@5, Recall@10 ausgibt.

Diese Messung hat sofort einen echten Bug gefunden: Das Embedding-Modell braucht aufgabenspezifische Prompt-Formatierung (Dokumente und Anfragen müssen vor dem Embedding unterschiedlich verpackt werden) — ohne das liegt Recall@1 bei rund 30%. Mit dem Fix sprang ein fokussiertes Test-Set auf über 80% bei Rang 1, 100% innerhalb der Top 5. Als mehr Projekte indexiert wurden und das Query-Set größer und schwieriger wurde, pendelte sich der Recall bei 60–70% ein — eine echte, bekannte Zahl jetzt, kein Gefühl, und etwas, gegen das jede künftige Änderung geprüft werden kann, statt „fühlt sich heute schlechter an".

Laut scheitern, nicht still

Die wertvollste Lektion aus dem echten Produktivbetrieb hatte nichts mit Suchrelevanz zu tun — sondern mit Ehrlichkeit im Fehlerfall.

Die erste Version des automatischen Injection-Hooks war bewusst gebaut und getestet, bei jedem Problem still zu scheitern — nicht erreichbarer Dienst, Timeout, nichts Relevantes gefunden, alles führte zum selben Ergebnis: kein Kontext eingefügt, Prompt läuft unberührt weiter. Zu dem Zeitpunkt war das die richtige Entscheidung: Retrieval war explizit Best-Effort, ein Bonus, wenn es klappte, nie etwas, worauf man sich verlassen musste.

Das änderte sich, als Retrieval für jeden Prompt in einem indexierten Projekt verpflichtend wurde, nicht mehr nur ein Nice-to-have. Genau da zeigte sich die eigentliche Schwäche des alten Designs: Stille ist mehrdeutig. Ist Retrieval gelaufen und hat nichts gefunden, oder ist es gar nicht erst gelaufen? Sobald man sich tatsächlich auf ein System verlässt, ist „vielleicht hat's geklappt" schlechter als ein sichtbarer Fehler — man kann nicht über einen Fehlermodus nachdenken, der von „System tut einfach nichts" nicht zu unterscheiden ist.

Der Fix war klein: Der Hook meldet jetzt immer, was passiert ist — Erfolg mit Treffern, ein echtes „nichts Relevantes gefunden", ein Timeout oder ein Fehler — statt jemals still zu bleiben (bis auf den einen Fall, in dem er zurecht gar nicht laufen sollte: ein Projekt außerhalb des Index). Vier ehrliche Ergebnisse statt eines mehrdeutigen.

Das CPU-Contention-Problem, vor dem einen niemand warnt

Embedding-Worker und interaktive Retrieval-Anfrage auf derselben kleinen Maschine laufen zu lassen, hat ein reales, messbares Problem erzeugt: Was auch immer die CPU gerade belegte, bremste das andere deutlich aus. Ein Hintergrund-Indexierungsjob und ein Mensch, der auf eine Antwort wartet, haben komplett unterschiedliche Prioritäten — konkurrierten aber standardmäßig gleichberechtigt um dieselbe Ressource.

Gemessen vor jedem Fix: Eine interaktive Anfrage während aktiver Hintergrund-Indexierung brauchte im Schnitt ~16 Sekunden — und ein Drittel der Versuche lief komplett in den Timeout.

Der Fix brauchte weder einen neuen Dienst noch eine Message-Queue. Eine einfache dateibasierte Advisory-Lock, ein Muster, das jedes Linux-basierte System bereits nativ unterstützt:

@contextlib.contextmanager
def interactive_priority(lock_path: str):
    with open(lock_path, "w") as lock_file:
        fcntl.flock(lock_file, fcntl.LOCK_EX)
        try:
            yield
        finally:
            fcntl.flock(lock_file, fcntl.LOCK_UN)

Der interaktive Pfad hält diese Sperre für die Dauer seiner Anfrage. Der Hintergrund-Worker prüft — mit kurzer, begrenzter Wartezeit — ob die Sperre gerade gehalten wird, bevor er seinen nächsten Batch startet. Er kann nichts unterbrechen, das schon läuft (dafür gibt es keinen Cancellation-Mechanismus), aber er stapelt keine neue Hintergrundarbeit mehr auf ein bereits ausgelastetes System, genau dann, wenn jemand auf eine Antwort wartet.

Gemessen nach dem Fix, gleicher Test, gleiche Last: null Timeouts, die meisten Anfragen unter sechs Sekunden zurück. Kein vollständiger Fix — eine von fünf Stichproben traf noch den alten langsamen Pfad, weil sie auf einen bereits laufenden Hintergrund-Call traf, den die Sperre nicht unterbrechen konnte — aber eine große, ehrliche, gemessene Verbesserung. Diese Einschränkung habe ich dokumentiert, statt sie zu „gelöst" aufzurunden.

Was das gekostet hat, und was nicht

Keine kostenpflichtige Embeddings-API. Kein verwalteter Vektor-Datenbank-Dienst — das hat Oracle 23ais native Vektor-Unterstützung übernommen. Keine neue Message-Broker. Nur Infrastruktur, die ohnehin schon lief, ein offenes Embedding-Modell, rund 15 Minuten Setup pro neuem Projekt, um es in den Index zu bringen, und eine Handvoll kleiner, gut getesteter Dienste, die jeweils genau eine Aufgabe erledigen.

Der Build-vs-Buy-Tradeoff ist real — eine verwaltete RAG-Plattform wäre schneller aufgesetzt gewesen. Was ich stattdessen bekommen habe: volle Sichtbarkeit auf jeden Fehlermodus, keine laufenden API-Kosten, eine gemessene (statt angenommene) Retrieval-Qualität, und — für mich wertvoller — eine konkrete, funktionierende Antwort auf „kannst du tatsächlich eine Retrieval-Pipeline bauen, nicht nur eine aufrufen".

Open Source und verwendete Technologien

Diese Pipeline basiert vollständig auf frei verfügbaren Tools:

Das Muster selbst ausprobieren

Man braucht keine drei Maschinen, um anzufangen. Die Grundidee funktioniert auch kleiner: ein Prozess, der Docs chunked und embedded, eine vektor-fähige Datenbank (viele unterstützen das mittlerweile nativ — erst prüfen, was schon läuft, bevor ein neuer Dienst dazukommt), ein kleines Golden-Query-Set, damit man messen statt raten kann, und ein kleiner Hook oder ein Skript, das die Datenbank abfragt, bevor ein Prompt an den Assistenten der Wahl geht. Dort anfangen, messen bevor etwas Cleveres dazukommt, und die Zahlen entscheiden lassen, was als Nächstes gebaut wird.