Es muss nicht immer ein großes LLM sein und schon gar nicht ein API Call in weite Ferne eines Cloud Anbieters, Mein Interesse gilt, wie ich AI lokal betreiben kann.
Die Aufgabe: ein Issue Tracker soll bereits erfasste Problemfälle als Liste vorschlagen um doppelte Erfassung zu verhindern,
Der Ansatz
Ein Embedding Modell wie Nomic Text. Es wandelt Informationen (wie Wörter, Sätze, Bilder oder sogar ganze Dokumente) in numerische Vektoren um – sogenannte Embeddings. Ziel ist es, die Bedeutung oder den Inhalt der Eingabe in einer mathematisch vergleichbaren Form darzustellen.
Eine Runtime fürs Model. LMStudio und das neue Windows eigene Foundry, können nur LLM. Bleibt die Allzweckwaffe Ollama. Es ist eine Open-Source-Plattform, mit der KI-Modelle lokal auf dem eigenen Rechner laufen– ganz ohne Cloud oder Internetverbindung. Auch mit großen Sprachmodellen (LLMs) wie LLaMA, Mistral, Gemma, Phi,, datenschutzkonform und offline.
Einen Datenbestand, der in meinem Fall in SQL Server liegt.
Zunächst muss der Text in einen Vektor umgewandelt und in der Datenbank gespeichert werden. Dazu wird ein neues Feld angelegt vom Typ Varbinary(Max). Vektoren brauchen eine Menge Platz!
Ollama vorrausgesetzt, kann das Modell in einer Command Shell gestartet werden ollama run nomic-embed-text und stellt dann einen HTTP Endpunkt bereit

Wer ein LLM lädt, kann dann per Commando Zeile einen Chat eröffnen. Mit einem Embedding Model kann man nicht quatschen.
Ich verwende eine Blazor App. Der Kern des .NET codes für das generieren eines Embedding Vektors ist ein einfacher REST Call
1: private HttpClient _http = new HttpClient();
2:
3: private async Task<float[]> GetEmbeddingAsync(string text)
4: {
5: var request = new
6: {
7: model = "nomic-embed-text",
8: prompt = text
9: };
10: var response = await _http.PostAsJsonAsync(
"http://localhost:11434/api/embeddings", request);
11: response.EnsureSuccessStatusCode();
12: var result = await response.Content.
ReadFromJsonAsync<EmbeddingResponse>();
13: return result?.Embedding?.ToArray() ?? Array.Empty<float>();
14: }
Die Antwort besteht aus einer Liste von Zahlen
1: private class EmbeddingResponse
2: {
3: public List<float> Embedding { get; set; }
4: }
Raus kommen rund 700 Fließkommazahlen, die dann zu Json serialisiert und final zum speichern in ein Byte Array umgewandelt werden.

Immerhin rund 8KB für den Satz “Netzwerk / Internet Wien”

Diesen Code lässt man einmal über den ganzen Datenbestand und dann bei jeder Änderung laufen.
Die Suche im Datenbestand erfolgt zunächst ähnlich. Ein Rest Call auf den API Service von OLLAMA wie vorher um den Satz in einen Vektor zu konvertieren.
Nun kommt die Magic, bzw. Mathe. Die Nähe von Vektoren im mehrdimensionalen Raum lässt sich sehr einfach mit per Cosinus Similarity berechnen. Wer das erleben will soll sich mal https://projector.tensorflow.org/ ansehen. November ist im Vektorraum nähe Dezember.

Das wollen wir jetzt mit C# Code nachbilden
1: private static float CosineSimilarity(float[] a, float[] b)
2: {
3: if (a.Length != b.Length) return 0;
4:
5: float dot = 0, normA = 0, normB = 0;
6: for (int i = 0; i < a.Length; i++)
7: {
8: dot += a[i] * b[i];
9: normA += a[i] * a[i];
10: normB += b[i] * b[i];
11: }
12:
13: return (float)(dot / (Math.Sqrt(normA) * Math.Sqrt(normB)));
14: }
Mehr ist es nicht. Das ist die KI! Muss man natürlich auch anwenden. Alle Daten laden und für jede Reihe die Methode aufrufen.
1: var cmd = new SqlCommand("SELECT Title,
Embedding FROM TaskCard WHERE Embedding IS NOT NULL", conn);
2: using var reader = await cmd.ExecuteReaderAsync();
3: while (await reader.ReadAsync())
4: {
5: var title = reader.GetString(0);
6: var bytes = (byte[])reader["Embedding"];
7: var jsonString = Encoding.UTF8.GetString(bytes);
8: var dbVector = JsonSerializer.Deserialize<float[]>(jsonString);
9: var score = CosineSimilarity(inputVector, dbVector);
10: similarResults.Add(new SimilarEntry { Title = title, Score = score });
11: }
12:
13: similarResults = similarResults
14: .OrderByDescending(r => r.Score)
15: .Take(5)
16: .ToList();
17: }
Wir erhalten dann die ähnlichen Werte mit einem Score
1: private class SimilarEntry
2: {
3: public string Title { get; set; }
4: public float Score { get; set; }
5: }
Das neuere 1.5er Nomic Text Modell benötigt bei mir 270MB. Im nächsten Schritt, beschäftige ich mich damit wie man das Inprocess ohne Ollama in einer lokalen .NET Anwendung einbinden kann. Vorweg, das ist um Dimensionen schwieriger.