
Künstliche Intelligenz ist überall – und jetzt auch im Report Designer. Mit der einfachen Designerfunktion AI$() kannst du die Power von OpenAI oder anderen LLMs direkt in deine List & Label-Berichte einbetten. Klingt gut? Dann schauen wir uns an, wie das funktioniert – und warum es eines deiner neuen Lieblingsfeatures werden könnte.
Lerne das neue AI$-Designerfeature in unserem YouTube-Video kennen oder lies den folgenden Blogartikel:
Eine kleine Designer-Erweiterungsfunktion für einen großen Schritt
Traditionell erlauben es dir die Funktionen von List & Label, Zeichenfolgen zu formatieren, Zahlen zu berechnen oder das Layout zu steuern. Mit der AI$()-Funktion betreten wir ein neues Terrain: Die Idee ist simpel – du übergibst dem KI-Modell einen Prompt und verwendest das Ergebnis direkt im Bericht.
Ob du eine Zusammenfassung, eine Übersetzung oder eine elegantere Formulierung möchtest – AI$() erledigt den Job. Im Hintergrund verbindet sich die Funktion über das Semantic Kernel SDK von Microsoft mit dem OpenAI-Dienst (oder einem anderen unterstützten LLM).
Legen wir mit dem Kerncode los.
So funktioniert es intern
Hier ist die Methode, die den eigentlichen KI-Aufruf durchführt. Die Builder- und Kernel-Objekte könnten hier natürlich zwischengespeichert werden. Du musst nur das Semantic Kernel-Paket zu deiner Anwendung hinzufügen und eine neue Designerfunktion zu deiner ListLabel-Instanz ergänzen.
private async Task<string> GetOpenAIResultAsync(string prompt) { var builder = Kernel.CreateBuilder(); var apiKey = ""; builder.AddOpenAIChatCompletion( modelId: "o4-mini", apiKey: apiKey ); var kernel = builder.Build(); var chatFunction = kernel.CreateFunctionFromPrompt(prompt); var result = await kernel.InvokeAsync(chatFunction); return result.ToString(); }
Da die Funktion asynchron ist, die Auswertung eines Berichts jedoch synchron erfolgen muss (da die Render-Engine das vollständige Ergebnis vor dem Rendern benötigt), brauchen wir eine pseudo-synchrone Umgehungslösung:
private void aiFunction_EvaluateFunction(object sender, EvaluateFunctionEventArgs e) { e.ResultValue = Task.Run(() => GetOpenAIResultAsync(e.Parameter1.ToString())).Result; e.ResultType = LlParamType.String; }
Das funktioniert – aber wiederholte Aufrufe mit demselben Prompt kosten unnötig Zeit und Tokens. Das lässt sich verbessern.
Caching für Geschwindigkeit und Übersicht
Ein einfacher In-Memory-Cache mit einem Dictionary<string, string>
verbessert die Performance erheblich:
private Dictionary<string, string> _llmResults = new(); private void aiFunction_EvaluateFunction(object sender, EvaluateFunctionEventArgs e) { string prompt = e.Parameter1.ToString(); e.ResultType = LlParamType.String; if (_llmResults.TryGetValue(prompt, out var result)) { e.ResultValue = result; return; } string aiResult = Task.Run(() => GetOpenAIResultAsync(prompt)).Result; _llmResults[prompt] = aiResult; e.ResultValue = aiResult; }
Warum nicht asynchron?
Die Berichtserstellung ist ein zeitkritischer Prozess. Jeder Funktionsaufruf muss sofort ein Ergebnis liefern, nicht „irgendwann später“. Auch wenn .NET asynchronen Code zulässt, muss die Auswertelogik von List & Label synchron bleiben, um Dinge wie Zellhöhe oder Seitenumbrüche im Voraus zu bestimmen.
Das macht den Task.Run(...).Result
notwendig – auch wenn er nicht ganz elegant ist. Es ist der Preis, den wir zahlen, um LLMs mit einer für Vorhersagbarkeit konzipierten Render-Engine zu kombinieren.
Was kannst du mit AI$()
tun?
Ein paar praktische Ideen:
- Produktbeschreibungen übersetzen:
AI$("Translate 'Kaffeemaschine mit Thermoskanne' to English")
- Lange Texte zusammenfassen:
AI$("Summarize this: ...")
- Prägnante Titel erzeugen:
AI$("Write a headline for this product: ...")
- Inhalte höflicher formulieren:
AI$("Rephrase: 'Return at your own cost' in a friendlier tone")
- Code-Snippets erzeugen:
AI$("Write a C# code snippet that calculates factorial")
Die Anwendungsmöglichkeiten sind nur durch deine Kreativität – und die Tokenlimits von OpenAI – begrenzt. Hier siehst du, was passiert, wenn ich diesen Blogbeitrag im Designer auf Französisch zusammenfassen lasse:
Fazit
Das Einbetten von KI in List & Label über die AI$()
-Funktion eröffnet neue Horizonte für dynamisches, intelligentes Reporting. Mit dem Semantic Kernel steht dir eine leistungsstarke Abstraktionsschicht zur Verfügung, die sich leicht an neue LLMs, Modelle oder Prompt-Vorlagen anpassen lässt. Dein Copilot im Report Design – nur einen Funktionsaufruf entfernt.
Probiere es aus. Und sag uns, welcher Prompt dich am meisten überrascht oder begeistert hat!
Für so ein tolles neues Feature wünschte ich mir ein Beispiel mit einer lokalen Open Source LLM über OLLAMA und nicht über OpenAI. Die Sache mit den Tokens bei den API-Schnittstellen bei OpenAI wird schnell unkalkulierbar, vor allem wenn man die Funktion für Kunden freischaltet, wo man am Anfang nicht weiß wie viel sie oder man selber damit rumspielen werden bzw. wird. Das wäre anders wenn nicht nach Tokens sondern pauschal bezahlt werden könnte.
Hallo Michael,
klar, das verstehe ich – deswegen ist das Beispiel auch für den Microsoft Semantic Kernel gebaut. Der unterstützt eine lokale OLLAMA-Instanz genauso wie (im Beispiel) OpenAI. Der Code sollte dann in etwa so aussehen:
private async Task GetOllamaResultAsync(string prompt)
{
var builder = Kernel.CreateBuilder();
// Configure Ollama instead of OpenAI
builder.AddOllamaChatCompletion(
modelId: „llama2“, // or any other model you have pulled locally
endpoint: new Uri(„http://localhost:11434“)
);
var kernel = builder.Build();
var chatFunction = kernel.CreateFunctionFromPrompt(prompt);
var result = await kernel.InvokeAsync(chatFunction);
return result.ToString();
}
Die MS Semantic Kernel-Klassen ändern sich aktuell noch recht regelmäßig und schnell, aber das Prinzip sollte klar sein. Hier scheint auch eine einigermaßen aktuelle Referenz für die verschiedensten – auch lokalen – Services zu sein:
https://learn.microsoft.com/de-de/semantic-kernel/concepts/ai-services/chat-completion/