KI-Komponenten für ASP.NET Core
16.09.2024, 00:00 Uhr
Kluge Klemmbausteine
Razor- und Blazor-Komponenten mit (etwas) Hirn.
Die Goldgräberstimmung im Bereich „irgendwas mit KI“ ist nicht zu übersehen. Auch Microsoft mischt kräftig mit, das Investment in OpenAI eingeschlossen. Auch die verschiedenen Microsoft-Angebote und -Produkte werden sukzessive mit „KI“-Features versehen, wobei man an der einen oder anderen Stelle darüber streiten kann, ob da wirklich Intelligenz hinzugefügt wird beziehungsweise ob das tatsächlich in den Bereich KI passt oder lediglich vom Marketing so gelabelt wurde. Es war also nur eine Frage der Zeit, bis es auch im Bereich Entwicklung Möglichkeiten aus dieser Richtung geben würde.
Mitte März 2024 war es schließlich so weit. Daniel Roth, seines Zeichens Program Manager für Webtechnologien bei Microsoft, kündigte in einem Blogbeitrag [1] „.NET Smart Components“ an, schmissig als „AI-powered UI controls“ bezeichnet. Es geht also um Oberflächen-Komponenten fürs Web, mit KI an Bord. Das Ganze versteckt sich aktuell auf GitHub [2]. Bezeichnend hierbei: Es ist kein Projekt innerhalb der dotnet-Organisation auf GitHub, sondern eine eigene Organisation namens smartcomponents, mit genau einem öffentlichen Repository, das denselben Namen trägt. Das Projekt ist explizit als experimentell gekennzeichnet. Da es separat von der restlichen .NET-Entwicklung läuft, hat es somit keinen offiziellen Status seitens Microsoft. Ist es also ein Hobbyprojekt eines Microsoft-Mitarbeiters, das hier in einer Zeit der Nachrichtenflaute verkündet wurde? Aber wieso befindet sich dann das Repository nicht einfach bei derjenigen Person im GitHub-Konto, sondern es wurde ein neues angelegt? Es gibt weitere Merkwürdigkeiten: Trotz des Hostens auf GitHub findet sich dort nicht der Quellcode der Komponenten selbst, sondern nur Dokumentation und einige Beispiele. Offizielle Begründung: Es liege noch keine Erlaubnis vor [3].
Der Blogbeitrag von Roth verlinkt auf ein Video, auf dem sein Kollege Steve Sanderson das „Experiment“ vorstellt [4]. Spätestens da sollten alle Alarmglocken klingeln, was positiver gemeint ist, als es vielleicht zunächst erscheint. Denn vor einigen Jahren hatte Steve Sanderson – damals für eine Demo auf einer Konferenz – ebenfalls ein Experiment gestartet und vorgestellt. Es hieß Blazor – der Rest ist Geschichte.
Es ist somit gut möglich, dass die .NET Smart Components einen Ausblick darauf geben, was Microsoft in Zukunft plant. Vielleicht werden diese Komponenten irgendwann einmal zu einem Teil von ASP.NET Core und Blazor – oder eben nicht. Zum Redaktionsschluss dieses Artikels Mitte Juli 2024 gab es seit der Ankündigung nur noch minimale Änderungen an der Codebasis. Wenn das Experiment also aktiv weiterentwickelt wird, findet das unter Umständen nicht öffentlich statt.
Diese Hintergründe sowie das illustre Microsoft-Duo Roth und Sanderson sind freilich Grund genug, einen Blick auf den aktuellen Stand der Smart Components zu werfen. Uns interessieren nicht nur der Einsatz und die Features des Projekts, sondern natürlich auch, soweit möglich, ein Blick unter die Haube – wie wurde die „KI“ in die Komponenten gepackt?
Vorbereitungen
Den besten Einstieg in die .NET Smart Components bietet das Projekt selbst. Nach einem Klonen des GitHub-Repositorys [2] ist die erste Anlaufstelle die Visual-Studio-Solution SmartComponentsSamples.sln im Ordner samples. Diese enthält zwei Beispielanwendungen:
- ExampleBlazorApp – enthält die .NET Smart Components im Rahmen einer Blazor-Server-App.
- ExampleMvcRazorApp – nutzt sowohl ASP.NET Core MVC als auch Razor Pages; die .NET Smart Components kommen allerdings nur in letzterem Projekttyp zum Einsatz.
Steht kein Visual Studio zur Verfügung, tut es auch das .NET-CLI. Im jeweiligen Projektverzeichnis, also ExampleBlazor oder ExampleMvcRazor, startet dotnet run die entsprechende Web-App (Bild 1). Bild 2 zeigt die Razor-Pages-Anwendung. Bei der Blazor-Variante gibt es ein Beispiel mehr („Local Embeddings“). Beiden gleich ist die unmittelbar angezeigte Fehlermeldung. Diese bezieht sich darauf, dass für einige der Beispiele eine zusätzliche Konfiguration notwendig ist (beispielsweise die Eingabe eines OpenAI-Keys). Das wird uns aber erst später beschäftigen, denn auch ohne zusätzliche Angaben ist die Anwendung in Teilen einsatzbereit. Die Komponenten selbst sind im Projekt als NuGet-Pakete hinterlegt (Bild 3). Das erlaubt einen beschränkten Einblick in die Implementierung; Open Source ist das Projekt wie erläutert aktuell nicht.

