Strukturierte Ausgabe: JSON nach einem Schema
Kurz gesagt: Strukturierte Ausgabe ist eine Antwort des Modells als JSON nach einem Schema mit Typen und erlaubten Werten, die das Programm prüft und bei einem Fehler ans Modell zurückschickt, damit es sie korrigiert.
Problem
Der Code, der JSON erwartet
Im Support-Postfach landen jeden Tag hunderte E-Mails. Ein Programm sortiert sie in Warteschlangen: Lieferungen, Rückgaben, Zahlungen, Allgemeines. Es fragt ein Modell nach dem Thema der E-Mail und erwartet JSON mit dem Feld intent. Hier sind drei Antworten des Modells auf dieselbe E-Mail:
List<String> answers = List.of( """ Klar! Hier sind die Daten: ```json {"intent": "delivery", "order_id": "1042", "urgent": true} ```""", """ {"intent": "Lieferung", "order_id": "1042", "urgent": "ja", "summary": "Der Kurier ist nicht gekommen."}""", """ {"intent": "delivery", "order_id": "1042", "urgent": true, "summary": "Der Kurier ist nicht gekommen, der Kunde reist morgen ab."}""");for (String answer : answers) { Map<String, Object> json = parseJson(answer); if (json == null) { System.out.println("kein JSON: " + answer.lines().findFirst().get()); continue; } String queue = QUEUES.get(json.get("intent")); System.out.println(queue == null ? "keine Warteschlange für \"" + json.get("intent") + "\"" : "Warteschlange: " + queue);}kein JSON: Klar! Hier sind die Daten:keine Warteschlange für "Lieferung"Warteschlange: LieferungenNur die dritte funktioniert. Die erste ist höflich, aber es ist Text um das JSON herum, und das Programm kann ihn nicht lesen. Die zweite ist JSON, aber das Thema ist ein Wort, das das Programm nicht kennt, und es gibt keine solche Warteschlange. In der Lektion über den Prompt hast du gesehen, dass Beispiele helfen. Aber das Programm muss auch in der Nacht funktionieren, in der das Modell beschließt, höflich zu sein.
Die Idee
Ein Schema, eine Prüfung, ein zweiter Versuch
Wenn die Antwort in Code geht und nicht zu einem Menschen, hat sie einen Vertrag. Der Vertrag ist ein Schema: welche Felder es gibt, welchen Typ jedes hat und welche Werte erlaubt sind.
- Der Prompt nennt das Schema. Die Felder, die Typen und die erlaubten Werte stehen in der Anweisung.
- Das Programm liest das JSON. Ist es kein JSON, ist das ein Fehler, kein Absturz.
- Das Programm prüft es gegen das Schema: Ist jedes Feld da, stimmt der Typ, ist der Wert erlaubt, gibt es keine überflüssigen Felder.
- Die Fehler gehen zurück ans Modell als neue Nachricht, und es versucht es noch einmal.
- Es gibt höchstens drei Versuche. Dann geht die E-Mail an einen Menschen.
Eine Antwort nach einem Schema, die das Programm prüft, ist strukturierte Ausgabe.
Die Ausgabe des Modells ist nicht vertrauenswürdige Eingabe wie alles, was von außen kommt: Das Programm vertraut nicht darauf, dass sie den Vertrag einhält, es prüft.Sieh es dir an
Lesen und prüfen
Das Schema der E-Mail: vier Felder, intent mit vier erlaubten Werten:
static final List<Field> TICKET = List.of( new Field("intent", "string", List.of("delivery", "return", "payment", "other")), new Field("order_id", "string", List.of()), new Field("urgent", "boolean", List.of()), new Field("summary", "string", List.of()));Das Lesen gibt das Objekt zurück, oder null, wenn der Text nicht genau ein JSON-Objekt ist:
static Map<String, Object> parseJson(String text) { return FlatJson.parse(text);}parseJson den kleinen Leser FlatJson aus „Mehr dazu“ auf: Er versteht ein flaches Objekt mit Text, Zahlen, true, false und null, so viel, wie die Lektion braucht.Der Typ eines Werts in den Worten von JSON selbst:
static String jsonType(Object value) { return switch (value) { case null -> "null"; case String s -> "string"; case Boolean b -> "boolean"; default -> "number"; };}Die Prüfung wirft keine Exception. Sie gibt eine Liste von Fehlern zurück, weil das Programm etwas damit anfangen kann:
static List<String> validate(Map<String, Object> json, List<Field> schema) { List<String> errors = new ArrayList<>(); for (Field f : schema) { if (!json.containsKey(f.name())) { errors.add("fehlt \"" + f.name() + "\""); continue; } Object value = json.get(f.name()); String type = jsonType(value); if (!type.equals(f.type())) { errors.add("\"%s\" ist %s, erwartet %s" .formatted(f.name(), type, f.type())); } else if (!f.allowed().isEmpty() && !f.allowed().contains(value)) { errors.add("\"%s\": \"%s\" gehört nicht zu %s".formatted( f.name(), value, String.join(", ", f.allowed()))); } } for (String key : json.keySet()) { if (schema.stream().noneMatch(f -> f.name().equals(key))) { errors.add("überflüssiges Feld \"" + key + "\""); } } return errors;}Vier Antworten des Modells:
List<String> answers = List.of( """ {"intent": "delivery", "order_id": "1042", "urgent": true, "summary": "Der Kurier ist nicht gekommen, der Kunde reist morgen ab."}""", """ {"intent": "Lieferung", "order_id": "1042", "urgent": "ja", "summary": "Der Kurier ist nicht gekommen."}""", """ {"intent": "return", "urgent": false, "summary": "Will eine Jacke zurückgeben.", "mood": "ruhig"}""", """ Klar! Hier sind die Daten: ```json {"intent": "delivery", "order_id": "1042", "urgent": true} ```""");for (String answer : answers) { Map<String, Object> json = parseJson(answer); List<String> errors = json == null ? List.of("kein JSON") : validate(json, TICKET); System.out.println(errors.isEmpty() ? "ok" : String.join("\n", errors)); System.out.println("---");}ok---"intent": "Lieferung" gehört nicht zu delivery, return, payment, other"urgent" ist string, erwartet boolean---fehlt "order_id"überflüssiges Feld "mood"---kein JSON---Probier es aus
Sag die Ausgabe voraus
Was gibt die Prüfung für diese Antwort aus?
List<String> answers = List.of( """ {"intent": "refund", "order_id": 1042, "urgent": false, "summary": "Will sein Geld zurück."}""");for (String answer : answers) { Map<String, Object> json = parseJson(answer); List<String> errors = json == null ? List.of("kein JSON") : validate(json, TICKET); System.out.println(errors.isEmpty() ? "ok" : String.join("\n", errors)); System.out.println("---");}Zwei Fehler. „refund“ gehört nicht zu den vier erlaubten Werten, und 1042 ohne Anführungszeichen ist eine Zahl, kein Text. Eine Bestellnummer sieht aus wie eine Zahl, ist aber eine Kennung: „0042“ als Zahl würde zu 42. Darum verlangt das Schema Text, und die Prüfung besteht darauf.
Probier es aus
Fülle die Lücke
Eine Zeile aus der Prüfung: Der Typ stimmt, jetzt muss der Wert einer der erlaubten sein. Was fehlt?
} else if (!f.allowed().isEmpty() && !f.allowed().(value)) {contains. Ohne diese Prüfung kommt „Lieferung“ durch, weil es Text ist, und landet in einer Warteschlange, die es nicht gibt.
Die Idee
Die Fehler zurück ans Modell
Was machst du mit einer Antwort, die nicht durchkommt? Wenn du die Frage noch einmal stellst, wiederholt das Modell oft denselben Fehler. Besser ist, ihm zu sagen, was falsch ist.
Die Antwort des Modells kommt mit der Rolle assistant in den Verlauf, danach eine neue user-Nachricht mit den Fehlern: „Die Antwort passt nicht zum Schema: intent … Gib nur JSON nach dem Schema zurück.“ Das Modell sieht seinen Versuch und die genauen Fehler und korrigiert sie. Das ist ein zweiter Versuch mit Rückmeldung.
Jeder Versuch schickt den ganzen Verlauf. Der zweite kostet mehr als der erste, der dritte mehr als der zweite: Aus der Lektion über den Kontext weißt du, dass die API kein Gedächtnis hat und alles von vorn beginnt. Darum sind die Versuche wenige, und danach geht die E-Mail an einen Menschen.
Sieh es dir an
Drei E-Mails, drei Schicksale
static Ticket triage(FakeClient client, String email) { List<Message> messages = new ArrayList<>(); messages.add(new Message("user", email)); for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { Reply reply = client.create(SYSTEM, messages); Map<String, Object> json = parseJson(reply.text()); List<String> errors = json == null ? List.of("kein JSON") : validate(json, TICKET); String verdict = errors.isEmpty() ? "ok" : String.join("; ", errors); System.out.printf(" Versuch %d (~%d Tokens): %s%n", attempt, reply.inputTokens(), verdict); if (errors.isEmpty()) { return toTicket(json); } messages.add(new Message("assistant", reply.text())); messages.add(new Message("user", "Die Antwort passt nicht zum Schema: " + verdict + ". Gib nur JSON nach dem Schema zurück.")); } return null;}static List<String> validate(Map<String, Object> json, List<Field> schema) { List<String> errors = new ArrayList<>(); for (Field f : schema) { if (!json.containsKey(f.name())) { errors.add("fehlt \"" + f.name() + "\""); continue; } Object value = json.get(f.name()); String type = jsonType(value); if (!type.equals(f.type())) { errors.add("\"%s\" ist %s, erwartet %s" .formatted(f.name(), type, f.type())); } else if (!f.allowed().isEmpty() && !f.allowed().contains(value)) { errors.add("\"%s\": \"%s\" gehört nicht zu %s".formatted( f.name(), value, String.join(", ", f.allowed()))); } } for (String key : json.keySet()) { if (schema.stream().noneMatch(f -> f.name().equals(key))) { errors.add("überflüssiges Feld \"" + key + "\""); } } return errors;}Schritt für Schritt, in Worten
- Die E-Mail geht mit einer Anweisung ans Modell: Gib nur JSON mit vier Feldern zurück.
- Die Antwort ist gültiges JSON und wird gelesen. Das heißt noch nicht, dass sie stimmt.
- Jedes Feld wird gegen das Schema geprüft: Ist es da, stimmt der Typ, ist der Wert einer der erlaubten.
- Zwei Fehler: intent und urgent. Sie sind eine Liste von Texten, keine Exception: Das Programm entscheidet, was es tut.
- Die Fehler gehen als neue Nachricht zurück ans Modell. Versuch 2 schickt den ganzen Verlauf: ~110 Tokens statt ~58.
- Die zweite Antwort besteht alle Prüfungen. Erst jetzt wird sie zu einem Ticket.
- Die E-Mail geht als dringend in die Warteschlange für Lieferungen. Höchstens 3 Versuche, danach übernimmt ein Mensch.
Die Anweisung und wie viele Versuche das Programm hat:
static final String SYSTEM = "Sortiere die E-Mail des Kunden ein. Gib nur JSON mit den Feldern " + "intent (delivery, return, payment oder other), order_id (Text), " + "urgent (true oder false) und summary (ein Satz) zurück.";static final int MAX_ATTEMPTS = 3;Eine E-Mail: fragen, lesen, prüfen, und bei einem Fehler ein neuer Versuch mit den Fehlern. Wenn alles durchkommt, wird die Map zu record Ticket, und der Code arbeitet ab da mit Feldern, nicht mit Text:
static Ticket triage(FakeClient client, String email) { List<Message> messages = new ArrayList<>(); messages.add(new Message("user", email)); for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { Reply reply = client.create(SYSTEM, messages); Map<String, Object> json = parseJson(reply.text()); List<String> errors = json == null ? List.of("kein JSON") : validate(json, TICKET); String verdict = errors.isEmpty() ? "ok" : String.join("; ", errors); System.out.printf(" Versuch %d (~%d Tokens): %s%n", attempt, reply.inputTokens(), verdict); if (errors.isEmpty()) { return toTicket(json); } messages.add(new Message("assistant", reply.text())); messages.add(new Message("user", "Die Antwort passt nicht zum Schema: " + verdict + ". Gib nur JSON nach dem Schema zurück.")); } return null;}static Ticket toTicket(Map<String, Object> json) { return new Ticket((String) json.get("intent"), (String) json.get("order_id"), (Boolean) json.get("urgent"), (String) json.get("summary"));}FakeClient client = new FakeClient();String[] inbox = { "Der Kurier ist gestern nicht gekommen, Bestellung 1042. Es ist dringend, ich reise morgen ab!", "Ich möchte Bestellung 1187 zurückgeben, die Jacke ist zu groß.", "hallo, irgendwas stimmt nicht",};for (String email : inbox) { System.out.println("E-Mail: " + email); Ticket ticket = triage(client, email); if (ticket == null) { System.out.println(" → an einen Menschen"); } else { String urgent = ticket.urgent() ? ", dringend" : ""; System.out.println(" → " + ticket.intent() + ", " + ticket.orderId() + urgent); }}E-Mail: Der Kurier ist gestern nicht gekommen, Bestellung 1042. Es ist dringend, ich reise morgen ab! Versuch 1 (~58 Tokens): "intent": "Lieferung" gehört nicht zu delivery, return, payment, other; "urgent" ist string, erwartet boolean Versuch 2 (~110 Tokens): ok → delivery, 1042, dringendE-Mail: Ich möchte Bestellung 1187 zurückgeben, die Jacke ist zu groß. Versuch 1 (~51 Tokens): ok → return, 1187E-Mail: hallo, irgendwas stimmt nicht Versuch 1 (~44 Tokens): kein JSON Versuch 2 (~74 Tokens): kein JSON Versuch 3 (~104 Tokens): kein JSON → an einen Menschen- Die E-Mail zu 1042: Die erste Antwort hat zwei Fehler, die zweite kommt durch. Der zweite Versuch kostet ~110 Tokens statt ~58.
- Die E-Mail zu 1187: kommt beim ersten Mal durch.
- „hallo, irgendwas stimmt nicht“: Das Modell findet kein Thema und antwortet alle drei Male mit einem Satz. Das Programm erfindet keine Kategorie; es gibt die E-Mail an einen Menschen.
Das Modell ist hier ein falscher Client, wie in der Lektion über den Prompt: Die Antworten sind aufgezeichnet und jedes Mal gleich. Seine Regel steht in „Mehr dazu“.
Probier es aus
Finde den Fehler
Ein Kollege schreibt eine kürzere Prüfung. Die Antwort {"intent": "return", "urgent": false, "summary": "…"} kommt als „ok“ durch, und eine Minute später stürzt das Programm irgendwo weit weg ab, wenn es die Bestellung nachschlägt. In welcher Zeile steckt der Fehler?
Wähle die Zeile mit dem Fehler.
Das fehlende Feld wird stillschweigend übersprungen: continue ohne Fehler. Die Antwort ohne order_id kommt durch die Prüfung, und das Programm stürzt später ab, wo es die Bestellung nach der Nummer sucht. Ein Fehler, der am Eingang gefangen wird, erklärt sich selbst: „order_id fehlt“. Derselbe Fehler drei Methoden tiefer ist ein Rätsel. Die Korrektur: den Fehler „fehlt“ vor continue hinzufügen.
Die Idee
Das Schema in der API selbst
Neuere APIs nehmen das Schema als Teil der Anfrage an. Dann erzeugt das Modell nur Text, der dazu passt: Jedes nächste Token wird unter denen gewählt, die das JSON nach dem Schema halten. Das höfliche „Klar!“ und "urgent": "ja" werden unmöglich.
Die Prüfung im Code bleibt aber:
- Eine Ablehnung oder eine abgeschnittene Antwort. Lehnt das Modell ab (
stop_reasonistrefusal) oder erreicht esmax_tokens, passt der Text vielleicht nicht zum Schema. - Die Regeln des Shops. Das Schema sagt „Text“, aber es weiß nicht, ob Bestellung 1187 existiert und diesem Kunden gehört. Das ist eine Prüfung im Programm.
- Ein anderes Modell oder ein anderer Anbieter. Der Code, der prüft, hängt nicht davon ab, wer das JSON erzeugt hat.
Bei dir ausführen. Dieses Programm gehört nicht zur Prüfung der Lektion: Du brauchst ein Konto bei einem Anbieter von Modellen, einen Schlüssel und Geld für jeden Aufruf. Der Schlüssel steht in LLM_API_KEY, nie im Code. Anbieter liefern offizielle SDKs für Java, C#, Python und JavaScript mit derselben Form (Nachrichten, Rollen, Tokens, Tools); die genauen Namen der Klassen und Methoden stehen in der Dokumentation des Anbieters. Die Anfrage als JSON: Das Schema steht im Feld output_config, und der Name des Modells ist ein Platzhalter:
{ "model": "<Name des Modells>", "max_tokens": 1024, "system": "Sortiere die E-Mail des Kunden ein. Gib nur JSON mit den Feldern intent (delivery, return, payment oder other), order_id (Text), urgent (true oder false) und summary (ein Satz) zurück.", "messages": [ { "role": "user", "content": "Der Kurier ist gestern nicht gekommen, Bestellung 1042. Es ist dringend, ich reise morgen ab!" } ], "output_config": { "format": { "type": "json_schema", "schema": { "type": "object", "properties": { "intent": { "type": "string", "enum": ["delivery", "return", "payment", "other"] }, "order_id": {"type": "string"}, "urgent": {"type": "boolean"}, "summary": {"type": "string"} }, "required": ["intent", "order_id", "urgent", "summary"], "additionalProperties": false } } }}Eine Beispielantwort. Das JSON nach dem Schema ist Text im Textblock, und das Programm liest und prüft es trotzdem:
{ "content": [ { "type": "text", "text": "{\"intent\": \"delivery\", \"order_id\": \"1042\", \"urgent\": true, \"summary\": \"Der Kurier ist nicht gekommen, der Kunde reist morgen ab.\"}" } ], "stop_reason": "end_turn", "usage": {"input_tokens": 58, "output_tokens": 23}}Ohne SDK geht die Anfrage mit dem Standard-HTTP-Client der Sprache raus. Die Adresse ist ein Platzhalter:
// requestJson ist das JSON der Anfrage oben.// Der genaue Header für den Schlüssel steht in der Doku des Anbieters.HttpClient http = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder(URI.create("https://<anbieter>/messages")) .header("Authorization", "Bearer " + System.getenv("LLM_API_KEY")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestJson)) .build();HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());Jedes Objekt im Schema hat additionalProperties: false: So kann ein überflüssiges Feld wie mood gar nicht erst auftauchen.
Die Idee
Wann es wehtut
- JSON für alles. Die Antwort an einen Kunden ist Text. Ein Schema gehört dorthin, wo die Antwort in Code geht: eine Warteschlange, eine Datenbank, ein anderer Dienst.
- Ein tiefes Schema. Fünf Ebenen verschachtelter Objekte und zwanzig Felder ergeben mehr Fehler und teurere Versuche. Zwei einfache Anfragen sind besser als eine komplizierte.
- Flicken statt Prüfung. „Schneide vom ersten
{bis zum letzten}“ funktioniert bis zur ersten Antwort mit zwei{. Wenn die API ein Schema unterstützt, nimm es. Wenn nicht, prüfe und versuche es noch einmal. - Die richtige Form ist nicht die richtige Antwort.
"order_id": "1043"besteht das Schema, aber das Modell kann sich bei einer Ziffer geirrt haben: Modelle erfinden plausibel. Die Nummer wird gegen die Datenbank geprüft, bevor man sie benutzt. - Versuche haben einen Preis. Jeder Versuch sind mehr Tokens und mehr Sekunden für den Kunden. Wie viel genau und wie man ein Budget hält, steht in der Lektion über Kosten und Geschwindigkeit.
- Das Schema und der Prompt ändern sich gemeinsam. Eine neue Kategorie im Schema ohne neue Kategorie im Prompt ergibt ein Modell, das sie nie nennt. Die Version des Schemas steht im Code, neben dem Prompt.
Genauer, für Neugierige
Die Wörter von JSON Schema. Das Schema in der API ist JSON Schema: type (object, string, number, boolean, array, null), properties mit den Feldern, required mit den Pflichtfeldern, enum mit den erlaubten Werten und additionalProperties: false gegen überflüssige. Unsere Prüfung macht eine Teilmenge davon, genug, um den Mechanismus zu zeigen. Ein Projekt nimmt eine fertige Bibliothek für JSON Schema.
Vom Text zu einem Typ. Nach der Prüfung wird die Map zu Ticket, und der übrige Code prüft nichts mehr: Der Compiler oder die Felder sagen, was da ist. Das ist die Idee „am Eingang lesen und prüfen, innen mit Typen arbeiten“. Die Prüfung steht an einer Stelle, nicht im ganzen Programm verstreut.
Tools nutzen dasselbe. In der nächsten Lektion bittet das Modell das Programm, eine Funktion auszuführen. Auch die Argumente der Funktion kommen als JSON nach einem Schema (input_schema) und werden ebenfalls geprüft.
Der falsche Client von innen. Seine Antworten sind nach der Bestellnummer in der E-Mail aufgezeichnet: Bei 1042 ist die erste falsch und nach der Korrektur richtig; bei 1187 ist sie sofort richtig; bei allem anderen ist sie ein Satz. Die Tokens sind Wörter × 1,3.
// Steht an der Stelle des echten Modells: aufgezeichnete Antworten,// ohne Netz und jedes Mal gleich.class FakeClient { static final String WRONG_1042 = """ {"intent": "Lieferung", "order_id": "1042", "urgent": "ja", "summary": "Der Kurier ist nicht gekommen."}"""; static final String GOOD_1042 = """ {"intent": "delivery", "order_id": "1042", "urgent": true, "summary": "Der Kurier ist nicht gekommen, der Kunde reist morgen ab."}"""; static final String GOOD_1187 = """ {"intent": "return", "order_id": "1187", "urgent": false, "summary": "Will die Jacke zurückgeben, sie ist zu groß."}"""; static final String PROSE = "Ich verstehe nicht, was der Kunde will.";
Reply create(String system, List<Message> messages) { String email = messages.get(0).content(); boolean corrected = messages.size() > 1; String text; if (email.contains("1042")) { text = corrected ? GOOD_1042 : WRONG_1042; } else if (email.contains("1187")) { text = GOOD_1187; } else { text = PROSE; } int input = tokens(system); for (Message m : messages) { input += tokens(m.content()); } return new Reply(text, "end_turn", input, tokens(text)); }
// Grob: Wörter × 1,3, aufgerundet. static int tokens(String text) { String t = text.strip(); int words = t.isEmpty() ? 0 : t.split("\\s+").length; return (words * 13 + 9) / 10; }}Der Leser FlatJson. Rekursiver Abstieg in einem Durchgang: object liest {, Paare aus Schlüssel und Wert und }, und value erkennt Text, true, false, null und eine Zahl. Verschachtelte Objekte und Arrays liest er nicht, und \n im Text auch nicht. Jede unerwartete Stelle wirft eine Exception, und parse macht daraus null.
// Java hat kein JSON in der Standardbibliothek. Dieser Leser versteht// ein flaches Objekt: Text, Zahlen, true, false und null. Im Projekt: Jackson.class FlatJson { private final String s; private int i;
private FlatJson(String s) { this.s = s; }
// Das Objekt oder null, wenn der Text nicht genau ein JSON-Objekt ist. static Map<String, Object> parse(String text) { FlatJson p = new FlatJson(text.strip()); try { Map<String, Object> obj = p.object(); return p.i == p.s.length() ? obj : null; } catch (RuntimeException e) { return null; } }
private Map<String, Object> object() { Map<String, Object> obj = new LinkedHashMap<>(); expect('{'); skip(); if (peek() == '}') { i++; return obj; } while (true) { skip(); String key = string(); skip(); expect(':'); skip(); obj.put(key, value()); skip(); if (peek() == '}') { i++; return obj; } expect(','); } }
private Object value() { if (peek() == '"') { return string(); } for (String word : new String[] {"true", "false", "null"}) { if (s.startsWith(word, i)) { i += word.length(); return word.equals("null") ? null : Boolean.valueOf(word); } } int start = i; while (i < s.length() && "+-.0123456789eE".indexOf(s.charAt(i)) >= 0) { i++; } return Double.parseDouble(s.substring(start, i)); }
private String string() { expect('"'); StringBuilder out = new StringBuilder(); while (peek() != '"') { char c = s.charAt(i++); out.append(c == '\\' ? s.charAt(i++) : c); } i++; return out.toString(); }
private void skip() { while (i < s.length() && Character.isWhitespace(s.charAt(i))) { i++; } }
private char peek() { return s.charAt(i); }
private void expect(char c) { if (peek() != c) { throw new IllegalStateException("erwartet " + c); } i++; }}Übung
Die Nummer hat vier Ziffern
Löse die Aufgabe in deinem Editor.
Füge der Prüfung eine Regel hinzu: order_id hat genau 4 Ziffern. Wenn nicht, lautet der Fehler "order_id": "10420" hat nicht 4 Ziffern. Lass die Prüfung für die drei Antworten laufen, die sich nur in der Nummer unterscheiden: 1042, 10420 und #1042.
ok---"order_id": "10420" hat nicht 4 Ziffern---"order_id": "#1042" hat nicht 4 Ziffern---Weitere Aufgaben
- Eine leere Zusammenfassung.
summarydarf kein leerer Text und nicht nur Leerzeichen sein. Füge die Regel und einen Test mit"summary": " "hinzu. - Eine begrenzte Zahl von Tokens. Beende die Versuche auch dann, wenn die Tokens der E-Mail zusammen 100 übersteigen, schon vor drei Versuchen. Wie viele Versuche macht dann die E-Mail „hallo, irgendwas stimmt nicht“?
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Das Kontextfenster · Die Grenzen des Modells · Ein Modell über eine API aufrufen · Der Prompt ist Code
- Geht weiter in: Tool Use: das Modell fragt, das Programm handelt · Der Agent-Loop: Plan, Aktion, Beobachtung · Prompt Injection: die E-Mail, die Befehle gibt · Guardrails: Prüfungen vor und nach dem Modell
- Aufgaben zum Thema: Anwendungen mit LLMs