Ein Parser liest eine Quelldatei und zerlegt sie in einen Strom von Syntaxknoten. Ein Algorithmus entscheidet dann, welche Knoten zu einer Einheit verschmelzen und welche getrennt bleiben.
Das sieht nach unspektakulärer Vorarbeit aus, entscheidet aber, ob ein KI-Codeagent später die passende Funktion findet oder an einer Handvoll bedeutungsloser Zeilen hängen bleibt. Adam Malek und Ashot Kazaryan aus dem Team hinter Air Context, dem Codesuch-Dienst von JetBrains, beschreiben diesen Weg in einem Entwicklertagebuch auf dem Firmenblog. Wer an einer RAG-Pipeline für semantische Codesuche baut, findet darin Entscheidungen, die man sonst erst nach mehreren Fehlversuchen trifft. Der erste Teil der Reihe behandelt die Anfänge: Parsing und Chunking von Quellcode sowie die Vektorisierung. Die Ausgangsbeobachtung der Autoren ist nicht exotisch.
Warum grep die Frage nach dem Sitzungstoken-Refresh nicht beantwortet
Ein Agent, der ein Feature in einer großen Codebasis umsetzen soll, verbringt einen großen Teil seiner Laufzeit damit, die relevanten Stellen zu finden. Sein übliches Werkzeug ist die Textsuche. Deren Grenze liegt nicht in der Geschwindigkeit, sondern in der Voraussetzung: Wer sucht, muss wissen, welcher exakte Text in der Datei steht. Bei abstrakten Fragen ist das selten der Fall.
Die Autoren führen ein Beispiel an. Ein Agent, der wissen will, wo abgelaufene Sitzungstoken erneuert werden, kann nicht darauf bauen, dass im Code das Wort „refresh“ auftaucht. Die zuständige Funktion kann völlig anders heißen, in eine Hilfsklasse ausgelagert sein oder über mehrere Schichten verteilt arbeiten. Keywords und Textsuche finden nur, was sie schon kennen.
Semantische Codesuche mit Retrieval Augmented Generation dreht die Richtung um. Gesucht wird nach Bedeutung, nicht nach Zeichenketten. Lässt sich der Quellcode so indexieren, dass seine Semantik erhalten bleibt, kann der Agent per Freitextabfrage die Ausschnitte abrufen, die für seine Aufgabe zählen. Für die Autoren entsteht damit eine Schnittstelle, die zu den Stärken eines Sprachmodells passt. Der Prototyp einer solchen Pipeline ist schnell gebaut. Bis zur Produktionsreife ist es ein weiter Weg.
Chunking: Warum feste Zeilenblöcke den Code an der falschen Stelle zerreißen
Man kann sich eine solche Pipeline wie ein Archiv vorstellen, in dem Akten nicht nach Aktenzeichen, sondern nach Inhalt abgelegt werden. Dann hängt alles daran, was als eine Akte gilt. Ist die Akte zu groß, findet man in ihr nichts wieder; ist sie zu klein, steht in ihr nichts, was sich lohnt.
Übertragen auf Quellcode heißt das: Eine ganze Datei als eine Einheit zu vektorisieren, wäre selbst dann wenig sinnvoll, wenn das Embedding-Modell die Länge verkraften würde. Die Suche gäbe die komplette Datei zurück, während es dem Agenten um eine bestimmte Funktion, ein Symbol oder einen kurzen Ausschnitt geht. Jede einzelne Zeile zu indizieren, führt in die andere Richtung ins Leere: Eine Zeile ohne Kontext trägt oft keine Bedeutung, ein generischer Funktionsname oder ein Kommentarfragment erzeugt Treffer, die niemand braucht. Der Agent versinkt in Micro-Ergebnissen.
Der naheliegende Kompromiss, eine Datei in Blöcke fester Zeilenzahl zu schneiden, funktioniert laut den Autoren nicht. Solche Blöcke gruppieren Unzusammenhängendes, etwa eine Import-Anweisung mit dem Rumpf einer Funktion. Das Ergebnis sind semantisch falsche Einheiten, und die Fehler wandern direkt in die Suche. Der Ausweg liegt in der Struktur, die jeder Quellcode ohnehin besitzt: Importe stehen meist oben, danach folgt eine Klassendefinition mit optionalem Dokumentationskommentar, darin Felder und Methoden, die wiederum eigene Kommentare tragen können.
Syntaxknoten, Präfixe, Normalisierung: Wie Air Context Chunks bildet
JetBrains arbeitet nach eigener Darstellung seit 26 Jahren an Parsern, die die Eigenheiten, Konventionen und Unebenheiten einzelner Sprachen abbilden. Diese Parser bilden zusammen mit weiteren Werkzeugen die interne Plattform Code Engine, auf der auch Air Context aufsetzt. Im beschriebenen Stand unterstützt Air Context Parsing und strukturbewusstes Chunking für neun große Sprachen: Kotlin, Java, Python, JavaScript, TypeScript, C#, PHP, Go und Rust. Für alle übrigen Sprachen fällt die Implementierung auf eine naive, zeilenbasierte Aufteilung zurück, damit grundsätzlich jede Datei indexierbar bleibt.
Der Parser zerlegt eine Datei in einen Strom von Syntaxknoten, die mitteilen, was sie repräsentieren: Kommentare, Leerraum, Modifikatorlisten und so weiter. Der Chunking-Algorithmus liest diesen Strom und entscheidet anhand von Typ, Größe und Unterknoten über den Umfang eines Chunks. Überschreitet ein Knoten die Größenschwelle und hat keine Kinder, greifen primitivere Aufteilungsstrategien. Einige sprachspezifische Konstrukte bleiben dagegen auch dann eine Einheit, wenn sie größer sind als erwünscht. Präfixe wie Dokumentationskommentare, Annotationen, Sichtbarkeitsmodifikatoren und Schlüsselwörter bleiben bei der Deklaration, schließende Syntax bleibt bei dem Konstrukt, das sie abschließt.
Hinzu kommt eine sprachspezifische Bereinigung. Häufige, semantisch wenig aussagekräftige Java-Annotationen wie @NotNull oder @Override werden entfernt. Die Autoren verweisen auf eine Ähnlichkeit zu cAST von Zhang und Kollegen aus dem Jahr 2025: Beide Verfahren behalten die größten Syntaxeinheiten, die noch passen, unterteilen nur die zu großen und gruppieren kleine benachbarte Einheiten, um Mini-Chunks zu vermeiden. Der Unterschied liegt nach eigener Darstellung in der Menge der einprogrammierten Sprachsemantik. Python-Dekoratoren bleiben bei ihren Definitionen, KDoc-Kommentare stehen neben ihren Kotlin-Deklarationen.
Nach dem Gruppieren folgt die Normalisierung: führender und abschließender Leerraum verschwinden, Leerzeilen werden gelöscht, gemeinsame Einrückung wird entfernt, während die relative Einrückung erhalten bleibt. Anschließend wandert der Chunk zusammen mit Metadaten in den nächsten Schritt, darunter der relative Pfad der Quelldatei, der gemeinsam mit dem normalisierten Inhalt eingebettet wird.
Ob ein Chunk wirklich gut geschnitten ist, lässt sich nicht rein formal entscheiden. Die Autoren setzen daher auf ein Sprachmodell als Richter. Es bekommt den Chunk und die Quelldatei zu sehen und beurteilt, ob die Grenze sinnvoll ist. Auffällig sind für den Richter abgelöste Dokumentation, verwaiste schließende Syntax oder Code, der mitten durch ein bedeutungstragendes Konstrukt geschnitten wurde. Jede Änderung an der Verarbeitungskette durchläuft zusätzlich eine vollständige Retrieval-Evaluierung von Ende zu Ende.
Vektorisierung: Bedeutung als Position in einem Raum aus tausend Dimensionen
Aus den vorbereiteten Textstücken wird schließlich eine Repräsentation, die semantische Suche ermöglicht. In der Vektorisierung liest ein Embedding-Modell ein Stück Text und gibt eine Liste fester Länge zurück, einen Vektor. Dieser Vektor ist ein Punkt in einem Raum mit einigen tausend Dimensionen. Trainiert ist das Modell so, dass Texte mit ähnlicher Bedeutung nahe beieinander landen.
Was in der klassischen Suche unsichtbar bleibt, wird hier zur Nachbarschaft. Eine Funktion, die gepufferte Schreibvorgänge ausleert, und eine, die eine anstehende Warteschlange abarbeitet, teilen womöglich kein einziges Schlüsselwort und liegen im Vektorraum trotzdem dicht beieinander. Der Abstand zwischen Vektoren wird damit zum Maß für Verwandtschaft. Eine Suchanfrage wird in dieselbe Position im Raum übersetzt, und zurück kommt, was am nächsten liegt.
Die Rechnung pro Byte: Warum Vektoren zum Kostenfaktor werden
Code-Vektorisierung für KI-Agenten ist zunächst billig und wird im Großen teuer. Ein einzelnes Embedding kostet fast nichts, doch ein großes Repository erzeugt Millionen von Chunks und damit Millionen von Vektoren, die gespeichert, im Arbeitsspeicher gehalten und bei jeder Anfrage verglichen werden müssen. Ein Vektor mit einigen tausend Dimensionen in 32-Bit-Gleitkommazahlen wiegt rund 16 Kilobyte. Ein paar Millionen Chunks summieren sich auf mehrere zehn Gigabyte Index, noch bevor irgendeine Verwaltungsstruktur dazukommt.
Ab dieser Größenordnung verschiebt sich die Leitfrage. Statt „Wie genau können wir werden?“ lautet sie: „Was bekommen wir pro Byte?“ Die Autoren nennen zwei Hebel, die unabhängig voneinander wirken und sich kombinieren lassen. Der erste ist, weniger Dimensionen zu behalten. Moderne Embedding-Modelle sind so trainiert, dass ein führender Abschnitt des Vektors für sich funktioniert; beim Training wird die Verlustfunktion über mehrere verschachtelte Präfixlängen gleichzeitig angewandt, sodass die grobe Struktur in den vordersten Dimensionen landet. Du kannst einen Vektor also abschneiden, neu normalisieren, und er sucht weiterhin. Der zweite Hebel lässt alle Dimensionen stehen und spart an jeder einzelnen, indem Präzision geopfert und pro Vektor weniger Bytes vorgehalten werden. Beide Wege sind keine Notlösung, sondern eine Budgetentscheidung.
Welche Mischung bei gleichem Budget besser sucht, zeigen die Autoren an einem Beispiel. 512 Byte pro Vektor reichen entweder für 128 Dimensionen in voller 32-Bit-Genauigkeit oder für alle 4.096 Dimensionen mit je einem einzigen Bit. Beide Varianten passen exakt ins Budget, im Test findet die zweite aber deutlich besser. Ihre Erklärung: Jede Dimension ist wie eine kleine Frage, die das Modell an den Text stellt – geht es um Fehlerbehandlung, um Netzwerkzugriffe, um Testcode? Wer auf 128 Dimensionen kürzt, behält sehr genaue Antworten auf drei Prozent der Fragen und wirft den Rest weg. Ein Bit pro Dimension bewahrt dagegen auf jede Frage eine grobe Ja-oder-Nein-Antwort. Ein langer Fragebogen mit Häkchen schlägt einen kurzen, der auf sechs Nachkommastellen ausgefüllt ist.
Air Context geht deshalb bis zum Äußersten: Jede Komponente ab null wird zur Eins, jede negative zur Null, die Beträge entfallen. Der Vektor ist damit 32-mal kleiner als in 32-Bit-Gleitkommazahlen – deutlich radikaler als Verfahren wie turbovec, das mit einem Achtel des Speichers auskommt. Mit der Darstellung ändert sich auch das Maß. Die Kosinus-Ähnlichkeit braucht die verworfenen Beträge, verglichen wird deshalb per Hamming-Distanz, also über die Zahl der Stellen, an denen sich zwei Bitmuster unterscheiden. Ein Vektor mit 4.096 Bit liegt als 64 Wörter zu je 64 Bit im Speicher; ein Vergleich besteht aus XOR und dem Zählen der Einsen, insgesamt in der Größenordnung von hundert Prozessorbefehlen statt Tausender Multiplikationen.
Den Preis nennen die Autoren offen: Die binäre Quantisierung kostet einige Punkte Recall gegenüber den unkomprimierten Vektoren. Sie nehmen das in Kauf, weil ein Agent die Ergebnisse liest und nicht ein Mensch. Für den Agenten zählt, dass die relevanten Dateien unter den ersten zwölf Treffern auftauchen; ob der beste Chunk auf Platz zwei oder fünf landet, ändert nichts, weil er die Kandidaten ohnehin öffnet. Eine zweite Grenze zeigte sich erst später: Die Bitvektoren stauchen die Spanne der Ähnlichkeitswerte. Zwei völlig unverwandte Vektoren stimmen schon zufällig in etwa der Hälfte der Bits überein, ein eng verwandtes Paar vielleicht in zwei Dritteln. Für eine Rangfolge reicht das, für einen Schwellenwert nicht. Wo ein Index absolut entscheiden muss, ob etwas relevant ist – etwa für eine Funktion, die ungefragt passenden Code vorschlägt und wissen muss, wann sie schweigt –, behält Air Context 16-Bit-Gleitkommazahlen und zahlt für den Speicher.
Dateipfade kürzen, Quellcode nicht speichern
Beim Einbetten trennt das Team Indexierung und Suche. Die Indexierung ist auf Durchsatz ausgelegt, eine GPU verarbeitet etwa 32 Chunks pro Batch, bevor sie ausgelastet ist. Die Suche muss dagegen innerhalb weniger Sekunden antworten. Eingesetzt wird ein Modell, das Anweisungen befolgt und beide Seiten unterschiedlich behandelt: Code-Chunks werden eingebettet, wie sie sind, einer Suchanfrage wird eine Anweisung vorangestellt, die sinngemäß lautet, zu dieser Anfrage den passenden Code zu finden.
Jeder Chunk wird zusammen mit seinem Dateipfad eingebettet, weil der Pfad verrät, in welchem Modul der Code liegt und was die Datei ist. In einem Monorepo wird das zum Problem. Das Monorepo von IntelliJ IDEA umfasst mehr als eine Million Dateien, die mittlere Quelldatei liegt neun Verzeichnisse tief hinter einem Pfad von 91 Zeichen, knapp 10.000 Dateien haben Pfade über 150 Zeichen, der längste misst 218 – die Datei an seinem Ende ist 24 Zeilen lang. Air Context kappt deshalb jeden Pfad, bevor er das Modell erreicht, und behält beide Enden: vorn die Segmente, die das Modul benennen, hinten Elternverzeichnis und Dateiname. Die Mitte fällt weg, so viel wie nötig. Schränkt jemand die Suche auf ein Verzeichnis ein, landet dieser Bereich in derselben gekürzten Form im Anfragetext, statt die Treffer nachträglich zu filtern.
Zum Schluss beschreiben die Autoren zwei Vorgaben zum Schutz des Quellcodes. Gespeichert wird kein Code, nur Koordinaten: ein Verweis, der Typ, der Dateipfad, Start- und End-Offset und ein Verweis auf den Vektor. Der Ausschnitt, den ein Nutzer sieht, wird erst auf seinem Rechner aus seinem eigenen Checkout zusammengesetzt. Eingebettet wird mit einem Open-Weight-Modell auf GPUs, die JetBrains selbst betreibt; keine Anfrage geht an OpenAI, Google oder andere Anbieter. Nach eigenen Benchmarks schnitt das offene Modell dabei besser ab als die gehosteten Embedding-APIs der großen Anbieter – eine Messung des Herstellers in eigener Sache.
Was von den Feldnotizen für den eigenen Aufbau bleibt
Der praktische Kern des Berichts lässt sich in wenigen Sätzen zusammenfassen. Die Qualität einer RAG-Pipeline für semantische Codesuche entscheidet sich früher, als die meisten erwarten – nicht im Modell, sondern an der Chunk-Grenze. Strukturbewusstes Parsing schlägt jede Aufteilung nach Zeilenzahl, weil es die Konventionen einer Sprache kennt und zusammengehörige Deklarationen zusammenhält. Wo diese Kenntnis fehlt, ist ein zeilenbasierter Rückfall immer noch besser als eine Lücke im Index.
Bewertung ist kein nachträglicher Prüfschritt. Ein Sprachmodell als Richter über Chunk-Grenzen und eine Ende-zu-Ende-Messung des Retrievals gehören zum Bau der Pipeline, nicht in eine Schublade daneben. Und beim Speicher wird es ernst: Wer semantische Codesuche über eine große Codebasis anbietet, betreibt ein Budgetproblem, in dem Genauigkeit, Speicherbedarf und Antwortzeit gegeneinander verrechnet werden. Die Antwort von Air Context: lieber alle Dimensionen mit einem Bit als wenige in voller Genauigkeit – und 16 Bit nur dort, wo ein Schwellenwert gebraucht wird.
Der Bericht ist der erste Teil einer Reihe. Offen sind noch die effiziente Speicherung der Millionen Bitvektoren, Antworten in Millisekunden, die laufende Evaluierung und die Frage, wie man den Agenten dazu bringt, die Suche tatsächlich zu nutzen. Das dürfte für jeden interessant sein, der semantische Codesuche als Dienst betreiben will. Air Context selbst ist als öffentliche Vorschau bereits in JetBrains-Lizenzen enthalten. Wer selbst baut, sollte aus diesem ersten Teil vor allem eines mitnehmen: Erst den Code verstehen, dann die Vektoren.
Quelle: blog.jetbrains.com