Motor stottert, aber läuft: Die Webanwendung mit den .NET Smart Components (Bild 2)
Quelle: Autor
Combobox
Das erste „smarte“ Steuerelement ist „Smart ComboBox“. Bei Verwendung von Razor Pages kommt technisch ein Tag Helper zum Einsatz, dessen Name <smart-combobox> lautet. In Blazor-Anwendungen gelten bekanntlich andere Vorgaben für die Benennung von Controls; hier müssen wir zu <SmartComboBox> greifen. Die Funktionsweise des Steuerelements ist schnell erzählt: Es repräsentiert eine ComboBox, aber anstelle einer langen Liste mit Auswahlmöglichkeiten ist eine direkte Texteingabe möglich. Der intelligente Part des Controls bietet dann die entsprechenden Vorschläge an. Der Charme an diesem Szenario ist, dass es hierzu kein KI-Backend wie etwa OpenAI braucht, sondern dass alles lokal funktionieren kann. Insofern können wir auch die Warnung aus Bild 2 ignorieren, denn die Smart ComboBox funktioniert komplett lokal.
Um genau zu sein, bedarf es natürlich trotzdem eines Backends. Wir benötigen eine Liste gültiger Werte und einen Mechanismus, der die jeweils passendste Option findet. Dieser Mechanismus muss als REST-API-Endpunkt zur Verfügung gestellt werden. Der Einbau in eine Razor Page erfolgt dann nach folgendem Muster (aus der Beispielanwendung entnommen):
<smart-combobox url="~/api/suggestions/accounting-
categories" id="accounting-category" placeholder=
"Type a category name..." required />
In der Blazor-Anwendung sieht das ganz ähnlich aus:
<SmartComboBox Url="api/suggestions/expense-category"
id="expense-category" @bind-Value="@expenseCategory"
placeholder=" " />
@code {
string? expenseCategory;
}
Der Endpunkt selbst ist in der Datei Program.cs implementiert. Der Code besteht aus zwei Bestandteilen. Zunächst werden alle gültigen Werte in einer Liste hinterlegt. Allerdings geschieht dies mit einem Zusatz-Feature, das die Smart Components mitbringen. Ein Kernaspekt von Machine Learning sind Embeddings. Damit lassen sich, stark vereinfacht formuliert, die einzelnen Begriffe in unserer Liste in einen Zusammenhang setzen. Die .NET Smart Controls bieten hierzu die Klasse LocalEmbedder. Dekompiliert lässt sich erkennen, dass in der konkreten Implementierung mit lokalen Modelldateien gearbeitet wird, anhand derer dann unserer Vorschlagsliste die erforderliche Semantik hinzugefügt wird (vielleicht sind diese Dateien auch der Grund dafür, dass der Quellcode noch nicht zur Verfügung steht). Die Implementierungsdetails sind aber für uns gar nicht interessant, wir nutzen die Funktionalität einfach. LocalEmbedder wird als Service geladen und dieser dann dazu eingesetzt, die Liste der gültigen ComboBox-Einträge zu verarbeiten:
var embedder = app.Services.GetRequiredService<
LocalEmbedder>();
var expenseCategories = embedder.EmbedRange(
["Groceries", "Utilities", "Rent", /* ...
*/]);
Der zweite Schritt besteht in der Implementierung des API. Das ist kein sonderlicher Aufwand, denn LocalEmbedder bietet die Methode FindClosest(), die genau das macht, was ihr Name andeutet: Der passendste Vorschlag für die aktuelle Eingabe wird ermittelt:
app.MapSmartComboBox(
"/api/suggestions/accounting-categories",
request => embedder.FindClosest(
request.Query, expenseCategories));
Es geht hier tatsächlich um Semantik, nicht um eine etwaige Suche nach „ähnlichen Zeichenketten“ à la Levenshtein-Distanz. Ein einfacher Test zeigt das (siehe auch Bild 4): Eine Suche nach „Bus“ würde bei einer rein lexigrafischen Analyse keine Ergebnisse liefern, denn dieser Teilstring ist in keinem der gültigen Werte enthalten. Allerdings kommen Busse unter anderem im öffentlichen Nahverkehr vor – und diesen Begriff (public transportation) gibt es in der Wortliste! Auch die anderen gemachten Vorschläge (Reise, Miete, Anderes) passen halbwegs zur Eingabe. Das kann also ein tatsächlicher Mehrwert gegenüber einer herkömmlichen String-Suche sein.

