Перейти до вмісту
18/30Розділ 18 з 30

Tool Calling і структуровані виходи: контракт, що витримує

24 виклики, жодного зламаного JSON і дві придатні дати. Той самий endpoint із кращим описом — і межі того, що schema не виправить.

На цій сторінці

Дайте моделі інструмент пошуку авіарейсів і попросіть її знайти рейс із Мадрида до Берліна. Ось що повертається:

TEXT
<tool_call>
{"name": "search_flights",
 "arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>

JSON валідний. Назва інструмента правильна. Усі обов’язкові поля присутні. І виклик марний: жоден API авіарейсів не прийме "Madrid" там, де очікує код аеропорту, або "3rd October 2026" там, де очікує дату.

Саме про цей розрив — синтаксично ідеально, семантично непридатно — цей розділ. І перше, що треба встановити: це не проблема JSON. У двадцяти чотирьох запитах із цим інструментом модель створила 24 валідні tool calls і жодного зламаного JSON. Вона жодного разу не провалилася на тій частині, яку всі зазвичай налагоджують.

Перед механікою — речення, яке усуває найбільше плутанини: tool call — це запит, а не дія.

Модель видає структуроване повідомлення, яке означає: я хотіла б викликати search_flights з такими аргументами. Потім вона зупиняється. Ваш код отримує це повідомлення, вирішує, чи виконувати його, викликає те, що має викликати, і надсилає результат назад як ще одне повідомлення. Модель ніколи не торкалася вашої бази даних, не робила HTTP-запиту, не мала облікових даних.

Усе про безпеку agent у Розділі 30 випливає з цього поділу, як і все про дизайн agent у Розділі 23: модель пропонує, а ваш код розпоряджається, і саме в коді живуть усі гарантії.

Отже, якщо прибрати термінологію, інструмент — це дві речі:

Схема. JSON Schema, що описує функцію: її назву, що вона робить і які аргументи приймає разом із їхніми типами та обмеженнями. Саме це потрапляє в prompt, і це єдине, що модель коли-небудь бачить.

Endpoint. Функція у вашому коді, яка приймає ці аргументи й щось повертає. Модель ніколи її не бачить, не знає, якою мовою вона написана, і не може відрізнити запит до бази даних від захардкодженого рядка.

Визначення інструментів потрапляють у prompt, серіалізовані у формат, на якому навчали модель. Вони коштують tokens у кожному окремому виклику — факт, який повернеться з числом далі в цьому розділі.

Модель відповідає викликом замість тексту

Посилання на розділ: Модель відповідає викликом замість тексту

Замість прози відповідь містить структурований запит, а API повідомляє відповідну причину завершення. Ця причина важлива: саме так ваш код розуміє, що треба запустити інструмент, а не показати користувачу відповідь.

Це крок, у якому моделі немає. Перевірте аргументи за схемою, вирішіть, чи має цей викликач право це робити, і виконайте.

Ви надсилаєте результат назад як повідомлення

Посилання на розділ: Ви надсилаєте результат назад як повідомлення

Результат стає ще одним ходом у розмові, у спеціально відведеній для нього ролі. Модель читає його як будь-який інший context.

Модель відповідає або просить ще один інструмент

Посилання на розділ: Модель відповідає або просить ще один інструмент

Це і є цикл із Розділу 23, і причина, чому один запит може перетворитися на десяток кругових проходів.

У цьому немає нічого емерджентного. Як показав Розділ 11, tool calling — це навчена поведінка:1 під час post-training модель бачила тисячі розмов, сформованих саме так. Ось чому формат залежить від моделі, чому надійність так сильно відрізняється між моделями схожого розміру і чому модель може викликати інструмент, якого ніколи не бачила: форму було навчено, а конкретний інструмент приходить із вашого prompt.

Ось інструмент у тому вигляді, у якому більшість пише його вперше. Зверніть увагу: у ньому немає нічого неправильного; він просто тонкий:

tools/badFlights.tsTS
{
  name: "search_flights",
  description: "Search for flights.",
  parameters: {
    type: "object",
    properties: {
      from: { type: "string", description: "Airport." },   
      to:   { type: "string", description: "Airport." },   
      date: { type: "string", description: "The date." },  
    },
    required: ["from", "to", "date"],
  },
}

Двадцять чотири запити, шість пар міст у поєднанні з чотирма способами виразити дату ("the 3rd of next month", "next Friday", "15 December", "tomorrow"), greedy decoding, щоб результати відтворювалися:

інструмент викликанозламаний JSONдата в ISOаеропорти як IATAусе правильно
схема вище24/2402/244/241/24

Прочитайте перші дві колонки перед останніми трьома. Модель щоразу викликає правильний інструмент і щоразу створює коректно сформований JSON. Помилка повністю у значеннях, і ці значення непридатні: "Madrid" замість MAD, "3rd October 2026" замість 2026-10-03.

На цьому варто наполягти, бо саме це визначає, куди дивитися, коли щось ламається. Інстинкт — додати JSON-парсер із повторною спробою або ще наполегливіше попросити модель видати валідний JSON. Жодне з цього не вирішує того, що сталося тут.

Той самий endpoint. Той самий код за ним. Та сама модель, ті самі prompts, те саме decoding. Змінюється лише текст у схемі:

tools/goodFlights.tsTS
{
  name: "search_flights",
  description: "Search scheduled flights between two airports on a given day.",
  parameters: {
    type: "object",
    properties: {
      from: {
        type: "string",
        description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",   
        pattern: "^[A-Z]{3}$",
      },
      to: { /* same */ },
      date: {
        type: "string",
        description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",   
        format: "date",
        pattern: "^\\d{4}-\\d{2}-\\d{2}$",
      },
    },
    required: ["from", "to", "date"],
  },
}
FORMAT датиVALUE датиFORMAT аеропортуVALUE аеропорту
тонка схема2/241/244/244/24
описана схема24/2412/2416/248/24

Формат дати зростає з 2 із 24 до 24 із 24. Ідеально — лише від зміни тексту, без жодної зміни коду і без логіки повторних спроб. Якщо з цього розділу варто винести одну операційну звичку, то ось її: коли інструмент викликається неправильно, виправлення майже завжди в описі, і це найдешевше виправлення в системі.

Тепер прочитайте другу колонку — важливішу половину.

Схема обмежує форму. Вона не може додати знання.

Посилання на розділ: Схема обмежує форму. Вона не може додати знання.

Дата у форматі ISO — 24 рази з 24. Це правильний день — 12 разів із 24.

Отже, половина викликів тепер несе ідеально відформатовану дату, яка є неправильною датою. Опис сказав моделі, яку форму створити, і модель створила її бездоганно — але перетворити "next Friday" на 2026-09-11 вимагає знати сьогоднішню дату й виконати календарну арифметику, а жоден опис цього не надає. Та сама історія з аеропортами: формат зріс із 4 до 16, але значення — лише з 4 до 8, бо написати MAD вимагає знати, що аеропорт Мадрида — MAD.

Ця відмінність — опорна ідея розділу:

Схема — це контракт про форму. Вона може зробити вихід моделі придатним для парсингу, типізованим і послідовним. Вона не може зробити його істинним, і кожен режим відмови, який переживає хорошу схему, є відмовою знання, а не відмовою формату.

Для цих двох потрібні різні виправлення, і плутанина між ними марнує тижні. Помилки формату виправляються в описі або за допомогою обмеженого decoding, нижче. Помилки знання виправляються тим, що знання кладуть у prompt — поточну дату в системне повідомлення, пошук аеропорту як другий інструмент, який модель викликає першим, enum у схемі, коли множина достатньо мала, щоб її перелічити. Зверніть увагу, що спільне в усіх трьох: вони переносять проблему з пам’яті моделі в її вхід, а це і є весь Розділ 24.

Структуровані виходи й що насправді означає "constrained decoding"

Посилання на розділ: Структуровані виходи й що насправді означає "constrained decoding"

Усе вище досі покладається на те, що модель обере правильну форму. Є сильніша гарантія, і це найкраща віддача від Розділу 17.

Згадайте, як працює генерація: на кожному кроці модель створює logit для кожного token у словнику, а семплер обирає один. Обмежений decoding вставляє між ними ще один крок. Маючи граматику — виведену з вашої JSON Schema — він обчислює, які tokens можуть легально йти далі, встановлює logits усіх інших у мінус нескінченність і дає семплеру обирати з того, що залишилося.

Якщо схема каже, що наступною має бути {, тоді кожен token, який не є {, має ймовірність нуль. Не "малоймовірно": нуль. Модель не може видати невалідний JSON, бо невалідні tokens були прибрані з розподілу ще до sampling.

Саме це лежить під капотом "structured outputs", "JSON mode" і "guided generation", і це пояснює їхні дві властивості. Гарантія повна для всього, що може виразити граматика — типів, обов’язкових полів, enums, вкладеності, — бо вона забезпечується механічно, а не ввічливо проситься. І вона нічого не каже про вміст: граматика може змусити "date" бути рядком, що відповідає шаблону дати, але не може змусити його бути правильним днем. Це та сама стіна, що й у попередньому розділі, тільки з іншого боку.

Дві практичні нотатки. Це не безкоштовно: маску треба обчислювати на кожному кроці, а складні граматики коштують вимірюваної затримки. І це змінює те, що робить модель: модель, яку відхилили від бажаного token, може створити гірший вміст, водночас створивши ідеальну структуру. Тому "ввічливо попросити й провалідувати" досі є розумним дефолтом для простих форм, а обмежений decoding виправдовує свою ціну тоді, коли форма складна або споживач суворий.

Побічні ефекти й єдина властивість, що має значення

Посилання на розділ: Побічні ефекти й єдина властивість, що має значення

Розділ 14 виміряв тайм-аут із подальшою повторною спробою, який виставив рахунок за дві генерації за одну відповідь. З інструментами та сама відмова стає гіршою, бо інструмент може щось зробити.

Якщо ваш код викликає charge_card, отримує тайм-аут і повторює спробу, у вас два списання. Модель не має уявлення, що це сталося; вона бачить один результат інструмента. Виправлення таке саме, як у будь-якій розподіленій системі, і це не проблема моделі: зробіть операцію ідемпотентною, давши виклику ключ, щоб друге виконання розпізнало перше й повернуло його результат замість повторної роботи.

Правило дизайну, яке з цього випливає, варто сформулювати прямо. Відокремлюйте читання від записів у своєму каталозі інструментів. Читання можна вільно повторювати, запускати паралельно й кешувати. Запис — ні; він має нести ключ, перевірку дозволів і — для всього, про що користувач захотів би знати до того, як це станеться, — крок approval, який ставить людину між запитом і дією. Цей крок approval — не ввічливість: це одна з небагатьох речей між prompt injection і реальним наслідком — і, як вимірює Розділ 30, найслабша з них.

Скільки інструментів потрібно, щоб якість просіла?

Посилання на розділ: Скільки інструментів потрібно, щоб якість просіла?

Фольклор каже, що завантаження багатьох інструментів змушує модель обирати гірше. Варто виміряти, а не повторювати, тож: ті самі двадцять чотири запити, інструмент для рейсів плюс зростаючий набір інших — зокрема три навмисно схожі, які можна сплутати (розклад поїздів, поромні переправи, автобусні маршрути).

завантажено інструментівprompt tokensобрано search_flightsдата в ISO
135324/2424/24
573024/2424/24
101,19321/2421/24
202,11924/2424/24

Вибір не погіршився. З двадцятьма інструментами, три з яких цілком можна сплутати, модель із пів мільярда parameters обрала правильний у двадцяти чотирьох випадках із двадцяти чотирьох. Просідання на десяти — це три виклики, що назвали інший інструмент, і воно не переживає переходу до двадцяти.

Це негативний результат, і його слід так і повідомляти: у цьому завданні, з цими інструментами, "занадто багато інструментів" не було проблемою. Те, що справді зростало монотонно й у шість разів, — це prompt: від 353 tokens до 2,119, оплачуваних у кожному запиті розмови, завжди, незалежно від того, чи використано якийсь інструмент.

Тож чесна версія фольклору — про вартість і context, а не про точність. Двадцять інструментів — це постійний податок на кожне повідомлення, а Розділ 16 уже показав, що постійний префікс робить із рахунком за сорок ходів. Коли люди повідомляють, що багато інструментів шкодять якості, механізм зазвичай у тому, що визначення витіснили важливий context — тобто це проблема Розділу 24 у костюмі Розділу 18. Інструменти, які справді майже дублюють один одного, теж є реальною проблемою, і виправлення для них — не менше інструментів, а кращі описи й namespaces: префіксуйте їх за системою (crm.search_customer, billing.search_customer), щоб два каталоги, злиті з двох команд, не конфліктували, і щоб модель мала за що зачепитися для розрізнення.

Три види інструментів і той, що відкриває наступну частину

Посилання на розділ: Три види інструментів і той, що відкриває наступну частину

Корисно сортувати інструменти за тим, що вони роблять зі світом, бо інженерія для кожного відрізняється.

Інструменти даних читають: шукають, отримують, запитують. Їх можна повторювати, паралелити, кешувати. Вони провалюються, повертаючи нічого корисного, а їхній головний ризик у тому, що вони приносять недовірений текст у context — а це вся поверхня атаки Розділу 30.

Інструменти дій записують: надсилають, створюють, списують, видаляють. Їх не можна безпечно повторювати без ключа, не можна безпечно паралелити, і саме через них існують approval flows.

Інструменти оркестрації викликають інші моделі. Інструмент, реалізація якого — інший agent, із власним prompt, власними інструментами й власним циклом, — а для моделі, що його викликає, він виглядає точно так само, як перші два, бо схема й endpoint — це все, що вона коли-небудь бачить.

Третій вид — не цікавинка. Це механізм за половиною agent-as-a-tool у Розділі 25 — інша топологія, handoff, віддає розмову й ніколи не отримує її назад — і він працює саме тому, що інтерфейс у цьому розділі достатньо вузький, щоб за ним помістився цілий agent.

Тепер у вас є модель, яка може просити про речі, і контракт, який робить це прохання придатним для парсингу. Чого у вас немає — так це чогось, про що вона може питати, крім того, що вміщується в її prompt.

Найпоширеніший інструмент у продакшені, з великим відривом, — це пошук у масиві тексту, якого модель ніколи не бачила під час навчання: ваша документація, ваші тікети, ваші контракти. Це звучить як розв’язана проблема — embed, знайти найближчих сусідів, вставити їх, — але нерозв’язані частини саме ті, що визначають, чи можна довіряти відповіді: як текст ріжеться перед embedding, який поріг схожості достатньо низький, щоб означати я не знаю, і як цитата прикріплюється до твердження, щоб читач міг його перевірити.

Розділ 19 — про retrieval, і це розділ, у якому неправильна відповідь перестає бути цікавинкою й стає відповідальністю.


Вимірювання в цьому розділі походять із Qwen/Qwen2.5-0.5B-Instruct з greedy decoding, на 24 згенерованих запитах, що поєднують шість пар міст із чотирма формулюваннями дат, використовуючи власний chat template моделі для визначень інструментів. Вони відтворюються точно, і це мала модель: читайте розділення format/value як демонстрацію механізму, а не як benchmark того, що роблять сучасні моделі. Frontier model правильно розв’язує "next Friday" значно частіше — і все одно schema не може змусити її це зробити, а саме ця частина узагальнюється.

Словник JSON Schema, використаний вище (type, properties, required, pattern, format, enum), визначено в draft JSON Schema, який називає документація вашого провайдера; корисна підмножина мала й однакова між провайдерами, а відмінності, які справді існують — які keywords забезпечуються constrained decoding, а не просто передаються моделі, — варто прочитати в guide провайдера зі structured output, а не припускати.

Щодо constrained decoding як техніки, бібліотеки guidance-стилю та проєкт outlines документують побудову grammar-to-logit-mask так, що вона прямо мапиться на семплер із Розділу 17. А для самого кругового проходу найчіткіша специфікація — не tutorial, а протокол: Розділ 26 читає його рядок за рядком.

  1. Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Стаття, яка зробила рецепт post-training стандартом; форма tool call вивчається там із демонстрацій — точно так само, як форма відповіді.


Створено

David Vicente Campos

Засновник NeuraLIA Labs і співзасновник MyRealFood

Я інженер-програміст, випускник Університету Леона. Я співзаснував MyRealFood, де на посаді CTO створив застосунок, яким користувалися мільйони людей, щоб харчуватися здоровіше, і заснував NeuraLIA Labs, де створюю AI-продукти. Тут я пишу про те, що мені довелося зрозуміти дорогою, — так, як я сам хотів би, щоб мені це свого часу пояснили.

Більше про автора

Опубліковано NeuraLIA Labs.

Отримуйте нові дописи на пошту

Новини AI, гайди й оновлення продукту — короткий лист, коли ми публікуємо щось варте вашого часу.

Зміст курсу

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev10 хв читання

AI-модель Jev створена для рішень, а не прози

Jev від TypeSafe AI привертає увагу, бо розглядає програмний інтелект як задачу ймовірності: вибрати правильну гілку, додати впевненість і не платити LLM за написання тексту, коли коду потрібне рішення.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering12 хв читання

Context engineering for long-horizon AI agents

Long-running agents do not fail only because the window is small. They fail when files, tool outputs and stale history crowd out the task the agent was supposed to finish.

Готові довірити вибір моделі LIA?

Створюйте з усіма моделями ШІ в одному місці — почніть безкоштовно вже сьогодні.