Function calling i schematy JSON: 9 błędów, które psują ekstrakcję danych w produkcji

Praktyczny przewodnik po function calling i JSON Schema w ekstrakcji danych: pipeline, 9 błędów produkcyjnych, walidacja, retry, testy kontraktowe oraz gotowe schematy i prompty.
07 kwietnia 2026
blog

1. Wprowadzenie: function calling i JSON Schema w ekstrakcji danych

Ekstrakcja danych z nieustrukturyzowanego tekstu, wiadomości e-mail, dokumentów czy transkrypcji rozmów przestała polegać wyłącznie na „sprytnym promptowaniu”. W produkcji liczy się powtarzalność, możliwość automatycznej walidacji oraz przewidywalny format wyjścia. Właśnie tutaj pojawiają się dwa pojęcia, które często idą w parze: function calling oraz JSON Schema.

Function calling to sposób prowadzenia modelu do zwrócenia danych jako argumentów wywołania określonej funkcji (czyli w praktyce: ustrukturyzowanego obiektu), zamiast generowania dowolnego tekstu. Ułatwia to integrację z systemami, które oczekują konkretnych pól, oraz zmniejsza ryzyko „lania wody” lub mieszania danych z komentarzem. JSON Schema natomiast jest formalnym opisem oczekiwanego kształtu danych: jakie pola mają się pojawić, jakich są typów, które są obowiązkowe, jakie wartości są dopuszczalne. Razem tworzą most między światem języka naturalnego a światem kontraktów danych.

Warto rozróżnić ich role:

  • Function calling ustawia „kanał” komunikacji i wymusza intencję: model ma zwrócić dane do dalszego przetworzenia, a nie narrację.
  • JSON Schema definiuje „kontrakt” na dane: struktura, typy i ograniczenia, które pozwalają automatycznie ocenić, czy wynik jest użyteczny.

W kontekście ekstrakcji danych to połączenie daje dwie kluczowe korzyści. Po pierwsze, zwiększa deterministyczność integracji: downstream nie musi zgadywać, gdzie kończy się opis, a zaczyna wartość pola. Po drugie, ułatwia kontrolę jakości: jeśli model zwróci coś niezgodnego z oczekiwaniami, można to wykryć programowo i zareagować (np. ponowić próbę, zastosować alternatywny krok lub oznaczyć rekord jako niekompletny).

Jednocześnie praktyka pokazuje, że samo użycie function calling i schematu nie gwarantuje sukcesu. Błędy projektowe w schemacie, nieprecyzyjne definicje pól, niejednoznaczne wymagania biznesowe albo zbyt rygorystyczne ograniczenia mogą prowadzić do cichych strat danych, częstych odrzuceń walidacji lub nieoczywistych awarii w produkcji. Najczęściej problem nie polega na tym, że model „nie umie”, tylko na tym, że kontrakt danych został opisany w sposób, który nie pasuje do realnych wejść, a integracja nie uwzględnia naturalnej zmienności języka.

Dlatego w ekstrakcji danych warto traktować function calling i JSON Schema jako elementy inżynierii kontraktu: jasno zdefiniowanego porozumienia między modelem, walidacją i systemem docelowym. Dobrze zaprojektowany kontrakt nie tylko porządkuje wynik, ale też ujawnia niejasności domenowe (np. co zrobić, gdy brakuje wartości, gdy są sprzeczne źródła, gdy dane są częściowe) i pozwala z góry zaplanować zachowanie systemu w trudnych przypadkach.

Jak działa pipeline ekstrakcji: prompt → wywołanie funkcji → walidacja → zapis

W produkcyjnej ekstrakcji danych z użyciem LLM kluczowe jest traktowanie odpowiedzi modelu nie jako „ładnego tekstu”, ale jako kontraktu danych. Ten artykuł powstał jako rozwinięcie jednego z najczęstszych tematów poruszanych podczas szkoleń Cognity. Pipeline zwykle składa się z czterech kroków: przygotowania promptu, kontrolowanego zwrócenia danych przez wywołanie funkcji, sprawdzenia zgodności z JSON Schema oraz trwałego zapisu w systemach docelowych. Każdy etap ma inne cele i inne typowe punkty awarii, dlatego warto rozumieć ich role zanim zacznie się optymalizować szczegóły.

1) Prompt: specyfikacja zadania i kontekstu

Prompt w tym kontekście pełni rolę instrukcji, która ma doprowadzić do uzyskania konkretnych pól w przewidzianej strukturze. Zwykle zawiera: cel ekstrakcji (co wyciągamy), źródło danych (np. e-mail, dokument, transkrypcja), zasady interpretacji (np. jak traktować brakujące wartości) oraz oczekiwania jakościowe (np. preferencja dla wartości jawnych zamiast domysłów). Różnica względem klasycznego „promptowania” polega na tym, że tekst nie jest finalnym wynikiem — jest sterowaniem procesu, który ma wygenerować dane zgodne z ustalonym formatem.

Na tym etapie ważne jest też ograniczanie swobody modelu: jednoznaczne definicje pojęć, wskazanie priorytetów (co ważniejsze, kompletność czy precyzja) oraz minimalizowanie niepotrzebnych instrukcji, które mogłyby skutkować dopisywaniem komentarzy zamiast danych.

2) Wywołanie funkcji: wymuszenie struktury odpowiedzi

Mechanizm function calling (lub odpowiedniki oparte o narzędzia) pozwala poprosić model, by zwrócił argumenty „funkcji” jako ustrukturyzowany JSON zamiast swobodnej wypowiedzi. W praktyce oznacza to, że aplikacja definiuje oczekiwane wejście (nazwy pól, typy, zagnieżdżenia), a model stara się wypełnić je treścią wyekstrahowaną z tekstu źródłowego.

To rozwiązanie jest szczególnie przydatne, gdy:

  • potrzebujesz deterministycznie parsowalnych wyników (bez ręcznego wyciągania fragmentów z tekstu),
  • chcesz od razu mapować wynik na obiekty domenowe lub rekordy w bazie,
  • ekstrakcja ma działać automatycznie, bez „człowieka w pętli”.

