25 Min. Lesezeit aktualisiert

Read the original in English →

Decision-Modelle: Das Modell klärt das Was, der Code das Wie

Decision-Modelle liefern Wahrscheinlichkeiten statt Text. Ein Dino-Spiel, zwei Modelle und acht Prompt-Varianten zeigen, wann sie eine if-Abfrage schlagen.

Viele KI-Anwendungsfälle im Unternehmen sind im Kern Zuordnungsprobleme mit echten Konsequenzen: Welches Team bekommt diese Störungsmeldung? Verlängert sich diese Vertragsklausel automatisch? Kündigt diese Lieferantenmail eine Verzögerung an? Ein generatives Sprachmodell kann das zwar beantworten, liefert aber Freitext zurück. Den muss man erst parsen, validieren und sich darauf verlassen können, bevor im System irgendetwas passiert.

Decision-Modelle sparen sich den Freitext. Du schickst eine Situation und eine geschlossene Frage hin und bekommst für jede Option eine Wahrscheinlichkeit zurück, in rund 300 Millisekunden für den Bruchteil eines Cents. Zwei davon lassen sich gerade leicht ausprobieren: Jev von TypeSafe, das Modell hinter Demos wie Jev spielt Doom, und Microsofts neues Microsoft-Decision-1 in Foundry.

Ich wollte wissen, wo sie wirklich helfen. Also ließ ich beide Modelle einen Chrome-Dino-Klon in .NET 10 spielen und habe jede einzelne Entscheidung ausgewertet. Die ersten Versionen des Spiels bewiesen gar nichts. Erst die Variante, die tatsächlich funktionierte, brachte mich auf die Grundregel dieses Artikels: Ein Decision-Modell lohnt sich dort, wo der Kontext als Text reinkommt und sich auch die Regeln in natürlicher Sprache ändern. Frag das Modell, was zu tun ist, und lös das Wie im Code. Dieselbe Regel erklärt auch, warum Microsofts Modell in meinen Läufen das deutlich bekanntere Jev geschlagen hat und warum ein einziges zusätzliches Adjektiv Jevs Punktzahl verfünffachte.

was das Modell siehtDecision-Modellwas es antwortetwas der Code tutim Spiela large cactus withbirds circling abovesign: full jump catchesgolden eggs (+100)choice-Frage4 Aktionen, jedebeschrieben wieim Handbuchshort_jump 0.93full_jump 0.05run 0.01duck 0.01Bewegungslogiklegt den Absprungper Spielphysik fest(rein aufs Überleben)in einem Geschäftsprozess“conveyor 7 stopped,motor overload”+ aktuelle Richtlinieals Textchoice, Ja/Neinund scorein einem Request~300 msteam Electricalstopped 1.00urgency 1.62 von 2Schwellenwerte,Routing, SLA-Timer,Einsatzplanung,Review bei Zweifeldas Modell entscheidet das Wasder Code das WieDas Modell sagt, was zu tun ist. Wie, wann und wie viel bestimmt der Code.

Ein Decision-Modell beantwortet eine geschlossene Frage, keinen Prompt

Ein Request besteht aus zwei Teilen: state beschreibt die Situation als Text oder JSON, und unter questions stehen typisierte Fragen. Davon gibt es drei Arten:

  • choice: eine Option aus einer festen Menge wählen. Du bekommst für jede Option eine Wahrscheinlichkeit.
  • noul: eine Ja/Nein-Frage. Du bekommst die Wahrscheinlichkeit für Ja.
  • score: die Eingabe auf einer geordneten Skala einordnen. Du bekommst einen Wert zwischen den Stufen und eine Wahrscheinlichkeit pro Stufe.

Das hier ist das Beispiel aus Microsofts Dokumentation, mit dem ich auch meinen ersten Testaufruf gemacht habe:

samples/team-routing.json
{
"model": "Decision-1",
"state": "The API returns 500 on every call.",
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Charges, invoices, and refunds",
"engineering": "Bugs, errors, and outages",
"support": "How-to and account questions"
}
}
}
}

Microsoft-Decision-1 wählte engineering mit einer Wahrscheinlichkeit von 0,998. Der erste Aufruf dauerte rund 700 ms, alle weiteren lagen bei 200 bis 300 ms. Das Modell kann keinen Satz formulieren und seine Wahl auch nicht begründen. Als Antwort kommt nur eine Options-ID samt Wahrscheinlichkeit zurück, die man im Code direkt gegen einen Schwellenwert prüfen kann. Liegt selbst die höchste Wahrscheinlichkeit zu niedrig, geht der Fall an einen Menschen.

Zwei Details gehen beim ersten Ausprobieren leicht schief: Ein Foundry-API-Key gehört in den Header api-key, denn Authorization: Bearer akzeptiert nur Microsoft-Entra-ID-Tokens. Und im Feld model muss der Name deines Deployments stehen, nicht der Modellname.

TypeSafe nennt dieses Muster System One. Microsofts Endpoint nutzt exakt dasselbe Request-Format und endet sogar auf /providers/microsoft/v1/systemone. OpenRouter stellt beide Modelle sowie eine Handvoll neuerer Decision-Modelle hinter einem gemeinsamen Endpoint bereit; damit ließ sich der direkte Vergleich ohne großen Aufwand bauen. Dort sieht man auch die aktuelle Verbreitung: Im Decision-Model-Ranking von OpenRouter (Stand 13. Oktober 2026) steht Jev 1.13 bei 1,06 Milliarden Requests, Microsoft-Decision-1 bei 13,8 Millionen. Jev bekommt also mehr als das 75-Fache an Traffic.

Der Versuchsaufbau: ein Dino-Spiel, das jede Antwort bewertet

