Structured output i walidacja odpowiedzi LLM w n8n: jak wymuszać format JSON i kontrolować strukturę danych w automatyzacjach AI
Structured output i walidacja odpowiedzi LLM w n8n: jak wymuszać format JSON i kontrolować strukturę danych w automatyzacjach AI
Nie polegam na dobrej woli modelu językowego, jeśli odpowiedź ma trafić do bazy danych, CRM lub kolejnego API. W przepływach n8n wymuszam structured output — zazwyczaj w formacie JSON — a następnie waliduję strukturę, zanim kolejny węzeł spróbuje jej użyć. Bez tego kroku nawet drobna zmiana w zachowaniu modelu potrafi przerwać integrację z CRM lub spowodować zapis niepełnego rekordu w bazie danych.
Dlaczego odpowiedź bez struktury psuje automatyzację
Gdy LLM zwraca swobodną prozę, nawet najprostsza integracja staje się niestabilna. Widzę to regularnie: węzeł HTTP Request pobiera odpowiedź z OpenAI, następnie węzeł Set próbuje odczytać pole email, ale model dodał wyjaśnienie przed JSON-em lub zwrócił markdown w postaci json ... . W efekcie parsowanie kończy się błędem, a cały przepływ przerywa działanie.
Ryzyka są konkretne:
- Zbędne znaczniki — markdownowe bloki, komentarze lub prefiksy typu "Oto wynik:".
- Brakujące klucze — model pominął pole, które kolejny system traktuje jako wymagane.
- Niepoprawne typy — liczba zapisana jako string, boolean jako "tak/nie", lub tablica zamiast obiektu.
- Halucynacje wartości — poprawna struktura, ale wymyślone dane, które przechodzą przez parser, choć nie powinny.
Bez walidacji na granicy między LLM a resztą stosu te błędy wykrywamy dopiero w logach zewnętrznego systemu.
Jak wymusić JSON mode w API LLM w n8n
Najskuteczniejsza metoda zależy od użytego modelu i węzła. W natywnym węźle OpenAI Chat Model ustawiam parametr response_format na { "type": "json_object" }. To zobowiązuje model do zwrócenia syntaktycznie poprawnego JSON-a. Przy pracy z Anthropic Claude przez API nie zawsze mam natywny JSON mode w starszych wersjach endpointu, więc łączę ścisły prompt systemowy z prefill assistant message, w którym zaczynam odpowiedź od nawiasu klamrowego.
W przypadku lokalnych modeli przez Ollama przekazuję w body zapytania parametr "format": "json". Jeśli natywny węzeł n8n nie eksponuje danej opcji, przechodzę na węzeł HTTP Request, aby mieć pełną kontrolę nad body i nagłówkami. To dodatkowy krok konfiguracji, ale w zamian eliminuję niepewność co do tego, czy flaga struktury została rzeczywiście wysłana. Węzeł HTTP Request pozwala też precyzyjnie ustawić temperature na niską wartość — np. 0.1 lub 0.2 — co zmniejsza kreatywność modelu i zwiększa przewidywalność formatu wyjściowego.
Prompt systemowy jako kontrakt strukturalny
Sam JSON mode nie gwarantuje poprawnej semantyki. Model może zwrócić valid JSON z zupełnie innymi polami niż oczekiwane. Dlatego traktuję prompt systemowy jako kontrakt.
Moja praktyka wygląda następująco:
- Definiuję schemat wprost — wypisuję nazwy pól, typy danych i ograniczenia. Przykład:
"priority": string, jedna z wartości ["low", "medium", "high"]. - Zakazuję dodatkowych wyjaśnień — dodaję instrukcję: "Zwróć wyłącznie obiekt JSON. Bez markdown, bez komentarzy, bez tekstu przed lub po."
- Podaję przykład — jeśli schemat jest złożony, dołączam jeden przykład poprawnego obiektu w treści promptu (few-shot), ale bez nadmiernego kontekstu, który mógłby zmylić model.
Przykład promptu systemowego dla ekstrakcji danych z formularza kontaktowego:
Jesteś parserem danych. Zwróć wyłącznie obiekt JSON w formacie: {"name": string, "email": string zawierający @, "topic": jedna z ["offer", "support", "other"], "summary": string do 200 znaków}. Bez markdown, bez wyjaśnień, bez tekstu poza JSONem.
Im bardziej restrykcyjny jest prompt, tym rzadziej muszę korzystać z pętli korekcyjnej.
Walidacja odpowiedzi wewnątrz przepływu n8n
Nawet przy JSON mode odpowiedź wymaga weryfikacji. W n8n umieszczam węzeł Code bezpośrednio po węźle wywołującym LLM. W nim parsuję treść i sprawdzam zgodność ze schematem.
W praktyce używam prostego skryptu w JavaScript:
const raw = $json.response;
let parsed;
try {
parsed = JSON.parse(raw);
} catch (e) {
return [{ json: { error: "INVALID_JSON", raw } }];
}
const required = ["email", "priority", "score"];
for (const key of required) {
if (!(key in parsed)) {
return [{ json: { error: "MISSING_KEY", key } }];
}
}
if (!["low","medium","high"].includes(parsed.priority)) {
return [{ json: { error: "INVALID_ENUM", field: "priority" } }];
}
return [{ json: parsed }];
Następnie węzeł IF rozdziela obiekty z polem error od poprawnych danych.
Typowa walidacja obejmuje:
- Parsowanie —
JSON.parse(); jeśli się nie udaje, przepływ od razu trafia na ścieżkę błędu. - Obecność kluczy — weryfikacja, czy istnieją wszystkie wymagane pola.
- Typy danych — sprawdzenie
typeoflubArray.isArray, a nie string zawierający cyfry. - Wartości wyliczeniowe — czy pole należy do dozwolonego zbioru.
- Długość i zakres — czy string nie przekracza maksymalnej długości lub czy liczba mieści się w oczekiwanym przedziale.
W bardziej złożonych scenariuszach można załadować bibliotekę walidacyjną przez npm w węźle Code, ale w większości przepływów zwykły JavaScript wystarcza i nie wprowadza zależności zewnętrznych.
Obsługa błędów formatu i pętla korekcyjna
Gdy walidacja wykryje problem, nie pozwalam przepływowi kontynuować z uszkodzonymi danymi. Stosuję jedną z dwóch strategii.
Ponowienie z ostrzejszym promptem — węzeł Loop Over Items lub Split In Batches w połączeniu z IF pozwala odesłać błędną odpowiedź do LLM z dodatkową instrukcją korekty. Ograniczam to do maksymalnie dwóch prób, aby nie wpaść w nieskończoną pętlę i nie generować zbędnych kosztów.
Fallback do człowieka — w krytycznych procesach, gdzie dane wrażliwe lub finansowe nie mogą być zautomatyzowane bez pewności, kieruję niepoprawną odpowiedź do ręcznej weryfikacji. W n8n realizuję to przez węzeł Wait z approval lub przez powiadomienie na Slacka lub e-mail z pełnym kontekstem oryginalnego zapytania i surowej odpowiedzi modelu.
W obu przypadkach loguję nieprawidłową odpowiedź — zapisuję ją w bazie lub pliku, aby później przeanalizować, który fragment promptu wymaga poprawy.
Checklist jakości dla structured output w n8n
Przed wdrożeniem przepływu do środowiska produkcyjnego sprawdzam następujące punkty:
- Włączony JSON mode w API lub jawnie wymuszony format w promptach przy użyciu węzła HTTP Request.
- Zdefiniowany schemat z typami danych, wymaganymi polami i dozwolonymi wartościami wyliczeniowymi.
- Węzeł Code weryfikujący strukturę bezpośrednio po odpowiedzi LLM.
- Ścieżka błędu z fallbackiem: ponowienie z korektą, przekierowanie do człowieka lub zatrzymanie przepływu.
- Limit ponowień ustawiony na maksymalnie 2–3 próby.
- Logowanie nieprawidłowych odpowiedzi w celu iteracyjnej poprawy promptów.
Jeśli Twoje automatyzacje AI w n8n muszą dostarczać niezawodną strukturę danych — np. przy integracji z CRM, systemami rezerwacji lub dedykowanym oprogramowaniem — projektuję przepływy z walidacją i obsługą błędów na poziomie produkcyjnym.
Michał Kasprzyk
Tworzę nowoczesne strony internetowe dla firm z całej Polski. Specjalizuję się w szybkich, bezpiecznych i zoptymalizowanych pod SEO witrynach.
Więcej o mniePowiązane artykuły
Automatyczna analiza konkurencji z AI w n8n: jak monitorować strony rywali, oferty i zmiany treści
Tworzę przepływy n8n, które automatycznie monitorują konkurencję, śledzą zmiany ofert i analizują treści stron rywali bez ręcznego przeglądu.
Automatyczna segmentacja bazy klientów z AI w n8n: jak grupować kontakty na podstawie zachowań i danych transakcyjnych bez ręcznej analityki
Jak zbudować w n8n automatyczną segmentację bazy klientów z AI? Opisuję przepływ danych, wybór kryteriów, walidację grup i integrację z CRM.
Automatyczne tłumaczenie treści stron i ofert z AI w n8n: jak budować pipeliney lokalizacji z kontekstem branżowym i glosariuszami
Dowiedz się, jak zbudować w n8n pipeline automatycznego tłumaczenia treści stron i ofert z glosariuszem branżowym, walidacją jakości i integracją z CMS.
Potrzebujesz strony internetowej?
Skontaktuj się ze mną, aby omówić Twój projekt. Pierwsza konsultacja jest bezpłatna.
Zamów bezpłatną wycenę