Należy pamiętać, że function calling zwiększa kontrolę nad formatem, ale nie gwarantuje jeszcze poprawności danych biznesowo ani zgodności ze wszystkimi ograniczeniami — dlatego kolejnym krokiem jest walidacja.

3) Walidacja: kontrola zgodności i jakości danych

Walidacja to etap, w którym wynik modelu jest sprawdzany względem JSON Schema (lub równoważnych reguł). Jej rola jest podwójna: techniczna (czy JSON ma właściwe typy i strukturę) oraz operacyjna (czy można go bezpiecznie przepuścić dalej w pipeline). Walidacja pozwala wcześnie wykryć problemy takie jak brak wymaganych pól, złe typy, nieoczekiwane wartości lub niedozwolone dodatkowe klucze.

W praktyce walidacja często kończy się jedną z decyzji:

  • Akceptacja — dane spełniają kontrakt, można je zapisać.
  • Odrzucenie — wynik jest niepoprawny i nie powinien trafić do systemów docelowych.
  • Naprawa lub ponowienie — wynik jest bliski poprawności, ale wymaga korekty (np. ponownego wywołania modelu lub uzupełnienia braków inną metodą).

Istotna różnica między walidacją a samym function calling polega na tym, że function calling jest mechanizmem generowania, a walidacja jest mechanizmem egzekwowania kontraktu po stronie aplikacji.

4) Zapis: utrwalenie i integracja z systemami docelowymi

Ostatni krok to zapis danych do miejsca, w którym będą dalej używane: bazy danych, indeksu wyszukiwarki, systemu analitycznego, kolejki zdarzeń czy CRM/ERP. Ten etap wymaga szczególnej ostrożności, bo błędne dane po zapisie stają się „prawdą operacyjną” i mogą zasilić raporty, automatyzacje lub decyzje biznesowe.

W dobrze zaprojektowanym pipeline zapis jest poprzedzony walidacją i często wzbogacony o podstawowe metadane, które ułatwiają utrzymanie procesu: identyfikator źródła, czas ekstrakcji, wersję schematu oraz informację o tym, czy wynik pochodził z pełnej ekstrakcji czy z procesu naprawczego. Dzięki temu łatwiej później analizować jakość, odtwarzać decyzje i kontrolować kompatybilność wstecz.

3. 9 typowych błędów produkcyjnych w schematach i integracji (z przykładami)

Poniższe błędy pojawiają się najczęściej wtedy, gdy schemat JSON „jakoś działa” w testach ręcznych, ale zaczyna psuć ekstrakcję przy realnych danych: różnorodnych, niekompletnych i z niespodziankami formatów. W tej sekcji chodzi o szybkie rozpoznanie symptomów i ich przyczyn — bez wchodzenia w pełne strategie napraw, które rozwiniemy później.

1) Niespójność: „required” nie zgadza się z realną dostępnością danych

Najczęstszy sabotaż jakości: oznaczasz pola jako wymagane, choć źródła często ich nie mają (np. e-mail w CV, numer VAT w fakturze, data zakończenia w trwającym zatrudnieniu). Efekt to walidacyjne „twarde” porażki albo wymuszanie przez model zgadywania.

  • Objaw: częste błędy walidacji na brakującym polu albo podejrzane wartości „N/A”, „unknown”, losowe liczby.
  • Przyczyna: schemat narzuca kompletność, której nie ma w danych.
{
  "type": "object",
  "required": ["email"],
  "properties": {
    "email": {"type": "string", "format": "email"}
  }
}

W produkcji taki schemat często „zmusza” model do wypełniania braków, zamiast uczciwie zwrócić brak danych.

2) Brak jawnej nullability albo mylenie null z pustym stringiem

LLM-y często zwracają null dla pól niepewnych lub nieobecnych. Jeśli schemat dopuszcza tylko string, walidacja się wywraca. Z kolei „puste stringi” maskują brak danych i psują downstream (np. indeksowanie, deduplikację).

  • Objaw: type error na null lub ciche przechodzenie pustych wartości jako „poprawnych”.
  • Przyczyna: brak spójnej decyzji: null vs brak pola vs pusty string.
{
  "type": "object",
  "properties": {
    "phone": {"type": "string"}
  }
}

Jeśli model zwróci "phone": null, walidacja poleci. Jeśli zwróci "", walidacja przejdzie, ale dane będą bezużyteczne.

3) Zbyt luźne typy: „string” na wszystko

„Na szybko” wszystko ląduje jako string: kwoty, daty, liczby, statusy. To ułatwia przejście walidacji, ale przerzuca koszt na późniejsze parsowanie, które w produkcji bywa bardziej awaryjne niż sama ekstrakcja.

  • Objaw: rośnie liczba wyjątków przy konwersji, a raporty mają rozjechane formaty (np. „1 234,50”, „1234.5”, „EUR 12”).
  • Przyczyna: schemat nie egzekwuje typów i formatów na granicy systemu.
{
  "type": "object",
  "properties": {
    "amount": {"type": "string"}
  }
}

W efekcie downstream musi rozumieć wszystkie warianty zapisu i lokalizacji.

4) Enum bez tolerancji na warianty (synonimy, wielkość liter, nowe wartości)

Ścisłe enum daje porządek, ale w produkcji wartości przychodzą w wielu wariantach: „paid”, „PAID”, „opłacone”, „zapłacona”. Dochodzą też nowe statusy. Jeśli schemat nie przewiduje normalizacji albo mapowania, walidacja zaczyna odrzucać poprawne rekordy.

  • Objaw: nagły wzrost odrzuceń po zmianie treści w źródłach lub po rozszerzeniu produktu.
  • Przyczyna: zbyt restrykcyjne enum bez warstwy normalizacji.
{
  "type": "object",
  "properties": {
    "status": {"type": "string", "enum": ["paid", "unpaid"]}
  }
}

Model zwróci „PAID” albo „opłacone” i od razu masz błąd kontraktu.

5) Brak ograniczeń dla tablic i obiektów: niekontrolowany rozmiar i „halucynowane” elementy

