Зміст
Коротко
Передаєте схему Zod 4 у помічник структурованого виводу — і кожне .refine() мовчки зникає, перш ніж модель побачить правила.
OpenAI Node SDK і Vercel AI SDK викликають z.toJSONSchema(). У карті contributors конвертера Zod 4.6.7 лише 13 ключів; .refine() лежить як перевірка custom і до цієї карти не входить.
Сама доробка все ще спрацьовує під час розбору відповіді. Модель просто ніколи не була нею обмежена.
Що сталося
Автор вихідної нотатки розбирає розрив між схемою, яку ви написали, і схемою, яку реально бачить модель.
Уявіть багаж на контролі: у валізі і паспорт, і рукописна записка «не класти посилання в поле note». Сканер пропускає лише те, що є в списку з тринадцяти категорій. Записку знімають без оголошення. Ви дізнаєтесь про це вже в залі видачі — коли валізу відкрили й звірили з вашим списком.
У json-schema-processors.ts один рядок вирішує, які перевірки потраплять у JSON Schema: для кожної шукають обробник у contributors. Там є greater_than, less_than, multiple_of, min_length, string_format і сусідні ключі. А .refine() в api.ts зберігається як custom — і необов’язковий виклик нічого не знаходить. Правило пропускають.
Автор збирає схему витягування повернення з листів підтримки. Чотири поля виражаються в JSON Schema напряму: .regex(), .positive(), z.enum, z.iso.date(). Решта — проблема: .refine() на полі note (без посилань), .refine() на всьому об’єкті (дата повернення не раніше замовлення) і .transform() на sku.
У режимі { io: "input" }, яким користується Vercel AI SDK, модель бачить pattern, exclusiveMinimum, enum, format: "date". Поле note стає голим рядком. Об’єктне уточнення зникає. sku у режимі input теж просто рядок; у режимі output конвертер може кинути помилку — Transforms cannot be represented in JSON Schema.
Тести Zod підтверджують картину: два z.string().refine(...) в об’єднанні згортаються до "type": "string". Те саме чекає .superRefine() і об’єктний .refine() — усе це знову custom.
Окремо — провайдер. Навіть ключове слово, що потрапило в JSON Schema, модель може не тримати жорстко. В Anthropic числові обмеження на кшталт minimum / multipleOf і довжини рядків у SDK часто йдуть у текст опису поля, а не в обмеження декодування. .positive() дає exclusiveMinimum — той самий клас. В OpenAI ширший набір (pattern, format, межі, anyOf), але allOf, not, if/then/else не підтримані; помічник ще переписує oneOf на anyOf.
Чому це важливо
На демо схема «зелена»: типи сходяться, помічник не скаржиться. У продукті модель спокійно кладе URL у note або міняє дати місцями — бо в JSON Schema цих правил не було.
Помилка спливає лише на parse. Якщо ви кидаєте виняток і не віддаєте текст помилки назад, користувач бачить збій, а не виправлений JSON.
Розрив «я написав схему» ↔ «модель побачила схему» — одна з перших пасток при переході від прототипу до функції, на яку спираються люди. Доробки й перетворення живуть у Zod; структурований вивід живе в підмножині JSON Schema плюс те, що провайдер ще не вирізав.
На практиці
Автор пропонує дві лінії захисту й винести перетворення зі схеми, яку відправляють мережею.
- Кожне правило, яке модель має дотримуватись, продублюйте в
.describe()— метаданіZodкопіюються вJSON Schema. Ставте.describe()останнім у ланцюжку: після.refine()метадані «відпадають», бо кожен метод повертає новий екземпляр. - Залиште
.refine()як другу лінію: вони все ще біжать під час розбору відповіді. - Приберіть
.transform()зі схеми, яку віддаєте в помічник. На проводі — прості типи; обрізання йtoUpperCaseдляskuзробіть уже після успішного розбору. - Не покладайтеся лише на
responses.parse, який кидає при помилці. Викличтеresponses.create, розберіть текст черезsafeParseі при провалі один раз поверніть моделіz.prettifyError(...)з проханням надіслати виправлений JSON. - В об’єктних
.refine()задавайтеpath: тоді в повідомленні для моделі з’явиться ім’я поля. - Зупиніться на одній повторній спробі. Два провали з текстом помилки перед очима — уже привід логувати сирий вивід і вирішувати, правило в інструкції чи в коді.
- Перед
JSON.parseперевірте статус відповіді: обрив або відмова моделі інакше маскуються під помилку розбору.
Підсумок
.refine() у Zod 4 не їде в JSON Schema: у карті конвертера немає ключа custom. Модель бачить .regex(), .positive(), enum і дати ISO; польові й об’єктні доробки — ні. Перетворення або брешуть тип у режимі input, або падають у output.
Виправлення просте за формою: описати правило словами через .describe(), залишити доробки другою лінією, винести перетворення, перевірити safeParse і один раз згодувати моделі z.prettifyError.
Багаж пройшов контроль. Звірте список самі — сканер не оголошував, що зняв.



Коментарі