Um die Ecke gedacht: Passende Combobox-Einträge zur Eingabe (Bild 4)
Quelle: Autor
Das Verhalten der Combobox lässt sich innerhalb gewisser Grenzen anpassen. Standardmäßig zeigt das Control bis zu zehn Vorschläge an, je nachdem, wie viele das API zurückliefert. Mit dem Attribut max-suggestions (Razor Pages) beziehungsweise MaxSuggestions (Blazor) lässt sich dieser Wert verändern. Ein weiteres Kriterium für die Auswahl möglicher Vorschläge ist der Wert der Übereinstimmung, er liegt normiert zwischen 0 und 1. Standardmäßig erfordert das Steuerelement mindestens 0,65, aber auch dies ist anpassbar. Das zugehörige Attribut ist similarity-threshold beziehungsweise SimilarityThreshold.
Zurück zu LocalEmbedder: In der Blazor-Anwendung – und aktuell nur dort – gibt es dafür ein eigenes Beispiel („Local Embeddings“). Hier können wir mehrere Begriffe eingeben, die dann in ein Embedding eingefügt werden. Bei jedem neu hinzugekommenen Begriff wird die Ähnlichkeit zum Rest der Einträge ermittelt. Bild 5 zeigt die Ausgabe für ein paar Beispiel-Termini. Implizit findet derselbe Prozess auch in der Smart ComboBox statt. Bei Eingabe eines Begriffs ermittelt das API, welche Begriffe der Liste wie „nah“ sind (vergleiche die vorherige Diskussion zu similarity-threshold/SimilarityThreshold). Die Treffer mit der größten semantischen Übereinstimmung werden dann angezeigt.

In guter Nachbarschaft: Wie gut passt ein Begriff zum Rest der Liste? (Bild 5)
Quelle: Autor
Offenkundig funktioniert dieses Vorgehen besonders gut, wenn es um die englische Sprache geht. Insofern wäre eine Open-Source-Veröffentlichung der .NET Smart Components – gegebenenfalls um lokalisierte Wortlisten oder Modelle erweitert oder erweiterbar – ein wünschenswerter Schritt.
Endpunkt-Konfiguration
Die bisherigen Szenarien können die .NET Smart Components noch selbst durchrechnen. Für komplexere Aufgaben ist ein entsprechendes Backend notwendig. Wer ein entsprechendes Tool im Netzwerk oder gar auf der lokalen Maschine verfügbar hat, kann das direkt einsetzen, solange nur die Software das API-Schema von OpenAI [5] unterstützt. Wir setzen auf Ollama [6], eine Open-Source-Software, die diverse Modelle lokal unter macOS, Linux und Windows laufen lassen kann, und dies auch auf weniger leistungsfähigen Systemen. Die Website des Projekts enthält ausführliche Informationen zu allen verfügbaren Modellen. Für die folgenden Ausführungen setzen wir auf das Modell Mistral 7B, das wie folgt gestartet (und gegebenenfalls zuvor installiert) werden kann (siehe auch Bild 6):
ollama run mistral
Läuft das Modell, wird gleichzeitig ein API-Endpunkt mit dem Standardwert http://localhost:11434 gestartet, über den mit dem Modell kommuniziert werden kann – der ollama-Kommandozeilenbefehl macht nichts anderes. Ein Aufruf von beispielsweise http://localhost:11434 liefert alle aktuell laufenden Modelle im JSON-Format zurück (Bild 7).