Jeżeli nie ograniczysz tablic (np. maxItems) albo nie kontrolujesz struktury obiektów, model może „dopisać” dodatkowe pozycje — zwłaszcza gdy tekst sugeruje listę („umiejętności”, „produkty na fakturze”). To psuje koszty, czas przetwarzania i jakość.

  • Objaw: rekordy z setkami elementów, duże payloady, długie czasy walidacji i zapisu, trudności z UI/raportami.
  • Przyczyna: schemat nie ustawia granic, a integracja nie ma limitów.
{
  "type": "object",
  "properties": {
    "skills": {"type": "array", "items": {"type": "string"}}
  }
}

Bez limitów i reguł model potrafi „wymyślić” brakujące pozycje albo mnożyć synonimy.

6) Niejasne nazwy pól i kolizje semantyczne

Pola typu date, value, name czy id bez kontekstu zwiększają ryzyko, że model wstawi niewłaściwą wartość (np. „data wystawienia” zamiast „data płatności”). LLM może działać „logicznie”, ale inaczej niż zakłada produkt.

  • Objaw: poprawny JSON, błędne znaczeniowo dane (najgorszy przypadek — trudno to wykryć automatycznie).
  • Przyczyna: schemat ma zbyt ogólne nazwy i brakuje doprecyzowania w kontrakcie.
{
  "type": "object",
  "properties": {
    "date": {"type": "string"},
    "id": {"type": "string"}
  }
}

Technicznie „OK”, biznesowo często „nie”.

7) Dopuszczanie nieznanych pól (albo przeciwnie: ścinanie ich bez planu)

Dwa skrajne problemy:

  • Za luźno: jeśli dopuszczasz dowolne pola, model może dorzucić „pomocnicze” klucze, które rozlewają się po systemie (np. logika mapowania, analityka, cache).
  • Za twardo: jeśli wszystko poza zdefiniowanymi polami jest odrzucane, tracisz potencjalnie przydatne informacje i utrudniasz ewolucję.
  • Objaw: „schema drift” w danych albo ciągłe porażki przy minimalnych zmianach.
  • Przyczyna: brak świadomej decyzji co do dodatkowych właściwości i procesu rozszerzania.
{
  "type": "object",
  "properties": {
    "title": {"type": "string"}
  }
  // brak decyzji o dodatkowych polach
}

W praktyce potrzebujesz jasnej polityki: czy nadmiar ignorujesz, zapisujesz do „extras”, czy blokujesz.

8) Rozjazd między schematem a kodem integracji (nazwa funkcji, ścieżki, wersje)

Nawet perfekcyjny schemat nie pomoże, gdy integracja oczekuje innej struktury: innej nazwy pola, innego poziomu zagnieżdżenia, innej wersji kontraktu. To częsty efekt zmian „w ostatniej chwili” albo równoległej pracy nad promptem i backendem.

  • Objaw: walidacja przechodzi, ale zapis/mapper sypie wyjątkami; albo odwrotnie: model generuje poprawnie, a walidator używa starej wersji.
  • Przyczyna: brak jednego źródła prawdy dla schematu i jego wersjonowania w kodzie.
// Integracja oczekuje:
// payload.customer.email
// Schemat/model zwraca:
// payload.client.email

To „błąd kleju” — często najdroższy w diagnozie, bo wygląda jak losowa awaria.

9) Brak deterministycznej polityki dla wartości niepewnych (confidence, cytaty, źródła)

W produkcji część pól jest niejednoznaczna. Jeśli schemat nie przewiduje miejsca na sygnał niepewności lub na wskazanie źródła (np. fragment tekstu), model bywa zmuszony do „jednej odpowiedzi” nawet wtedy, gdy nie powinien.

  • Objaw: dane wyglądają kompletne, ale rośnie liczba błędów merytorycznych; trudność w audycie „skąd ta wartość się wzięła”.
  • Przyczyna: kontrakt wymaga wartości, a nie dopuszcza bezpiecznego „nie wiem” lub uzasadnienia.
{
  "type": "object",
  "properties": {
    "invoice_number": {"type": "string"}
  },
  "required": ["invoice_number"]
}

Jeśli numer jest nieczytelny lub go brak, model może wymyślić. Problem ujawnia się dopiero na etapie reklamacji lub ręcznego audytu.

Tabela skrótowa: błąd → typowy skutek

BłądTypowy skutek w produkcji
„required” nieadekwatnewalidacja pada albo model zgaduje
Brak nullabilitytype error na null / maskowanie braków
Wszystko jako stringkosztowna normalizacja później, rozjazd formatów
Sztywne enumodrzuty na wariantach i nowych statusach
Brak limitów tablic/strukturpayload rośnie, „dopisywanie” elementów
Niejasne nazwy pólbłędy semantyczne niewykrywalne walidacją
Nieustalona polityka dodatkowych pólschema drift albo blokowanie ewolucji
Rozjazd schemat–integracjalosowe awarie mappera, wersjonowanie chaos
Brak strategii dla niepewności„pewne” dane, które są błędne i nieaudytowalne
💡 Pro tip: Traktuj schemat jak kontrakt produkcyjny: jeśli jakieś pole bywa nieobecne lub niepewne, nie wymuszaj go „required” bez dopuszczenia null i jasnej polityki braków, bo model zacznie zgadywać, a pipeline będzie losowo pękał.

4. Dobre praktyki projektowania JSON Schema pod LLM

Dobrze zaprojektowany JSON Schema nie tylko „opisuje dane”, ale też steruje zachowaniem modelu: ogranicza pole manewru, zmniejsza liczbę halucynacji i ułatwia walidację po stronie aplikacji. Poniżej praktyki, które najczęściej poprawiają jakość ekstrakcji w produkcji, bez wchodzenia w szczegóły implementacyjne.

Doświadczenie Cognity pokazuje, że rozwiązanie tego problemu przynosi szybkie i zauważalne efekty w codziennej pracy — zwłaszcza gdy schemat jest traktowany jak kontrakt, a nie „luźna sugestia” dla modelu.