Das Spiel ist ein Klon von Chromes Offline-Dino auf Basis von MonoGame 3.8.6. Von Decision-Modellen weiß es nichts. Es stellt lediglich ein Controller-Interface bereit, und ein separates Player-Projekt fragt das Modell über das provider-neutrale IDecisionClient, was als Nächstes zu tun ist (mehr zu dieser API weiter unten). Ob Foundry oder OpenRouter, Microsoft oder Jev, entscheidet ein Kommandozeilen-Flag. Der Code liegt auf GitHub: swimmesberger/raptor-rules.

Damit die Zahlen unten belastbar sind, stecken drei Mechanismen im Aufbau:

  • Ein Orakel bewertet jede Antwort. Für jedes Hindernis simuliert das Spiel sämtliche Optionen auf einer Kopie der Spielwelt und speichert, mit welchen der Dino überlebt und wie viele Punkte sie einbringen. Diese Auswertung bekommt das Modell nie zu sehen.
  • Baselines mit festen Regeln laufen gegen exakt dieselben Hindernisse, sodass ein Modell zuerst eine einfache if-Abfrage schlagen muss.
  • Der Benchmark läuft headless und deterministisch. Feste Seeds liefern jedem Modell dieselbe Hindernisfolge, und die simulierte Welt hält an, bis die Antwort da ist, sodass Netzwerklatenz das Ergebnis nicht verfälscht.

Akt 1: Wenn eine feste Regel 100 % schafft, brauchst du kein Modell

Die erste Version stellte pro Kaktus eine choice-Frage: „Wann soll der Raptor springen?“, mit acht möglichen Zeitpunkten von 0,75 s bis 0,05 s vor dem Aufprall. Im State stand bereits vorberechnet, in welchem Zeitfenster der Raptor hoch genug in der Luft wäre und wann der Kaktus unter ihm durchläuft.

Microsoft-Decision-1 wählte in einem 60-Sekunden-Lauf 44 von 44 Mal einen gültigen Zeitpunkt und überlebte komplett. Dann baute ich die Baseline ein: „Immer 0,25 s vor dem Kaktus springen“ funktionierte ebenfalls jedes einzelne Mal. Das Modell hatte lediglich zwei Zahlenbereiche verglichen, die mein eigener Code ohnehin schon ausgerechnet hatte, und dafür 300 ms Latenz plus einen Netzwerkaufruf verbraten. Eine einfache if-Abfrage erledigt das kostenlos.

Auch als ich nur die Physikwerte (Schwerkraft, Absprunggeschwindigkeit) mitschickte, traf das Modell 32 von 32 Mal; mit den sichtbaren Werten (Abstand in Pixeln, Sprunghöhe, Flugzeit) waren es 28 von 29. Die starre Regel schlug trotzdem keine der Varianten. Das Spiel war schlicht zu einfach, um Urteilsvermögen zu erfordern, und die Lösung stand schon im Prompt.

Das ist der erste Check für jeden Anwendungsfall eines Decision-Modells: Miss zuerst die simpelste Regel, die funktionieren könnte. Schafft sie 100 %, nimm die Regel. Wenn deine Eingabe die berechnete Antwort schon enthält, misst du die Genauigkeit deines Codes, nicht die des Modells.

Akt 2: Rohe Zahlen rein, Münzwurf raus

Der naheliegende Einwand folgte sofort: Wozu überhaupt ein Decision-Modell, wenn mein Code die Antwort vorab ausrechnet? Also strich ich alle vorberechneten Werte. Das Modell bekam nur noch Positionen und Geschwindigkeit sowie bei jeder Abfrage die Ja/Nein-Frage: „Jetzt springen?“. Gemessen mit angehaltener Spielwelt pro Anfrage, 90 Sekunden Spielzeit je Zeile:

State Modell Deckung mit Orakel p(Ja): richtig / falsch Tode
roh Jev 40 % 0,37 / 0,33 9
roh Microsoft 39 % 0,29 / 0,29 9
Timings Jev 98 % 0,78 / 0,17 2
Timings Microsoft 99 % 0,85 / 0,10 1

„Roh“ steht für Positionen und Geschwindigkeit, „Timings“ für die vorberechnete Variante aus Akt 1. Die vierte Spalte zeigt die durchschnittliche Wahrscheinlichkeit des Modells für „jetzt springen“, getrennt nach Momenten, in denen ein Sprung über den Kaktus reicht, und solchen, in denen er zu früh oder zu spät kommt.

Mit rohen Positionsdaten lieferten beide Modelle praktisch dieselbe Wahrscheinlichkeit, egal ob Springen richtig oder falsch war. Sie konnten die Fälle nicht auseinanderhalten. Mit vorberechneten Timings lagen sie wieder fast bei 100 %, womit wir zurück in Akt 1 wären.

Ein schwierigeres Spiel änderte daran nichts. Ich baute Vögel auf drei Flughöhen und vier Aktionen ein (laufen, ducken, kurzer Sprung, voller Sprung), beschrieb jedes Hindernis in Pixelhöhen und ließ das Modell zusätzlich den Absprungabstand wählen. Beide Modelle starben in allen 5 Läufen. Bei einem Vogelschwarm verteilte Microsoft seine Wahrscheinlichkeiten fast gleichmäßig auf die vier Aktionen: 0,25, 0,22, 0,20, 0,33. Die Hersteller schreiben das auch selbst: TypeSafes Notizen zu jev-1.13 bringen es auf die Formel „Jev is not a calculator“, und Microsofts Dokumentation empfiehlt „code-enriched state“. Zahlenwerte zu vergleichen ist Aufgabe des Codes. Ein Decision-Modell braucht das Ergebnis dieses Vergleichs als Begriff oder Kategorie.

Akt 3: Die Situation beschreiben, die Regeln wechseln lassen

