KI-Modelle
Die letzte Meile
Ich hab 5 KI-Modelle ein Next.js-Mahnwesen-Cockpit bauen lassen und jeden Build echt ausgeführt. Drei liefen. Drei nicht. Hier ist was falsch lief.

Teil 3a der Reihe „5 KI-Modelle, dieselbe Aufgabe". Stand: Juni 2026.
Auf einen Blick
- Das Mahnwesen-Cockpit: eine vollständige Next.js-App als Aufgabe, nicht nur eine Funktion.
- Drei von fünf Modellen: Build out-of-the-box grün. Zwei: erst nach manuellem Eingriff.
- Qwen3-max lieferte eine App mit leerer Tabelle ab Sekunde 1 — weil eine API-Route schlicht nicht existierte, obwohl das Modell sie als „implementiert" gelistet hatte.
- GLM-4.6 hat seinen eigenen Output nie gebaut, bevor es „fertig" meldete. Abhängigkeit: 0-Byte-Dist.
- Mistral: Tailwind-Versions-Mix + JavaScript-only-Redirect statt serverseitigem
redirect().- Architektur saß bei fast allen. Die letzte Meile nicht.
Warum eine App ein härterer Test ist als eine Funktion
In Teil 1 und Teil 2 haben die Modelle eine einzelne Funktion geschrieben. Eine Funktion ist übersichtlich — sie hat einen Input, einen Output, ein paar Randfälle.
Eine App ist anders. Da kommen Dinge zusammen, die einzeln trivial sind und zusammen tückisch: Frontend und Backend, die Server/Client-Grenze in Next.js, ein Datenfluss von der Eingabe bis zur Anzeige, Typen, die über mehrere Dateien konsistent sein müssen — und am Ende die einfachste Frage der Welt: Startet das Ding, wenn man npm install && npm run build tippt?
Genau das ist der Test, ob ein Modell „Code-Schnipsel" kann oder „Software".
Die Aufgabe
Mahnwesen-Cockpit: Next.js 14+ (App Router) + TypeScript (strict) + Tailwind. In-Memory-Datenspeicher. Rechnungen mit Mahnstufen 0–4, 14-Tage-Frist zwischen Stufen, Mahngebühren (Stufe 2: 5 €, 3: 10 €, 4: 40 €). Dashboard mit KPIs. Route Handler mit server-seitiger Validierung. Optimistic UI mit Rollback bei Server-Fehler. Geld immer als Integer in Cent, formatiert mit
Intl. Domänenlogik getrennt von UI. Unit-Tests. Komplette, lauffähige Dateistruktur.
Ich habe jede App wirklich gebaut und ausgeführt — nicht nur gelesen. npm install && npm run build && npm test war der Test, nicht der Anblick von Code. Das ist der Punkt, an dem Beschreibungen aufhören und Realität anfängt.
Das Ergebnis
| Modell | Build out-of-the-box | Tests | Note | Entscheidender Defekt |
|---|---|---|---|---|
| Claude Opus | ✅ | 21/21 | 8,5 | HTTP-Status per String-Matching; keine Tests für Service/Route |
| DeepSeek v4-pro | ✅ | 33/33 | 8,5 | Singleton-Snapshot beim Rollback (Race bei Parallel-Klicks) |
| DeepSeek v4-flash | ✅ | 39/39 | 8,0 | je nach Lauf: fehlendes letzteAktionAm-Feld + Rollback-Race |
| GLM-4.6 | ❌ (fehlende Dist-Dateien) | 30/30 nach Fix | 6,5 | hat seinen Output nie gebaut; lokale TZ in Datumslogik |
| Mistral | ❌ (Tailwind-Fehler, Redirect-JS) | — | ~6,5 | nicht out-of-box; Root / nur per JS-Redirect |
| Qwen3-max | ❌ (Typfehler) | 26/26 | 5,5 | Startseite ruft nicht-existierende API → leeres Dashboard |
Das Muster ist deutlich: Architektur saß bei fast allen — saubere Trennung von Domäne und UI, Geld in Cent, Validierung serverseitig. Der Unterschied lag in der letzten Meile.
Und die letzte Meile ist genau das, was man meistens nicht sieht, bevor man auf „Deploy" drückt.
Qwen3-max: die App ohne Daten
Qwens App sah fertig aus. Tabelle, Filter, KPI-Kacheln, Buttons mit Spinner. Der Code war strukturiert, die Domänen-Typen stimmten. Nur: die Tabelle blieb leer.
Das Dashboard lädt seine Rechnungen so:
// app/page.tsx (Qwen3-max)
useEffect(() => {
loadInvoices();
}, [filter]);
const loadInvoices = async () => {
setLoading(true);
const params = filter !== 'all' ? `?filter=${filter}` : '';
const response = await fetch(`/api/invoices${params}`); // GET-Liste
const data = await response.json();
setInvoices(data);
setLoading(false);
};
Das Problem: GET /api/invoices gibt es nicht. Im Projekt existiert ausschließlich app/api/invoices/[id]/route.ts — für einzelne Datensätze per PATCH:
// app/api/invoices/[id]/route.ts (Qwen3-max) — die EINZIGE Invoices-Route
export async function GET(request: NextRequest) {
// filtert store.getAll() — aber unter /api/invoices/[id], nicht /api/invoices
}
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) { /* ... */ }
Eine app/api/invoices/route.ts (die Listen-Route) ist schlicht nicht vorhanden. fetch("/api/invoices") gibt 404 zurück, response.json() liefert ein Fehlerobjekt statt eines Arrays, setInvoices bekommt keinen gültigen Wert — die Tabelle bleibt leer, alle KPIs zeigen 0, ab der ersten Sekunde.
Was das besonders prägnant macht: Qwens eigene STRUKTUR.md listet app/api/invoices/route.ts explizit als implementiert auf. Die Datei existiert schlicht nicht.
Dazu kam ein Typfehler, der den Build verhinderte. In components/InvoiceActions.tsx:
// InvoiceActions.tsx (Qwen3-max) — Typfehler
const neueMahnstufe = invoice.mahnstufe + 1 > 4 ? 4 : (invoice.mahnstufe + 1);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Typ: number — aber Invoice.mahnstufe erwartet Mahnstufe = 0|1|2|3|4
optimisticUpdate = {
mahnstufe: neueMahnstufe, // TS2322: Type 'number' is not assignable to type 'Mahnstufe'
betrag: invoice.betrag + gebuehr,
letzteAktionAm: new Date().toISOString(),
};
invoice.mahnstufe + 1 ergibt in TypeScript number, nicht Mahnstufe. Ein as Mahnstufe Cast fehlt. Build bricht ab. Die App wäre nach Fix trotzdem leer geblieben — wegen der fehlenden Route.
Außerdem enthielt die .eslintrc.json module.exports-Syntax statt JSON — was Next.js ebenfalls als Fehler wertet.
Drei separate Probleme, bevor die erste Zeile Businesslogik ausgeführt wird. Die fehlende Route war das Hauptproblem; der Typfehler war das sichtbarere. Zwei unabhängige Bugs, einer verdeckt den anderen.
GLM-4.6: hat seinen eigenen Output nie gebaut
GLMs App war architektonisch sauber. Die Tests liefen nach Fix grün (30/30). Aber out-of-the-box lief gar nichts.
GLM verlangte Tailwind v4 mit @tailwindcss/postcss — und die zugehörigen Dist-Dateien im generierten node_modules waren nicht vollständig vorhanden. Das @jridgewell/sourcemap-codec-Paket fehlte:
node_modules/@jridgewell/sourcemap-codec/
LICENSE ← vorhanden
dist/ ← leer
package.json ← vorhanden
Manuelle Fix-Reihenfolge: npm install --force, dann lief der Build. Danach grüne Tests.
Was das zeigt: Das Modell hat npm install && npm run build nicht selbst durchgespielt, bevor es „fertig" meldete. Das ist kein Fehler in der Architektur — der Code war gut. Das ist ein Fehler im Prozess: Es fehlt der letzte Schritt, der bestätigt, dass das Gebaute auch baut.
Separat fiel mir die Datums-Logik auf:
// lib/domain/mahnlogik.ts (GLM-4.6) — lokale Zeitzone
export function neueAktionDatum(letzteAktion: Date, tage: number): Date {
const datum = new Date(letzteAktion);
datum.setDate(datum.getDate() + tage);
datum.setHours(0, 0, 0, 0); // ← lokale Zeitzone der Maschine
return datum;
}
setHours(0, 0, 0, 0) arbeitet in der lokalen Zeitzone der laufenden Maschine. Wenn ein Date-Objekt per JSON-Roundtrip (new Date(isoString)) erstellt wird, kann setHours(0,0,0,0) je nach TZ-Offset eine andere Kalendertagsgrenze als erwartet treffen. Die Tests liefen grün — auf einer Maschine mit einer bestimmten Zeitzone. Unter TZ=UTC und TZ=Asia/Kolkata produziert neueAktionDatum unterschiedliche Ergebnisse.
Ich verweise hier auf Teil 1 dieser Reihe — da ist genau dieses Muster ausführlich beschrieben. GLM hat den gleichen Zeitzone-Grundfehler auch in der App-Aufgabe reproduziert.
Mistral: zwei Probleme, die beide unabhängig voneinander nerven
Problem 1: Tailwind-Versions-Mix. Mistral verwendete Tailwind v3 mit klassischem PostCSS-Setup, lieferte aber eine globals.css mit Tailwind v4-Syntax:
/* globals.css (Mistral) — v4-Syntax in einem v3-Projekt */
@import "tailwindcss"; /* gehört zu v4, braucht @tailwindcss/postcss */
Statt korrekt:
/* v3-Standard */
@tailwind base;
@tailwind components;
@tailwind utilities;
Das ergibt beim Build CSS-Fehler, weil PostCSS nicht weiß, was @import "tailwindcss" bedeuten soll. Das Plugin, das das verarbeitet (@tailwindcss/postcss), war nicht installiert.
Problem 2: JavaScript-only-Redirect. Die Root-Route / war eine Client Component, die per useEffect auf /dashboard weiterleitet:
// src/app/page.tsx (Mistral) — JS-Redirect
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export default function Home() {
const router = useRouter();
useEffect(() => {
router.replace("/dashboard");
}, [router]);
return null;
}
Korrekt wäre entweder redirect() in einer Server Component oder ein Eintrag in next.config.js redirects. Der useEffect-Redirect funktioniert nur, wenn JavaScript im Browser läuft und der Hydrations-Zyklus abgeschlossen ist. Das Dashboard selbst (src/app/dashboard/page.tsx) war eine saubere Server Component — die eigentliche Logik war also vorhanden, nur der Einstiegspunkt kaputt.
Beide Probleme ließen sich in wenigen Minuten fixen. Der Punkt ist: Sie hätten nicht vorhanden sein sollen.
Was das konkret bedeutet
„Sieht fertig aus" und „läuft" sind zwei verschiedene Dinge. Qwen lieferte eine optisch vollständige App — strukturierter Code, korrekte Domänenfunktionen, stimmige Typen. Nur die Tabelle blieb leer, weil die API-Route fehlte. Das ist genau die Art Bug, die man in einem Code-Review sofort findet — wenn man den Code ausführt.
Build-Erfolg ist kein Zufall. Die drei, die out-of-the-box liefen (Opus, DeepSeek v4-pro, v4-flash), haben ihre eigene Build-Chain ausgeführt und geprüft. Das ist kein Merkmal, das man einem Modell von außen ansieht. Es ist ein Schritt im Prozess, den manche Modelle überspringen.
Selbst-Verifikation unterscheidet die Gruppen. Nur Opus und DeepSeek haben das, was ich in Teil 2 als Selbst-Verifikation beschrieben hab, auch in der App-Aufgabe durchgehalten: den eigenen Output gebaut, ausgeführt, gegen echte Erwartungen geprüft. GLM, Mistral und Qwen nicht — alle drei mit unterschiedlichen Ausprägungen, aber demselben Grundfehler.
Methodik-Kasten
- Aufgabe: vollständige Next.js-App „Mahnwesen-Cockpit", 8 Bewertungsachsen (Build, Tests, Architektur, Domänentrennung, Datums-Korrektheit, Geld-Handling, Optimistic UI, API-Design).
- Verfahren: jede App
npm install && npm run build && npm testreal ausgeführt — ausgeführt oder nicht, kein „sieht vollständig aus". - Out-of-the-box gebaut + gelaufen: Claude Opus, DeepSeek v4-pro, v4-flash (3 von 5 Anbietern).
- Erst nach manuellem Eingriff: GLM-4.6, Mistral, Qwen3-max.
- Heimvorteil-Hinweis: Claude Code ist für Claude gebaut. Nicht-US-Modelle liefen im „fremden Anzug" — leichter struktureller Nachteil. DeepSeek schlägt Opus trotzdem. Das ist der Punkt.
- Mistral-Asymmetrie: Mistral hat keinen Anthropic-kompatiblen Endpoint und wurde über Le Chat getestet — andere Umgebung als alle anderen.
Häufige Fragen
Was ist das Mahnwesen-Cockpit genau, und warum diese Aufgabe? Eine Next.js-App mit In-Memory-Datenspeicher, die Rechnungen und Mahnstufen verwaltet. Ich hab die Aufgabe gewählt, weil sie mehrere Schwierigkeiten gleichzeitig hat: Server/Client-Grenze, Geld als Integer, Datums-Logik, Optimistic UI. Eine Funktion zeigt, ob ein Modell denken kann. Eine App zeigt, ob es bauen kann.
Drei von fünf — bedeutet das, dass 60 % der KI-Apps nicht starten? Nein, das lässt sich nicht direkt verallgemeinern. Es bedeutet, dass in diesem Test mit dieser Aufgabe drei von fünf Anbietern (sechs Läufe insgesamt, zwei für DeepSeek) out-of-the-box einen lauffähigen Build lieferten. Als Tendenz ist es trotzdem ein Signal: Die letzte Meile ist der häufigste Stolperstein.
Würde es mit einem anderen Prompt besser laufen? Möglicherweise. Aber der Prompt war klar und vollständig. „Lauffähige Dateistruktur" war explizit verlangt. Wer das ignoriert, tut es aus einem der zwei Gründe: entweder fehlt der Selbst-Verifikations-Schritt, oder das Modell optimiert auf das Aussehen des Outputs statt auf seine Funktion.
Was ist der Unterschied zwischen GLM und Qwen hier? GLM hat einen guten Code geliefert, nur nicht gebaut. Das war ein Prozess-Fehler. Qwen hat gebaut (nach Fix) und saubere Tests geliefert — aber die App hat ab Sekunde 1 leere Daten gezeigt, weil eine zentrale Route fehlte, die das Modell selbst als implementiert ausgegeben hatte. Das ist ein strukturell anderer Fehler: nicht „gebaut nicht", sondern „lügt über was gebaut ist".
Sind das Fehler, die ein Entwickler sofort findet?
Ja. Den fehlenden GET /api/invoices-Endpunkt findet man in 30 Sekunden, wenn man die App öffnet und die Netzwerk-Tab im Browser aufmacht. Das ist der Punkt. KI-Code braucht denselben Realitäts-Check wie jeder andere Code auch.
Weiter in Teil 3b: Die subtilen Bugs, die durch Tests rutschen — Singleton-Snapshot-Race, fehlendes letzteAktionAm-Feld, HTTP-Status per String-Matching, Architektur-Vergleich.
Zurück zur Methodik und zum Test-Setup.
Reihe: Methodik & Setup · Grüne Tests beweisen nichts (Teil 1) · Selbstbewusst falsch (Teil 2) · Die letzte Meile (Teil 3a) · Teil 3b · Kosten-Effizienz (Teil 4) · Souveränität (Teil 5)