Wie wir einen KI-Mobilitaetsassistenten fuer eine Fleetmanagement-Plattform gebaut haben

20. Februar 202615 min readJonathan Kramer
Wie wir einen KI-Mobilitaetsassistenten fuer eine Fleetmanagement-Plattform gebaut haben

Wir haben einen KI-Assistenten fuer eine Fleetmanagement-Plattform gebaut, die Mobilitaetsrichtlinien fuer Unternehmen verwaltet: Leasingautos, Fahrraeder, OEPNV-Karten, Carsharing, Homeoffice-Zuschlaege. Ueber den Assistenten koennen Mitarbeiter Fragen zu ihrer persoenlichen Mobilitaetsregelung stellen. Persoenliche Fragen, Live-Daten, Echtzeit-Antworten.

Das hier ist ein technischer Bericht darueber, wie ich das gebaut habe. Welche Muster ich verwendet habe, welche Entscheidungen ich getroffen habe, und wo die eigentliche Komplexitaet lag.

Function Calling als Kernmuster

Das Herz des Assistenten ist Function Calling. Das ist das Muster, bei dem ein KI-Modell nicht nur Text generiert, sondern auch Werkzeuge benutzen kann. Man gibt dem Modell eine Liste von Funktionen mit Namen, Beschreibungen und Parameter-Definitionen. Bei jeder Benutzernachricht entscheidet das Modell selbst, ob es eine Funktion aufrufen muss, welche, und mit welchen Argumenten.

In der Praxis: Ein Mitarbeiter tippt "Wie viel Fahrradbudget habe ich noch?" Das Modell analysiert die Frage, erkennt, dass es sich um eine Budgetfrage fuer die Modalitaet Fahrrad handelt, und ruft eine Funktion auf, die ueber die interne API der Plattform das verbleibende Budget abruft. Die API liefert strukturierte Daten zurueck. Das Modell liest diese Daten und formuliert eine klare Antwort darum herum.

Der entscheidende Unterschied zum traditionellen Chatbot: Das Modell waehlt die Funktion, nicht der Entwickler. Bei einem klassischen Chatbot programmiert man jede Route selbst. Wenn Benutzer X sagt, tue Y. Das skaliert nicht. Bei Function Calling beschreibt man, was die Funktionen tun, und das Modell bestimmt, wann welche Funktion passt. Neue Fragetypen, die in den Bereich der bestehenden Funktionen fallen, funktionieren automatisch. Null Code noetig.

Das Design der Funktionen

Das ist der Teil, in den ich die meiste Denkarbeit gesteckt habe. Nicht die Implementierung. Das Design.

Jede Funktion, die der Assistent aufrufen kann, ist ein Vertrag. Der Name sagt, was die Funktion tut. Die Description sagt dem Modell, wann es die Funktion verwenden soll und wann nicht. Parameter sind strikt typisiert mit Enums wo moeglich.

Ein vereinfachtes Beispiel (nicht die exakte Produktionsimplementierung, aber das Muster stimmt):

{
  "name": "get_mobility_budget",
  "description": "Rufe das aktuelle Mobilitaetsbudget
    fuer einen Mitarbeiter und eine bestimmte
    Modalitaet ab. Verwenden bei Fragen zu
    Restbudget, Ausgaben oder verfuegbarem Betrag
    pro Verkehrsmittel. Nicht verwenden fuer
    allgemeine Richtlinienfragen ohne
    personenbezogene Komponente.",
  "parameters": {
    "modality": {
      "type": "string",
      "enum": ["car", "bike", "public_transport",
               "shared_car", "wfh"]
    }
  }
}

Diese Description ist Prompt Engineering in Verkleidung. Das Modell liest diesen Text bei jedem Turn, um zu entscheiden, ob diese Funktion zur Frage passt. Ist die Description zu vage, waehlt das Modell die falsche Funktion. Zu strikt, und es traut sich nicht, die Funktion zu verwenden, wenn es sollte.

Ich habe mehr Stunden mit dem Kalibrieren dieser Beschreibungen verbracht als mit dem Bau der eigentlichen API-Integrationen. Das ist keine Uebertreibung. Der Unterschied zwischen "funktioniert in der Demo" und "funktioniert zuverlaessig in Produktion" liegt zu 80% daran, wie gut die Funktionsbeschreibungen geschrieben sind.

Multi-Step Reasoning

Einfache Fragen sind trivial. Eine Funktion, eine Antwort. Aber Mitarbeiter stellen auch komplexere Dinge. "Ich ueberlege, mein Leasingauto gegen ein E-Bike und eine OEPNV-Karte einzutauschen. Geht das mit meiner Regelung und was bedeutet das finanziell?"