Die ersten beiden Akte waren ein guter Stresstest, aber ein schlechter Showcase. Der Ausweg ist eine andere Arbeitsteilung: Das Modell wählt den Zug, und ein lokaler Controller übersetzt ihn Bild für Bild in Tastendrücke. Schlägt ein Aufruf fehl oder ist die Antwort zu unsicher, übernimmt ein klar gekennzeichneter Fallback. Nach genau diesem Prinzip habe ich das Spiel umgebaut und Raptor Rules genannt. Da Spiel und Prompts auf Englisch laufen, stehen die Zitate unten im Original:

  • Das Modell wählt die Aktion: laufen, ducken, kurzer oder voller Sprung. Jede Aktion ist wie in einem Spielhandbuch erklärt: „Duck: crouch low; anything flying at head height or higher passes over you“.
  • Das Hindernis wird in Worten beschrieben, wie von einem Kommentator: „a tall swarm of birds flying at head height“, „two red large cacti“.
  • Etwa alle 20 Sekunden ändern Schilder am Wegesrand die Spielregeln in Alltagssprache: „Red cacti are made of paper: run straight through them for +50 points.“ oder „Golden eggs float above some cacti: catch one with a full jump for +100 points.“ Das jeweils aktive Schild wandert in den State.
  • Den genauen Absprungzeitpunkt bestimmt der Code. Die Bewegungslogik berechnet den Absprungpunkt anhand der Spielphysik allein so, dass der Dino sicher über das Hindernis kommt. Ob sich das Risiko eines vollen Sprungs für ein goldenes Ei lohnt, entscheidet das Modell.

Das hier ist der echte Request zu der Szene im Video weiter unten, mitgeschnitten in einem Lauf mit Seed 18:

POST https://openrouter.ai/api/alpha/decisions
{
"model": "microsoft/microsoft-decision-1",
"state": {
"obstacle": "a single green large cactus with birds circling high above it",
"speed": "medium",
"active_rule": "Golden eggs float above some cacti: catch one with a full jump for +100 points. (for another 7 s)"
},
"questions": {
"action": {
"type": "choice",
"instructions": "The raptor runs to the right towards the obstacle. Choose the move that gets it past without touching anything. If a sign is active, follow it to score bonus points.",
"criteria": {
"run": "Keep running upright; anything flying above your head passes over you",
"duck": "Duck: crouch low; anything flying at head height or higher passes over you",
"short_jump": "Short jump: a low hop over a single cactus or a low flock; stays below birds circling high above",
"full_jump": "Full jump: a high leap over wide groups of cacti; reaches things floating high above a cactus"
}
}
}
}

Der hervorgehobene State ist alles, was das Modell sieht: keine Pixelwerte, keine Timings, keine Historie. Microsoft bewertete short_jump mit 0,93 und full_jump mit 0,05. Das Eier-Schild ist zwar aktiv, aber über diesem Kaktus kreisen Vögel und kein Ei. Der Aufruf kostete 0,000008 $.

Hier ist der Benchmark über 5 Läufe à maximal 60 Sekunden (Seeds 42 bis 46), beide Modelle angesprochen über OpenRouter:

Microsoft Jev Orakel
Läufe überlebt 5/5 2/5 5/5
Ø Punkte 1.932 1.282 ~1.950
Optimale Aktionen 99 % 98 % 100 %
Schild-Bonus +5.850 +3.850 +5.900
Kosten, 5 Läufe 0,0015 $ 0,0029 $
Latenz (Median) 333 ms 289 ms

Microsoft traf 197 Entscheidungen und nahm 5.850 der 5.900 möglichen Bonuspunkte mit („Orakel“ ist ein Autopilot, der direkt die interne Spielsimulation nutzt). Feste Regeln im Code reichen zwar, um die Hindernisse zu überleben, brauchen aber für jedes neue Schild eine Codeänderung; das Modell nimmt die +5.850 Bonuspunkte allein über den neuen Regeltext mit.

Unten kannst du es selbst spielen. Es ist derselbe C#-Code, nach WebAssembly kompiliert: die Simulation, die Schilder und der Renderer, mit denen die Modelle bewertet wurden, nur ohne Modell. Es startet mit dem Lauf aus dem Video weiter unten (Seed 18, 20 Sekunden), du bekommst also dieselben Hindernisse und Schilder wie die Modelle. Jev kam auf 565 Punkte, bevor es gescheitert ist, Microsoft-Decision-1 auf 706, mehr ist mit diesem Seed nicht möglich.

raptor rules · the C# game, in WebAssembly

The run from the video: seed 18, 20 seconds, the same obstacles and signs the models got. Jev scored 565 before it crashed, Microsoft-Decision-1 706, a perfect run.

space or ↑ full jump · z short jump · ↓ duck · p pause

Microsoft gegen Jev: ein Adjektiv, fünffache Punktzahl

Vor dem obigen Benchmark überlebte Jev keinen einzigen Lauf. Jeder Tod lief nach demselben Muster ab: Solange „red cacti are made of paper“ aktiv war, rannte Jev auch in normale Kakteen. Ein gezielter Test mit drei Aufrufen pro Modell und Fall zeigte die Ursache:

Situation Richtige Aktion Microsoft Jev
„a single small cactus“, kein Schild springen springen (0,99) springen (1,00)
„a single small cactus“, Papier-Schild aktiv springen kurzer Sprung (0,94 bis 0,97) laufen (0,69 bis 0,75)
ein roter Kaktus, Papier-Schild aktiv laufen laufen (0,58 bis 0,69) laufen (0,88 bis 0,90)
ein Kaktus, beschrieben als „green (not red)“, Papier-Schild aktiv springen springen (0,99 bis 1,00) springen (0,91 bis 0,94)

Das Schild gilt nur für rote Kakteen. Dass ein normaler Kaktus nicht rot ist, ging im State aber nur daraus hervor, dass das Wort „red“ fehlte. Microsoft interpretiert das fehlende „red“ als „nicht rot“. Jev sieht „Kaktus plus ein Schild, das Durchlaufen verlangt“ und ignoriert die Bedingung. TypeSafe dokumentiert genau dieses Verhalten: Jev „answers the question you wrote, not the one you meant.“

