Kontext, Vorgaben, Ausgabe: Eine Prompt-Vorlage für verlässlichen KI-Code
Vage Prompts liefern vagen Code. Mit einer dreiteiligen Vorlage aus Kontext, Vorgaben und Ausgabeformat bekommen Sie produktionsreife Funktionen schon beim ersten Versuch.
Bitten Sie einen KI-Assistenten, „eine Funktion zu schreiben, die Benutzereingaben validiert“, und Sie bekommen oft Text, der nur wie Code aussieht: Er läuft, hat eine Signatur und tut nichts von dem, was Sie brauchten. Es gibt keine Fehlerbehandlung und keinen Hinweis darauf, was „gültig“ überhaupt heißen sollte.
Meist wird das dem Modell angelastet, doch in den allermeisten Fällen liegt es am Prompt. Behandeln Sie einen Prompt wie einen API-Vertrag statt wie eine beiläufige Bitte, und die Qualität der Ausgabe springt nach oben – ohne dass das Modell klüger geworden wäre. Es muss nur nicht mehr raten. Im Folgenden eine dreiteilige Struktur, die dieses Muster behebt.
Warum vage Prompts vagen Code liefern
Bei einer unscharfen Anfrage füllt das Modell jede Lücke mit einer Annahme, und diese Annahmen decken sich selten mit Ihren. Welche Sprachversion? Welche Konvention hat Ihr Team für die Fehlerbehandlung? Muss der Code bei nebenläufigem Zugriff sicher sein? Soll ein Fehler zurückgegeben werden oder eine Panic auslösen? Eine einzeilige Anfrage beantwortet nichts davon, also greift das Modell auf das häufigste Muster zurück, das es kennt. Für eine echte Codebasis ist dieses häufige Muster oft das falsche, und die Lücke fällt erst im Review auf – oder schlimmer, in der Produktion.
Eine Zauberformel dafür gibt es nicht. Die Lösung besteht darin, das mitzuliefern, was man einem neuen Engineer am ersten Tag erzählt: Kontext, Vorgaben und eine klare Definition von „fertig“.
Die drei Abschnitte
Jeder Prompt zur Codegenerierung lässt sich in drei Teile gliedern, und zwar in dieser Reihenfolge.
- Kontext beschreibt, wo der Code leben wird: Sprache und Version, Framework, Muster, zu denen er passen muss, und bereits verwendete Abhängigkeiten.
- Vorgaben listen die nicht verhandelbaren Regeln auf, etwa den Stil der Fehlerbehandlung, Performance-Ziele, Thread-Sicherheit, Eingabevalidierung und was zu vermeiden ist (keine externen Bibliotheken, kein globaler Zustand und so weiter).
- Ausgabeformat legt fest, in welcher Form die Antwort kommen soll. Nur die Funktion oder die Funktion plus Anwendungsbeispiel? Mit oder ohne Kommentare? Mit Tests?
Fehlt einer davon, geht etwas schief. Ohne Kontext entsteht Code, der nicht zu Ihrem Stack passt. Ohne Vorgaben entsteht Code, der „funktioniert“, aber Ihre Konventionen ignoriert. Ohne Ausgabeformat werden die fünfzehn Zeilen, die Sie wollten, in drei Absätze Erklärung eingepackt.
Dieselbe Aufgabe, zwei Prompts
Ein konkreter Vergleich macht den Unterschied leicht sichtbar, deshalb hier eine Aufgabe, auf beide Arten formuliert.
Die vage Version einer Anfrage sieht so aus:
Schreib eine Funktion, die einen Stapel Bestellungen verarbeitet.
Und jetzt die strukturierte Version:
Context: Go 1.22 service, part of an order-processing microservice.
Uses the existing Order struct (ID string, Amount float64, Status string).
Errors are wrapped with fmt.Errorf and %w, never panicked.
Constraints:
- Process orders concurrently, max 10 goroutines at a time
- If any single order fails, log it and continue (don't abort the batch)
- Return a slice of errors for failed orders, not a single error
- No third-party libraries, standard library only
Output format: just the function and a short usage example, no explanation.
Beim ersten Versuch kam dabei Folgendes heraus:
func ProcessOrders(ctx context.Context, orders []Order, process func(Order) error) []error {
sem := make(chan struct{}, 10)
var wg sync.WaitGroup
var mu sync.Mutex
var errs []error
for _, o := range orders {
wg.Add(1)
sem <- struct{}{}
go func(o Order) {
defer wg.Done()
defer func() { <-sem }()
if err := process(o); err != nil {
mu.Lock()
errs = append(errs, fmt.Errorf("order %s: %w", o.ID, err))
mu.Unlock()
}
}(o)
}
wg.Wait()
return errs
}
// usage
errs := ProcessOrders(ctx, orders, chargeOrder)
for _, e := range errs {
log.Println(e)
}
Das ist so, wie es ist, brauchbar. Der Prompt war nicht raffiniert. Er hat einfach jede Mehrdeutigkeit beseitigt, die das Modell sonst allein aufgelöst hätte: das Nebenläufigkeitslimit, das Verhalten bei Fehlern, das Fehlerformat und den Umfang der Abhängigkeiten.
Es funktioniert in jeder Sprache
Die Vorlage ist an keine Sprache gebunden. Hier dieselben drei Abschnitte auf einem anderen Stack:
Context: Python 3.11 data pipeline script, uses pandas, runs as a scheduled job.
Constraints:
- Validate that required columns exist before processing, raise ValueError with column name if missing
- Handle empty DataFrames without crashing (return empty result, log a warning)
- No mutation of the input DataFrame
Output format: function only, type hints included, no docstring needed.
Und das Ergebnis:
import pandas as pd
import logging
def clean_transactions(df: pd.DataFrame, required: list[str]) -> pd.DataFrame:
missing = [col for col in required if col not in df.columns]
if missing:
raise ValueError(f"missing required column(s): {', '.join(missing)}")
if df.empty:
logging.warning("received empty DataFrame, returning empty result")
return df.copy()
result = df.copy()
result = result.dropna(subset=required)
return result
Die Syntax unterscheidet sich, die Disziplin ist dieselbe. Es funktioniert sprachübergreifend, weil es die eigentliche Schwäche des Modells adressiert: Lücken mit generischen Standardwerten statt mit Ihren zu füllen.
Hilft die Struktur wirklich?
Eine informelle Zählung über ein paar Dutzend Prompts – wie oft eine generierte Funktion vor dem Merge nachgebessert werden musste – ergab ungefähr diese Werte:
Vague prompts |################........| ~80% needed rework
Context only |##############..........| ~55% needed rework
Context + constraints |######...................| ~25% needed rework
All three sections |##.......................| ~10% needed rework
Das ist kein kontrolliertes Experiment, nur ein persönliches Protokoll, aber der Trend war stabil. Zehn Sekunden für Vorgaben kosten weniger als zehn Minuten Debugging von Code, der stillschweigend das Falsche angenommen hat – selbst bei Wegwerf-Skripten.
Kleine Gewohnheiten, die sich auszahlen
Geben Sie das Ausgabeformat an. Lassen Sie eine Formulierung wie „nur den Code“ weg, kommt die Antwort jedes Mal in eine Erklärung verpackt, über die man hinwegscrollen muss. Beim schnellen Iterieren ist das zusätzliche Reibung.
Nutzen Sie negative Vorgaben. „Keine Drittanbieter-Bibliotheken“ oder „hier keine Rekursion“ schließen eine ganze Kategorie von Ausgaben aus, die Sie sonst ablehnen und neu anfordern müssten. Dinge vorab auszuschließen ist besser, als sie hinterher abzufangen.
Stapeln Sie Vorgaben in Folgeanfragen. Ist die erste Antwort nah dran, fangen Sie nicht neu an. Fügen Sie eine weitere Vorgabe hinzu und bitten Sie um eine Überarbeitung. „Dieselbe Funktion, aber mach das Goroutine-Limit konfigurierbar“ bringt Sie weiter als ein frischer Prompt, weil das Modell bereits eine funktionierende Version als Ausgangsbasis hat.
Nichts davon ersetzt es, den zurückgelieferten Code zu lesen. Es bleibt nur weniger zu reparieren.