16. Jun 2025
Lesedauer 18 Min.
MongoDB und LLMs in .NET
Datenstrukturen treffen auf Sprachmodelle
Wer KI-Funktionalität in bestehende .NET-Anwendungen integrieren will, ohne das Backend neu zu erfinden, braucht dafür das Zusammenspiel aus Daten, Logik und Kontext.

Wenn moderne KI auf echte Produktivsysteme trifft, sind pragmatische Lösungen gefragt. Denn wer mit LLMs wie GPT arbeitet, merkt schnell: Ohne passende Datenbasis bleiben viele Potenziale ungenutzt. Genau hier spielt MongoDB seine Stärken aus – besonders in Kombination mit .NET.Die dokumentenbasierte Datenbank erlaubt es, unstrukturierte Texte, Metadaten und Embeddings flexibel zu verwalten – und das skalierbar, performant und ohne Schema-Fesseln. Noch besser: Mit Atlas Vector Search wird MongoDB zum Vektor-Store für semantische Suchen – ein idealer Baustein für das RAG-Prinzip (Retrieval Augmented Generation). In diesem Artikel zeigen wir, warum MongoDB und .NET so gut harmonieren, wie man beide im KI-Kontext einsetzt und wie sich smarte Anwendungen – vom intelligenten Chatbot bis zur Wissensdatenbank – effizient umsetzen lassen.
Wenn Sprache Daten braucht
Große Sprachmodelle wie GPT-4 beeindrucken durch ihr sprachliches Können und ihre breite Wissensbasis. Doch wer Large-Language-Modelle produktiv in Anwendungen integriert, merkt schnell: Ihr Wissen ist nicht unendlich – und vor allem nicht aktuell. Sie können allgemeine Fragen beantworten, aber sobald es um spezifische Informationen, interne Prozesse oder aktuelle Inhalte geht, stoßen sie an Grenzen. Die Lösung? Man versorgt sie mit zusätzlichem Kontext – zum Beispiel aus einer eigenen Wissensdatenbank.Hier kommt die Retrieval Augmented Generation (RAG) ins Spiel: Statt dem LLM einfach eine Frage zu stellen, wird zunächst in einer externen Wissensbasis nach passenden Inhalten gesucht. Diese Inhalte – häufig Textdokumente, FAQ-Einträge oder Support-Tickets – übergibt man dem Modell dann zusammen mit der Frage. Das LLM generiert auf dieser Grundlage eine Antwort, die sprachlich elegant und gleichzeitig inhaltlich korrekt ist. Es verbindet damit das Beste aus zwei Welten: generative Fähigkeiten und faktische Genauigkeit.Doch wie findet man die passenden Inhalte zur jeweiligen Frage? Klassische Schlüsselwortsuchen (zum Beispiel SQLs LIKE oder einfache Volltextsuchen) reichen nicht aus – sie sind zu starr und erkennen keine inhaltliche Nähe. Wenn ein Nutzer etwa fragt: „Wie kann ich mein Kennwort ändern?“, sollen auch Dokumente gefunden werden, in denen von „Passwort zurücksetzen“ die Rede ist. Semantisch passt das perfekt – aber textuell gibt es kaum Übereinstimmung.Die Lösung liegt in der semantischen Suche auf Basis von Vektoren. Dabei wird jeder Text (zum Beispiel eine Benutzerfrage oder ein Supportartikel) durch ein Embedding-Modell in einen Vektor im hochdimensionalen Raum übersetzt. Ähnliche Inhalte erzeugen ähnliche Vektoren. Für jede neue Anfrage berechnet man ebenfalls ein Vektor-Embedding – und sucht dann im Datenbestand nach den „nächsten Nachbarn“. Das ist deutlich leistungsfähiger als einfache Keyword-Suche, denn es berücksichtigt Bedeutung statt nur Formulierung.Damit dieser Ansatz funktioniert, braucht es eine Vektordatenbank, die Millionen solcher Vektoren effizient speichern, indizieren und durchsuchen kann – inklusive Filtermöglichkeiten nach Metadaten, Sprache, Datum oder Kategorie. Die Wahl des Vektorspeichers entscheidet maßgeblich darüber, wie leistungsfähig und wartbar eine KI-Anwendung wird. Und genau hier setzt MongoDB an.MongoDB als idealer Vektorspeicher für KI-Apps
Stellt euch vor, ihr entwickelt einen Support-Chatbot für eine fiktive Maschine, nennen wir ihn den „ACME Kernfusions-Apparat“. Eine hochkomplexe Maschine mit einem ebenso komplexen Handbuch: Anleitungen zur Kalibrierung, Fehlercodes, Wartungsschritte, Sicherheitshinweise – alles muss verfügbar sein, wenn ein Techniker eine Frage stellt wie: „Wie ersetze ich die Plasmadüse bei Fehlermeldung 317-B?“Damit der KI-gestützte Chatbot sinnvolle Antworten liefern kann, muss er dieses Wissen nicht nur speichern, sondern auch semantisch durchsuchen können. Klassische Datenbanken können zwar nach Keywords suchen, aber nicht nach Bedeutung. Genau dafür braucht es Vektorspeicher, also eine semantisch durchsuchbare Repräsentation der eigenen Inhalte.Viele Teams greifen hier zu spezialisierten Vektor-Datenbanken wie Pinecone, Weaviate oder FAISS. Diese sind leistungsfähig, bringen aber einen Haken mit: Sie sind Silos. Man muss die Daten dort separat pflegen, synchronisieren, neben dem eigentlichen „operational store“ – der zum Beispiel Userdaten, Logs oder Metainformationen in einer anderen DB speichert. Änderungen im Handbuch? Neue Sprache? Metadaten-Update? Das alles muss in zwei Systemen synchron gehalten werden – fehleranfällig und komplex.MongoDB geht einen anderen Weg: Mit Atlas Vector Search kann ein Entwickler in einem bestehenden MongoDB-Dokument (und damit auch in einer bestehenden Datenbank) einfach ein Feld für die Vektoren/Embeddings hinzufügen – und dann auf dieses Feld einen speziellen Index vom Typ Vector-Search definieren. Dieser Index funktioniert wie gewohnt: Er wird über das gewünschte Feld gelegt (zum Beispiel embedding) und ermöglicht darauf semantische Ähnlichkeitssuchen nach dem Prinzip: „Finde die Dokumente, deren Vektor am nächsten an diesem hier liegt.“Ein zugehöriges Dokument könnte daher so aussehen:
{
<span class="hljs-attr">"title"</span>: <span class="hljs-string">"Plasmadüse austauschen"</span>,
<span class="hljs-attr">"category"</span>: <span class="hljs-string">"Wartung"</span>,
<span class="hljs-attr">"content"</span>: <span class="hljs-string">"Schalten Sie den Apparat aus und öffnen </span>
<span class="hljs-string"> Sie die Sicherungsklappe..."</span>,
<span class="hljs-attr">"embedding"</span>: [<span class="hljs-number">0.812</span>, <span class="hljs-number">0.591</span>, <span class="hljs-number">0.443</span>, ...],
<span class="hljs-attr">"language"</span>: <span class="hljs-string">"de"</span>,
<span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-01-15"</span>
}
Der Index wird auf das Feld embedding gelegt – und ab diesem Moment kann man mit einer einzigen Abfrage ähnliche Inhalte finden, zum Beispiel zu einer Nutzerfrage wie: „Wie baue ich die Düse aus?“ Das ist nicht nur schneller umgesetzt als mit Pinecone oder Weaviate, sondern auch einfacher zu pflegen: Ein Datenspeicher, ein Index, ein System.Dazu kommt: MongoDB erlaubt uns zusätzlich, diese Vektor-Suche mit klassischen Filtern zu kombinieren. Man kann zum Beispiel sagen: „Gib mir die drei Dokumente, die dem Embedding am ähnlichsten sind – aber nur aus der Kategorie ‚Wartung‘, in deutscher Sprache und neuer als 2023.“ Diese hybriden Abfragen – semantisch plus strukturiert – sind ein echter Vorteil gegenüber reinen Vektor-Datenbanken.
Mehr als nur Vektoren: MongoDB plus .NET als KI-Datenplattform
Viele .NET-Entwickler kennen MongoDB vor allem als performante Alternative zu relationalen Datenbanken – gerade wenn es um flexible oder unstrukturierte Daten geht. Doch MongoDB ist weit mehr als ein Dokumentenspeicher – es ist eine Plattform, die natürlich mit KI-Anwendungsfällen mitwächst. Das zeigt sich besonders, wenn man mit C# arbeitet.Stellen wir uns also vor, wir haben eine Anwendung, in der Einträge in Handbüchern bereits umgesetzt sind. Es gibt vielleicht sogar ein Benutzer- und Rechtemanagement, ähnlich einem Content-Management-System. Unsere Handbuch-Einträge könnten dann als C#-Klasse wie folgt aussehen:
<span class="hljs-keyword">using</span> MongoDB.Bson;
<span class="hljs-keyword">using</span> MongoDB.Bson.Serialization.Attributes;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ManualEntry</span>
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"title"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"content"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Content { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
Um nun Embeddings als Vektoren zu speichern, können wir auf eine einfache Erweiterung unserer Klasse zurückgreifen. Diese werden als float[] gespeichert – was aus Sicht eines
.NET-Entwicklers nahezu ideal ist. Es muss nicht selbst ein MongoDB-eigener Typ eingeführt oder per Custom Serializer abgebildet werden, und auch Hilfsklassen aus Drittbibliotheken braucht man nicht zu bemühen.Wenn Sie beispielsweise mit dem OpenAI-API arbeiten und ein Text-Embedding erzeugen, erhalten Sie direkt ein float[], das sich ohne Umwege in die MongoDB-Dokumente schreiben lässt – typisiert, verständlich, sicher. Das macht nicht nur die Integration unkompliziert, sondern erleichtert auch die Weiterverarbeitung, etwa beim Debugging oder in automatisierten Tests. Die beiden Welten – Vektorraum und typisierte C#-Anwendung – greifen hier nahtlos ineinander.Die Klasse könnte also wie folgt erweitert werden:
.NET-Entwicklers nahezu ideal ist. Es muss nicht selbst ein MongoDB-eigener Typ eingeführt oder per Custom Serializer abgebildet werden, und auch Hilfsklassen aus Drittbibliotheken braucht man nicht zu bemühen.Wenn Sie beispielsweise mit dem OpenAI-API arbeiten und ein Text-Embedding erzeugen, erhalten Sie direkt ein float[], das sich ohne Umwege in die MongoDB-Dokumente schreiben lässt – typisiert, verständlich, sicher. Das macht nicht nur die Integration unkompliziert, sondern erleichtert auch die Weiterverarbeitung, etwa beim Debugging oder in automatisierten Tests. Die beiden Welten – Vektorraum und typisierte C#-Anwendung – greifen hier nahtlos ineinander.Die Klasse könnte also wie folgt erweitert werden:
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ManualEntry</span>
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"title"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"content"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Content { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"embedding"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">float</span>[] Embedding { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"tags"</span>)]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>[] Tags { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"gridFsId"</span>)]
<span class="hljs-keyword">public</span> ObjectId GridFsId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
[BsonElement(<span class="hljs-string">"createdAt"</span>)]
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
<span class="hljs-keyword">public</span> DateTime CreatedAt { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } =
DateTime.UtcNow;
}
Neben dem Embedding-Feld, in dem wir später unsere Vektoren speichern möchten, wurden auch weitere Metadaten hinzugefügt – auf diese kommen wir später zu sprechen.Damit wir also nun diese neue Spalte nutzen können, müssen wir nach Erstellung einen passenden Index hinzufügen. Hierfür gibt es aktuell noch keine Lösung (zum Beispiel über Attribute) im MongoDB-SDK für .NET. Hier müssen wir auf einen manuell ausgeführten Befehl zurückgreifen oder die entsprechenden MongoDB-UI-Tools verwenden.
{
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"embedding_index"</span>,
<span class="hljs-attr">"definition"</span>: {
<span class="hljs-attr">"mappings"</span>: {
<span class="hljs-attr">"dynamic"</span>: <span class="hljs-literal">false</span>,
<span class="hljs-attr">"fields"</span>: {
<span class="hljs-attr">"embedding"</span>: {
<span class="hljs-attr">"type"</span>: <span class="hljs-string">"vector"</span>,
<span class="hljs-attr">"numDimensions"</span>: <span class="hljs-number">1536</span>,
<span class="hljs-attr">"similarity"</span>: <span class="hljs-string">"cosine"</span>
}
}
}
}
}
Dieser Befehl, ausgeführt in unserer dazugehörigen Collection, erstellt den Index für das Feld embedding. Als Typ wird vector angegeben, und die Anzahl an Dimensionen beträgt 1536. Diese Zahl leitet sich aus dem verwendeten Large-Language-Modell für die Erstellung der Embeddings ab, das uns Ergebnisse in einer gewissen Länge zurückgibt. Während OpenAIs Embedding-Modelle 1536 Dimensionen zurückgeben, kann Metas Llama je nach Konfiguration und Version zwischen 4096 und 8192 Dimensionen zurückliefern. Wichtig ist hierbei also, zu prüfen, welches Modell man verwendet und wie viele Dimensionen im Vektorraum genutzt werden. Diese müssen in exakt der richtigen Größe angegeben werden.Mit diesem Index wird Ihre Collection nun suchfähig für semantische Anfragen und bleibt gleichzeitig voll in dem typisierten .NET eingebettet.
Vom PDF zur semantischen Suche: MongoDBs GridFS für KI nutzen
Viele Unternehmen verfügen über wertvolle Daten – allerdings nicht in Form strukturierter Tabellen, sondern als PDFs, Word-Dokumente, Handbücher oder E-Mail-Anhänge. Gerade bei KI-Anwendungen mit Sprachmodellen ist der Wunsch groß, diese Inhalte nutzbar zu machen: für semantische Suche, intelligente Chatbots oder automatisierte Dokumentation. Doch wie geht man damit um, wenn das Ausgangsmaterial binär vorliegt?MongoDB bietet die Möglichkeit, diese Dokumente direkt in der Datenbank abzulegen – über das eingebaute Dateisystem-Feature GridFS. Es wurde entwickelt, um große Binärdateien effizient in MongoDB zu speichern und in Teilstücken zu verwalten. Statt Dokumente extern in einem Blob-Store oder Dateiserver zu speichern, liegen sie an einem Ort – zusammen mit den Metadaten, Textauszügen und Embeddings.
<span class="hljs-keyword">var</span> bucket = <span class="hljs-keyword">new</span> GridFSBucket(database);
<span class="hljs-keyword">await</span> bucket.UploadFromBytesAsync(
<span class="hljs-string">"acme_manual_de_v1.pdf"</span>, pdfBytes);
Mit diesen zwei Zeilen lassen sich Binärdaten wie unser Handbuch, das wir als pdfBytes eingelesen haben, im Handumdrehen im MongoDB-eigenen GridFS-Store speichern. Die neu erstellte Objekt-ID könnte man später dafür verwenden, die Quelle in unserer bestehenden ManualEntry-Klasse GridFsId zu setzen.So entsteht eine saubere Trennung: Die Binärdaten bleiben in GridFS, die semantisch relevanten Inhalte sind sofort abfragbar. Später können Sie semantische Suchen durchführen, basierend auf dem Inhalt – und bei Bedarf den Original-PDF-Anhang wieder ausliefern, etwa als Download oder zur Einsicht.Wieder wird der große Vorteil dieser Architektur ersichtlich: Sie speichern Quelldaten, Text, Kontext und Embedding zentral in MongoDB. Es ist kein externer Blob-Store nötig, keine Synchronisation zwischen Speichersystem und Datenbank. Das erleichtert nicht nur die Entwicklung, sondern schafft auch konsistente Abläufe für Berechtigungen, Backup, Logging und Replikation – alles innerhalb einer Plattform.
Von unstrukturiert über Rohtexte hin zu Vektoren
Bevor ein Large Language Model überhaupt seine Stärken ausspielen kann, muss es etwas geliefert bekommen, mit dem es arbeiten kann: Text. Und genau hier beginnt oft eine unterschätzte, aber essenzielle Herausforderung – denn viele der relevanten Informationen liegen in Formaten vor, die für ein Sprachmodell zunächst unzugänglich sind. PDFs, Word-Dokumente, technische Handbücher oder interne Präsentationen enthalten zwar Wissen – aber nicht in einer Form, die sich semantisch erschließen lässt.Gerade im .NET-Umfeld, wo zahlreiche Unternehmen auf bewährte Office-Formate setzen, ist die Konvertierung dieser Inhalte in lesbaren, gut strukturierten Text ein kritischer Prozess. Die Zielsetzung ist dabei nie einfach nur „alles irgendwie auslesen“, sondern gezielt das zu extrahieren, was später als Kontext für ein LLM relevant sein kann. Kapitelüberschriften, semantische Gliederung, Tabelleninhalte oder auch technische Parameter müssen erhalten bleiben – oder zumindest so interpretiert werden, dass daraus nützliche Informationen werden.Im praktischen Projektalltag zeigt sich hier schnell, wie wichtig passende Tools sind. Während Python-Ökosysteme mit Libraries wie LangChain oder LlamaIndex längst umfassende Pipelines bieten – inklusive Parsern für PDFs, Markdown, HTML, E-Mails und mehr –, gibt es im .NET-Bereich bislang nur Teilstücke. Zwar existiert ein inoffizieller Port von LangChain für .NET, dieser ist jedoch noch experimentell und bei Weitem nicht so modular oder vollständig wie sein Vorbild. Der Großteil der Arbeit in .NET-Projekten besteht daher heute noch aus der gezielten Auswahl und Kombination spezialisierter Komponenten.Bibliotheken wie PdfPig oder TikaOnDotNet ermöglichen solide Textextraktion, insbesondere bei einfachen Layouts oder linearen Dokumenten. Wer es strukturierter braucht, greift oft zu kommerziellen Lösungen, die auch Formatierungen, Tabellen oder visuelle Layoutmerkmale berücksichtigen. In allen Fällen gilt jedoch: Der Kontext der späteren KI-Nutzung sollte die Extraktion leiten. Wer beispielsweise einen Chatbot plant, benötigt andere Strukturen als jemand, der FAQ-artige Antworten generieren will. Die Art der Zielanwendung beeinflusst also schon die Art und Weise, wie Texte aus Quellen gewonnen werden.Zudem zeigt die Erfahrung: Die beste Textextraktion ist wertlos, wenn nicht verstanden wird, was aus den Quelldaten entfernt wurde – oder eben nicht. Fußnoten, Marginalien, eingebettete Objekte oder OCR-Texte aus gescannten Dokumenten können kritische Informationen enthalten. Hier braucht es nicht nur gute Parser, sondern auch eine manuelle oder automatische Qualitätssicherung – etwa durch Heuristiken, Checksummen oder Strukturvergleiche.Wer diesen ersten Schritt gewissenhaft durchführt, legt das Fundament für eine funktionierende KI-Anwendung. Denn das Modell kann nur mit dem arbeiten, was es bekommt, und je klarer, strukturierter und semantisch relevanter der extrahierte Text ist, desto besser wird die spätere Antwortqualität. LLMs sind keine Magier. Sie sind leistungsfähig, aber abhängig von sauber aufbereitetem Input. Und genau hier trennt sich im Alltag die Spielerei vom Produktiveinsatz.Viel Text ist nicht gleich besser
Ein Sprachmodell wie GPT kann viel – aber nicht alles auf einmal. Große Kontexte mit mehreren Tausend Tokens sind zwar technisch möglich, dennoch bleibt die Aufnahmefähigkeit eines LLM begrenzt. Wer versucht, ein ganzes Handbuch in einer einzigen Anfrage zu verarbeiten, wird schnell enttäuscht: Die Reaktionszeit steigt, die semantische Genauigkeit sinkt und Halluzinationen nehmen zu.Das Problem liegt nicht im Modell, sondern in der fehlenden Struktur. LLMs brauchen konzentrierten Kontext – kompakte, thematisch zusammenhängende Informationseinheiten, auf die sie sich bei der Generierung stützen können. Lange Texte müssen in kleinere Abschnitte aufgeteilt werden – nicht blind, sondern sinnvoll. Ein Fließtext braucht andere Trennkriterien als Quellcode, eine tabellarische Anleitung eine andere Behandlung als ein erzählender Erfahrungsbericht.Im .NET-Umfeld wurde diese Herausforderung lange Zeit mit selbst gebauten Routinen gelöst: einfache String-Splits nach Satzzeichen, harte Abschnittsgrenzen oder heuristische Chunking-Logik. Doch mit dem zunehmenden Fokus auf LLM-Integration entstehen auch im Microsoft-Ökosystem neue Werkzeuge, die genau hier ansetzen.Ein prominentes Beispiel ist Semantic Kernel, das Open-Source-KI-Framework von Microsoft. Es erlaubt nicht nur die Orchestrierung von Prompts und Modellen, sondern bringt auch Tools mit, um Texte automatisch in sogenannte Chunks zu zerlegen. Dabei berücksichtigt das Framework sowohl die Zeichen- oder Satzlängen als auch semantische Trennpunkte. Besonders spannend: Die Schnittstellen des Semantic Kernel lassen sich problemlos mit MongoDB kombinieren. Texte, die in der Datenbank liegen – etwa in GridFS oder als Content-Feld eines Dokuments –, können direkt geladen, segmentiert und für die spätere Verarbeitung vorbereitet werden.Ein solcher Segmentierungsprozess kann zum Beispiel dafür sorgen, dass eine Anleitung zur Wartung unserer fiktiven Maschine in sinnvolle Abschnitte aufgeteilt wird: Ein Chunk behandelt nur einzelne Absätze (oder Sätze) über die Düse, ein anderer nur über den Kühlkreislauf, ein dritter widmet sich den einzelnen Sicherheitshinweisen. Das Ergebnis sind kleinere Einheiten, die jeweils in einen Embedding-Vektor übersetzt und gezielt über Vektorsuche wiedergefunden werden können – klar, übersichtlich und vor allem modellkompatibel.Ein gutes Split-Konzept ist daher nicht nur ein technisches Detail, sondern ein strategischer Baustein: Es entscheidet darüber, wie präzise ein LLM später antwortet – und wie effizient Sie Ihre Datenbasis nutzen können. Besonders in Kombination mit MongoDB als Vector Store ergibt sich so ein intelligentes System aus gespeicherten Textabschnitten, semantisch indiziert, kontextsensitiv abrufbar. Bereit für jede LLM-Anfrage, die mehr will als nur ein paar Schlagwörter.Lesen, Splitten und Embedden nach MongoDB
Sobald ein Textabschnitt bereitsteht, lässt sich dieser direkt in einen semantischen Vektor überführen – etwa mithilfe des OpenAI Embedding API. Die Integration in ein .NET-System funktioniert dabei erstaunlich geradlinig. Alles, was Sie benötigen, ist ein API-Key, den vorbereiteten Textinput sowie das OpenAI SDK, das über NuGet installierbar ist. Für die Embeddings wird das Modell text-embedding-ada-002 verwendet, das auch mit der Dimensionsgröße unseres zuvor erstellten Vektor-Index übereinstimmt.
<span class="hljs-keyword">using</span> <span class="hljs-type">OpenAI</span>;
<span class="hljs-keyword">using</span> <span class="hljs-type">OpenAI</span>.<span class="hljs-type">Embeddings</span>;
<span class="hljs-keyword">var</span> apiKey = <span class="hljs-string">"..."</span>; // <span class="hljs-type">Sicher</span> speichern, zum <span class="hljs-type">Beispiel</span>
// über <span class="hljs-type">Secret</span> <span class="hljs-type">Store</span>
<span class="hljs-type">var</span> openAiClient = new <span class="hljs-type">OpenAIClient</span>(apiKey);
<span class="hljs-keyword">var</span> inputText = <span class="hljs-string">"Im Falle eines kritischen Fehlers muss </span>
<span class="hljs-string"> der Hauptschalter aktiviert werden."</span>;
//<span class="hljs-type">Text</span> aus dem <span class="hljs-type">Text</span>-<span class="hljs-type">Chunker</span>
<span class="hljs-type">var</span> <span class="hljs-literal">result</span> = await
openAiClient.<span class="hljs-type">EmbeddingsEndpoint</span>.<span class="hljs-type">CreateEmbeddingAsync</span>(
inputText,
model: <span class="hljs-string">"text-embedding-ada-002"</span>
);
<span class="hljs-keyword">var</span> embedding = <span class="hljs-literal">result</span>.<span class="hljs-type">Data</span>[<span class="hljs-number">0</span>].<span class="hljs-type">Embedding</span>.<span class="hljs-type">ToArray</span>();
Die zurückgelieferte Variable embedding kann also so an unser MongoDB-Dokument übergeben werden.
var <span class="hljs-attr">manualEntry</span> = new ManualEntry
{
<span class="hljs-attr">Title</span> = <span class="hljs-string">"Notfallabschaltung"</span>,
<span class="hljs-attr">Content</span> = inputText,
<span class="hljs-attr">Embedding</span> = embedding,
<span class="hljs-attr">GridFsId</span> = gridFsId
};
var <span class="hljs-attr">collection</span> =
database.GetCollection<ManualEntry>(<span class="hljs-string">"manual_entries"</span>);
await collection.InsertOneAsync(manualEntry);
Dadurch haben wir nun ein neues Dokument geschaffen, das die Referenz auf das ursprüngliche Dokument (PDF) als GridFsId mitliefert, einen – in unserem Fall statischen – Titel verwendet sowie den Textschnipsel als Content und die dazugehörigen Embeddings nutzt. So entsteht für jeden Schnipsel ein neues Dokument, das wir dann über die Vektorensuche passend finden können.Ziel ist es, bei der Suche nach Informationen nicht das gesamte Dokument als Informationsbasis an das LLM zu übermitteln, sondern nur die dazugehörigen kleineren Informations-Schnipsel.
Danach folgt: Abfragen
Sprachmodelle sind auf Kontext angewiesen. Und dieser Kontext will gefunden werden – präzise, performant und semantisch sinnvoll. Hier kommt die Vektorsuche in MongoDB ins Spiel: Ein einmal gespeicherter Embedding-Vektor lässt sich über einen vektorbasierten Index effizient durchsuchen – und das direkt mit dem offiziellen .NET SDK.Das Beispiel in Listing 1 zeigt, wie Sie einen freien Suchtext (in diesem Fall über die Konsoleneingabe) entgegennehmen, daraus ein Embedding generieren (zum Beispiel mit OpenAI) und anschließend über MongoDB Atlas die fünf ähnlichsten Texte zurückgeben. Ideal etwa für eine Kommandozeilen-Anwendung zur schnellen Wissensrecherche.Listing 1: Abfrage zur Wissensrecherche
Console.WriteLine(<span class="hljs-string">"Geben Sie Ihre Frage ein:"</span>);<br/><span class="hljs-built_in">var</span> queryText = Console.ReadLine();<br/>// Schritt <span class="hljs-number">1</span>: Embedding erzeugen<br/>var embeddingResult = <br/> await openAiClient.EmbeddingsEndpoint.<br/> CreateEmbeddingAsync(<br/> queryText,<br/> model: <span class="hljs-string">"text-embedding-ada-002"</span><br/>);<br/><span class="hljs-built_in">var</span> queryEmbedding = <br/> embeddingResult.Data[<span class="hljs-number">0</span>].Embedding.ToArray();<br/>// Schritt <span class="hljs-number">2</span>: Vektor-Suche <span class="hljs-keyword">in</span> MongoDB (Atlas Vector // Search Aggregation)<br/><span class="hljs-built_in">var</span> pipeline = <span class="hljs-built_in">new</span> BsonDocument[]<br/>{<br/> <span class="hljs-built_in">new</span> BsonDocument(<span class="hljs-string">"$vectorSearch"</span>, <span class="hljs-built_in">new</span> BsonDocument<br/> {<br/> { <span class="hljs-string">"index"</span>, <span class="hljs-string">"embedding_index"</span> }, // Name des <br/> // Vector-Index <span class="hljs-keyword">in</span> MongoDB<br/> { <span class="hljs-string">"queryVector"</span>, <br/> <span class="hljs-built_in">new</span> BsonArray(queryEmbedding) },<br/> { <span class="hljs-string">"path"</span>, <span class="hljs-string">"embedding"</span> },<br/> { <span class="hljs-string">"numCandidates"</span>, <span class="hljs-number">100</span> },<br/> { <span class="hljs-string">"limit"</span>, <span class="hljs-number">5</span> },<br/> { <span class="hljs-string">"similarity"</span>, <span class="hljs-string">"cosine"</span> }<br/> }),<br/> <span class="hljs-built_in">new</span> BsonDocument(<span class="hljs-string">"$project"</span>, <span class="hljs-built_in">new</span> BsonDocument<br/> {<br/> { <span class="hljs-string">"_id"</span>, <span class="hljs-number">0</span> },<br/> { <span class="hljs-string">"title"</span>, <span class="hljs-number">1</span> },<br/> { <span class="hljs-string">"content"</span>, <span class="hljs-number">1</span> },<br/> { <span class="hljs-string">"score"</span>, <span class="hljs-built_in">new</span> BsonDocument(<br/> <span class="hljs-string">"$meta"</span>, <span class="hljs-string">"vectorSearchScore"</span>) }<br/> })<br/>};<br/><span class="hljs-built_in">var</span> results = await collection.Aggregate&lt;<br/> BsonDocument&gt;(pipeline).ToListAsync();<br/>// Schritt <span class="hljs-number">3</span>: Ausgabe der ähnlichsten Inhalte<br/>Console.WriteLine(<span class="hljs-string">"\nTop 5 passende Inhalte:"</span>);<br/>foreach (<span class="hljs-built_in">var</span> doc <span class="hljs-keyword">in</span> results)<br/>{<br/> <span class="hljs-built_in">var</span> <span class="hljs-built_in">title</span> = doc.GetValue(<span class="hljs-string">"title"</span>, <span class="hljs-string">""</span>).AsString;<br/> <span class="hljs-built_in">var</span> <span class="hljs-built_in">content</span> = doc.GetValue(<span class="hljs-string">"content"</span>, <span class="hljs-string">""</span>).AsString;<br/> <span class="hljs-built_in">var</span> score = doc.GetValue(<span class="hljs-string">"score"</span>, BsonNull.Value).<br/> ToString();<br/> Console.WriteLine($<span class="hljs-string">"\n--- {title} </span><br/><span class="hljs-string"> ---\n{content}\n(Relevanzscore: {score})"</span>);<br/>}
Der Ablauf der Suche gestaltet sich wie folgt:
- Der Benutzer gibt über die Konsole eine Frage ein.
- Diese Frage wird per OpenAI SDK in ein Embedding (float[]) übersetzt.
- MongoDB wird mit diesem Vektor abgefragt – der Vektor-Index liefert die ähnlichsten Dokumente.
- Der Textinhalt dieser Dokumente wird gesammelt und ausgegeben.
Vektoren und Wissen in Prompts verwenden
Nachdem wir mithilfe eines LLM Wissen im Sinne unseres Dokuments als Embedding/Vektoren gespeichert haben und nun in der Lage sind, diese Informationen abzurufen, gehen wir in die nächste Stufe: Das Wissen in LLMs verwenden.MongoDB übernimmt in diesem Setup die Rolle des zentralen Datenlagers. Es speichert sowohl strukturierte Metadaten als auch unstrukturierte Inhalte – etwa Fließtexte, technische Anleitungen oder Support-Dokumente – und zusätzlich die semantischen Embeddings, die eine vektorbasierte Suche ermöglichen. Die Datenhaltung ist dabei bewusst generisch: Ein Eintrag enthält Titel, Inhalt, Quelle, Embedding, Timestamp – aber keine spezialisierte LLM-Logik. Das erlaubt maximale Wiederverwendbarkeit für unterschiedliche Szenarien.Auf Applikationsebene kümmert sich .NET Core um die Orchestrierung: Texte einlesen, Embeddings erzeugen, Kontext aus MongoDB holen, Prompts formulieren und das Ergebnis mit dem Modell verarbeiten. Dieser Teil ist oft spezifisch für den jeweiligen Use Case. Ob Sie einen Chatbot bauen, ein FAQ-System oder ein intelligentes Autorentool – entscheidend ist, wie Sie mit den Texten und Suchergebnissen umgehen.Der vielleicht wichtigste Aspekt ist: Nicht jeder Fund aus der Vektor-Suche ist automatisch Kontext. Die Anwendung muss entscheiden, wie viele Textabschnitte verwendet werden, ob diese inhaltlich konsistent sind, ob es Wiederholungen gibt – und in welcher Form sie dem Sprachmodell präsentiert werden. Hierin steckt viel Logik, und genau hier zeigen sich die Stärken des .NET-Stacks: Klarer Code, typisierte Strukturen, flexible Templates.In diesem Beispiel verwenden wir ein einfaches Text-Template, um einen Prompt via OpenAI-API abzusetzen.
var userQuestion = "Wo finde ich den Hauptschalter?";
var contextChunks = results // Die BsonDocuments aus der
// Vektor-Suche
.Select(doc => doc.GetValue("content", "").AsString)
.ToList();
var contextBlock = string.Join("\n---\n",
contextChunks);
var finalPrompt = $@"
Sie sind ein technischer Assistent für den ACME
Kernfusions-Apparat.
Beantworten Sie die folgende Frage **ausschließlich**
basierend auf dem bereitgestellten Wissen.
Wissen:
{contextBlock}
Frage:
{userQuestion}
Antwort:";
Nachfolgend können wir diesen finalPrompt als Anfrage an unser LLM nutzen. Wichtig: Dies ist äußerst rudimentär. Kriterien wie Tonalität, Rollenverständnis oder gar Regeln, was zu beantworten ist oder eben nicht, sind hier nicht aufgeführt.
var chatRequest = new ChatRequest(
new[]
{
new ChatMessage(ChatRole.System, "Sie beantworten
technische Fragen basierend auf bereitgestellten
Dokumenten."),
new ChatMessage(ChatRole.User, finalPrompt)
},
model: "gpt-4"
);
var chatResponse = await openAiClient.ChatEndpoint.
GetCompletionAsync(chatRequest);
Console.WriteLine("\nAntwort:\n" +
chatResponse.FirstChoice.Message.Content);
Spätestens hier dürfte klar sein: In unserem Beispiel ist OpenAI/GPT „nur“ unser Gesprächspartner. Das Kontextwissen kommt aber vollständig aus unserer MongoDB-Datenbank und den entsprechenden Vektoren.Wir können hier die Suche nach geeigneten Informationen noch viel weiter aufziehen. Das ist auch genau das, was wir in KI-Projekten, die wir mit unseren Kunden umsetzen, zwingend tun müssen. Welche Informationen sind wichtig? Gibt es eine zweite Möglichkeit, wie zum Beispiel den Text-Index von MongoDB, um die Suchergebnisse vorab oder nachfolgend einzugrenzen? Gibt es vielleicht andere strukturierte Informationen, die wir nutzen können, und so weiter. Daher kann es sich empfehlen, Eigenschaften wie Tags zu hinterlegen oder auch durch ein LLM generieren zu lassen, um anhand einzelner Stichpunkte bessere Ergebnisse zu erzielen.