4.1. Dobieraj typy tak, by minimalizować interpretację

LLM świetnie radzi sobie z tekstem, ale w ekstrakcji produkcyjnej liczy się jednoznaczność. Im bardziej precyzyjny typ, tym mniej miejsca na domysły.

PotrzebaZalecany typ w schemacieDlaczego działa lepiej
Liczby, kwoty, miarynumber lub integer (czasem string + format)Eliminuje „3 tys.” vs „3000”; upraszcza agregacje
IdentyfikatorystringUnikasz obcinania zer wiodących i konwersji
Flagi/checkboxybooleanWymusza jednoznaczne tak/nie
Klasy/stanstring + enumWymusza słownik wartości, redukuje synonimy
Listy elementówarray + itemsModel rzadziej „spłaszcza” lub skleja elementy

Praktyczna zasada: jeśli pole ma ograniczony słownik — użyj enum. Jeśli ma stałą strukturę — użyj obiektu z opisanymi właściwościami. Jeśli to „cokolwiek” — rozważ, czy to pole jest w ogóle potrzebne w schemacie.

4.2. required traktuj jako kontrakt, nie życzenie

W kontekście LLM required działa jak silny sygnał: „to musi się pojawić”. Używaj go tylko tam, gdzie naprawdę chcesz wymusić obecność klucza — nawet jeśli wartość ma być pusta/nieznana.

  • Wymagaj klucza, gdy downstream (baza, analityka, UI) zakłada jego istnienie.
  • Nie wymagaj pól, które bywają nieobecne w źródle i nie chcesz sztucznego zgadywania.
  • Jeśli potrzebujesz stabilnej struktury, często lepiej jest wymagać pola, ale dopuścić null (patrz niżej), niż pozostawiać je całkiem opcjonalne.

Unikaj projektów, gdzie prawie wszystko jest required — to zwykle prowadzi do „wymyślania” wartości, byle przejść walidację.

4.3. Nullability: rozróżnij „brak” od „nie wiadomo”

W ekstrakcji masz co najmniej trzy stany: wartość znana, wartość nieznana, wartość nie dotyczy. JSON Schema pozwala to modelować, ale trzeba być konsekwentnym.

  • Pole opcjonalne (nie ma klucza) — sygnał: „nie oczekujemy tego zawsze”.
  • Pole obecne, ale null — sygnał: „oczekujemy, ale nie udało się ustalić / brak w źródle”.
  • Pole obecne z wartością — stan docelowy.

W praktyce dla LLM najczytelniejsze jest: stała obecność kluczy kluczowych i dopuszczenie null tam, gdzie dane bywają niepełne — ogranicza to chaos w integracji i ułatwia mapowanie.

{
  "type": "object",
  "properties": {
    "email": { "type": ["string", "null"], "format": "email" },
    "phone": { "type": ["string", "null"] }
  },
  "required": ["email", "phone"]
}

4.4. Ograniczenia: mniej „swobody”, mniej błędów

Ograniczenia w schemacie działają jak barierki na autostradzie: model nadal jedzie, ale trudniej mu „wypaść” w dziwne formaty.

  • enum — najlepszy sposób na spójne wartości statusów, typów dokumentów, kategorii.
  • minLength/maxLength — ogranicza puste lub przesadnie długie pola (np. komentarze sklejone z innymi danymi).
  • minimum/maximum — dla ilości, cen, procentów; redukuje „odjazdy” modeli.
  • pattern — ostrożnie; przydatne dla identyfikatorów, ale zbyt restrykcyjne regexy powodują częste odrzuty.
  • additionalProperties: false — wymusza, by model nie dopisywał własnych kluczy; zwiększa przewidywalność kontraktu.

Warto pamiętać o kompromisie: im bardziej restrykcyjny schemat, tym częściej walidacja odrzuci wynik. Dlatego ograniczenia powinny odzwierciedlać realne dane, a nie idealny świat.

4.5. Modeluj zagnieżdżenia i listy w sposób „LLM-friendly”

Najczęstszy problem w zagnieżdżeniach to mieszanie poziomów (np. pole z nagłówka trafia do elementu listy). Pomaga projektowanie struktur, w których:

  • obiekty mają jasne granice (nagłówek vs pozycje),
  • listy mają jednorodny typ elementów (items jako obiekt z konkretnymi polami),
  • pola „globalne” nie występują powtórnie w elementach listy, jeśli nie muszą.

Jeśli masz relacje typu „pozycje faktury”, trzymaj je w array obiektów, a nie w pojedynczym stringu z separatorami.

4.6. Opisy i nazewnictwo: steruj semantyką, nie tylko strukturą

JSON Schema to nie tylko typy. Dla LLM ogromne znaczenie mają nazwy pól i opisy (np. description). Dobre praktyki:

  • Stosuj jednoznaczne nazwy: issue_date zamiast date, total_gross zamiast sum.
  • Trzymaj spójny styl: snake_case lub camelCase — konsekwentnie.
  • W description doprecyzuj: jednostki, źródło prawdy, zasady (np. „ISO 8601”, „kwota w walucie z dokumentu”).
  • Unikaj nazw wieloznacznych: status bez słownika; lepiej payment_status z enum.

Opisy powinny być krótkie i operacyjne — to nie miejsce na dokumentację biznesową, tylko na wskazówki, które zmniejszają ryzyko błędnej interpretacji.

4.7. Versioning schematu: planuj zmiany bez zrywania kontraktu

W produkcji schemat będzie ewoluował. Zamiast „cichych” zmian w miejscu, wprowadź minimalną strategię wersjonowania:

  • Numer wersji w danych (np. pole schema_version) lub w metadanych wywołania — ułatwia routing i diagnostykę.
  • Zmiany kompatybilne wstecz: dodawanie opcjonalnych pól, rozszerzanie enum (ostrożnie), poluzowanie ograniczeń.
  • Zmiany niekompatybilne: zmiana typu, zmiana znaczenia pola, usunięcie pola — wymagają nowej wersji schematu.
  • Deprecation: pozostaw pole przez pewien czas, ale oznacz jako wycofywane w opisie, zanim je usuniesz.