JSON statt Dashboard: Mistral läuft (Bild 7)
Quelle: Autor
Jetzt kann das Modell eingesetzt werden. Dazu benötigt die Anwendung mit den .NET Smart Components eine entsprechende Konfigurationsdatei. Es gibt hier zwei Optionen:
- Die Konfiguration wird in die Standarddatei appsettings.json gepackt oder gegebenenfalls in die Variante für die aktuelle Umgebung, also etwa appsettings.Development.json.
- Alternativ kann auch eine eigene Konfigurationsdatei namens RepoSharedConfig.json zum Einsatz kommen.
Egal wo die Konfiguration landet, sie muss dem nachfolgenden Schema entsprechen. Der Name des Modells und gegebenenfalls der Endpunkt-Host können dabei an die lokalen Gegebenheiten angepasst werden. Das folgende Element muss der jeweiligen JSON-Datei hinzugefügt werden:
"SmartComponents": {
"SelfHosted": true,
"DeploymentName": "mistral",
"Endpoint": "http://localhost:11434/"
}
Eine Alternative ist die Verwendung eines gehosteten Dienstes. Nicht überraschend schlägt das .NET-Team hier OpenAI vor. Auch dies ist vergleichsweise schnell in der Beispielanwendung konfiguriert. Wieder müssen wir eine der Konfigurationsdateien anpassen, doch diesmal sieht das hinzugefügte Element etwas anders aus:
"SmartComponents": {
"ApiKey": "***,
"DeploymentName": "gpt-3.5-turbo"
}
Von entscheidender Bedeutung ist hier der API-Key. Diesen können Sie, ein entsprechendes Konto bei OpenAI vorausgesetzt, unter [7] anlegen (Bild 8) und dann in der Konfiguration hinterlegen. Setzen Sie auf Azure OpenAI, ist eine erweiterte Einstellung vonnöten:

Generalschlüssel: Einen API-Key bei OpenAI anlegen (Bild 8)
Quelle: Autor
"SmartComponents": {
"ApiKey": "***",
"DeploymentName": "gpt-3.5-turbo",
"Endpoint": "https://***.openai.azure.com/"
}
Die drei Sterne sind durch die jeweils konkreten Angaben zu ersetzen. Als Modell können Sie natürlich auch jedes andere außer GPT 3.5 Turbo einsetzen, aber die Ergebnisse damit sind in den meisten Tests mit am besten. Andere Modelle, auch die mit Mistral und anderen von Ollama angebotenen Optionen, liefern teilweise keine oder falsche Ergebnisse. Die Dokumentation der .NET Smart Components gibt unter [8] (unten auf der Seite) entsprechende Empfehlungen und Erfahrungswerte ab.
Paste
Nach den ganzen Installations- und Konfigurationsorgien ist es nun endlich Zeit für den Einsatz der .NET Smart Components, die auf dieses Backend setzen. Erster Repräsentant ist Smart Paste, in Razor Pages und MVC durch den Tag-Helper <smart-paste-button> dargestellt, bei Blazor durch das Control <SmartPasteButton>. Dieser rendert eine Schaltfläche, die bei Klick per JavaScript-Code auf die Zwischenablage des Systems zugreift (natürlich nur, wenn vom Nutzer bestätigt) und deren Inhalt ausliest. Gerade bei der Variante für Razor Pages sieht man dies sehr schön: Die Komponente ist ein <button>-Element, und aus der Datei SmartComponents.AspNetCore.Components.lib.module.js kommt der click-Eventhandler (Bild 9).
Der Clou ist freilich, was dann im Hintergrund passiert. Der Smart-Paste-Button ist Teil eines umfangreicheren Formulars. Die Komponente ermittelt – ebenfalls per JavaScript – alle Eingabefelder im Formular und versucht, auf Basis der Beschriftungen (<label>-Elemente) deren Semantik zu verstehen. Daraus wird dann ein Prompt erstellt, mit dem das Modell befragt werden kann. Die Idee: Das, was in der Zwischenablage war, wird „verstanden“ und dann korrekt in die jeweiligen Formularfelder eingefügt. Dies lässt sich bereits am einfachsten Beispiel gut nachvollziehen, hier gekürzt aus der Razor Page entnommen:
<label for="name">Name</label>
<input type="text" id="name" placeholder=
"Your name" required />
<label for="age">Age</label>
<input type="number" id="age"
placeholder="Your age"
required />
<smart-paste-button
default-icon />
Die Blazor-Anwendung zeigt dasselbe Szenario und noch ein paar weitere; sehr nützlich ist etwa das Formular zum Erstellen eines Bug-Reports. Abhängig von Eingabedaten, Modell und Tagesform funktioniert das Steuerelement meist ziemlich ordentlich, manchmal auch weniger gut (Bild 10). Feintuning ist theoretisch möglich, etwa durch Anpassung des Prompts selbst, wie in der Dokumentation [9] ausgeführt.

