vyvchy
    Теми розділу

    14 · AI для QA

    Tool use і function calling

    Зміст

    LLM — це генератор тексту. Вона не має доступу до реального світу: не знає поточної дати, не бачить вашого баг-трекера, не вміє запустити тест і подивитися, чи він зелений. Усе, що вона «знає», зафіксоване на момент навчання (knowledge cutoff — див. /ai/iak-pratsiuiut-llm), а решту вона просто вигадує правдоподібно. Tool use (використання інструментів), він же function calling (виклик функцій), — це механізм, який дає моделі руки: спосіб попросити ваш код виконати конкретну дію і повернути результат.

    Для AQA це фундамент усього агентного стека. Claude Code, що читає файли й запускає тести; MCP-сервер, що керує браузером; асистент, що заводить баг у Jira — усе це той самий цикл «модель попросила інструмент → код виконав → модель побачила результат». Хто розуміє цей цикл, той розуміє, чому агент іноді «застряє», чому вигадує неіснуючий метод замість того, щоб покликати тул, і де проходить межа відповідальності між моделлю і вашим кодом.

    Чому модель не виконує функції сама

    Головна пастка — у назві. «Function calling» звучить так, ніби модель сама викликає ваші функції. Це не так. Модель не виконує коду — ані вашого, ані свого. Усе, що вона робить, — генерує токени.

    Що відбувається насправді: ви разом із запитом передаєте моделі список інструментів (їхні описи). Модель, замість звичайної текстової відповіді, може згенерувати структурований JSON: «я хочу викликати інструмент create_bug з аргументами {"title": "...", "severity": "major"}». На цьому її участь закінчується. Далі ваш код (той, що крутить цикл) вирішує, чи виконувати запит, реально виконує функцію й повертає моделі результат. Модель його «бачить» і продовжує.

    Ця межа — не деталь реалізації, а суть безпеки. Модель нічого не запускає. Вона лише пропонує. Виконує — ваш код, і саме він відповідає за валідацію, дозволи й наслідки. Тому «AI видалив прод» — це завжди про те, що хтось дав інструменту з правом видалення виконуватися без підтвердження, а не про те, що «модель сама вирішила».

    Опис інструмента: ім'я, опис, JSON-схема

    Інструмент описують трьома частинами:

    • Ім'я (name) — унікальний ідентифікатор, за яким код розрізняє тули: get_test_case, create_bug, run_playwright.
    • Опис (description) — текст природною мовою, який пояснює, що інструмент робить і, головне, коли його викликати. Це фактично промпт для тулу.
    • JSON-схема (input schema) — опис аргументів: типи полів, які обов'язкові, які значення допустимі (enum), опис кожного поля.
    const createBug = {
      name: "create_bug",
      description:
        "Заводить баг у трекер. Виклич, коли користувач описав дефект " +
        "і явно попросив його зафіксувати.",
      input_schema: {
        type: "object",
        properties: {
          title: { type: "string", description: "Короткий заголовок дефекту" },
          severity: {
            type: "string",
            enum: ["blocker", "critical", "major", "minor"],
            description: "Ступінь впливу",
          },
          steps: { type: "string", description: "Кроки відтворення" },
        },
        required: ["title", "severity"],
      },
    };

    Ключова думка про опис: модель вибирає інструмент за описом, а не за назвою. Розмитий опис → модель кличе тул не тоді, коли треба, або не кличе взагалі. На сучасних моделях опис має бути прескриптивним: не лише «що робить», а «коли викликати» — на кшталт «Виклич, коли користувач питає статус конкретного тесту».

    JSON-схема — це контракт. required каже, які поля модель мусить заповнити; enum обмежує значення набором; опис поля підказує формат. Ті самі принципи, що й у схемах для API-тестування (розділ «API-тестування»): required проти опціонального, типи, обмеження. Різні провайдери називають поля трохи по-різному (в одних input_schema, в інших — parameters), але всередині це та сама JSON Schema.

    Цикл виклику

    Function calling — це не одноразовий обмін, а цикл, яким керує ваш код, а не модель. Кроки:

    1. Ви надсилаєте моделі: системний промпт + повідомлення + список тулів.
    2. Модель відповідає. Або звичайним текстом (готова відповідь), або запитом на виклик тулу. Ознака другого — окремий статус завершення (наприклад, stop_reason: "tool_use"). У відповіді — ім'я тулу та аргументи (JSON).
    3. Ваш код парсить запит, виконує реальну функцію, дістає результат.
    4. Ви повертаєте результат моделі окремим повідомленням — tool_result, прив'язаним до id того виклику.
    5. Модель бачить результат і продовжує: або дає фінальну текстову відповідь, або просить наступний тул. Цикл крутиться, доки модель не завершить (stop_reason: "end_turn").
    ІнструментМодельВаш кодІнструментМодельВаш кодзапит + список тулівtool_use (ім'я + аргументи)виконати функціюрезультатtool_result (за id) + уся історіяфінальна відповідь (end_turn)ІнструментМодельВаш кодІнструментМодельВаш кодзапит + список тулівtool_use (ім'я + аргументи)виконати функціюрезультатtool_result (за id) + уся історіяфінальна відповідь (end_turn)

    Ось той самий цикл кодом — мінімальний, але робочий кістяк:

    let messages = [{ role: "user", content: userInput }];
    
    while (true) {
      const res = await model.create({ tools, messages });
      messages.push({ role: "assistant", content: res.content });
    
      if (res.stop_reason !== "tool_use") break; // фінальна відповідь
    
      const results = [];
      for (const call of res.content.filter((b) => b.type === "tool_use")) {
        const output = await runTool(call.name, call.input); // виконує ВАШ код
        results.push({ type: "tool_result", tool_use_id: call.id, content: output });
      }
      messages.push({ role: "user", content: results });
    }

    Важлива деталь: історія розмови накопичується, і API без стану (stateless). Ви щоразу надсилаєте всю розмову цілком — включно з попередніми викликами тулів і їхніми результатами. Модель нічого «не пам'ятає» між запитами; пам'ять — це те, що ви їй передаєте (а разом з нею росте кількість токенів і вартість — див. /ai/tokeny-kontekst-i-vartist).

    Structured output і валідація

    Той самий механізм тулів — це основа структурованого виводу (structured output). Якщо вам треба, щоб модель повернула дані у фіксованій формі (наприклад, розпарсити вільний баг-репорт у поля title / severity / steps), ви описуєте тул зі схемою й змушуєте модель його викликати — і на виході отримуєте JSON за схемою.

    Але без строгого режиму схема — це сильна підказка, а не гарантія: модель може повернути значення поза enum, пропустити обов'язкове поле чи зіпсувати тип. Виглядає як валідний JSON, а насправді severity там "Critical", коли схема чекала на "critical". Строгий режим (strict: true) прибирає саме ці структурні порушення — вивід обмежується схемою через grammar-constrained sampling, тож тип, enum і обов'язкові поля гарантовано збігаються. Але й тоді валідуйте на своєму боці (ajv, zod): схема стежить за формою, не за змістом — значення в межах enum усе одно може бути неправильним по суті, а строгий режим підтримує лише підмножину JSON Schema.

    Практичний наслідок для QA: якщо ви генеруєте тест-артефакти у структурованому форматі (див. /ai/veryfikatsiia-rezultativ-ai) — не довіряйте формі наосліп. Валідуйте так само, як валідували б відповідь чужого API.

    Помилки інструментів

    Помилки бувають двох родів.

    Модель покликала погано. Вигадала неіснуючий тул, передала аргумент не того типу, пропустила обов'язкове поле. Тут рятує схема (строгий режим ловить частину помилок) і валідація перед виконанням.

    Реальна функція впала. API повернуло 500, таймаут, «не знайдено». Тут ключове правило: не роняйте цикл. Поверніть помилку як tool_result з ознакою is_error: true — і модель прочитає її та адаптується (спробує інші аргументи, інший підхід або чесно скаже користувачу, що не вийшло). Якщо ви замість цього кинете виняток і обірвете цикл, агент просто «замовкне».

    Дві додаткові дисципліни:

    • Ліміт ітерацій. Модель може зациклитися: кличе тул → падає → кличе знову. Обмежуйте кількість кроків, інакше цикл крутитиметься нескінченно й палитиме токени та гроші.
    • Аргументи тулу — недовірений вхід. Це вивід моделі, а не ваш код. Тул, що виконує shell або SQL з аргументів моделі, — це вектор ін'єкції. Валідуйте перед виконанням так само параноїдально, як користувацький ввід.

    Паралельні виклики

    Модель може попросити кілька тулів в одній відповіді — коли вони незалежні. «Дай статус тесту A і тесту B» → два виклики get_test_case одразу. Ваш код виконує їх (можна конкурентно) і повертає всі результати разом, в одному повідомленні.

    Дві межі:

    • Паралель має сенс лише для незалежних викликів. Якщо результат першого потрібен для аргументів другого (створити баг → прикріпити його id до тесту), виклики мусять бути послідовними.
    • Повертайте всі tool_result однією порцією. Якщо розбити їх на кілька окремих повідомлень, ви «привчаєте» модель не робити паралельних викликів — вона бачить, що формат ламається, і в наступних кроках переходить на послідовні виклики.

    Де це в житті QA

    Tool use — не абстракція, а те, з чим AQA стикається щодня, навіть не називаючи це так.

    • Агенти. Агент (див. /ai/ahenty-i-agentic-loop) — це і є цикл тулів, обгорнутий у план. Claude Code читає файли, запускає тести, редагує код — усе це виклики функцій Read, Bash, Edit.
    • MCP. Model Context Protocol (див. /ai/mcp-protokol-servery-instrumenty) — це стандартизований tool use. Playwright MCP віддає моделі дії браузера (клік, введення тексту, скриншот) як інструменти.
    • Генерація артефактів. Структурований вивід через тул — надійний спосіб отримати тест-кейси чи тестові дані у формі, яка одразу лягає в TMS.
    • Тестування застосунків із function calling. Якщо продукт, який ви тестуєте, сам використовує тули (чат-бот, що заводить заявки), то ваш об'єкт перевірки — уже не лише текст. Ви тестуєте: чи модель кличе правильний тул, чи коректно заповнює схему, чи оброблено помилку тулу, що буде за паралельних викликів. Це місток до /ai/testuvannia-ai-zastosunkiv і /ai/evals-ta-llm-as-judge.

    Типові помилки

    • Виглядає як «модель сама виконала функцію», а насправді вона лише згенерувала запит на виклик — виконав ваш код, і саме він відповідає за наслідки.
    • Виглядає як баг агента (не кличе потрібний тул), а насправді розмитий або неповний опис інструмента: модель не зрозуміла, коли його застосувати.
    • Виглядає як валідний structured output, а насправді значення поза схемою (severity не з enum, зайве поле) — бо схема це підказка, а не гарантія без валідації.
    • Виглядає як «агент завис» після помилки тулу, а насправді код кинув виняток і обірвав цикл замість повернути tool_result з is_error, який модель могла б обробити.
    • Виглядає як розумний вибір моделі не робити паралельні виклики, а насправді ви розбили tool_result на кілька повідомлень і привчили її до послідовності.

    Підсумок

    • Модель не виконує функцій — вона генерує структурований запит на виклик; виконує і відповідає за наслідки ваш код.
    • Інструмент = ім'я + опис + JSON-схема; модель вибирає тул за описом, тож опис має казати, коли його кликати.
    • Цикл «запит → tool_use → виконання → tool_result → продовження» крутить ваш код, доки модель не завершить; історія передається щоразу цілком.
    • Помилку тулу повертайте моделі (is_error), а не роняйте цикл; став ліміт ітерацій і валідуй аргументи як недовірений вхід.
    • Structured output — це той самий механізм тулів; схема не звільняє від валідації результату.

    Що питають на співбесіді

    • «Чим function calling відрізняється від того, що модель просто пише код?» Дивляться, чи розумієте, що модель не виконує нічого — лише повертає структурований запит, а виконує зовнішній код.
    • «Опишіть цикл виклику інструмента.» Чекають на послідовність: тули в запиті → tool_use → виконання на вашому боці → tool_result за id → повтор до end_turn.
    • «Що станеться, якщо інструмент упаде посеред роботи агента?» Перевіряють, чи знаєте про повернення помилки моделі (is_error) і про ліміт ітерацій замість обірваного циклу.
    • «Як ви тестували б чат-бота, що заводить заявки через тули?» Хочуть побачити перевірки: правильний тул, коректна схема, обробка помилок, паралельні та послідовні виклики, недетермінізм.
    • «Чи гарантує JSON-схема правильний вивід?» Червоний прапорець — сказати «так». Правильно: схема сильно допомагає, але результат усе одно валідують.

    Джерела