Kluczowe jest to, by wersjonowanie traktować jak część kontraktu integracyjnego: pomaga utrzymać stabilność pipeline’u mimo iteracji nad promptami i logiką ekstrakcji.

💡 Pro tip: Projektuj JSON Schema tak, by ograniczać interpretację LLM: precyzyjne typy, kontrolowane enumy, limity dla tablic, jednoznaczne nazwy z krótkimi opisami oraz wersjonowanie schematu dają stabilną integrację i mniej halucynacji.

5. Odporność i jakość danych: walidacja, retry, partial parsing, fallbacki i obserwowalność

W produkcyjnej ekstrakcji danych z użyciem function calling i JSON Schema problemem rzadko bywa „czy model coś zwróci”, a częściej: czy zwróci to w formie, którą da się bezpiecznie zapisać, zinterpretować i utrzymać w czasie. Odporność (resilience) dotyczy tego, jak system zachowuje się przy błędach i niepewności, a jakość danych (data quality) — czy wynik jest kompletny, poprawny i spójny z oczekiwaniami biznesowymi.

Walidacja: kontrakt techniczny vs jakość semantyczna

Walidacja to pierwsza linia obrony, ale warto rozdzielić dwa poziomy:

  • Walidacja schematu (syntaktyczna/strukturalna) — sprawdza zgodność z JSON Schema: typy, wymagane pola, zakresy, formaty.
  • Walidacja reguł domenowych (semantyczna) — sprawdza sens danych: zależności między polami, logikę biznesową, spójność z kontekstem wejścia.

JSON Schema dobrze pilnuje kształtu danych, ale reguły domenowe zwykle wymagają dodatkowej warstwy (np. osobne asercje po walidacji schematu). Kluczowe jest, by mieć jednoznaczną decyzję: co uznajemy za „twardy błąd”, a co za „brak danych/niepewność”, którą można oznaczyć i zapisać.

Retry: kiedy ponawiać, a kiedy od razu degradować

Retry ma sens, gdy błąd jest przejściowy lub wynika z niejednoznaczności generacji, którą model może poprawić po doprecyzowaniu. Typowe sytuacje:

  • Błąd walidacji schematu (np. zły typ, brak required) — często pomaga retry z informacją zwrotną o błędzie.
  • Problemy transportowe (timeout, 5xx, limity) — retry z backoffem.
  • Niska pewność ekstrakcji — retry tylko jeśli masz mechanizm oceny (heurystyki, scoring) i koszt jest uzasadniony.

Retry nie jest uniwersalnym lekarstwem: jeśli wejście jest ubogie lub sprzeczne, powtarzanie generacji podnosi koszty bez poprawy jakości. W takich przypadkach lepsza jest degradacja: częściowy wynik, fallback lub oznaczenie pola jako „nieustalone”.

Problem Lepsze podejście Uwaga produkcyjna
JSON niezgodny ze schematem Retry z komunikatem walidatora Limit prób + logowanie diffu błędów
Brak danych w źródle Partial parsing + oznaczenia braków Nie wymuszaj halucynowania wartości
Timeout / 5xx / rate limit Retry z backoffem i jitterem Wydziel ścieżkę „transient errors”
Sprzeczne informacje w wejściu Fallback + flagi niepewności Priorytety źródeł i reguły rozstrzygania

Partial parsing: częściowy wynik jest lepszy niż żaden (jeśli jest oznaczony)

W ekstrakcji biznesowej często ważniejsze jest pozyskanie części poprawnych pól niż odrzucenie całego rekordu. Partial parsing polega na:

  • zapisaniu pól, które przeszły walidację i reguły domenowe,
  • odnotowaniu pól niepewnych / błędnych jako brak,
  • dodaniu metadanych: co się nie udało, dlaczego i w jakim stopniu.

To podejście wymaga, by schemat i model danych przewidywały stan „nie udało się ustalić” (np. poprzez dodatkowe pola statusowe albo jawne rozróżnienie między null, pustym stringiem i brakiem klucza). W przeciwnym razie system będzie „maskował” problemy, a jakość spadnie niezauważenie.

Fallbacki: alternatywne ścieżki, gdy function calling zawodzi

Fallback to kontrolowana alternatywa, gdy podstawowy tryb ekstrakcji nie daje poprawnego wyniku. Najczęstsze klasy fallbacków:

  • Fallback promptowy — prostszy schemat/zakres, mniejsza liczba pól, bardziej jednoznaczne instrukcje.
  • Fallback deterministyczny — regex/parsowanie klasyczne dla pól łatwych (np. daty, numery), gdy model jest niestabilny.
  • Fallback źródłowy — przełączenie na inne źródło danych lub inny „widok” dokumentu (np. OCR vs tekst natywny), jeśli to dostępne.

Ważne, by fallback nie był „cichym” obejściem. Powinien zostawiać ślad: jaka ścieżka została użyta i dlaczego. To umożliwia późniejsze usprawnienia oraz ocenę kosztów i skuteczności.

Obserwowalność: mierzalność jakości i szybka diagnoza

Bez obserwowalności trudno odróżnić spadek jakości modelu od problemów schematu, danych wejściowych lub infrastruktury. Minimalny zestaw sygnałów dla pipeline’u ekstrakcji to:

  • Metryki walidacji: odsetek rekordów zgodnych ze schematem, top błędy walidatora, liczba prób na rekord.
  • Metryki kompletności: wypełnienie pól (coverage), odsetek null/braków per pole, dystrybucje wartości.
  • Metryki jakości domenowej: odsetek rekordów odrzuconych przez reguły biznesowe, konflikty między polami.
  • Metryki wydajności i kosztu: latencja end-to-end, tokeny/koszt na rekord, retry rate, udział fallbacków.
  • Tracing i logi diagnostyczne: korelacja wejście → odpowiedź → błędy walidacji → zapis; próbkowanie danych z anonimizacją.