Knapp daneben: Manchmal halluziniert die KI (Bild 10)
Quelle: Autor
Textbox
Die finale .NET Smart Component hat es in sich, denn sie nimmt das mit der vermeintlichen Intelligenz wirklich ernst. Sie hat einen schnöden Namen: Smart TextArea, also „kluges“ mehrzeiliges Textfeld. Der Clou ist hier, dass Texte während der Eingabe in dieses Feld automatisch ergänzt werden sollen. Als einer der Ersten hat Google diesen Effekt in einige Produkte (Google Mail, Google Docs et cetera) integriert, und es funktioniert geradezu beängstigend gut. Die smarte Textarea kann da noch nicht komplett mithalten, aber die Idee ist ähnlich. Im Unterschied zu den anderen Komponenten ist etwas mehr Konfigurationsarbeit vonnöten.
Im Wesentlichen erwartet die Logik des Steuerelements, dass zwei Parameter angegeben werden:
- Die Rolle der Person, die das Textfeld bedient. Ein schönes Beispiel in der Beispielanwendung ist ein Open-Source-Maintainer, der ein Ticket im Repository des Projekts bearbeiten möchte.
- Typische Sätze, die diese Rolle verwenden würde. Dabei kann NEED_INFO als Platzhalter verwendet werden, wenn in dem Satz eine anfragespezifische Information vorkommen soll.
Hier ein syntaktisch etwas veränderter Auszug aus dem Beispielcode der Blazor-Anwendung, der dies illustriert:
string role = "Maintainer of this open-source
project replying to GitHub issues";
string[] phrases = [
"Thank you for contacting us.",
"To investigate, we’ll need a repro as a public
Git repo.",
"Could you please post a screenshot of NEED_INFO",
"This sounds like a usage question. This issue
tracker is intended for bugs and feature
proposals. Unfortunately, we don’t have
capacity to answer general usage questions
and would recommend StackOverflow for a faster
response.",
"We don’t accept ZIP files as repros.",
"The issue may be that NEED_INFO"
];
Theoretisch reicht auch eine einfache Rollenbeschreibung, aber es gilt die übliche Regel für Prompts: Je spezifischer, desto besser. Auch hier hängt die Qualität des Ergebnisses wieder vom Modell und der Problemdomäne ab und ist außerdem nichtdeterministisch.
Ein wichtiger Hinweis noch zum Schluss: Ein Blick in die Konsole lohnt sich, um etwaige .NET-Fehlermeldungen einsehen zu können (die Konsole der Browser-Dev-Tools ist hier nicht sehr aufschlussreich, denn die Modell-Aufrufe werden vom Server erzeugt, nicht direkt vom Client). Bild 11 zeigt eine typische Exception, wenn das Quota zum API-Schlüssel überschritten ist. Ein weiterer typischer Fehler ist die Verwendung eines nicht installierten oder laufenden Modells; auch dies verrät die Konsolenausgabe. ◾
Dokumente
Artikel als PDF herunterladen
Fußnoten
- Daniel Roth kündigt .NET Smart Components an
- GitHub-Repository der .NET Smart Components
- .NET Smart Components sind (noch?) nicht Open Source
- Steve Sanderson zeigt .NET Smart Components
- Das OpenAI-API-Schema
- Ollama
- API-Keys für OpenAI anlegen
- Dokumentation zur Einsatzfähigkeit verschiedener Modelle
- Prompt anpassen für Smart Paste