Мовна модель завжди повертає текст, який виглядає правильним. Це підступно: відповідь звучить впевнено навіть тоді, коли поле вигадане, число поза діапазоном, а ключ у JSON названо інакше, ніж учора. Якщо цей вихід потрапляє прямо до бази, на рахунок-фактуру чи до іншої системи, один спотворений запис може зупинити весь процес. Нижче описуємо шаблон валідації, який ми використовуємо, щоб вихід LLM можна було сприймати як дані, а не як надію.
Чому «виглядає добре» — недостатньо
Сирий текст з моделі має три класи вад, які потрібно розділяти, бо кожна вимагає іншого захисту. Перша — вади форми: відсутня кома, обрізаний JSON, додатковий коментар перед дужкою, поле як string замість числа. Друга — вади контенту: структурно правильний об’єкт, в якому значення є галюцинацією — вигаданий номер договору, дата поза діапазоном, категорія, якої немає у словнику. Третя — небезпечні виходи: спроба ін’єкції інструкцій, витік даних, контент, який не можна показувати користувачеві.
Валідація має охоплювати всі три. Перевірка самого JSON не виявить галюцинацій; перевірка бізнес-правил не спрацює, поки текст взагалі не розпарситься. Тому ми будуємо її шарами.
Примус форми: схема як контракт
Відправною точкою є JSON Schema — формальний опис того, як має виглядати вихід: які поля, які типи, які обов’язкові, які допустимі значення (enum). Схема виконує дві ролі одночасно: є інструкцією для моделі та контрактом для валідатора. Є два шляхи, щоб її примусити.
| Підхід | Як працює | Сильні сторони | Обмеження |
|---|---|---|---|
| Structured output нативний | Провайдер гарантує JSON, що відповідає схемі (constrained decoding) | Відсутність помилок форми на стороні декодування | Не кожна модель/хост це підтримує; може бути повільнішим для складних схем |
| Structured output prompt-based | Схема в промпті + валідація на стороні застосунку | Працює з будь-якою моделлю, повний контроль | Модель може бути багатослівною або відхилятися від схеми — валідація обов’язкова |
У наших впровадженнях за замовчуванням використовуємо варіант prompt-based з валідацією: схема в промпті, а після отримання відповіді жорстка перевірка бібліотекою, що валідує JSON Schema. Причина практична — нативне примушування зі строгим response_format на деяких хостах буває повільним (секунди, іноді тайм-аути) і недоступне для кожної моделі, а ми хочемо мати змогу вибрати модель, найкращу для завдання, а не найкращу для режиму API. Як обирати модель під конкретне завдання, описуємо в тексті про вибір моделі LLM.
Незалежно від варіанту принцип той самий: схема валідує структуру, а не сенс. Нестрогі обмеження довжини (модель пише багато), суворі типи та enum там, де значення потрапляє далі до системи.
Цикл валідація + виправлення
Одиничний виклик не завжди потрапляє в схему — і це нормально, а не аварія. Тому після валідації, якщо вона не вдалася, робимо одну контрольовану спробу виправлення: повертаємо моделі її власний вихід разом із конкретним повідомленням про помилку валідатора («поле kwota має бути числом», «відсутнє обов’язкове kategoria») і просимо виправити. Зазвичай цього достатньо, бо модель тепер має точну інформацію, що було не так.
Цикл має мати жорсткий ліміт. У нашій практиці добре працює одна, максимум дві спроби виправлення — більше рідко щось змінює, а множить вартість і затримку. Після вичерпання спроб не вгадуємо: вихід іде на обробку помилки (черга, людина в циклі, значення за замовчуванням), ніколи до цільової системи. Схематично:
- Генеруй → вихід за схемою в промпті.
- Валідуй → парсинг JSON + перевірка JSON Schema + бізнес-правила.
- Виправ → при помилці повертай моделі повідомлення валідатора; повторюй валідацію (ліміт спроб).
- Вирішуй → успіх: пропусти; невдача після ліміту: fail-closed.
Guardrails: контент і безпека
#Схема контролює форму, але не скаже, що номер замовлення вигаданий або що модель намагається виконати ін’єкцію інструкції. Це завдання для guardrails — шару правил, що працює після структурної валідації, а часто й до моделі (на вході). Три перевірки, які ми використовуємо найчастіше:
- Grounding значень — значення, які мають існувати в реальності (номер договору, категорія, ID клієнта), порівнюємо з джерелом істини. Поле, яке не збігається з жодним записом, вважаємо галюцинацією та відхиляємо, навіть якщо тип збігається.
- Контроль діапазонів і правил — суми, дати, числа мають вкладатися в бізнес-рамки. Ціна поза діапазоном або дата в минулому для терміну в майбутньому — сигнал помилки, а не дані.
- Безпека виходу — фільтр проти витоку даних, спроб injection та контенту, який не можна показувати. Це той самий напрямок мислення, що й при безпеці агентів AI — вихід моделі — це недовірені дані, поки ми їх не перевіримо.
Guardrails підключаємо також там, де модель класифікує — результат класифікатора має належати до закритого набору міток, інакше маршрутизація звернень відправить справу в невідоме місце. Коли вихід живить корпоративний GPT на базі знань або систему RAG, ця сама дисципліна захищає від надання користувачеві впевненої, але неправдивої відповіді.
Коли fail-closed, а коли fail-open
#Найважливіше проектне рішення: що робити, коли валідація після виправлення все ще не вдається. Fail-closed означає відмову — система не пропускає ненадійний вихід, лише ескалує до людини, повертає помилку або використовує безпечне значення за замовчуванням. Це режим за замовчуванням скрізь, де наслідок незворотний або дорогий: платежі, зміни в базі, контент, що надсилається клієнту, рішення з юридичними наслідками.
Fail-open (пропустити попри невизначеність) допускаємо лише там, де помилка дешева та зворотна, а відсутність відповіді гірша за недосконалу відповідь — наприклад, підказка тегу, яку людина все одно верифікує. Правило великого пальця: якщо не можеш дешево скасувати наслідки помилкового виходу, проектуй fail-closed. Як перейти з таким шаблоном від пілоту до стабільного продакшену, розповідаємо в тексті від пілоту AI до продакшену.
FAQ
#Чи structured output сам вирішує валідацію?
#Ні. Нативний structured output контролює, щоб вихід був коректним JSON, що відповідає схемі — це вада форми, один із трьох класів. Він не перевіряє, чи значення правдиві або безпечні, тому валідація контенту та guardrails все одно потрібні. Сприймаємо його як фундамент, а не як повний комплекс.
Prompt-based чи нативний structured output?
#Залежить від хоста та моделі. Нативний дає гарантію форми, але не кожна модель його підтримує і може бути повільнішим для складних схем. На практиці часто обираємо prompt-based з жорсткою валідацією на стороні застосунку, бо працює з будь-якою моделлю та залишає контроль над циклом виправлення.
Скільки разів пробувати виправляти вихід?
У нашій практиці одна, максимум дві спроби виправлення. Після передачі моделі конкретного повідомлення про помилку перше виправлення зазвичай достатньо; наступні рідко допомагають, а множать вартість і затримку. Після вичерпання ліміту вихід іде на обробку помилки, а не до системи.
Як валідація захищає від галюцинацій?
Сама схема не захищає — галюцинація може бути структурно правильною. Захищають guardrails: порівняння значень із джерелом істини (grounding), контроль діапазонів та закритих словників. Якщо поле не збігається з жодним реальним записом, відхиляємо його незалежно від того, наскільки впевнено воно звучить.
Чи валідація сильно сповільнює систему?
Структурна валідація та правила дешеві — це мілісекунди. Реальна вартість — це можливий раунд виправлення, тобто один додатковий виклик моделі, і лише коли перший вихід не пройшов. Натомість отримуєш передбачуваність, яку важко переоцінити, коли вихід живить інші системи.