Praktyczna zasada: loguj dość, aby odtworzyć przyczynę (np. błędy walidacji i skrót wejścia), ale nie przechowuj niepotrzebnie wrażliwych danych. Warto też mieć alerty na trendy, nie tylko na awarie: rosnący udział fallbacków albo spadek coverage pojedynczego pola to często pierwszy objaw regresji.

Krótki przykład: pętla walidacja → retry → fallback (szkic)

result = call_llm_function(input)
if validate_schema(result) and validate_domain(result):
    save(result, meta={"path":"primary"})
else:
    for attempt in range(2):
        result2 = call_llm_function(input, feedback=get_validation_errors())
        if validate_schema(result2) and validate_domain(result2):
            save(result2, meta={"path":"retry", "attempt": attempt+1})
            break
    else:
        partial = partial_extract(result)  # tylko to, co przechodzi walidacje
        save(partial, meta={"path":"fallback", "reason":"validation_failed"})

Ten szkic pokazuje intencję: nie przepuszczać błędów „na ślepo”, ale też nie tracić całego rekordu, jeśli część danych jest poprawna.

Najważniejsze decyzje architektoniczne w tej sekcji

  • Co jest twardym błędem (blokuje zapis), a co brakiem (może być zapisane z oznaczeniem)?
  • Jakie błędy kwalifikują się do retry, a jakie od razu do fallback?
  • Jak reprezentujesz niepewność i brak danych, aby nie mieszać ich z poprawnymi wartościami?
  • Jakie metryki jakości są „kontraktowe” i podlegają alertowaniu?

6. Testy i utrzymanie: testy kontraktowe, zestawy regresyjne, fixture’y i migracje schematów

W produkcji ekstrakcja danych z użyciem function calling i JSON Schema jest tak dobra, jak jej utrzymanie. Modele, prompty, biblioteki walidacyjne i źródła danych zmieniają się niezależnie, a najmniejsze przesunięcia (np. nowy wariant formatu daty, dodatkowe pole, inne nazewnictwo) potrafią zepsuć stabilność pipeline’u. Dlatego potrzebujesz czterech filarów: testów kontraktowych (zgodność z umową schematu), regresji (czy dziś działa tak jak wczoraj), fixture’ów (powtarzalnych danych testowych) oraz migracji schematów (kontrolowane zmiany wersji i kompatybilności).

Testy kontraktowe: „umowa” pomiędzy LLM a systemem

Testy kontraktowe sprawdzają, czy odpowiedź z function calling jest zgodna z obowiązującym JSON Schema i czy spełnia kluczowe reguły biznesowe (np. wymagane pola, typy, zakresy). Ich celem nie jest ocena „jakości językowej” modelu, tylko egzekwowanie niezmienników, od których zależy zapis do bazy, integracje downstream lub analityka.

  • Co testują: walidację JSON Schema, obecność wymaganych pól, formaty (np. data), spójność relacji w obiektach zagnieżdżonych, ograniczenia typu enum/min/max.
  • Kiedy uruchamiać: przy zmianie schematu, promptu, wersji modelu, narzędzi walidacji lub formatu wejścia.
  • Jak interpretować wynik: fail oznacza złamanie kontraktu; to sygnał do korekty schematu/promptu albo dodania obsługi kompatybilności.

Zestawy regresyjne: stabilność w czasie

Regresja odpowiada na pytanie: czy po zmianie czegokolwiek w systemie nadal uzyskuję porównywalne, akceptowalne wyniki? W ekstrakcji danych regresja bywa ważniejsza niż „peak accuracy”, bo produkcja wymaga przewidywalności. Zestaw regresyjny powinien reprezentować typowe i trudne przypadki wejściowe oraz obejmować zarówno sukcesy, jak i kontrolowane porażki (np. brak danych).

  • Co mierzyć: odsetek poprawnie zwalidowanych odpowiedzi, częstość braków w polach krytycznych, rozkłady wartości (np. liczby), stabilność normalizacji (np. waluty, jednostki).
  • Co porównywać: wyniki „przed/po” dla tej samej paczki wejść; zmiany powinny być jawne i uzasadnione (np. poprawa parsera lub nowa wersja schematu).
  • Jak używać: jako bramka w CI/CD dla zmian promptów, konfiguracji function calling i schematów.

Fixture’y: powtarzalne dane testowe zamiast „losowych przykładów”

Fixture’y to uporządkowane próbki wejść (oraz oczekiwań) wykorzystywane w testach kontraktowych i regresyjnych. Dobrze przygotowane fixture’y minimalizują flaki testów i ułatwiają debugowanie, bo nie zależą od przypadkowych danych z produkcji ani od ręcznie dobieranych, niepowtarzalnych przykładów.

  • Rodzaje fixture’ów: proste przypadki (happy path), przypadki brzegowe (puste pola, wieloznaczności), przypadki „brudne” (literówki, różne formaty), oraz przypadki długie/skomplikowane (zagnieżdżenia, listy).
  • Oczekiwania: nie zawsze muszą być pełnym JSON-em „1:1” (to bywa kruche); często wystarczą asercje na polach krytycznych i warunkach walidacji.
  • Higiena: wersjonuj fixture’y razem ze schematem i promptami; usuwaj lub oznaczaj te, które przestały reprezentować realne dane.

Migracje schematów: zmiany bez wywracania produkcji

Schematy żyją: dochodzą pola, zmieniają się nazwy, pojawiają się nowe typy lub relacje. Bez planu migracji ryzykujesz, że nowy schemat złamie kompatybilność z zapisami historycznymi, konsumentami danych albo narzędziami walidacji. Migracje schematów to praktyka kontrolowania zmian tak, aby wdrożenia były przewidywalne.

  • Wersjonowanie: utrzymuj jawny numer wersji schematu (np. w metadanych) i traktuj go jak kontrakt API.
  • Kompatybilność: rozróżniaj zmiany kompatybilne (np. dodanie opcjonalnego pola) i niekompatybilne (np. zmiana typu, usunięcie pola wymagwanego).
  • Strategia wdrożenia: umożliwiaj współistnienie wersji (odczyt wielu wersji, zapis jednej) przez okres przejściowy; dopiero potem porządkuj dane.
  • Backfill: jeżeli zmiana wpływa na dane historyczne, zaplanuj proces uzupełnienia/przeliczenia oraz walidację po migracji.

