1. Навіщо взагалі розбиратися з tool-call
Якщо спростити, звичайний вебзастосунок живе за схемою: «користувач натиснув кнопку — ми викликали функцію». У світі ChatGPT Apps усе інакше: користувач щось написав, модель подумала і, якщо визнала це доречним, сформувала структурований виклик інструмента (tool-call).
Тобто ви не пишете:
onClick={() => callSuggestGiftsApi(formData)}
а натомість:
- Описуєте інструмент suggest_gifts (імʼя, опис, схему аргументів).
- Пояснюєте моделі в system-prompt, чим цей інструмент корисний.
- Передаєте керування моделі: вона сама вирішує, коли й як його викликати.
Звідси важливо якомога раніше усвідомити дві речі:
- GPT не бачить ваш код бекенду. Вона бачить лише «шапку» інструмента: імʼя, опис і схему параметрів.
- Те, наскільки «розумно» модель використовуватиме ваш застосунок, майже напряму залежить від того, як ви написали ці описи. Якісні описи — це ваш «промпт для інструмента».
Сьогоднішня лекція — саме про цей «мозок» між користувачем і вашим сервером.
2. Ментальна модель tool-call: що взагалі відбувається
Почнемо із загальної картини. Типовий сценарій для GiftGenius:
- Користувач: «Підбери подарунок другу 30 років, бюджет 100 доларів, він любить відеоігри».
- GPT читає це повідомлення й дивиться, які інструменти доступні. У нашому застосунку, наприклад, є suggest_gifts.
- GPT вирішує: «Щоб відповісти якісно, мені потрібно викликати цей інструмент».
- Замість звичайної текстової відповіді вона генерує структуру: імʼя інструмента + аргументи JSON.
- Клієнт ChatGPT розуміє: «Ага, це tool-call» — і надсилає його на ваш MCP/сервер.
- Ваш сервер виконує бізнес-логіку й повертає структурований результат.
- GPT отримує відповідь, читає її й уже на основі результату інструмента формує зрозуміле повідомлення користувачеві та/або оновлює віджет.
З погляду OpenAI API це той самий механізм LLM-function-calling: у відповіді моделі замість звичайного тексту зʼявляється обʼєкт із name інструмента та arguments, а finish_reason позначається як tool_calls. Модель не запускає код сама — вона лише пропонує, який інструмент викликати. Реальний виклик робить клієнт (ChatGPT/Apps SDK).
Виглядає це приблизно так (спрощена послідовність):
sequenceDiagram
participant U as Користувач
participant G as GPT (модель)
participant C as Клієнт ChatGPT
participant S as Ваш MCP/Бекенд
U->>G: "Підбери подарунок другу..."
G->>C: tool-call: { name: "suggest_gifts", args: {...} }
C->>S: HTTP /mcp tools/call (suggest_gifts, args)
S-->>C: Результат (JSON зі списком подарунків)
C-->>G: tool result
G-->>U: Відповідь + оновлений віджет
Головний висновок: ви не пишете if (userAskedAboutGifts) callSuggestGifts(). Ви створюєте інструмент і його опис, а рішення ухвалює модель.
3. Що бачить модель: system prompt + список інструментів
Щоб зрозуміти, як GPT ухвалює рішення, потрібно чітко уявляти, який набір інформації вона має в момент вибору.
Якщо спростити, модель бачить:
- system‑prompt вашого застосунку (ми ще докладно розберемо це в модулі 5);
- історію діалогу: повідомлення користувача, власні відповіді, результати попередніх tool-callів;
- список доступних інструментів (tools) з їхніми іменами, описами та схемами параметрів;
- додаткові анотації інструментів (readOnly/destructive тощо).
Вона не бачить:
- реалізацію функцій;
- SQL-запити;
- структуру ваших таблиць;
- вміст приватного репозиторію із сервісом.
Трохи пізніше ми докладно поговоримо про MCP. Поки достатньо знати ось що: на рівні MCP інструменти оголошуються як дескриптори. У кожного є name, description і inputSchema (JSON Schema). Під час handshake ChatGPT запитує в MCP-сервера список інструментів і починає сприймати їх як доступні «дії».
Приклад такого дескриптора для GiftGenius (спрощений JSON):
{
"name": "suggest_gifts",
"description": "Підбирає ідеї подарунків за віком, інтересами та бюджетом",
"inputSchema": {
"type": "object",
"properties": {
"age": { "type": "integer" },
"budget": { "type": "number" }
},
"required": ["age", "budget"]
}
}
Модель «читає» тут лише текст і структуру: що таке age, що таке budget, і що інструмент робить загалом. Наступна лекція буде саме про те, як грамотно описувати inputSchema. А зараз — про те, як із цього опису народжується рішення: «а давай-но я викличу suggest_gifts».
4. Як виглядає tool-call очима API
ChatGPT викликає інструменти (tools) вашого MCP-сервера приблизно так само, як OpenAI Agent викликає функції на вашому бекенді. У ChatGPT Apps SDK це трохи «загорнуто», але базова механіка однакова.
Уявімо, що ми на своєму бекенді робимо звичайний запит до OpenAI API і водночас передаємо інструмент suggest_gifts, який модель може викликати у своїй відповіді:
const response = await openai.responses.create({
model: 'gpt-5-mini',
messages: [
{
role: 'user',
content: 'Потрібен подарунок другу 30 років, бюджет 100 доларів'
}
],
tools: [ // тут ми передаємо список функцій, які LLM може "викликати"
{
name: 'suggest_gifts',
description: 'Підбирає подарунки за віком, бюджетом та інтересами',
parameters: {
type: 'object',
properties: {
age: { type: 'integer' },
budget: { type: 'number' }
},
required: ['age', 'budget']
}
}
]
});
Якщо модель вирішить викликати інструмент, ви отримаєте у відповідь не текст, а повідомлення асистента з чимось на кшталт:
{
"role": "assistant",
"tool_calls": [
{
"id": "call_1",
"name": "suggest_gifts",
"arguments": "{\"age\":30,\"budget\":100}"
}
],
"content": []
}
У такий спосіб LLM «каже» вашому бекенду, що їй потрібно викликати функцію suggest_gifts(30,100).
Тут важливі три речі:
- Імʼя інструмента (name) — модель справді підставляє сюди той рядок, який ви вказали в описі tools, коли надсилали перший запит.
- Аргументи (arguments) — JSON-рядок, зібраний на основі parameters/inputSchema.
- Поки немає звичайної текстової відповіді — замість неї ви отримуєте структуру для виклику інструмента.
Під час роботи застосунку ChatGPT усе так само: модель повертає «хочу викликати suggest_gifts з такими параметрами», а клієнт (ChatGPT) робить HTTP-запит на ваш MCP/сервер: tools/call із назвою інструмента та аргументами.
5. Як саме модель вирішує: tool чи текст
Тепер найцікавіше: коли GPT узагалі згадує про ваші інструменти?
Якщо дуже спростити, механіка така:
- Модель бачить чергове повідомлення користувача й поточний контекст.
- Усередині в неї є «шар», який генерує наступне повідомлення асистента. Але замість того, щоб завжди видавати звичайний текст, модель може обрати один із варіантів завершення:
- звичайна текстова відповідь (finish_reason: "stop");
- один або кілька tool-callів (finish_reason: "tool_calls");
- іноді інші варіанти (наприклад, «потрібне ще повідомлення користувача»).
- На цей вибір впливає:
- наскільки користувацький запит схожий на задачі, описані у ваших інструментах;
- наскільки ваш опис інструмента прямо каже: «використовуй мене саме в такому випадку»;
- дані із system prompt застосунку, які в Apps SDK задаються в конфігурації.
Якщо сказати простіше, модель «приміряє» ваш інструмент до поточного запиту. Якщо в описі сказано: «Підбирає подарунки за віком та інтересами», а користувач просить «аналіз бюджету держави», модель навіть не спробує його викликати. Якщо ж опис надто розмитий — на кшталт «робить круті штуки», — модель не зрозуміє, для яких запитів інструмент узагалі варто використовувати.
Є ще один нюанс: модель не зобовʼязана викликати інструмент, навіть якщо ви його описали. GPT може вирішити: «Тут і так усе зрозуміло — відповім сама, без tool-call». Тому далі в курсі ми будемо багато практикуватися й учитимемося писати такі описи, які роблять використання інструмента для моделі максимально очевидним і вигідним.
6. Назва інструмента: чому tool1 — погана ідея
Назва інструмента — це, по суті, ідентифікатор, який модель використовуватиме у своїх викликах. Здавалося б, поле суто технічне. Але на практиці імʼя сильно впливає на поведінку моделі.
Якщо ви назвете інструмент tool1, модель майже нічого з цього не зрозуміє: для неї це просто набір символів. Якщо ж ви назвете його suggest_gifts, search_products або fetch_user_orders, уже саме імʼя дає потужний сигнал, про що цей інструмент.
Подумайте, як ви самі читаєте незнайомий код. Побачивши функцію calculateCartTotal, ви приблизно уявляєте, чого від неї чекати. Моделі потрібен такий самий «семантичний якір».
Для GiftGenius логічні назви інструментів можуть бути такими:
suggest_gifts
search_products
get_product_details
create_order
Добре, якщо імʼя:
- коротке, але змістовне;
- в єдиному стилі (snake_case, латиниця, дієслово_іменник);
- відображає одну конкретну дію.
Погана ідея — змішувати різні дії в одному інструменті, наприклад do_all_gift_stuff. Моделі складніше зрозуміти, коли саме його використовувати. А в наступних лекціях ми побачимо, як це ламає схему аргументів і ускладнює налагодження.
7. Опис інструмента: ваш промпт для моделі
Якщо імʼя — це заголовок, то description — це міні-документація. Але не для розробника, а саме для GPT. Розробник прочитає код, а модель — ні. Вона спирається на текст опису, коли вирішує, чи викликати інструмент і які аргументи туди підставляти.
Важливо писати опис у стилі «інструкції із застосування»:
- коли використовувати інструмент;
- які в нього обмеження;
- чого він робити не повинен.
Візьмемо наш suggest_gifts. Ось три варіанти описів.
Надто широке:
"Підбирає подарунки."
Модель не зрозуміє: з якого приводу, для кого і з якими параметрами. Цей інструмент може «конкурувати» для моделі з її загальними знаннями про подарунки, і вона нерідко вирішить просто відповісти текстом.
Надто вузьке:
"Підбирає подарунки тільки для молодших братів на день народження."
Тут ми фактично заборонили використовувати інструмент майже завжди. Будь-який інший сценарій — мама, колега, річниця — вже «не підходить», тож модель уникатиме виклику.
Оптимальне:
"Використовуй цей інструмент, коли потрібно підібрати подарунки людині за віком, типом відносин (друг, партнер, колега тощо), бюджетом та інтересами.
Не викликай його для запитань, не пов’язаних із подарунками (наприклад, політика чи погода)."
Тут чітко описано, що інструмент робить, які параметри для цього є і коли його викликати. Також додано заперечну умову — для яких запитів його краще не використовувати.
Модель «любить» такі чіткі рамки. Чим ясніше ви окреслите, за яких користувацьких формулювань (інтентів) інструмент доречний, тим передбачуванішою буде поведінка застосунку.
Міні-вправа
Можете просто зараз, не відходячи від монітора, взяти свій майбутній застосунок (можливо, не про подарунки) і придумати для одного з його інструментів три описи: дуже широке, дуже вузьке та збалансоване. А потім — протестувати, як GPT поводиться з різними версіями.
8. Схема аргументів: як вона допомагає рішенню
Докладно про JSON Schema ми говоритимемо в наступній лекції. Але щоб розуміти tool-callи, важливо мати хоча б загальне уявлення.
Коли модель вирішує викликати інструмент, їй потрібно:
- Зрозуміти, які аргументи цей інструмент узагалі очікує.
- Витягти ці значення з тексту користувача (або з контексту).
- Сформувати JSON із цими аргументами.
Для цього в описі інструмента є схема параметрів (inputSchema), яка підказує моделі:
- які поля є (age, budget, relationship_type, interests тощо);
- які поля обовʼязкові (required);
- які бувають типи (integer, number, string, масиви тощо);
- інколи — які значення допустимі (enum) і які є пояснення до полів (description).
Найпростіший TypeScript-інтерфейс для параметрів suggest_gifts може виглядати так:
interface SuggestGiftsParams {
age: number;
relationship_type: 'friend' | 'partner' | 'colleague';
budget: number;
interests?: string[];
}
На рівні моделі це перетворюється на JSON Schema, а модель уже за назвою та описом кожного поля здогадується, що:
- age варто брати з фраз «30 років», «для підлітка» тощо;
- budget — з «бюджет 100 доларів», «до 50 євро»;
- relationship_type — з «друг», «колега»;
- interests — з «любить відеоігри».
Якщо ви дасте схему без описів і з абстрактними назвами полів (a, b, c), модель набагато частіше помилятиметься, заповнюючи аргументи. Ми ще повернемося до цього в модулі про локалізацію та UX-підказки. Ключова думка проста: схема — це не лише валідація на бекенді, а передусім підказка моделі, що куди класти.
Ми поговорили про те, як схема допомагає моделі правильно зібрати аргументи. Але, крім «що і як викликати», важливо ще й «чи можна викликати це просто зараз і наскільки це безпечно». Тут у гру вступають дозволи та метаінформація про інструменти.
9. Дозволи та контекст: не кожен інструмент доступний завжди
Окрім імені, опису та схеми аргументів, інструменти мають ще один важливий вимір — безпеку й доступ. У реальному застосунку інструменти сильно різняться за рівнем «небезпечності». Одна справа — пошук подарунків у публічному каталозі. Інша — списання грошей із картки користувача.
Apps SDK і MCP дають змогу відобразити це в описі та анотаціях інструментів — наприклад, позначати їх як read-only або destructive.
Ідея така:
- Інструменти, які лише читають публічні дані (search_products, get_weather), можна викликати без зайвих підтверджень.
- Інструменти, які щось змінюють (create_order, cancel_order, charge_user), позначаються як «деструктивні». ChatGPT UI може просити в користувача додаткове підтвердження («Ви впевнені, що хочете оформити замовлення?»), а сама модель рідше пропонуватиме їх без явного запиту.
У майбутніх модулях, коли ми налаштовуватимемо MCP, ви побачите, як ці анотації (_meta, destructiveHint, readOnlyHint) виглядають у реальних JSON-дескрипторах. Також розберемо, як вони впливають на UX і на те, як ChatGPT формує діалоги «Are you sure?» перед викликом. Зараз достатньо розуміти:
- GPT враховує не лише текст опису, а й метаінформацію про безпеку.
- Інструмент, що потребує автентифікації, не використовуватиметься, доки користувач не увійшов у систему (або доки застосунок не отримав потрібний токен).
Це ще один фактор, що впливає на рішення «запускати tool чи ні»: навіть якщо за змістом інструмент підходить, він може бути недоступним через дозволи. Тоді модель обере інший шлях.
10. Звідки інструменти взагалі беруться в ChatGPT
З архітектурного погляду інструмент може потрапити до моделі двома основними шляхами.
По-перше, з конфігурації вашого ChatGPT App. Коли ви реєструєте застосунок, ви вказуєте, які MCP-сервери (та їхні інструменти) до нього привʼязані, або які вбудовані tools є в самому застосунку. На старті сесії ChatGPT отримує цю конфігурацію й розуміє, які інструменти загалом доступні.
По-друге, безпосередньо з MCP. MCP (Model Context Protocol) визначає стандартний спосіб, у який клієнт (у нашому випадку ChatGPT/Apps SDK) дізнається, що вміє ваш сервер. Він робить запит tools/list, отримує JSON з описами інструментів і зберігає їх як capabilities. Докладну механіку ми розберемо в окремому модулі про MCP. Зараз важливо лише зрозуміти загальну ідею.
Схематично:
flowchart LR A[ChatGPT Client] -->|handshake| B[MCP Server] B -->|tools/list| A A -->|передає список| G[GPT Model]
Після цього список інструментів стає частиною контексту для моделі. Якщо ви зміните схему або опис інструмента на сервері й перезапустите застосунок, новий дескриптор потрапить у ChatGPT під час наступного handshake. Відповідно, модель почне по-новому ухвалювати рішення про виклик.
І важлива практична думка: коли ви правите лише бекенд (реалізацію інструмента), модель про це не знає. А от коли ви змінюєте name/description/schema, ви справді змінюєте «мозок» застосунку. Іноді корисніше підкоригувати один рядок у description, ніж писати 200 рядків коду з евристиками.
11. Застосовуємо до GiftGenius: робимо інструмент, який модель захоче викликати
Тепер акуратно поєднаємо все з нашим навчальним застосунком GiftGenius. Уявімо, що в нас уже є MCP-сервер або бекенд-шар, у якому ми реєструємо інструменти. Зареєструймо інструмент suggest_gifts за допомогою server.registerTool(...).
Примітивний нарис на TypeScript (поки без реальної логіки):
// pseudo-mcp-server/tools/suggestGifts.ts
server.registerTool(
'suggest_gifts', // імʼя інструмента
{
title: 'Підбір подарунків',
description:
'Використовуй цей інструмент, щоб підібрати ідеї подарунків за віком, ' +
'типом відносин і бюджетом. Не викликай для запитань, не повʼязаних із подарунками.',
inputSchema: { // опис параметрів інструмента
type: 'object',
properties: {
age: { type: 'integer', description: 'Вік отримувача в роках' },
relationship_type: {
type: 'string',
description: 'Тип відносин: friend, partner, colleague'
},
budget: {
type: 'number',
description: 'Максимальний бюджет на подарунок у валюті користувача'
}
},
required: ['age', 'budget']
}
},
async ({ age, relationship_type, budget }) => { // код функції/інструмента
// Реальна логіка буде пізніше
return { suggestions: [] };
}
);
Зверніть увагу на деталі, які ми продумали вже на цьому етапі, хоча логіка поки «заглушка»:
- Імʼя: suggest_gifts, а не tool1.
- Опис: у явному вигляді пояснює, коли викликати інструмент і коли його не викликати.
- Описи полів: допомагають моделі правильно зіставляти користувацький текст з аргументами.
У результаті, коли користувач напише «Підбери подарунок колезі за 50 доларів», модель побачить, що:
- є інструмент з іменем suggest_gifts і описом про підбір подарунків;
- у нього є поля age, relationship_type, budget;
- budget — це «максимальний бюджет на подарунок», а relationship_type — «тип відносин: friend, partner, colleague».
Навіть якщо користувачі висловлюватимуться неточно («до полтинника», «для напарника по проєкту»), у моделі буде достатньо контексту, щоб спробувати грамотно зібрати JSON аргументів.
Коли наш інструмент запрацює по-справжньому (в модулі про бекенд і MCP), ви вже чудово орієнтуватиметеся в темі. GPT викликатиме його передбачувано — просто тому, що ми добре спроєктували інтерфейс і опис.
12. Невелика практика для вас
Щоб тема не залишилася суто теоретичною, рекомендую зробити невеликий експеримент одразу після лекції.
Спочатку візьміть один зі своїх сценаріїв GiftGenius або придумайте новий застосунок. Випишіть на папері або в редакторі одну функцію, яку ви явно хочете «дати» моделі, — щось на кшталт search_products, find_hotels, calculate_shipping.
Потім придумайте для цього ж інструмента три пари «імʼя + опис»:
- Дуже абстрактне імʼя та опис.
- Занадто конкретне (майже special-case).
- Добре збалансоване імʼя + опис, де чітко сказано, коли викликати інструмент і чого він не повинен робити.
Далі — за бажанням — ви можете, використовуючи звичайний OpenAI SDK, зробити простий запит із цими варіантами й подивитися, як змінюється поведінка моделі: чи викликається інструмент і як вона заповнює аргументи. У дослідженнях на цю тему якраз наводять вправу такого типу для suggest_gifts.
13. Типові помилки під час проєктування tool-call і описів
Помилка № 1: Називати інструменти tool1, handler, doStuff.
Таке іменування абсолютно безкорисне для моделі. GPT не вгадує «намірів розробника» за назвою файла — їй потрібне семантично зрозуміле імʼя. Якщо ви дасте набір tool1, tool2, tool3 без описів, інструменти майже не викликатимуться: модель просто не зрозуміє, що саме робить кожен, і або проігнорує їх, або вибере випадково.
Помилка № 2: Ставитися до description як до коментарів для людей.
Багато хто пише в описі щось формальне, на кшталт «Функція для підбору подарунків», вважаючи, що подробиці й так видно з коду. Але код модель не бачить: вона має лише текст опису та схему аргументів. Нечіткий опис стає джерелом галюцинацій: GPT або спробує відповісти сама, навіть коли треба було викликати інструмент, або викличе інструмент у дивних ситуаціях.
Помилка № 3: Робити опис занадто широким або занадто вузьким.
Якщо ви пишете «Робить круті речі», модель не розуміє меж застосування. Якщо пишете «Підбирає подарунок тільки для молодшого брата на 18-річчя», ви фактично забороняєте використовувати інструмент майже завжди. Оптимальний опис задає чітку область задач (підбір подарунків за низкою параметрів), перелічує ключові параметри (вік, відносини, бюджет, інтереси) і уточнює, для яких класів запитань інструмент використовувати не треба.
Помилка № 4: Ігнорувати схему аргументів як частину «промпта».
Деякі розробники сприймають JSON Schema лише як спосіб валідації на сервері. Насправді модель активно аналізує назви полів, їхні типи та описи, щоб зрозуміти, які дані треба витягти з користувацького тексту. Якщо ви назвете поле x без опису і зробите його опційним, GPT почне заповнювати його хаотично або не заповнюватиме взагалі. Коректна схема з ясними назвами та короткими описами суттєво зменшує кількість невалідних tool-callів.
Помилка № 5: Очікувати, що модель «зобовʼязана» викликати інструмент.
Розробники іноді дивуються: «Чому GPT не викликала мій інструмент, адже він є?». Відповідь майже завжди одна: з опису або system prompt не випливає, що інструмент потрібен саме для такого запиту, або текст запиту потрапив у зону, де модель вважає, що їй простіше відповісти самій.
Помилка № 6: Змішувати кілька різнотипних дій в одному інструменті.
Іноді хочеться зробити універсальний manage_orders, який і шукає замовлення, і створює нові, і скасовує старі. Для людини це ще можна пояснити, а для моделі виходить «туманний» інструмент без чітких меж. GPT гірше розуміє, коли саме його викликати, і складніше заповнює аргументи — адже всередині зʼявляється купа опційних полів. Краще розділяти такі дії на кілька вузьких інструментів (get_order, create_order, cancel_order) з ясними описами та схемами.
Помилка № 7: Не враховувати дозволи й безпеку в tool-дизайні.
Якщо ви описуєте інструмент, який може виконувати руйнівні дії (списання коштів, видалення даних), але не позначаєте його як destructive і не обмежуєте область використання в описі, ви створюєте ризик. ChatGPT UI може не попросити зайвого підтвердження, а модель здатна запропонувати виклик інструмента навіть у «прикордонних» сценаріях. Правильні анотації та акуратний опис («використовуй тільки після явної згоди користувача») допомагають знизити такі ризики вже на рівні tool-call.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