Structured output to odpowiedź w kształcie ustalonym z góry: obiekt JSON z nazwanymi kluczami, tabela ze stałymi kolumnami albo szablon z tymi samymi nagłówkami za każdym razem. Potrzebujesz go zawsze, gdy odpowiedź czyta program, a pomaga też, gdy czyta ją człowiek, bo wtedy każda odpowiedź wygląda tak samo i łatwo ją przejrzeć albo porównać.
Cała sztuka polega na tym, żeby opisać kształt tak dokładnie, by model nie miał już nic do wyboru. Samo słowo "JSON" nie jest opisem. Poniższy blok wyciąga szczegóły ze zgłoszenia błędu. Porównaj prośbę o "najważniejsze szczegóły" z prośbą, która rozpisuje schemat.
Oto najważniejsze szczegóły ze zgłoszenia błędu:
- Problem: aplikacja się wysypuje przy eksporcie projektów
- Wyzwalacz: kliknięcie Eksportuj w projektach z ponad 50 zdjęciami
- Od kiedy: od ostatniej aktualizacji
- Platforma: Android 14
- Wersja aplikacji: 3.2.0
- Wpływ: wysoki, bo blokuje oddanie projektu klientowi
Mniejsze projekty eksportują się najwyraźniej bez problemów.
Odpowiedź w wolnym tekście jest trafna i czytelna, ale każde uruchomienie użyje innych etykiet, waga błędu jest zdaniem, a nie wartością, a program musiałby zgadywać, gdzie zaczyna się każde pole. Odpowiedź w JSON może trafić prosto do systemu zgłoszeń. Zwróć uwagę, że odpowiedź i tak przyszła w bloku kodu: aplikacje czatowe zwykle tak opakowują JSON, co ma znaczenie, gdy parsujesz go w kodzie.
Jak prosić o JSON
Dobra prośba o JSON odpowiada na każde pytanie, na które model inaczej odpowiedziałby za ciebie:
- Każdy klucz, zapisany dokładnie. Wpisz nazwy kluczy w cudzysłowie dokładnie tak, jak mają się pojawić. Napisz "dokładnie te klucze", żeby model nie dodawał innych.
- Typ każdej wartości. String, liczba, wartość logiczna, tablica stringów, zagnieżdżony obiekt.
- Dozwolone wartości dla każdego pola o stałym zestawie, takiego jak waga błędu czy kategoria. Bez tej listy w czterech uruchomieniach dostaniesz "High", "high", "severe" i "P1".
- Co zrobić, gdy w danych wejściowych brakuje wartości. Jeśli nie napiszesz "null, jeśli nie podano", model ma skłonność do wypełniania luki wiarygodnym strzałem, a zgadnięta wersja aplikacji wygląda dokładnie jak prawdziwa.
- Nic dookoła. "Zwróć tylko JSON, bez tekstu przed nim ani po nim" usuwa uprzejme zdanie na początku.
Gdy kształt jest zagnieżdżony albo nietypowy, pokazanie jednego kompletnego przykładowego obiektu działa lepiej niż opis. To few-shot prompting zastosowany do formatu. Wartości w przykładzie powinny wyraźnie różnić się od prawdziwych danych wejściowych, żeby model ich nie skopiował.
Prompt do wyciągania danych wielokrotnego użytku
Ten blok zawiera tę samą prośbę podzieloną na części. Wyłącz część z formatem, a model nadal zwróci JSON, bo wymagają tego ograniczenia, ale sam dobierze nazwy kluczy, na przykład title zamiast job_title, i kod, który oczekuje twoich kluczy, przestanie działać. To część z ograniczeniami powstrzymuje go przed dokończeniem niepełnego numeru telefonu albo wywnioskowaniem firmy z domeny adresu e-mail. Wklej prawdziwą stopkę do pola wejściowego, żeby to przetestować.
{
"name": "string",
"job_title": "string lub null",
"company": "string lub null",
"email": "string lub null",
"phone": "string lub null"
}{
"name": "Priya Nair",
"job_title": "Kierowniczka ds. danych",
"company": "Northwind Labs",
"email": "priya.nair@northwind.example",
"phone": null
}
Tabele i stałe szablony
Structured output nie jest tylko dla programów. Gdy odpowiedź czytasz ty, tabela markdown albo stały szablon dają tę samą korzyść: zanim spojrzysz, wiesz, gdzie będzie każda informacja.
| Typ | Uporządkowany | Modyfikowalny | Dopuszcza duplikaty | Typowe zastosowanie |
|---|---|---|---|---|
| list | Tak | Tak | Tak | Sekwencja, do której dodajesz elementy, usuwasz je albo ją sortujesz |
| tuple | Tak | Nie | Tak | Stała grupa wartości, na przykład współrzędne |
| set | Nie | Tak | Nie | Usuwanie duplikatów i szybkie sprawdzanie przynależności |
Szablon działa tak samo przy dłuższym tekście: podaj nagłówki w kolejności i napisz, co ma się znaleźć pod każdym. "Odpowiedz z trzema pogrubionymi etykietami: Przyczyna, Poprawka, Jak sprawdzić" daje za każdym razem te same trzy etykiety, więc partię odpowiedzi łatwo porównać.
Tryb JSON w API
Kilka API modeli ma ustawienie, które wymusza składniowo poprawny JSON. W SDK OpenAI dla Pythona to response_format. Tryb JSON wymaga, żeby słowo "JSON" pojawiło się gdzieś w wiadomościach, więc poniższy prompt systemowy je wymienia i wypisuje klucze.
import json
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
report_text = "Since yesterday's update the app crashes when I tap Export..."
response = client.chat.completions.create(
model=MODEL,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"Extract the bug report into JSON with the keys "
"summary (string), severity (one of low, medium, high, critical) "
"and steps_to_reproduce (array of strings)."
),
},
{"role": "user", "content": report_text},
],
)
data = json.loads(response.choices[0].message.content)
Tryb JSON gwarantuje, że tekst da się sparsować (chyba że odpowiedź zostanie ucięta na limicie tokenów), a nie że pasuje do twojego schematu. Klucza nadal może brakować, a waga błędu nadal może wynosić "urgent". Kilku dostawców przyjmuje też pełny JSON Schema, jako format wyjściowy albo jako schemat wejściowy definicji narzędzia (funkcji), a niektóre z tych trybów ograniczają odpowiedź do schematu. Dokładny parametr sprawdź w dokumentacji swojego dostawcy, bo te funkcje różnią się między API.
Waliduj wynik w kodzie
Traktuj JSON od modelu tak jak każde dane wejściowe spoza twojego programu: sparsuj go, a potem sprawdź. Parsowanie wyłapuje zepsutą składnię. Sprawdzenie wyłapuje poprawny obiekt z niewłaściwą zawartością.
ALLOWED_SEVERITIES = {"low", "medium", "high", "critical"}
def problems(data):
if not isinstance(data, dict):
return ["the answer must be a JSON object"]
found = []
if not isinstance(data.get("summary"), str):
found.append("summary must be a string")
if data.get("severity") not in ALLOWED_SEVERITIES:
found.append("severity must be low, medium, high or critical")
steps = data.get("steps_to_reproduce")
if not isinstance(steps, list) or not all(isinstance(s, str) for s in steps):
found.append("steps_to_reproduce must be an array of strings")
return found
Gdy sprawdzenie nie przejdzie, jedna ponowna próba często wystarcza: wyślij modelowi jego własną odpowiedź razem z listą problemów i poproś o poprawiony JSON. Ogranicz liczbę ponowień i loguj niepowodzenia, bo pole, które często nie przechodzi, jest sygnałem, że prompt jest niejasny. W większych projektach ręcznie napisaną funkcję zastępuje biblioteka do walidacji, taka jak Pydantic, albo walidator JSON Schema.
Dwa kolejne nawyki zapobiegają cichym błędom. Ustaw limit tokenów wyjściowych na tyle wysoko, żeby zmieściła się największa spodziewana odpowiedź, bo ucięty obiekt nigdy się nie sparsuje. A gdy dane wejściowe są długie albo pochodzą od użytkowników, oddziel je od instrukcji delimiterami albo tagami XML, żeby tekst w środku rzadziej był odczytywany jako instrukcja. Jeśli jeden prompt ma jednocześnie rozumować i tworzyć JSON, rozważ prompt chaining: niech jeden krok myśli w wolnym tekście, a drugi zamieni wynik w strukturę.
Najczęściej zadawane pytania
Jak sprawić, żeby ChatGPT zwracał JSON?
Napisz, że odpowiedź ma być w JSON, wypisz każdy klucz z typem i podaj dozwolone wartości dla każdego pola o stałym zestawie wartości. Dodaj "Zwróć tylko JSON, bez tekstu przed nim ani po nim". W API włącz też tryb JSON przez response_format={"type": "json_object"}, który wymaga, żeby słowo JSON pojawiło się w twoich wiadomościach.
Dlaczego model dodaje tekst wokół JSON?
Modele czatowe są trenowane do prowadzenia rozmowy, więc często zaczynają od zdania typu "Oto JSON" albo opakowują obiekt w blok kodu markdown. Poproś o sam JSON, a w kodzie użyj trybu JSON w API albo usuń otaczający blok kodu przed parsowaniem.
Czym jest tryb JSON?
Tryb JSON to ustawienie API, które sprawia, że model generuje składniowo poprawny JSON, o ile odpowiedź nie zostanie ucięta przez limit tokenów wyjściowych. Nie sprawia, że model trzyma się twojego schematu: kluczy nadal może brakować, mogą też mieć literówki albo zły typ. Niektórzy dostawcy oferują też bardziej rygorystyczny tryb, który przyjmuje pełny JSON Schema i ogranicza do niego odpowiedź.
Czy LLM zawsze może zwrócić poprawny JSON?
Nie przy samym prompcie. Nawet jasna instrukcja czasem zawodzi, a odpowiedź ucięta przez limit tokenów wyjściowych zawsze jest niepoprawna. Parsuj każdą odpowiedź prawdziwym parserem JSON, sprawdzaj potrzebne pola i ponawiaj próbę albo zgłaszaj wyraźny błąd, gdy sprawdzenie nie przejdzie.