Noel Röhrig
Softwareentwickler auf Rädern 🚎
27.02.2026 | 10 min Lesezeit
Im letzten Blog haben wir festgestellt, dass wir Tools benötigen, um das volle Potenzial eines LLMs auszuschöpfen. Die „KI“ ist ohne Tools ein Gehirn ohne Hände und ohne etwaige Verbindungen zur echten (oder eher digitalen) Welt nur begrenzt nützlich. Entsprechend braucht es eigens entwickelte KI-Anwendungen, um die Geschäftsprozesse wirklich tiefgreifend zu unterstützen. Also müssen wir uns die Frage stellen: Wie baut man denn eine eigene KI-Anwendung?
Bevor wir uns an die Details von Tools machen, ist es wichtig, immer zu bedenken: LLMs arbeiten textbasiert mit natürlicher Sprache. Technisch heißt das vereinfacht: Man kann dem LLM nur einen string schicken und kriegt auch einen string zurück. Und da das LLM im Zentrum der KI-Anwendung steht, ist das als Entwickler erst einmal eine Umgewöhnung.
In der Fachliteratur kursieren einige Definitionen für Tools. Ich finde die Beschreibung von Wang et al. aus „What are Tools anyway?“ am treffendsten. LM (Language Model) ist hier ein Oberbegriff, zu dem LLMs gehören:
An LM-used tool is a function interface to a computer program that runs externally to the LM, where the LM generates the function calls and input arguments in order to use the tool.
Ein Tool besteht einerseits aus einem ausführbaren Programm, das sich außerhalb des LLMs selbst befindet. Vor allem aber enthält es eine Interface-Definition des Programms für das LLM, um besagtes Programm mit Parametern auszuführen und mit den Rückgabewerten zu arbeiten. Auch hier gilt wieder: Die Interface-Definition muss im Textformat an das LLM übergeben werden – da dieses nur mit einem string arbeiten kann.
Die Definition des Tools für das LLM besteht also aus bis zu drei Elementen:
JSON-Schema der Parameter und RückgabewerteDa LLMs noch sehr nah an der Forschungsebene sind und in diesem Kontext trainiert werden, rät z. B. Microsoft dazu, die Bezeichnungen für Properties und Funktionen im Python typischen snake_case an das LLM zu reichen. Das erhöht die Wahrscheinlichkeit für korrekte Aufrufe der Tools leicht.
Aufgrund der Art und Weise, wie LLMs Texte generieren, muss die Tool-Definition bei jedem Request an das LLM im Kontext enthalten sein. Da passiert es schnell mal, dass eine User-Anfrage an das LLM mit 200 Tokens einen Overhead von 7.000 Tokens hat. Und das ist in einem noch relativ einfachen System. Der Overhead besteht dabei nur aus der SystemMessage und den Tool-Definitionen.
Aber: Die Verfügbarkeit der Tools für das LLM erhebt es von einem Textgenerator zu einem Dirigenten der Software-Umgebung. Basierend auf einer Nutzeranfrage „entscheidet“ das LLM:
Der Paradigmenwechsel für uns Entwickler ist, dass wir nicht mehr jeden Klick-Pfad und jede Integration zwischen den Tools selber programmieren müssen, sondern die Tools dem LLM zur Hand geben. Hier liegt auch der Übergang von deterministischer Software zu nicht deterministischer Software. Ich kann als Entwickler nicht mehr garantieren, dass Eingabe A auch zu Ausgabe B führt, da das LLM im Zentrum der Ergebnisgenerierung steht.
Die Tool-Aufrufe des LLMs dann tatsächlich auch in der Laufzeit zu koordinieren, ist ein komplexes Unterfangen – das Diagramm oben gibt dazu vielleicht einen kleinen Einblick. Deshalb braucht es ein starkes Framework, das uns mühselige Arbeit abnimmt und uns ermöglicht, uns auf den Kern unserer KI-Anwendung zu fokussieren.
Bei codeunity setzen wir hier auf Microsofts SemanticKernel. Das Framework dient als Brücke zwischen der eigenen Codebase und einem gehosteten LLM. Als Entwickler muss ich nur die Tools schreiben und hänge diese an den zentral liegenden „Kernel“. Zudem ist es möglich, eigenen Code in die Koordinierung mit dem LLM über Hooks oder Filter zwischenzuschieben. Die Abstraktionsebene über den SemanticKernel hilft auch, den LLM-Provider ohne viel Aufwand auszutauschen und sogar ein lokales Modell einzubinden.
Wo das LLM gehostet wird, ist bei SemanticKernel nämlich (fast) egal. Es gibt native Konnektoren für alle großen AI-Hosts und generischen Support für alle Hosts, die ihre API nach dem OpenAI-Standard designt haben. Als Integration ins eigene Azure-Ökosystem bietet es sich an, Azure AI Foundry zu nutzen.
In der Praxis werden die Tools im Semantic Kernel in einem Plugin zusammen gruppiert. Ein Tool ist dabei eine Methode im Plugin, welche klassische Business-Logik enthält. Dabei sollten jegliche Methoden und Parameter, die für das LLM offen sind, mit [Description]-Annotations versehen werden. Eine Beschreibung in natürlicher Sprache vereinfacht es dem LLM, das Tool zu bewerten. Wichtig ist nämlich zu bedenken, dass das LLM nur die Methodensignatur und die Description sieht und nicht den Methodenkörper.
Als Praxisbeispiel stellen wir uns eine Chat-Anwendung für die Mitarbeiter eines Online-Händlers vor. Die Mitarbeiter sind oft nur mit der E-Mail-Adresse ihrer Kunden konfrontiert und nicht geschult, über Datenbankabfragen alle Details der Kunden herauszusuchen. Gleichzeitig will das Unternehmen nicht in ein typisches Frontend für ein „Kunden-Management“ investieren, sondern flexibel bleiben. Die Idee: Für das ganze Unternehmen soll ein Chatbot zur Verfügung stehen, um den Mitarbeitern Zugriff auf die Datenbanken in natürlicher Sprache zu ermöglichen. Da das LLM nicht auf Basis der Daten des Unternehmens trainiert wurde, braucht es die nötigen Tools, um auf die Daten zuzugreifen und sie zu aggregieren:
public record CustomerData
{
[Description("The unique identifier of the customer.")]
public int Id { get; init; }
[Description("The name of the customer.")]
public string Name { get; init; }
[Description("The email of the customer.")]
public string Email { get; init; }
[Description("The country where the customer resides.")]
public string Country { get; init; }
[Description("The total value of all purchases by this customer")]
public int TotalGross { get; init; }
}
public class CustomerPlugin(ICustomerDatastore customerDatastore, IOrderHistoryService orderHistoryService)
{
[KernelFunction("get_customer_data")]
[Description("Returns the data of a customer given their email. Returns an object with the customers data or null if the customer doesn't exist.")]
public async Task<CustomerData?> GetCustomerData([Description("The email of the customer which data is to be retrieved. Example: test@example.com")] string customerEmail)
{
var customer = await customerDatastore.GetCustomerByEmailAsync(customerEmail);
if (customer is null)
{
return null;
}
var orderHistory = await orderHistoryService.GetOrderHistoryByCustomerIdAsync(customer.Id);
return new CustomerData()
{
Country = customer.Addess.Country,
Email = customer.Email,
Id = customer.Id,
Name = customer.Name,
TotalGross = orderHistory.Sum(order => order.NetGross)
};
}
}In GetCustomerData nehmen wir eine E-Mail-Adresse entgegen und suchen in der Kundendatenbank nach einem entsprechenden Eintrag. Falls es diesen gibt, suchen wir zusätzlich nach vergangenen Bestellungen des Kunden und errechnen die Summe, um sie im Rückgabewert zu berücksichtigen.
Sobald wir dieses Tool auch im Kernel registrieren:
kernelBuilder.Plugins.AddFromType<CustomerPlugin>("Customer")kann das LLM dieses Tool finden und berücksichtigen. Diese Anfrage eines Mitarbeiters an den LLM-Chat:
Bitte finde mir alle Details, die du zu dem Kunden test@example.com finden kannst.
sollte in einem Aufruf der oben erstellten Methode enden und die Antwort des LLMs die Kundendaten berücksichtigen lassen.
Auch wenn schnell die Illusion entsteht, dass ein produktives KI-System schnell aus dem Boden gestampft und mit ausreichend Tools versehen ist – in Wahrheit ist noch viel Code drumherum nötig, um das Ganze sinnvoll zum Laufen zu bringen. Wollen die Mitarbeiter im obigen Beispiel noch Nachfragen stellen und wissen, was der Kunde gekauft hat, ob offene Rechnungen ausstehen, wie oft man mit dem Kunden in Kontakt stand oder welche Produkte noch zum Kunden passen könnten, dann braucht es für all die Daten dahinter auch noch Tools und gegebenenfalls zusätzliche Tools, die für konkrete Use-Cases bestimmt sind. Die Veränderung für die Entwickler zeigt sich dadurch, dass wir uns nicht mehr fragen müssen, wie wir ein Dashboard aufbauen, welche Endpunkte die API dafür braucht und welche Daten den Nutzer interessieren könnten, sondern wie wir ein robustes Tool-Ökosystem bauen, das möglichst viele Use-Cases abbilden kann.
Auch müssen wir uns Gedanken machen, wie viel „Macht“ wir dem LLM geben. Die Menge an Möglichkeiten für das LLM ergibt sich aus der Auswahl an Tools. Ein GetCustomerData Tool ergibt ein sehr limitiertes LLM, das nur den oben beschriebenen Use-Case bearbeiten kann. Wohingegen Tools wie CreateFileOnSystem und RunFile dem LLM plötzlich die Möglichkeit gäben, Skripte zu erstellen und auszuführen. Das würde das LLM in unserem System digital allmächtig (und vor allem allzerstörerisch) machen und wahrscheinlich für viele Probleme und wenig brauchbare Ergebnisse sorgen.
In der Theorie klingt es verlockend: Warum geben wir dem LLM nicht einfach Zugriff auf jede einzelne API-Schnittstelle, die unser Unternehmen besitzt? In der Praxis stoßen wir dabei jedoch schnell an zwei Grenzen, die uns zwingen, strategisch statt wahllos vorzugehen.
get_customer_by_id und search_client_database), fängt das LLM an zu raten oder gar Parameter zu erfinden, die gar nicht existieren. Das System wird instabil und unzuverlässig.Also: Wie lösen wir das?
Einerseits müssen wir immer ein Auge darauf behalten, wie viel unsere KI-Anwendung wirklich können muss, und dem LLM nicht zu viele Tools an die Hand geben. Braucht man aber eine große KI-Anwendung, die viele Bereiche abdeckt, braucht es hierfür Lösungen. Man kann beispielsweise auf Arbeitsteilung setzen. Anstatt einem einzelnen Generalisten alles aufzubürden, teilen wir die Aufgaben in spezialisierte Agenten auf. Ein Agent ist Experte für Kunden, einer für Logistik und ein dritter für die Buchhaltung. Sie werden von einem Planner orchestriert und kriegen jeweils nur das Set an Tools, das sie wirklich brauchen. Darauf werde ich aber in einem zukünftigen Blog genauer eingehen.
Wir müssen umdenken: Eine moderne KI-Anwendung ist weit mehr als nur ein schicker Chat-Prompt. Im Kern der Anwendung steht zwar das LLM als intelligenter Dirigent, doch ohne eine solide Infrastruktur drumherum bleibt es wirkungslos.
Wir bauen heute keine starren Klick-Pfade mehr, sondern eine Applikation, die als schützende und unterstützende Hülle fungiert. Wir füttern das LLM mit dem nötigen Kontext und drücken ihm über Frameworks wie den Semantic Kernel die passenden Tools in die Hand, um eine möglichst hilfreiche Antwort zu generieren. Das Ziel ist ein robustes Ökosystem, in dem das LLM befähigt wird, reale Probleme in der digitalen Welt zu lösen – sicher und vor allem mit echtem Mehrwert für die Geschäftsprozesse.

