Усі уроки · 44
Шлях 3 · Дорожня карта AI-інженера·Урок 21 з 33

Інструменти та виклик функцій

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

РівеньСереднійЧас~15 хв

Почніть звідси: виклик інструмента — це текст

Якщо ви ніколи цим не займалися, почніть з одного речення, бо з нього випливає все інше:

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

Нічого екзотичного не відбувається. Немає ніякої системи плагінів, жодного виконання коду всередині моделі, жодного таємного каналу до вашої бази даних. Модель пише «Хочу викликати get_weather(city="Prague")», а API віддає вам цей запит в охайному полі, щоб не доводилося вичленовувати його з речення. Після цього модель чекає й нічого не робить, доки ви не викличете її знову з відповіддю.

Уявіть колегу в кімнаті без вікон із блокнотом для записок. Він уміє читати, думати й писати — і все. Ви просуваєте під двері повідомлення; він у відповідь просуває записку: «Будь ласка, запустіть get_weather(city='Prague') і скажіть, що вийшло». Ви йдете й запускаєте. Пишете результат на аркушику, просуваєте його назад під двері — і лише тоді він продовжує. Будь-який агентний фреймворк — це машина для передавання записок під цими дверима.

Два наслідки, які варто запам’ятати:

  • Модель не може зрозуміти, що відбулося в реальності. Реальна температура, значення з кешу, рядок із помилкою, який ви написали вручну, — для моделі все це текст. Тому, якщо повернути «Error: city not found» (замість падіння), модель зможе виправитися на наступному кроці.
  • Модель нічого не вирішує щодо виконання. Вона може попросити про будь-що, що має ім’я. Чи буде запит виконано взагалі, з чиїми обліковими даними та з якими обмеженнями — цілком рішення вашого коду; це і є межа безпеки.

Модель не запускає ваші функції

Модель ніколи не запускає ваші функції. Вона не може. У неї немає рук, доступу до мережі, файлової системи — це скринька, що перетворює текст на текст. Усе, що відбувається у світі, відбувається у вашому коді.

Картинка, якою я користуюся: модель — це консьєрж у готелі, який не може відійти від стійки. Консьєрж може вирішити, що потрібно зробити, й охайно записати це на бланку — «рушники в номер 412», — але віднести їх сам не може. Хтось інший читає бланк і виконує роботу. Якщо бланк розпливчастий, стається не те; якщо в консьєржа сто форм на вибір, він частіше обирає не ту. Ваше завдання — дати йому невелику кількість зрозумілих форм.

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

Коли ми кажемо, що модель «викликає інструмент», відбувається ось що:

  1. Ви описуєте доступні інструменти в запиті.
  2. Модель видає структурований JSON: «Хочу викликати get_weather з такими аргументами».
  3. Ваш код бачить цей JSON, запускає справжню функцію й надсилає результат назад.
  4. Модель читає результат і продовжує.

Модель — це планувальник, здатний видавати лише текст і JSON. Кожна реальна можливість — пошук, база даних, файлова система, платежі — живе у вашому коді, за функцією, яку модель може попросити вас запустити. Це межа безпеки, і обходити її не потрібно. Модель може запитувати дії, а ваш код вирішує й виконує їх.

Опис інструмента: схема

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

weather_bot.py — схема інструмента
1import json
2import requests
3 
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), пишіть опис так, ніби інструктуєте нового співробітника, і тримайте список полів достатньо коротким, щоб нічого не було двозначним.

Повний круг виклику

Тепер подивіться на саму розмову — з кількістю токенів, щоб бачити, у що обходиться кожен крок:

виклик функцій крок за кроком
press the button below to send the first message
sent to the modelnothing yet — every message is sent in full

Третє повідомлення дивує людей найбільше: це повідомлення асистента, вмістом якого є запит до інструмента; звичайного тексту в ньому немає. У світі поки що нічого не сталося — модель лише написала записку-запит. Четверте — результат інструмента, доданий до розмови як повідомлення з роллю 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 OpenAI
2import json
3 
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].message
17 messages.append(msg)
18 
19 if not msg.tool_calls: # інструмент не запитано -> підсумкова відповідь
20 print(msg.content)
21 break
22 
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: відкритий стандарт, який дає змогу опублікувати інструменти так, щоб їх знаходила й використовувала будь-яка модель, не лише ваша.