Усі уроки · 44
Інструменти та виклик функцій
Як модель викликає ваш код: схеми інструментів, повний круг виклику функції, робочий бот погоди та практики, що роблять агентів надійними.
Почніть звідси: виклик інструмента — це текст
Якщо ви ніколи цим не займалися, почніть з одного речення, бо з нього випливає все інше:
«Виклик інструмента» — це текст, який пише модель і в якому просить вас запустити функцію. Ваш код запускає функцію й вставляє результат назад — теж у вигляді тексту.
Нічого екзотичного не відбувається. Немає ніякої системи плагінів, жодного
виконання коду всередині моделі, жодного таємного каналу до вашої бази даних.
Модель пише «Хочу викликати get_weather(city="Prague")», а API віддає вам цей запит
в охайному полі, щоб не доводилося вичленовувати його з речення. Після цього модель
чекає й нічого не робить, доки ви не викличете її знову з відповіддю.
Уявіть колегу в кімнаті без вікон із блокнотом для записок. Він уміє читати, думати
й писати — і все. Ви просуваєте під двері повідомлення; він у відповідь просуває
записку: «Будь ласка, запустіть get_weather(city='Prague') і скажіть, що вийшло».
Ви йдете й запускаєте. Пишете результат на аркушику, просуваєте його назад під
двері — і лише тоді він продовжує. Будь-який агентний фреймворк — це машина для
передавання записок під цими дверима.
Два наслідки, які варто запам’ятати:
- Модель не може зрозуміти, що відбулося в реальності. Реальна температура, значення з кешу, рядок із помилкою, який ви написали вручну, — для моделі все це текст. Тому, якщо повернути «Error: city not found» (замість падіння), модель зможе виправитися на наступному кроці.
- Модель нічого не вирішує щодо виконання. Вона може попросити про будь-що, що має ім’я. Чи буде запит виконано взагалі, з чиїми обліковими даними та з якими обмеженнями — цілком рішення вашого коду; це і є межа безпеки.
Модель не запускає ваші функції
Модель ніколи не запускає ваші функції. Вона не може. У неї немає рук, доступу до мережі, файлової системи — це скринька, що перетворює текст на текст. Усе, що відбувається у світі, відбувається у вашому коді.
Картинка, якою я користуюся: модель — це консьєрж у готелі, який не може відійти від стійки. Консьєрж може вирішити, що потрібно зробити, й охайно записати це на бланку — «рушники в номер 412», — але віднести їх сам не може. Хтось інший читає бланк і виконує роботу. Якщо бланк розпливчастий, стається не те; якщо в консьєржа сто форм на вибір, він частіше обирає не ту. Ваше завдання — дати йому невелику кількість зрозумілих форм.
Це серце будь-якого агента і водночас межа безпеки: модель пропонує, ваш код вирішує.
Коли ми кажемо, що модель «викликає інструмент», відбувається ось що:
- Ви описуєте доступні інструменти в запиті.
- Модель видає структурований JSON: «Хочу викликати
get_weatherз такими аргументами». - Ваш код бачить цей JSON, запускає справжню функцію й надсилає результат назад.
- Модель читає результат і продовжує.
Модель — це планувальник, здатний видавати лише текст і JSON. Кожна реальна можливість — пошук, база даних, файлова система, платежі — живе у вашому коді, за функцією, яку модель може попросити вас запустити. Це межа безпеки, і обходити її не потрібно. Модель може запитувати дії, а ваш код вирішує й виконує їх.
Опис інструмента: схема
Визначення інструмента складається з трьох частин: імені, опису та JSON Schema для аргументів. Схема — це просто бланк: у ньому перелічено поля, які модель має заповнити, тип кожного поля та те, які з них обов’язкові. Опис — єдине, на що модель спирається, вирішуючи, коли й як викликати інструмент.
1import json2import requests3 4tools = [5 {6 #98c379">"type": "function",7 #98c379">"function": {8 #98c379">"name": "get_weather",9 #98c379">"description": (10 #98c379">"Get current weather for a city. Use for any question "11 #98c379">"about temperature, rain, wind or forecast."12 ),13 #98c379">"parameters": {14 #98c379">"type": "object",15 #98c379">"properties": {16 #98c379">"city": {17 #98c379">"type": "string",18 #98c379">"description": "City name, e.g. 'Prague'"19 },20 #98c379">"units": {21 #98c379">"type": "string",22 #98c379">"enum": ["celsius", "fahrenheit"],23 #98c379">"description": "Temperature units, default celsius"24 }25 },26 #98c379">"required": ["city"]27 }28 }29 }30]31 32def get_weather(city: str, units: str = #98c379">"celsius") -> str:33 # СПРАВЖНЯ функція — звичайний код, без магії34 r = requests.get(35 #98c379">"https://wttr.in/" + city,36 params={#98c379">"format": "j1"},37 timeout=10,38 )39 data = r.json()[#98c379">"current_condition"][0]40 temp = data[#98c379">"temp_C"] if units == "celsius" else data["temp_F"]41 return json.dumps({42 #98c379">"city": city,43 #98c379">"temp": temp,44 #98c379">"desc": data["weatherDesc"][0]["value"],45 })Зверніть увагу на enum в units: схеми дають змогу обмежити вибір моделі, а це
усуває цілі класи помилок ще до того, як вони виникнуть. enum просто означає
«відповідь має бути одним із цих значень», а required — «це поле пропускати
не можна».
Ось частина зі схемою окремо — її можна вставити у власний запит:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city. Use for any question about temperature, rain, wind or forecast. Returns temp and a short description.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name, e.g. 'Prague'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units, default celsius"
}
},
"required": ["city"]
}
}
}Схеми працюють завдяки трьом звичкам: називайте інструмент за дією
(get_weather, а не weather), пишіть опис так, ніби інструктуєте нового
співробітника, і тримайте список полів достатньо коротким, щоб нічого не було
двозначним.
Повний круг виклику
Тепер подивіться на саму розмову — з кількістю токенів, щоб бачити, у що обходиться кожен крок:
Третє повідомлення дивує людей найбільше: це повідомлення асистента, вмістом
якого є запит до інструмента; звичайного тексту в ньому немає. У світі поки що нічого не
сталося — модель лише написала записку-запит. Четверте — результат інструмента,
доданий до розмови як повідомлення з роллю tool: так ваш код звітує. Потім модель
викликають знову, і тепер вона бачить усю історію й пише відповідь для людини.
Ось як ця відповідь буквально виглядає на момент надходження і що ваш код надсилає під час наступного виклику — лише JSON на вході, текст на виході:
1// Що повернула модель. Зверніть увагу: content ПОРОЖНІЙ, а2// запит лежить у tool_calls. Це текст — модель3// нічого не виконала.4{5#98c379">"role": "assistant",6#98c379">"content": null,7#98c379">"tool_calls": [8 {9 #98c379">"id": "call_8Fk2",10 #98c379">"type": "function",11 #98c379">"function": {12 #98c379">"name": "get_weather",13 #98c379">"arguments": "{\"city\":\"Prague\",\"units\":\"celsius\"}"14 }15 }16]17}18 19// Що далі робить ВАШ код: запускає справжню функцію, потім20// додає результат як повідомлення з роллю "tool" і тим самим21// id. Значення — РЯДОК, тобто знову текст.22{23#98c379">"role": "tool",24#98c379">"tool_call_id": "call_8Fk2",25#98c379">"content": "{\"city\": \"Prague\", \"temp\": \"14\", \"desc\": \"Light rain shower\"}"26}Промовте це вголос один раз: модель просить, ваш код діє, модель читає, що повернулося. Будь-який агентний фреймворк, яким ви колись скористаєтеся, — це обгортка навколо цього речення.
Ось цей цикл у вигляді коду:
1from openai import OpenAI2import json3 4client = OpenAI()5messages = [6 {#98c379">"role": "system", "content": "You are a weather assistant."},7 {#98c379">"role": "user", "content": "Should I take an umbrella in Prague today?"},8]9 10while True:11 response = client.chat.completions.create(12 model=#98c379">"gpt-4o-mini",13 messages=messages,14 tools=tools,15 )16 msg = response.choices[0].message17 messages.append(msg)18 19 if not msg.tool_calls: # інструмент не запитано -> підсумкова відповідь20 print(msg.content)21 break22 23 for call in msg.tool_calls: # виконуємо кожен запитаний інструмент24 args = json.loads(call.function.arguments)25 result = get_weather(**args)26 messages.append({27 #98c379">"role": "tool",28 #98c379">"tool_call_id": call.id,29 #98c379">"content": result,30 })31 # цикл: модель тепер бачить результат інструмента й продовжуєЦей цикл while і є агент. Усе інше — зручності.
Практики, що роблять усе надійним
- Пишіть описи для нового співробітника. «Отримати погоду» — слабкий опис. Скажіть, для чого інструмент («використовуй для будь-якого запитання про температуру, дощ, вітер»), що він повертає і коли ним не варто користуватися. Опис — це ваш промпт для моделі про цей інструмент.
- Тримайте набір інструментів невеликим. Десять добре описаних інструментів кращі за сорок розпливчастих. Коли варіантів забагато, моделі частіше обирають не той інструмент або вигадують аргументи.
- Повертайте помилки рядками, а не винятками. Якщо виклик API не вдався, поверніть у цикл «Error: city not found» — модель часто сама виправиться. Процес, що впав, відновитися не може, а цикл — може.
- Обмежуйте схемою. Переліки (enum), обов’язкові поля й типи перетворюють «модель може написати що завгодно» на «модель має вибрати з цього».
- Логуйте кожен виклик і результат. Коли агент поводиться дивно, запис викликів інструментів — ваші дані для налагодження; без нього ви гадаєте.
Що робить модель, коли «викликає інструмент»?
Інструмент постійно викликають із хибними аргументами. Перше, що потрібно покращити:
Виклик API всередині інструмента завершився помилкою. Що має зробити ваш код?
Підсумок
Тепер ви можете замкнути цикл: описати інструменти, дозволити моделі запитувати їх,
а потім самим запускати запитане й повертати результати. Цей цикл while — увесь
двигун. Єдине, що варто прочитати далі, — MCP: відкритий
стандарт, який дає змогу опублікувати інструменти так, щоб їх знаходила й
використовувала будь-яка модель, не лише ваша.