Also ergänzte ich ein einziges Wort. Da normale Kakteen im Spiel grün gezeichnet sind, heißt die Beschreibung nun „a single small green cactus“. Ein menschlicher Spieler sieht die Farbe schließlich auch, die Beschreibung wurde also nur vollständiger. Jevs Punkteschnitt sprang damit von 278 auf 1.282. Bei Microsoft änderte sich nichts.

Auch der verbleibende Tod, dem ich nachgegangen bin, folgte demselben Muster unter dem anderen Schild: Während „catch golden eggs with a full jump“ aktiv war, machte Jev über einen Kaktus mit hoch kreisenden Vögeln einen vollen Sprung, mitten in den Schwarm hinein. Darauf hatte es unsere eigene Optionsbeschreibung gebracht: „reaches things floating high above a cactus“ passt sprachlich auf ein Ei genauso wie auf Vögel. Microsoft löste die Mehrdeutigkeit über den Kontext auf, Jev nicht.

Gleicher Seed, gleiche Hindernisse, jede Entscheidung live. Oben: Microsoft-Decision-1 wählt den kurzen Sprung (0,91). Unten: Jev wählt den vollen Sprung (0,58) und landet bei Sekunde 16 in den Vögeln.

Das Video wird Einzelbild für Einzelbild aus der Simulation gerendert; dadurch ruckelt trotz API-Latenz nichts, und beide Modelle sehen identische Hindernisse. Es zeigt bewusst einen Fehlerfall: Für den 20-Sekunden-Clip habe ich 30 Seeds getestet. Microsoft überlebte alle 30, Jev scheiterte in 11 Fällen. In den übrigen 19 kamen beide durch.

Als Pauschalurteil gegen Jev taugt das trotzdem aus zwei Gründen nicht: Bei klassischer Text-Triage liegen beide Modelle gleichauf, wie das .NET-Beispiel weiter unten zeigt. Außerdem habe ich die Prompts zuerst gegen Microsoft entwickelt; die fehlende Farbangabe fiel mir erst auf, als ich Jevs Fehltritte gezielt untersuchte. Weder die hohen Traffic-Zahlen auf OpenRouter noch ein auf ein einzelnes Modell zugeschnittener Prompt beantworten, welches Modell für deine Aufgabe besser passt. Ob ein Prompt fair formuliert ist, muss man für jedes Modell einzeln prüfen.

Acht Prompt-Varianten, zwei Modelle

Um zu testen, wie stark einzelne Formulierungen ins Gewicht fallen, ließ ich denselben Benchmark mit jeweils einer gezielten Änderung laufen. Jede Zelle zeigt die überlebten Läufe von 5 sowie den Punkteschnitt:

Variante Was sich ändert Microsoft Jev
baseline Textbeschreibung, Farben benannt, Aktionen erklärt, Code bestimmt den Absprung 5/5 · 1.932 2/5 · 1.282
no-colors „a small cactus“ statt „a small green cactus“ 5/5 · 1.932 0/5 · 278
bare-moves nur „Duck“ statt der einzeiligen Erklärung zum Ducken 1/5 · 604 0/5 · 216
numbers Pixelgrößen und -höhen statt Worten 0/5 · 329 0/5 · 67
model-timing das Modell wählt zusätzlich den Absprungabstand 2/5 · 791 0/5 · 101
reversed-options voller Sprung zuerst, laufen zuletzt 5/5 · 1.922 4/5 · 1.748
shuffled-options Reihenfolge der Optionen pro Hindernis zufällig gemischt 5/5 · 1.922 2/5 · 1.282
none-option Zusatzoption „None of these moves fits the situation“ 5/5 · 1.942 3/5 · 1.481

Microsoft-Decision-1 ließ sich von Farbadjektiven, Optionsreihenfolge oder der Zusatzoption kaum beeindrucken. Eingebrochen ist es erst, als im Prompt wichtige Informationen fehlten oder die Aufgabe zu einer Rechenübung wurde: bloße Labels ohne Beschreibung, rohe Pixelwerte, Timing-Wahl. Jev hingegen reagierte auf fast jede Umformulierung spürbar.

Die umgedrehte Reihenfolge der Optionen verhinderte zwar zwei von Jevs drei Toden; wurden die Optionen jedoch pro Hindernis zufällig gemischt, traf Jev exakt dieselben Entscheidungen wie in der Standardreihenfolge. Es lag also nicht an der dokumentierten Vorliebe für die erstgenannte Option, sondern das Umdrehen kippte schlicht ein paar knappe Fälle. Im Abschnitt zu den Fallstricken weiter unten gehe ich auf die Varianten ein, die gekracht haben.

Wo sich Decision-Modelle lohnen: geschlossene Fragen auf unstrukturiertem Freitext

Lässt man die Kakteen weg, kommt einem das Muster schnell bekannt vor. In den Bereichen, in denen ich meistens arbeite (Shopfloor, Produktion, Intralogistik), lesen Mitarbeitende den ganzen Tag Freitext und ordnen ihn einer festen Menge von Optionen zu. Diese Anwendungsfälle würde ich als Erstes damit angehen:

Prozess Frage(n) an das Modell Bleibt im Code
Störmeldungen von Bedienern an Maschinen choice: welches Instandhaltungsteam (Mechanik, Elektrik, Steuerung); Ja/Nein: „Steht die Linie?“; score: Dringlichkeit Ticket-Routing, SLA-Timer, Ersatzteilsuche
Schichtübergabe-Notizen Ja/Nein: „Ist noch etwas offen?“; choice: betroffene Linie oder Station wer benachrichtigt wird, Eskalations-Timer
Ausnahmen in der Intralogistik (beschädigte Paletten, falsche Etiketten, blockierte Fördertechnik) aus Kommentaren von Bedienern und Fahrern choice: Ursache; Ja/Nein: „Muss die Ladung manuell bearbeitet werden?“ Umlagerung, Lagerplatz, Bestandsbuchungen
Mails von Spediteuren und Lieferanten Ja/Nein: „Kündigt die Mail eine Verzögerung an?“; choice: Ursache neues Zeitfenster, Rampenplanung
Prüfnotizen aus der Qualitätssicherung choice: Fehlerklasse; score: Schweregrad Toleranzgrenzen und Messwerte
Engineering Change Requests choice: betroffener Bereich; Ja/Nein: „Ist die Änderung sicherheitsrelevant?“ Freigabe-Workflow
Antworten eines generativen Modells Ja/Nein: „Verspricht diese Antwort eine Rückerstattung?“ Reaktion, wenn die Prüfung anschlägt