Krótka mapa: co do czego służy

ElementCelTypowy sygnał problemuKiedy najbardziej pomaga
Testy kontraktoweWymuszenie zgodności z JSON SchemaBłędy walidacji, brak wymaganych pólZmiany schematu/promptu/modelu
Zestawy regresyjneStabilność wyników w czasieSpadek pass rate, „dryf” wartościRefaktoryzacje i optymalizacje
Fixture’yPowtarzalne i reprezentatywne testyFlaki, trudny debugBudowa wiarygodnego CI
Migracje schematówKontrolowane zmiany kontraktuNiezgodność danych historycznychEwolucja produktu i danych

Minimalny szkic testu kontraktowego (uzupełniająco)

// Pseudokod: waliduj wynik function calling względem schematu
// i dodaj kilka asercji na polach krytycznych.

const result = callLLMWithFunction(input);
assert(isValidJsonSchema(result, schema));
assert(result.order_id && typeof result.order_id === "string");
assert(Array.isArray(result.items));

Najważniejsze jest, by testy i migracje traktować jak element produktu: utrzymywać je w repozytorium, uruchamiać automatycznie i aktualizować razem ze schematami. To obniża koszt zmian i sprawia, że ekstrakcja danych jest przewidywalna nawet wtedy, gdy otoczenie (modele, dane wejściowe, wymagania) stale się przesuwa.

7. Przykładowe schematy oraz prompty do ekstrakcji (warianty: proste, zagnieżdżone, wieloźródłowe)

Poniżej znajdują się trzy krótkie wzorce: prosty (pojedynczy rekord), zagnieżdżony (obiekt + lista elementów) oraz wieloźródłowy (łączenie kilku fragmentów wejścia w jeden ustandaryzowany wynik). Każdy wariant pokazuje, jak dopasować styl promptu do poziomu złożoności danych i tego, czy model ma tylko przepisać fakty, czy również dokonać lekkiej normalizacji (np. ujednolicić walutę lub format daty) bez dopowiadania braków.

Wariant A: Prosta ekstrakcja (pojedynczy rekord)

Zastosowanie: gdy chcesz wyciągnąć kilka pól z jednego krótkiego tekstu (np. e-mail, notatka, fragment czatu) i zapisać je w płaskiej strukturze. To najlepszy start do wdrożeń, bo minimalizuje ryzyko rozjazdów między treścią a schematem.

Minimalny schemat (opis pól):

  • source_id (string): identyfikator dokumentu/wiadomości przekazany z systemu.
  • entity_type (string): typ encji, np. „order”, „lead”, „ticket”.
  • fields (object): słownik prostych wartości, np. email, phone, order_number, total_amount, currency.
  • confidence (number): ocena pewności (0–1) dla całości rekordu.

Prompt (szablon):

  • Wyciągnij tylko informacje wprost obecne w tekście. Nie zgaduj.
  • Jeśli pole nie występuje, pomiń je albo ustaw na null (zgodnie z Twoją umową danych).
  • Nie dodawaj komentarzy. Zwróć wyłącznie dane zgodne ze schematem.

Kiedy działa najlepiej: gdy dane wejściowe są krótkie i jednoznaczne, a system downstream oczekuje płaskich pól.

Wariant B: Zagnieżdżona ekstrakcja (obiekt + elementy listy)

Zastosowanie: faktury, zamówienia, paragony, podsumowania, gdzie masz nagłówek (np. dane klienta) oraz listę pozycji (np. linie towarowe). Zagnieżdżenie porządkuje dane i ułatwia walidację relacji (np. suma pozycji vs. total).

Minimalny schemat (opis pól):

  • document (object): metadane dokumentu, np. document_type, document_number, issue_date.
  • seller (object): dane sprzedawcy (np. nazwa, identyfikatory, adres).
  • buyer (object): dane nabywcy.
  • line_items (array): lista pozycji; każda pozycja zawiera np. description, quantity, unit_price, line_total, tax_rate.
  • totals (object): podsumowania, np. net, tax, gross, currency.
  • notes (string | null): uwagi, jeśli występują.

Prompt (szablon):

  • Najpierw zidentyfikuj nagłówek dokumentu, potem wypisz wszystkie pozycje w kolejności występowania.
  • Nie scalaj pozycji o podobnych nazwach; każda linia to osobny element listy.
  • Kwoty i daty zapisuj w jednym, spójnym formacie; jeśli w tekście jest inny format, dokonaj wyłącznie konwersji, bez „uzupełniania” brakujących danych.
  • Jeśli nie ma pewności co do pola, pozostaw je puste/null zamiast zgadywać.

Kiedy działa najlepiej: gdy wejście ma naturalną strukturę „nagłówek + pozycje” i potrzebujesz zachować granularność elementów.

Wariant C: Wieloźródłowa ekstrakcja (łączenie wielu fragmentów wejścia)

Zastosowanie: gdy dane o tej samej encji przychodzą z kilku kanałów naraz (np. opis transakcji + dane z formularza + metadane systemowe). Celem jest ujednolicenie wyniku w jeden kontrakt, z możliwością wskazania, skąd dana wartość pochodzi i czy nie ma konfliktów.

Minimalny schemat (opis pól):

  • record_id (string): identyfikator rekordu w Twoim systemie.
  • normalized (object): kanoniczne pola po ujednoliceniu (np. customer_email, amount, currency, event_time).
  • sources (array): lista źródeł z danymi surowymi i ich typem (np. „form”, „email”, „log”).
  • provenance (object): mapowanie: dla każdego pola w normalized wskazanie źródła (np. identyfikator fragmentu wejścia) oraz krótkiej podstawy (cytat/odwołanie), jeśli to dozwolone.
  • conflicts (array): wykryte sprzeczności (np. dwie różne waluty lub kwoty) wraz z listą wartości i źródeł.

