Model obiecał JSON, parser zwrócił null. Pięć przebiegów i trzy przyczyny
Odpowiedź zaczyna się poprawnie, a parser zwraca pustkę. Trzy przyczyny naraz i rozwiązanie, które przeszło za pierwszym podejściem.

👁 149 przeczytań
Prosisz model o JSON, model obiecuje JSON, a `json_decode` zwraca `null`. Trafiłem na to w tym tygodniu przy automacie, który pisze teksty - i przy okazji zrozumiałem, dlaczego cała kategoria nowych narzędzi powstaje właśnie wokół tego problemu. Jak wymusić poprawny JSON schematem, krok po kroku z działającym kodem, pokazuję w tekście Structured output. Innym wyjściem są modele decyzyjne, które zwracają prawdopodobieństwa zamiast tekstu do parsowania.
Wniosek w jednym akapicie
Model generujący tekst nigdy nie da gwarancji struktury - może ją tylko obiecać. Tryb JSON i structured outputs bardzo podnoszą skuteczność, ale nadal wymuszają format na systemie, który pod spodem produkuje kolejne tokeny. Przy krótkich odpowiedziach to wystarcza. Przy długich - takich, gdzie w polu siedzi cały artykuł HTML - zaczyna się sypać. Trzy rzeczy, które realnie pomagają: tolerancyjny parser, krótsze pola i rezygnacja z jednego wielkiego obiektu.
Co dokładnie się psuje
Mój przypadek: automat prosi o obiekt z czterema polami, gdzie ostatnie zawiera artykuł na 1200 słów w HTML. Model zwracał odpowiedź, która zaczynała się poprawnie - w logu widziałem czysty początek JSON-a z tytułem i opisem. A parser zwracał pustkę.
Przyczyny są trzy i występują razem:
- Ucięcie na limicie tokenów. Długi artykuł nie mieści się w limicie, odpowiedź kończy się w połowie łańcucha, JSON nie ma domknięcia. Treść jest kompletna, struktura nie.
- Surowe znaki nowej linii w łańcuchu. W poprawnym JSON-ie muszą być zapisane jako
n. Model wstawia prawdziwe. - Cudzysłowy w HTML.
class="pw-lead"w środku łańcucha zamyka go przedwcześnie, jeśli nie zostanie poprawnie zabezpieczone.
Ślepy zaułek, w który wszedłem
Skoro JSON się łamie, pomyślałem, żeby go porzucić i poprosić o format ze znacznikami: ===TYTUL===, ===HTML=== i tak dalej. Napisałem parser, wdrożyłem, uruchomiłem. Model dalej zwracał JSON.
Powód okazał się banalny i wart zapamiętania: funkcja, przez którą szło zapytanie, wymuszała tryb JSON na poziomie API. Model nie miał wyboru - moja instrukcja o znacznikach była ignorowana, bo format narzucała warstwa niżej. Straciłem na tym trzy przebiegi, zanim sprawdziłem, co robi sama funkcja, zamiast poprawiać prompt.
Wniosek: gdy model uporczywie ignoruje instrukcję formatu, sprawdź najpierw, czy coś w warstwie transportowej nie narzuca mu innego.
Co zadziałało
Parser dwustopniowy. Najpierw zwykły json_decode. Gdy ten padnie - wyciąganie pól znak po znaku, z obsługą sekwencji n, t i uXXXX, kończone na pierwszym niezabezpieczonym cudzysłowie.
Kluczowa właściwość: taki parser odzyskuje treść z odpowiedzi uciętej. Jeśli JSON urwał się po polu z artykułem, wszystkie wcześniejsze pola są kompletne i da się je odczytać. Zwykły dekoder odrzuca całość.
| json_decode | Parser tolerancyjny | |
|---|---|---|
| Poprawny JSON | działa | działa |
| Ucięty na limicie | null | odzyskuje kompletne pola |
| Surowe znaki nowej linii | null | odzyskuje |
| Śmieci przed obiektem | null | odzyskuje |
Po tej zmianie ten sam automat przeszedł za pierwszym podejściem i wyprodukował tekst na 1228 słów.
Cztery rzeczy, które robię teraz zawsze
- Loguję koniec odpowiedzi, nie początek. Początek zawsze wygląda dobrze. Dopiero ostatnie 100 znaków mówi, czy odpowiedź jest ucięta.
- Długa treść nie jedzie w JSON-ie. Jeśli pole ma mieć więcej niż kilkaset słów, lepiej osobne wywołanie albo format bez ucieczek.
- Waliduję zawartość, nie tylko format. Że JSON się sparsował, nie znaczy, że treść ma sens - u mnie osobny test sprawdza, czy w tekście są wszystkie liczby, które miały być.
- Trzy próby, potem wpis do logu. Po trzech nieudanych model nie „naprawi się” za czwartym razem. Lepiej odnotować porażkę niż opublikować śmieć.
Dlaczego powstaje osobna kategoria narzędzi
Ten problem jest na tyle powszechny, że zaczęły powstawać modele, które w ogóle nie generują tekstu. Zamiast prosić model o JSON i go parsować, zadajesz typowane pytanie i dostajesz wartość z rozkładem prawdopodobieństwa - bez etapu tekstowego, więc bez etapu parsowania.
Opisałem jeden taki przypadek osobno: TypeSafe i model Jev, gdzie API ma trzy typy pytań i zwraca decyzje zamiast łańcuchów. To jest opinia, nie rekomendacja - nie testowałem tego jeszcze na własnym zadaniu. Ale kierunek jest logiczny: jeśli wynik ma konsumować kod, etap pośredni w postaci tekstu jest kosztem, nie funkcją.
Kiedy który sposób
| Sytuacja | Co wybrać |
|---|---|
| Krótka odpowiedź, kilka pól | Tryb JSON wystarczy |
| Długa treść w polu | Osobne wywołanie na treść plus tolerancyjny parser |
| Klasyfikacja, ocena, wybór z listy | Model zwracający typ zamiast tekstu |
| Tysiące drobnych rozstrzygnięć | Model decyzyjny - tam koszt i czas robią różnicę |
Najczęstsze pytania
Dlaczego model zwraca niepoprawny JSON, skoro prosiłem o poprawny?
Bo pod spodem generuje kolejne tokeny, a nie strukturę. Tryb JSON podnosi skuteczność, ale nie daje gwarancji - zwłaszcza gdy odpowiedź zostanie ucięta na limicie.
Czy structured outputs rozwiązują problem?
Bardzo pomagają przy krótkich i średnich odpowiedziach. Przy długiej treści w jednym polu nadal zostaje ryzyko ucięcia.
Jak sprawdzić, czy odpowiedź była ucięta?
Zaloguj jej ostatnie 100 znaków i długość. Ucięta kończy się w środku zdania albo łańcucha, bez domknięcia.
Model ignoruje mój format. Co robić?
Sprawdź, czy warstwa transportowa nie wymusza innego. U mnie funkcja opakowująca narzucała tryb JSON na poziomie API - instrukcja w promptcie nie miała szans.
Czytasz Promptowego częściej? Dodaj go do preferowanych źródeł w Google, a nowe teksty będą wyżej w Twoich wynikach.
Skąd to wiem
Wszystkie przypadki pochodzą z budowy automatu redakcyjnego na tym serwisie we wrześniu 2026. Pięć nieudanych przebiegów, trzy różne przyczyny, każda zdiagnozowana z logów. Parser dwustopniowy działa na produkcji - pierwszy tekst po poprawce przeszedł za pierwszym podejściem i miał 1228 słów.
Strona przez chwilę pokazuje się bez stylów. Znalazłem przyczynę w hookach czyszczenia cacheWarsztat
Grok 4.7 w Cursorze: jak włączyć i kiedy przełączyć na droższy modelWarsztat
Jev od TypeSafe: model decyzyjny, wynik w rankingu AA i mój testWarsztatCały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →- przelomowy.plModele AI domyślają się, że ktoś czyta ich rozumowanie. Preprint testuje 5 modeli
- generatornia.plNajlepszy darmowy generator obrazów AI: 3 modele, te same 4 prompty, 12 obrazów bez poprawek
- dobieracz.plNajlepsze AI do napisów do filmów - Submagic, CapCut, Filmora i trzy inne
- dygot.plLidl i Europa: plan na własną AI. Właściciel dyskontów buduje zaplecze z twórcą superkomputerów
lokalne ai·LM Studio, MCP i lokalne modele: ile VRAM naprawdę potrzebujeszModel na Twoim dysku plus MCP do Twoich plików. Podaję progi VRAM, które decydują o wszystkim, i mówię wprost, kiedy to nie ma sensu.