Auch die Schilder aus dem Spiel haben ein direktes Gegenstück im Unternehmensalltag: Richtlinien, die sich kurzfristig ändern. Ein Hinweis wie „Bis Freitag läuft auf Linie 3 das neue Produkt: Etikettenprobleme von Linie 3 gehen an die Qualität, nicht an die Logistik“ wandert einfach als Text in den State, genau wie active_rule im Spiel, und greift ab dem nächsten Request ohne neues Deployment. Die Lektion mit den Papier-Kakteen gilt hier genauso: Wenn eine Richtlinie „nur für Paletten von externen Lieferanten“ gilt, muss im State stehen, woher jede Palette kommt, auch bei den internen.

Zwei Arten von Entscheidungen haben im Modell nichts verloren: Alles, was sich über eine if-Abfrage oder eine Formel klären lässt, bleibt im Code (Akt 1). Und laut Microsofts eigener Dokumentation dürfen die Scores niemals als alleinige Grundlage für weitreichende Entscheidungen über Menschen dienen, etwa bei Kreditvergabe, Bewerbungen oder rechtlichen Fragen. Dort liefert das Modell höchstens eine Vorselektion für einen Menschen, der entscheidet. Bei Sicherheit gilt dasselbe: Eine Meldung, die eine Gefahr für Personen bedeuten könnte, geht direkt an einen Menschen, und keine Wahrscheinlichkeit entscheidet darüber.

In .NET: IDecisionClient vor dem offiziellen Release nutzen

Microsoft.Extensions.AI bekommt eine eigene Abstraktion für Decision-Modelle. dotnet/extensions#7795 führt IDecisionClient als Gegenstück zu IChatClient ein, inklusive der Fragetypen BinaryDecisionQuestion, ChoiceDecisionQuestion und ScoreDecisionQuestion, vollständigen Wahrscheinlichkeitsverteilungen in der Antwort sowie einer typisierten Schicht darüber. Noch ist die API experimentell (Diagnose-ID MEAI001) und der PR offen; die Design-Diskussion läuft in Issue #7764, während #7796 eine zweite Schicht für Tool-Auswahl und Routing ergänzt.

Ich wollte weder auf das fertige NuGet-Paket warten noch ein eigenes Wegwerf-Interface erfinden. Deshalb kopiert das Testprojekt die vorgeschlagenen Typen unverändert (gleicher Namespace, gleiche Methodennamen), und ein schlanker Adapter übersetzt sie auf das Wire-Format von Foundry und OpenRouter. So sieht die Störungstriage aus der Tabelle oben als C#-Code aus. Die Ergebnistypen sind einfache Records und ein Enum; aus den [Description]-Attributen baut der Client automatisch die Fragetexte und Optionsbeschreibungen:

Triage.cs
using System.ComponentModel;
public sealed record FaultReport(string Text);
public enum Team {
[Description("Bearings, belts, gearboxes, jams: anything that moves or wears")]
Mechanical,
[Description("Motors, drives, sensors, cabling and power supply")]
Electrical,
[Description("PLC programs, HMI screens, network and software errors")]
Controls,
}
public sealed record Triage(
[property: Description("Which maintenance team should handle this report?")] Team Team,
[property: Description("Is the line or machine stopped because of this?")] double LineStopped,
[property: Description("How quickly does someone need to act?")] double Urgency);

Die Definition legt fest, welche Property über welchen Fragetyp befüllt wird. Ein einziger Aufruf von GetResponseAsync bündelt alle drei Fragen in einem Request und schreibt die Ergebnisse zurück in eine Triage-Instanz:

Program.cs
#pragma warning disable MEAI001 // experimentell, siehe dotnet/extensions#7795
using Decision.Sdk;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
// Die einzige providerspezifische Zeile: Foundry oder OpenRouter, gewählt über die Konfiguration.
using IDecisionClient client = new SystemOneClient(SystemOneClientOptions.FromConfiguration(configuration))
.AsIDecisionClient();
var triage = DecisionDefinition<Triage>.Create(b => {
b.Choice(t => t.Team);
b.BinaryProbability(t => t.LineStopped);
b.Score(t => t.Urgency, [
new("routine", "Can wait for the next planned maintenance window"),
new("shift", "Should be handled during this shift"),
new("now", "Needs someone at the machine right now"),
]);
});
var report = new FaultReport(
"Conveyor 7 stopped with a motor overload fault. It trips again a few seconds after every reset.");
var response = await client.GetResponseAsync(report, triage);
Triage result = response.Result;
Console.WriteLine($"{result.Team} stopped {result.LineStopped:0.00} urgency {result.Urgency:0.00} ({response.Evidence.Provenance?.ModelId})");

Jede Property von Triage wird zu einer Frage, benannt nach der Property: Team zu einer choice mit den Enum-Werten als Optionen, LineStopped zu einer Ja/Nein-Frage (noul), Urgency zu einem score mit den drei Stufen in ihrer Reihenfolge. So sieht der Request für die erste Meldung aus, mitgeschnitten auf dem Netzwerk:

Was der Client schickt (gekürzt)
{
"state": { "text": "Conveyor 7 stopped with a motor overload fault. It trips again a few seconds after every reset." },
"questions": {
"team": {
"type": "choice",
"instructions": "Which maintenance team should handle this report?",
"criteria": {
"Mechanical": "Bearings, belts, gearboxes, jams: anything that moves or wears",
"Electrical": "Motors, drives, sensors, cabling and power supply",
"Controls": "PLC programs, HMI screens, network and software errors"
}
},
"lineStopped": {
"type": "noul",
"instructions": "Is the line or machine stopped because of this?"
},
"urgency": {
"type": "score",
"instructions": "How quickly does someone need to act?",
"criteria": [
"Can wait for the next planned maintenance window",
"Should be handled during this shift",
"Needs someone at the machine right now"
]
}
}
}
Was Microsoft-Decision-1 antwortet (Zahlen gerundet)
{
"answers": {
"team": { "type": "choice", "choice": "Electrical", "probabilities": { "Mechanical": 0.018, "Electrical": 0.976, "Controls": 0.007 } },
"lineStopped": { "type": "noul", "noul": 0.999 },
"urgency": { "type": "score", "score": 1.62, "probabilities": { "0": 0.003, "1": 0.377, "2": 0.621 } }
}
}

GetResponseAsync schreibt die Antworten zurück in ein Triage. Team ist die Option mit der höchsten Wahrscheinlichkeit, LineStopped die Wahrscheinlichkeit für Ja und Urgency der Score: der mit den Wahrscheinlichkeiten gewichtete Mittelwert der Stufenpositionen 0 (routine), 1 (shift) und 2 (now), hier 0,377 × 1 + 0,621 × 2 ≈ 1,62. response.Evidence enthält die vollständigen Verteilungen.

Alle drei Meldungen habe ich an beide Modelle geschickt. Eine Zeile pro Modell, eine Spalte pro Property (Team mit seiner Wahrscheinlichkeit, LineStopped, Urgency von 0 bis 2):

Meldung Modell Team Steht Dringlichkeit
Förderer 7, Überlast Microsoft Electrical (0,98) 1,00 1,62
Jev Electrical (0,95) 0,96 1,75
Lager an Linie 2 Microsoft Mechanical (1,00) 0,00 0,01
Jev Mechanical (1,00) 0,03 0,30
HMI-Fehler 1042 Microsoft Controls (1,00) 0,27 0,92
Jev Controls (1,00) 0,49 1,02

Beide Modelle weisen jede Meldung demselben Team zu: die Überlast mit 0,98 (Microsoft) und 0,95 (Jev) der Elektrik, die anderen beiden mit 1,00. Einig sind sie sich auch, dass Förderer 7 steht und Linie 2 nicht. Beim HMI, das die Station nur für einen Neustart anhält, bleiben beide in der Mitte (0,27 und 0,49), und genau das sollte eine mehrdeutige Meldung bekommen. Auch die Reihenfolge der Dringlichkeit stimmt überein, nur die Zahlen nicht: Microsoft bewertet das quietschende Lager mit 0,01, Jev mit 0,30. Ohne gelabelte Echtdaten lässt sich nicht entscheiden, welche Kalibrierung zum eigenen Instandhaltungsteam passt. Genau deshalb rät Microsofts Dokumentation dazu, Scores für Sortierungen und vorab validierte Schwellenwerte zu nutzen, nicht als absolute Noten.

Über response.Evidence kommt man an die vollständigen Wahrscheinlichkeitsverteilungen und die Provenance samt genauem Modell-Snapshot. Sobald das offizielle Paket auf NuGet landet, beschränkt sich der Umstieg darauf, das kopierte Projekt zu löschen und Microsoft.Extensions.AI.Abstractions zu aktualisieren. Sollte sich im API-Review noch ein Typname ändern, zeigt der Compiler sofort alle Stellen.

Fallstricke aus der Praxis

Eine Bedingung, die sich in einem fehlenden Wort versteckt

Variante: no-colors. Das Schild galt für „red cacti“, während normale Kakteen einfach ohne Farbangabe beschrieben wurden. Jev wendete das Schild prompt auf jeden Kaktus an und starb in allen fünf Läufen. Die Lösung: das relevante Merkmal immer explizit positiv benennen. Im Geschäftsprozess entspricht das einer Sonderregel für „EU-Kunden“, die auf ein Ticket trifft, in dessen State das Land schlicht fehlt.

Options-Labels ohne Beschreibung

Variante: bare-moves. Mit dem bloßen Label „Duck“ statt „Duck: crouch low; anything flying at head height or higher passes over you“ fiel Microsoft von 5 überlebten Läufen auf einen einzigen zurück. Erst aus dem Beschreibungstext lernt das Modell, was eine Option bewirkt. Die Kehrseite zeigte sich bei Jev: Passt eine Beschreibung auf zwei verschiedene Dinge („reaches things floating high above a cactus“), wendet das Modell sie auch auf beide an. Beschreib daher klar, was eine Option tut und wovor sie nicht schützt.

Zahlen, wo Worte hingehören

Variante: numbers. Mit Pixelhöhen statt Textbeschreibungen starben beide Modelle in jedem einzelnen Lauf. Vergleiche, Abstände und Fristen rechnest du im Code aus und übergibst nur das fertig eingeordnete Ergebnis: „a tall swarm at head height“ oder „seit mehr als 30 Tagen überfällig“.

Das Modell das Timing bestimmen lassen

Variante: model-timing. Als Microsoft zusätzlich den Absprungabstand wählen musste, entschied es sich zwar weiterhin in 100 % der Fälle für die richtige Aktion, überlebte aber nur noch 2 von 5 Läufen. Die Aktion zu wählen ist das Was, der genaue Zeitpunkt ist das Wie. Auf einen Geschäftsprozess übertragen: Das Modell entscheidet „eskalieren“, die konkrete Frist berechnet der Code.

Sich blind auf „none of the above“ verlassen

Variante: none-option. Beide Hersteller empfehlen eine Ausweichoption („keine davon passt“). In rund 400 Entscheidungen hat keines der beiden Modelle sie auch nur ein einziges Mal gezogen. In diesem Spiel gab es allerdings immer eine passende Aktion; falsch war der Verzicht also nie, und geschadet hat die Zusatzoption auch nicht. Bevor du dich in Produktion darauf verlässt, teste sie gezielt mit Eingaben, auf die wirklich keine Option passt, und nutz weiterhin primär den Wahrscheinlichkeits-Schwellenwert, um unsichere Fälle auszusortieren.