Prompt (szablon):

  • Potraktuj wejście jako zestaw niezależnych źródeł; nie mieszaj ich w trakcie odczytu.
  • Zbuduj normalized wybierając najbardziej wiarygodną wartość na podstawie hierarchii źródeł podanej w instrukcji (np. metadane systemowe > formularz > treść e-mail).
  • Jeśli dwie wartości są sprzeczne i nie da się rozstrzygnąć, wpisz konflikt i nie „uśredniaj”.
  • Dodaj pochodzenie każdej kluczowej wartości, żeby downstream mógł audytować decyzję.

Kiedy działa najlepiej: gdy priorytetem jest spójny rekord i audytowalność pochodzenia danych, a nie tylko „wydobycie pól” z jednego tekstu.

Jak dobrać wariant do problemu

  • Prosty: maksymalna stabilność i szybkość wdrożenia; minimalna złożoność danych.
  • Zagnieżdżony: gdy ważna jest struktura i powtarzalne elementy (listy), np. pozycje.
  • Wieloźródłowy: gdy chcesz scalić informacje i zachować ślad pochodzenia oraz obsłużyć konflikty.

W praktyce te trzy podejścia można łączyć: zacząć od płaskiego kontraktu, a następnie przejść na zagnieżdżenie i dodać warstwę wieloźródłową tam, gdzie rośnie liczba wejść i ryzyko sprzeczności.

Podsumowanie i checklisty wdrożeniowe

Function calling i JSON Schema pozwalają zamienić nieustrukturyzowany tekst w dane o przewidywalnym kształcie. Function calling ustala „kanał” zwracania informacji (wywołanie narzędzia i argumenty), a JSON Schema definiuje kontrakt danych: typy, wymagane pola, ograniczenia i oczekiwany format. W praktyce sukces zależy mniej od samego modelu, a bardziej od tego, czy schemat i integracja wymuszają spójność, walidują wynik i potrafią obsłużyć odchylenia bez psucia produkcji.

Najczęściej problemy nie biorą się z jednego spektakularnego błędu, tylko z drobnych decyzji: zbyt luźnych typów, niejednoznacznej nullowalności, braku wersjonowania, niedoszacowania retry i braku obserwowalności. Poniższe checklisty pomagają przejść od prototypu do stabilnego wdrożenia, utrzymując jakość danych i kontrolę nad zmianami.

Checklist: kontrakt danych (JSON Schema)

  • Jasny cel schematu: schemat opisuje to, co ma trafić do systemu, a nie wszystko, co może pojawić się w tekście.
  • Spójne typy: unikaj „uniwersalnych” typów; każdy atrybut ma jeden sens i jeden typ.
  • Wymagalność pól: z góry zdecyduj, które pola są obowiązkowe, a które opcjonalne; to wpływa na downstream.
  • Nullability i brak danych: ustal jedną zasadę (np. brak pola vs null) i konsekwentnie ją stosuj.
  • Ograniczenia: tam gdzie to ma znaczenie dodaj limity (formaty, zakresy, długości, enumeracje), aby ograniczyć „kreatywność” modelu.
  • Nazewnictwo: jednoznaczne, stabilne klucze; bez synonimów i dublowania znaczeń.
  • Wersjonowanie: każda zmiana kontraktu ma wersję i plan migracji; unikaj „cichych” breaking changes.

Checklist: prompt i wywołanie funkcji

  • Instrukcje zgodności: jasno powiedz, że wynik musi pasować do schematu; bez dodatkowych komentarzy w danych.
  • Priorytety ekstrakcji: wskaż, co jest ważniejsze, gdy dane są niepełne lub sprzeczne.
  • Reguły normalizacji: zdefiniuj oczekiwane formaty (daty, waluty, jednostki), aby wynik był gotowy do użycia.
  • Deterministyczne zachowanie: preferuj stabilność nad „bogactwem” odpowiedzi; dane mają być powtarzalne.
  • Obsługa niepewności: przewidź, jak model ma sygnalizować brak pewności (np. pola pomocnicze, statusy), zamiast zgadywać.

Checklist: walidacja, odporność, fallback

  • Walidacja po stronie aplikacji: zawsze waliduj wynik; nie zakładaj poprawności tylko dlatego, że jest to function calling.
  • Retry z polityką: zdefiniuj kiedy ponawiać, ile razy i z jaką modyfikacją (np. doprecyzowanie instrukcji).
  • Degradacja kontrolowana: ustal, czy lepszy jest wynik częściowy, czy twardy błąd; nie mieszaj strategii przypadkowo.
  • Fallback: miej plan awaryjny (np. prostszy schemat lub tryb ograniczony), aby nie blokować przetwarzania.
  • Bezpieczeństwo danych: filtruj i minimalizuj dane wejściowe/wyjściowe, pilnuj zgodności z wymaganiami prywatności.

Checklist: obserwowalność i utrzymanie

  • Metryki jakości: mierz odsetek walidacji, typy błędów, kompletność pól i rozkłady wartości.
  • Logowanie diagnostyczne: zapisuj kontekst potrzebny do debugowania (z kontrolą wrażliwych danych), w tym wersję schematu.
  • Alerty: ustaw progi na nagłe skoki błędów walidacji, pustych pól i zmianę rozkładów.
  • Kontrola zmian: traktuj schemat jak API; przeglądy zmian, kompatybilność wsteczna, plan migracji danych.
  • Regresja: utrzymuj zestaw przykładów wejść, na których weryfikujesz, czy zmiany nie psują ekstrakcji.

Jeśli te checklisty są spełnione, function calling i JSON Schema stają się narzędziami do budowania przewidywalnych, audytowalnych pipeline’ów ekstrakcji: takich, które nie tylko „działają na demo”, ale utrzymują jakość danych mimo szumu wejścia, zmian w modelach i ewolucji wymagań biznesowych.

W Cognity łączymy teorię z praktyką – dlatego ten temat rozwijamy także w formie ćwiczeń na szkoleniach.

icon

Formularz kontaktowyContact form

Imię *Name
NazwiskoSurname
Adres e-mail *E-mail address
Telefon *Phone number
UwagiComments