Das erfordert mehrere Schritte. Das Modell muss die aktuelle Regelung abrufen. Den Leasingvertrag pruefen. Berechnen, wie das Budget bei einem Modalitaetswechsel aussehen wuerde. Dann den Vergleich praesentieren. Vier Funktionsaufrufe in Sequenz, wobei die Ausgabe von Schritt 1 Eingabe fuer Schritt 2 ist.

Function Calling unterstuetzt das nativ. Nach jedem Aufruf erhaelt das Modell das Ergebnis zurueck und entscheidet, ob ein weiterer Schritt noetig ist. Das Modell plant gewissermassen seine eigene Recherche.

Warum Function Calling und nicht RAG

Fuer den Assistenten passt RAG nicht. Mobilitaetsrichtlinien sind dynamisch und personenbezogen. Ein Dokument, das sagt "das maximale Fahrradbudget betraegt 1.500 Euro", ist nutzlos, wenn man nicht weiss, wie viel davon bereits ausgegeben wurde. Dieser aktuelle Stand steht nicht in einem Dokument. Der steht in einem Live-System.

Function Calling loest das fundamental anders. Statt in Dokumenten zu suchen, fragt das Modell die Quelle der Wahrheit ab: die Live-Daten aus der Plattform. Fuer allgemeine Richtlinienfragen gibt es eine Knowledge Base als Fallback. Aber das Schwergewicht liegt bei Function Calling.

Die Sicherheitsarchitektur

Der Assistent hat Zugang zu Mitarbeiterdaten. Budgets, Vertragsdetails, Regelungen. Vier unabhaengige Sicherheitsschichten schuetzen diese Daten.

Schicht 1: Read-only Scope. Der Assistent kann nur lesen. Alle Funktionen machen ausschliesslich GET-Requests. Es gibt keine Funktion zum Aendern von Daten, Kuendigen von Vertraegen oder Ausloesen von Zahlungen.

Schicht 2: Identity Scoping. Jede Session ist an einen authentifizierten Benutzer gebunden. Funktionen koennen nur Daten fuer diesen spezifischen Benutzer abfragen. Die User-ID wird serverseitig injiziert, nicht vom Modell bestimmt. Immutable, wird immer vom Server ueberschrieben.

Schicht 3: Output Sanitization. Bevor ein Funktionsergebnis an das Modell zurueckgegeben wird, werden unnoetige Felder entfernt. Datenminimierung durch Design.

Schicht 4: Prompt Injection Prevention. Benutzereingaben werden validiert. Das System Prompt enthaelt explizite Grenzen. Und selbst wenn jemand das Modell manipulieren wuerde: Schicht 2 blockiert jede Anfrage ausserhalb des Session-Scopes.

Ein wichtiges Detail: Das KI-Modell selbst hat keinen direkten Zugang zu Benutzerdaten. Alle Daten bleiben innerhalb der Infrastruktur der Plattform. Das Modell kann nur bestimmte, vordefinierte Datenpunkte ueber die Funktionsschicht anfordern, und selbst dann werden die Ergebnisse gefiltert, bevor das Modell sie sieht. Kein Massenzugriff, keine Datenbankabfragen, kein Datenexport. Das Modell arbeitet mit dem Minimum, das es braucht, um die Frage zu beantworten, nicht mehr.

Defense in Depth. Vier unabhaengige Mauern, nicht eine.

Optimierungen nach v1

Temperature auf 0. Determinismus statt Kreativitaet. Dieselbe Frage mit denselben Daten ergibt dieselbe Antwort.

Versionierte Funktionsbeschreibungen. Jede Aenderung an einer Description ist nachvollziehbar. Wenn ein Edge Case auftaucht, kann ich genau zurueckverfolgen, welche Version aktiv war.

Observability. Jeder Schritt wird geloggt: welche Funktion das Modell in Betracht gezogen hat, welche es gewaehlt hat, API-Latenz, Ergebnisgroesse. Ohne diese Logs debuggt man blind.

Kompakter Kontext. Weniger Tokens pro Turn bedeuten schnellere und genauere Antworten. System Prompt kompakt halten. Funktionsergebnisse auf das Minimum strippen.

Das groessere Bild

Function Calling als Abstraktionsschicht zwischen einem KI-Modell und bestehenden APIs ist eines der maechtigsten Muster, die es gerade gibt. Jedes System mit internen Daten, zu denen Menschen Fragen haben, kann dieses Muster verwenden.

Was es schwierig macht, ist nicht die KI. Was es schwierig macht, ist das Engineering drumherum. Scope begrenzen. Output validieren. Edge Cases testen. Funktionsbeschreibungen kalibrieren. Sicherstellen, dass das Modell ehrlich sagt, wenn es etwas nicht weiss. Das ist Softwareentwicklung. Keine KI-Magie.

Ready to automate?

Let us show you what AI automation can do for your business.

Get in touch