98 % pro Entscheidung sind nicht 98 % pro Durchlauf

Microsoft wählte in 99 % der Fälle die optimale Aktion, Jev in 98 %. Das klingt nach einem vernachlässigbaren Unterschied. Bei rund 40 Hindernissen pro Minute bedeuten 2 % Fehlerquote allerdings 0,9840 ≈ 45 % Überlebenschance für eine volle Minute, und genau so kam Jev nur in 2 von 5 Läufen durch. Jede Pipeline, die mehrere automatisierte Entscheidungen hintereinanderschaltet, multipliziert ihre Fehlerraten auf dieselbe Weise.

In einer Echtzeitschleife synchron auf das Modell warten

Die frühe Ja/Nein-Variante fragte 10- bis 20-mal pro Kaktus. Unser Foundry-Test-Deployment erlaubte 60 Requests pro Minute, also gab es 429-Fehler, aber dieses Limit kommt von der Kapazität, die für das Deployment eingestellt ist, nicht vom Modell. Das eigentliche Problem bleibt bei jedem Kontingent: Eine Antwort braucht rund 300 ms, über OpenRouter gelegentlich mehr als 2 Sekunden, und so lange kann eine Spielschleife nicht warten. Bewährt hat sich stattdessen: den Request abschicken, sobald das Hindernis ins Bild kommt, die Spielschleife niemals blockieren und bei einer verspäteten Antwort auf einen lokalen Fallback zurückfallen (im HUD sichtbar als FALLBACK markiert und in der Statistik separat gezählt). Jeder produktive Prozess rund um ein Decision-Modell braucht genau so einen definierten Standardpfad für Timeouts und Fehlantworten.

Was diese Zahlen nicht hergeben

  • Die Stichprobe ist klein: fünf Läufe pro Variante, jeweils mit denselben festen Seeds, wodurch die Ergebnisse untereinander korreliert sind. Klare Abstände (5/5 gegen 0/5) sind belastbar, ein einzelner Lauf Unterschied ist Rauschen.
  • Der Baseline-Prompt bevorzugt Microsoft, weil er zuerst gegen Microsoft entwickelt wurde. Die fehlende Farbangabe fiel erst auf, als ich Jevs Fehler gezielt untersuchte.
  • Modellversionen entwickeln sich weiter. Alle Messwerte gelten für die Snapshots microsoft-decision-1-20261009 und jev-1.13-20260917, gemessen am 13. Oktober 2026.
  • Ein Spiel ist keine Ticket-Queue. Übertragbar sind die Fehlermuster: implizite Bedingungen, mehrdeutige Optionsbeschreibungen, Rechnen im Modell, Timing. Die nackten Punktzahlen selbst lassen sich nicht übertragen.

Welches Werkzeug für welche Entscheidung

Situation Werkzeug
Die Regel passt in eine if-Abfrage oder eine Formel Code. Ein Modell bringt hier nur Latenz, Kosten und eine zusätzliche Fehlerquelle.
Beträge, Datumswerte oder Größen vergleichen Code; das Ergebnis geht als Begriff oder Kategorie an das Modell
Aus Freitext eine von mehreren festen Optionen wählen, inklusive Konfidenz Ein Decision-Modell
Regeln in Textform, die sich laufend ändern (Richtlinien, Hinweise, Schilder) Ein Decision-Modell, mit dem Regeltext im State und explizit benannten Bedingungen
Texte formulieren, zusammenfassen oder erklären Ein generatives Sprachmodell
Entscheidungen über Menschen Entscheidungsunterstützung mit menschlicher Freigabe, niemals vollautomatisch

Schreib vor jedem Einsatz eines Decision-Modells zuerst die if-Abfrage, die es schlagen muss. Gewinnt die if-Abfrage, lass sie stehen. Scheitert sie daran, dass die Eingabe aus natürlicher Sprache besteht und sich auch die Regeln in Sprache ändern, hast du den passenden Anwendungsfall für ein Decision-Modell: Frag es, was zu tun ist, und lass Wie, Wann und Wie viel im Code.

Update vom 15. Oktober 2026: Laya, ein lokales Decision-Modell

Als drittes Modell ist Laya dazugekommen, ein kleines Decision-Modell mit offenen Gewichten. Technisch ist es ein Encoder (ModernBERT-large) mit Entscheidungsköpfen obendrauf, veröffentlicht unter Apache-2.0. Die Runtime laya-mlx führt es lokal auf Apple Silicon aus. Ich habe den Checkpoint für typisierte Fragen verwendet, aac6fef/laya-typed-decisions-mlx, und davor einen kleinen lokalen Server gesetzt, der dasselbe Request-Format spricht wie Foundry und OpenRouter. Spiel, Benchmark und Video-Export liefen dadurch unverändert, nur der Provider-Schalter ist ein anderer. Auf einem M4 Pro dauert eine Entscheidung rund 30 ms und kostet nichts.

In diesem Spiel verliert Laya in jeder Konfiguration. Über alle acht Prompt-Varianten hinweg überlebte es 0 von 5 Läufen, im Schnitt mit 29 bis 55 Punkten, also stets schon an den ersten Hindernissen. Warum, zeigen die acht typischen Situationen des Spiels als einzelne Requests:

Situation Richtiger Zug Microsoft Jev Laya
ein einzelner kleiner Kaktus springen kurzer Sprung (1,00) kurzer Sprung (0,98) kurzer Sprung (0,39)
drei große Kakteen als breite Gruppe voller Sprung voller Sprung (0,98) voller Sprung (0,96) voller Sprung (0,40)
ein tief fliegender Schwarm knapp über dem Boden springen kurzer Sprung (0,98) kurzer Sprung (0,97) kurzer Sprung (0,38)
ein hoher Schwarm auf Kopfhöhe ducken ducken (0,98) ducken (0,97) voller Sprung (0,29)
ein hoher Schwarm knapp über dem Kopf laufen oder ducken laufen (0,58) ducken (0,58) kurzer Sprung (0,31)
ein großer Kaktus mit kreisenden Vögeln darüber kurzer Sprung kurzer Sprung (0,99) kurzer Sprung (0,93) kurzer Sprung (0,38)
Papier-Schild aktiv, zwei rote Kakteen laufen laufen (0,58) laufen (0,91) laufen (0,33)
Eier-Schild aktiv, ein Kaktus mit goldenem Ei voller Sprung voller Sprung (0,97) voller Sprung (0,80) voller Sprung (0,60)

Kakteen und beide Schilder löst Laya richtig, in beide Schwärme springt es hinein. Aussagekräftiger als die Fehler sind aber die Werte in Klammern: Bei vier Optionen verteilt ein Modell, das nur rät, 0,25 auf jede, und Laya liegt bei seiner Wahl meist zwischen 0,29 und 0,40. Microsoft und Jev beantworten alle acht Fälle richtig, meist mit über 0,9. Schwärme tauchen in den meisten Läufen früh auf, deshalb kommt Laya selten über die ersten Sekunden hinaus.

Warum Laya genau an den Schwärmen scheitert

Vergleicht man die Situationen mit den Optionstexten aus Akt 3, sieht man schnell, warum Laya ausgerechnet in die Schwärme springt. In allen Fällen, die Laya richtig löst, tauchen dieselben Begriffe sowohl in der Situation als auch in der passenden Option auf: „a single small cactus“ trifft auf „a low hop over a single cactus“, „a low flock“ auf „a low flock“, „birds circling above“ auf „stays below birds circling high above“ und das „run straight through them“ des Papier-Schilds auf „keep running“.

In der Option zum Ducken kommen Schwärme oder Vögel dagegen gar nicht vor; dort steht nur die Wirkung: „crouch low; anything flying at head height or higher passes over you“. Um sie zu wählen, muss das Modell zwei Dinge zusammenbringen: Die Vögel fliegen auf Kopfhöhe, und wer sich duckt, bleibt darunter. Weil die Sprung-Optionen explizit von Schwärmen und Vögeln sprechen, landet Laya jedes Mal beim Sprung.

Das passt zum Größenunterschied der Modelle. Laya basiert auf ModernBERT-large, einem vergleichsweise kleinen Encoder, der stark darin ist, sprachliche Nähe zwischen Situation und Optionsbeschreibung zu erkennen. Microsoft und Jev sind deutlich größere Modelle und bringen genug Weltwissen mit, um „auf Kopfhöhe“ mit „ducken“ zu verknüpfen. Als Faustregel aus diesen acht Testfällen:

Lässt sich die richtige Option schon am Wortlaut ablesen, hat ein kleines lokales Modell wie Laya gute Chancen. Muss man erst überlegen, welche Folge eine Situation hat, braucht es ein größeres Modell.

Beim Sortieren von E-Mails fällt das Zuordnen nach Thema („Meine Rechnung vom März ist nie angekommen“ → billing) in die erste Kategorie und kommt ohne Keyword-Regeln aus. Zu erkennen, dass eine höflich formulierte Mail eigentlich eine Kündigungsdrohung ist, entspricht dagegen dem Schwarm: Das Wort „kündigen“ kommt darin womöglich gar nicht vor.

Mit dem Seed aus dem Video oben fängt Laya vier goldene Eier (+400) und springt dann nach 13,6 Sekunden voll in den Schwarm auf Kopfhöhe, mit 0,63 für den vollen Sprung und 0,10 fürs Ducken:

Seed 18, dieselben Hindernisse und Schilder wie im Video mit Microsoft und Jev. Laya holt +400 und kracht nach 13,6 s in den Schwarm.

Mogelt Layas eigene T-Rex-Demo?

In seiner eigenen Dino-Demo, laya-vs-jev, schlägt sich Laya gut. Der Unterschied hat einen Grund: Dort simuliert ein deterministischer Planer jeden Zug und schreibt das Urteil direkt in die Optionen, etwa "jump: Safe. Clears the 2 large cacti. Best.". Ein Schutzschild ersetzt danach jede Wahl, die der Planer als unsicher markiert hat. Gemogelt ist das nicht, denn das README sagt ausdrücklich, dass die unterstützten Scores das Gesamtsystem messen, und ein Schalter (--unassisted) nimmt die Schilde heraus. Gemessen wird damit aber etwas anderes, nämlich der Aufbau aus Akt 1 dieses Posts: Der Code rechnet die Antwort aus, das Modell liest sie nur noch ab.

Selbst mit dieser Hilfe zeigen die Messungen des Projekts die Lücke: Laya wählte in 74 bis 75 % der Entscheidungen den besten Zug des Planers, Jev in 100 %. Im letzten unterstützten Match griffen die Schilde bei Laya 705-mal ein, bei Jev 18-mal. Die Autoren nennen das Match selbst „a show result, not evidence of better model skill“.

Was dieses Update nicht zeigen kann: Getestet habe ich einen einzigen Laya-Checkpoint, mit Prompts, die ich an den Cloud-Modellen entwickelt habe, und die Runtime warnt selbst, dass die Konfidenzwerte dieses Checkpoints teilweise nicht kalibriert sind. Die Grenze zwischen Wortlaut-Abgleich und Schlussfolgern oben stützt sich auf acht Testanfragen und ist damit erst einmal eine Hypothese. Lokal, gratis und in 30 ms zu entscheiden ist für das Sortieren großer Mengen nach Thema ein echter Vorteil. In diesem Spiel hilft das aber nicht weiter, solange Laya bei einem Schwarm auf Kopfhöhe nach oben springt statt sich zu ducken.