Przejdź do treści
Piątek, 6 listopada 20264 203 teksty w archiwumNewsletterSklep
na czasie

Warsztat

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.

Tekst: 5 min czytania
Model obiecał JSON, parser zwrócił null. Pięć przebiegów i trzy przyczyny

👁 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:

  1. 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.
  2. Surowe znaki nowej linii w łańcuchu. W poprawnym JSON-ie muszą być zapisane jako n. Model wstawia prawdziwe.
  3. 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_decodeParser tolerancyjny
Poprawny JSONdziaładziała
Ucięty na limicienullodzyskuje kompletne pola
Surowe znaki nowej liniinullodzyskuje
Śmieci przed obiektemnullodzyskuje

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

  1. Loguję koniec odpowiedzi, nie początek. Początek zawsze wygląda dobrze. Dopiero ostatnie 100 znaków mówi, czy odpowiedź jest ucięta.
  2. 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.
  3. 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ć.
  4. 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

SytuacjaCo wybrać
Krótka odpowiedź, kilka pólTryb JSON wystarczy
Długa treść w poluOsobne wywołanie na treść plus tolerancyjny parser
Klasyfikacja, ocena, wybór z listyModel 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.

// Newsletter

Cały tydzień w AI, w jednym mailu

Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.

Zapisz się za darmo →
Za darmo. Wypisujesz się jednym kliknięciem.
// z sieci - nowe teksty w moich serwisach

Wszystkie moje projekty →

Piotr Olszewski

Piotr Olszewski

AUTOR I WYDAWCA

Piotr Olszewski - twórca i autor Promptowego, polskiego serwisu o sztucznej inteligencji. Codziennie śledzi premiery modeli, narzędzia i regulacje AI, i tłumaczy je prostym, konkretnym językiem.

czytaj dalejLM Studio, MCP i lokalne modele: ile VRAM naprawdę potrzebujeszlokalne aiLM 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.

Najczęściej czytane

7 dni
  1. 130 promptów do ChatGPT Images i 10 do przerabiania zdjęć
  2. 2Lokalne AI: modele na własnym komputerze - tekst, obraz, wideo, muzyka
  3. 3Gemini 4 Carbon: co to jest, dostęp w Polsce, cena i benchmarki

// mapa strony

🛒 Sklep Kinetyka CapCut Pro 460 zł/rok Lovable Pro 450 zł/rok Replit Core 119 zł/rok Zobacz wszystko →
promptowy w liczbach 0tekstów w archiwum0newsów z ostatnich 7 dni0modeli wideo w obserwatorium0zagadek w grach
× ‹ powiększenie ›