Kurzfassung

Volltextsuche findet nur exakte Wörter. Ich wollte, dass meine Notizen verstehen, was ich meine. Mit Embeddings, einer Vektorsuche und einem lokalen Sprachmodell wurde aus 1.500 Markdown-Dateien eine semantische Such-KI — auf eigener Hardware, ohne Cloud, ohne Token-Kosten. Der Prototyp stand an einem Nachmittag. Hier ist, wie es funktioniert.

Das Problem: Suche findet Wörter, nicht Bedeutung

Über die Jahre sind bei mir rund 1.500 Markdown-Notizen zusammengekommen: Projektpläne, Sitzungsprotokolle, technische Entscheidungen, Marketing-Ideen. Eine ordentliche Wissensbasis — solange ich das richtige Stichwort kenne.

Genau das ist die Schwäche jeder klassischen Suche, ob Strg+F oder grep: Sie findet nur, was wörtlich dasteht. Suche ich nach „Influencer“, aber in der Notiz steht „Leute mit Reichweite ansprechen“, bleibt der Treffer aus. Die Bedeutung ist da — die Buchstaben passen nur nicht.

Der Beweis: dieselbe Frage, anderes Wort

Genau das war mein Test. Ich stellte dem neuen System absichtlich Fragen mit anderen Wörtern, als in den Dokumenten standen:

Meine FrageGefunden — obwohl das Wort nicht vorkam
„Leute mit Reichweite überzeugen“meine Influencer-Anschreiben & der Marketingplan
„Sensor-App zu Rennstrecken-Werkzeugdie Roadmap zur Racing-Plattform
„eigene KI-Wissensdatenbank, Kosten?“genau die richtige Kosten-Tabelle

Das ist der Unterschied zwischen „Buchstaben vergleichen“ und „Bedeutung verstehen“. Und er ist überraschend einfach zu bauen.

Wie es funktioniert: RAG in drei Schritten

Die Technik dahinter heißt RAG — Retrieval-Augmented Generation. Klingt sperrig, ist aber eine klare Kette: erst die passenden Stellen finden, dann eine KI daraus antworten lassen. Drei Bausteine genügen.

Schritt 1 — Notizen in Häppchen schneiden

Eine ganze Datei als eine Einheit zu durchsuchen ist zu grob. Ich schneide jede Notiz an ihren Überschriften in „Chunks“ — kleine, in sich geschlossene Abschnitte.

def chunk_md(text):
    chunks, header, buf = [], "Anfang", []
    for line in text.split("\n"):
        if line.startswith("#"):
            chunks.append((header, "\n".join(buf)))
            header, buf = line.strip("# "), []
        else:
            buf.append(line)
    return chunks

Schritt 2 — Text in Bedeutung übersetzen (Embeddings)

Jetzt der Kern: Jedes Häppchen wird in einen Vektor verwandelt — eine Zahlenreihe, die seine Bedeutung abbildet. Texte mit ähnlichem Sinn liegen in diesem Zahlenraum nah beieinander, auch wenn sie andere Wörter benutzen. Dafür nutze ich das Open-Source-Modell bge-m3 (mehrsprachig, 1024 Dimensionen) über Ollama — lokal auf einem Mac mini.

def embed(texts):
    body = dict(model="bge-m3", input=texts)
    vecs = ollama_post("/api/embed", body)   # laeuft auf dem Mac mini
    a = np.array(vecs, dtype=np.float32)
    return a / np.linalg.norm(a, axis=1, keepdims=True)

Schritt 3 — die nächsten Nachbarn finden

Die Suche wird damit zur Geometrie: Auch die Frage wird zum Vektor, und ich suche die Häppchen, die ihr am nächsten liegen. Bei dieser Größe reicht reines numpy — keine schwere Datenbank nötig.

def search(query, k=4):
    qv = embed([query])[0]            # Frage  -> Vektor
    scores = mat @ qv                 # Aehnlichkeit zu allen Chunks
    return [meta[i] for i in np.argsort(-scores)[:k]]

Das allein ist schon die semantische Suche. Wer nur Treffer will, ist hier fertig.

Der vierte Schritt: eine echte Antwort, lokal

Treffer sind gut — eine formulierte Antwort ist besser. Dafür gebe ich die gefundenen Häppchen als Kontext an ein Sprachmodell und lasse es ausschließlich daraus antworten. Wichtig: auch das läuft bei mir lokal, auf einem kleinen KI-NAS (Ugreen AI Console) mit dem Modell Qwen3.5-35B auf einer Intel-Arc-GPU. Kein OpenAI, kein Anthropic, keine Cloud.

Hier zeigt sich ein hübsches Prinzip: leichte Dauerarbeit, schwere Arbeit nur bei Bedarf. Das Finden (Embeddings, Vektorsuche) erledigt der stromsparende Mac mini, der ohnehin durchläuft. Die rechenintensive Antwort-Formulierung weckt nur dann die GPU-Maschine, wenn wirklich eine Antwort gebraucht wird.

Die Zahlen aus dem ersten Lauf

Damit es nicht bei Behauptungen bleibt — das sind die echten Messwerte vom Prototyp über 50 ausgewählte Notizen:

KennzahlWert
Notizen → Häppchen50 Dateien → 916 Chunks
Index einmalig gebaut67 Sekunden
Größe des kompletten Index~4 MB
Formulierte Antwort (lokales 35B-Modell)~37 Sekunden
Kosten an eine Cloud0 € / 0 Token

Hochgerechnet auf alle 1.500 Notizen läge der Index-Aufbau bei wenigen Minuten — und danach werden nur noch geänderte Dateien nachgezogen, in Sekunden.

Ehrlich: was es nicht kann

Damit die Erwartung stimmt. Die lokale Antwort braucht rund 37 Sekunden — spürbar langsamer als ein großes Cloud-Modell, das in Sekunden antwortet. Und das 35B-Modell auf einer integrierten GPU ist solide, aber kein Spitzenmodell: Für das Wiederfinden und Zusammenfassen eigener Notizen ist es stark, für komplexes Schlussfolgern bleibt ein großes Cloud-Modell überlegen.

Der eigentliche Engpass ist ohnehin nicht die Sprache, sondern die Trefferqualität: Findet die Suche die richtigen Häppchen, wird die Antwort gut. Findet sie die falschen, klingt die Antwort trotzdem überzeugend — nur eben falsch. Gute Suche ist wichtiger als ein großes Modell.

Warum überhaupt lokal?

Drei Gründe, die für mich den Ausschlag geben:

  • Datenschutz. Private Notizen verlassen die eigene Hardware nicht. Nichts wird zu einem Anbieter hochgeladen.
  • Kosten. Keine Abos, keine Token-Rechnung. Die Maschinen laufen ohnehin.
  • Unabhängigkeit. Kein Dienst, der morgen die Preise ändert oder das Modell abschaltet.

Der Preis dafür ist etwas Geduld und etwas Bastelei. Für eine Wissensbasis, die mir gehört, ist mir das den Tausch wert.

Transparenzhinweis: Dieses Projekt ist selbst finanziert. Die genannten Werkzeuge (Ollama, bge-m3, Qwen, numpy) sind quelloffen; die Hardware habe ich selbst angeschafft. Es bestehen keine bezahlten Kooperationen mit den genannten Herstellern. Alle Zahlen stammen aus einem echten Testlauf am 17. Juni 2026.