1. Що таке багатокроковий run і чим він відрізняється від «разового» запиту
Якщо ви працювали лише з ChatGPT App і MCP tools, то звична картина була доволі лінійною: приходить користувацький запит → GPT вирішує викликати один або кілька інструментів → ви відповідаєте користувачеві. Це все ще можна вважати «одним логічним кроком», навіть якщо всередині інструмента ви робите щось складніше.
Для агента run — це ціль і серія кроків. Ми більше не мислимо в категоріях «один промпт — одна відповідь», а сприймаємо завдання як мініпроєкт, який агент веде від початку й до кінця.
Різницю можна уявити так:
| Тип взаємодії | Що робить модель | Де логіка |
|---|---|---|
| Звичайний виклик інструмента в ChatGPT App | Визначає, чи викликати інструмент, підставляє аргументи, формує відповідь на основі результату | Основна бізнес-логіка й послідовність дій — в одному інструменті або на бекенді |
| Агентний run (Agents SDK) | Планує кілька кроків, вирішує, коли й який tool викликати, аналізує проміжні результати та за потреби переглядає план | Логіка «як дійти до мети» частково закладена в system-інструкції агента, а частково формується в самій моделі |
Тут важливий момент: ви не зобовʼязані віддавати планування повністю на відкуп моделі. Зазвичай виходить гібридний підхід: ви жорстко кодуєте великі фази сценарію (наприклад, «спочатку зібрати вимоги, потім підбирати подарунки, потім готувати картку»), а всередині кожної фази дозволяєте агенту доволі вільно користуватися інструментами.
Мініаналогія
Разовий виклик інструмента — це як викликати курʼєра: «забери один документ і привези його в офіс».
Багатокроковий агентний run — це як особистий асистент: «Підготуй мені подарунок для колеги до дня народження: дізнайся, що йому подобається, підбери кілька варіантів, перевір доставку й зведи все в гарну презентацію». Асистент сам вирішує, які дії виконувати в процесі.
Трохи далі в лекції ми також поглянемо, як такі багатокрокові запуски run вбудовуються в уже знайомий вам стек Apps SDK → MCP → backend. Так для ChatGPT і віджета агентна логіка виглядатиме як звичайний охайний MCP-інструмент.
2. Як модель сама планує кроки: погляд згори
Якщо говорити в термінах Agents SDK, кожен run зручно уявляти як трійку:
- Goal (ціль): текстовий опис завдання, який потрапляє в system/user-інструкції агента.
- Tools: набір доступних інструментів із якісними описами та JSON Schema.
- State: історія кроків і структурований стан, який ви зберігаєте зовні (БД, Redis — що завгодно).
Далі запускається знайомий цикл run: модель дивиться на ціль і доступні інструменти та на кожному кроці вирішує:
- «Зараз у мене достатньо інформації — можна видати фінальний результат користувачеві»;
- або «Мені потрібно викликати інструмент X з такими аргументами»;
- або «Я отримав результат інструмента — тепер треба його інтерпретувати, відфільтрувати й, можливо, викликати інший інструмент».
На рівні псевдокоду ідея виглядає так (важливо: це ментальна модель, а не реальний API):
while (!done && steps < MAX_STEPS) {
const modelResponse = await callModel({
system: agentPolicy,
messages: history,
tools,
});
if (modelResponse.type === "tool_call") {
const toolResult = await callTool(modelResponse.toolName, modelResponse.args);
history.push({ role: "tool", content: toolResult });
} else {
// фінальна відповідь
done = true;
return modelResponse.content;
}
steps++;
}
У реальному Agents SDK увесь цей цикл уже реалізований і «схований» усередині бібліотеки. Ви описуєте агента декларативно, а SDK запускає модель та інструменти циклічно, доки не отримає фінальну відповідь або не впирається в обмеження за кроками чи часом.
Завдання архітектора — подбати про таке:
- сформулювати goal і system-інструкцію так, щоб модель планувала розумні кроки;
- побудувати набір інструментів без перетинів за змістом;
- задати обмеження за кроками та часом;
- продумати, які кроки можуть бути паралельними.
Коли в нас є ціль, інструменти й уявлення про стан, наступне питання — якими кроками йти до цієї цілі. Не всі кроки однакові: частина з них строго послідовні, а частину можна розпаралелити.
3. Послідовні та паралельні кроки
Тепер, коли в нас є базове розуміння циклу run, важливо розібратися, якими взагалі бувають кроки всередині такого процесу. В агентному workflow є два великі типи: послідовні та паралельні.
Послідовні кроки
Це ситуація, коли результат кроку A критично потрібен для кроку B. Наприклад, у нашому навчальному GiftGenius:
- Спочатку потрібно зрозуміти, хто отримувач подарунка: колега чи родич, який вік, які інтереси.
- Потім підібрати набір кандидатів через tool search_gifts.
- Далі відфільтрувати їх за бюджетом і обмеженнями.
- Потім гарно оформити картки для віджета.
- І лише потім, можливо, запропонувати перехід до checkout.
Кожен наступний крок залежить від даних попереднього, тож виконання виходить строго послідовним.
У псевдокоді агентної поведінки це може виглядати як «внутрішній план» моделі:
1. Розпитати користувача про отримувача та бюджет
2. Викликати tool search_gifts(profile, budget)
3. Викликати tool filter_by_constraints(gifts, constraints)
4. Сформувати підсумковий список і опис
Модель сама не «пише» такий список кодом, але ми можемо підштовхувати її до подібної структури через system-інструкції, приклади діалогів і опис інструментів.
Паралельні кроки
Іноді кроки можна виконувати незалежно. Наприклад, ми хочемо порівняти пропозиції щодо подарунків одразу з трьох магазинів:
- search_gifts_amazon
- search_gifts_etsy
- search_gifts_local_store
З погляду агента це три незалежні виклики інструментів. Їх можна запускати паралельно, щоб скоротити загальний час відповіді.
В Agents SDK (і загалом у сучасних агентних фреймворках) часто є вбудована підтримка паралельних викликів інструментів, якщо модель в одному кроці пропонує одразу кілька викликів. Канонічний сценарій такий: модель у відповіді описує список викликів, SDK запускає їх паралельно, збирає результати й підкладає їх як набір tool-повідомлень до наступного кроку моделі.
З погляду планування це виглядає так:
// Крок агента: модель вирішила викликати три інструменти
const calls = [
{ name: "search_gifts_amazon", args: {...} },
{ name: "search_gifts_etsy", args: {...} },
{ name: "search_gifts_local_store", args: {...} },
];
const results = await Promise.all(
calls.map(c => callTool(c.name, c.args))
);
// Далі всі результати додаються в контекст перед наступним кроком моделі
Якщо ви писали фронтенд на JS/TS, ви вже стикалися з ідеєю паралельних запитів: наприклад, коли через Promise.all запускаєте кілька fetch() одночасно. Тепер та сама ідея зʼявляється всередині циклу run. Щоправда, рішення що саме можна виконувати паралельно значною мірою ухвалює вже сама модель.
4. Приклад workflow для GiftGenius: кроки, цілі та інструменти
У розділі про послідовні кроки ми вже інтуїтивно розбили поведінку GiftGenius на етапи. Тепер давайте формалізуємо цей самий багатокроковий сценарій як агентний workflow: опишемо ціль, кроки й привʼяжемо їх до інструментів і конфігурації агента. Ми поки не будемо привʼязуватися до конкретного API Agents SDK, а опишемо структуру й додамо трохи умовного TypeScript-коду, щоб закріпити матеріал.
Мета (goal)
Нехай ціль звучить так:
Допомогти користувачеві підібрати 3–5 варіантів подарунка для конкретного отримувача, з урахуванням бюджету, приводів і обмежень на доставку, та видати структурований список карток подарунків для віджета GiftGenius.
Основні кроки
Опишемо мінімальний варіант із чотирьох кроків:
- Уточнення контексту отримувача
Мета: зібрати інформацію про те, кому дарують подарунок (вік, стать, інтереси, стосунок до дарувальника), а також бюджет і дату події.
Інструменти: можливо, взагалі без інструментів — суто діалог «модель ↔ користувач». - Пошук і первинний відбір подарунків
Мета: отримати «сиру» вибірку подарунків.
Інструменти: search_gifts(profile, budget) — tool, який звертається до нашого каталогу/пошукової системи й повертає список кандидатів. - Фільтрація та сортування
Мета: відсіяти невідповідні варіанти (немає доставки в регіон, вихід за бюджет, невідповідні обмеження) і відсортувати за релевантністю.
Інструменти: filter_and_score_gifts(candidates, constraints) — чистий, ідемпотентний інструмент. - Форматування результату для віджета
Мета: привести дані до формату, зручного для UI: заголовок, короткий опис, зображення, ціна, CTA.
Інструменти: format_gift_cards(gifts) — це може бути і кодовий інструмент (генерація структури), і LLM-інструмент (естетичні тексти).
Як це може виглядати в конфігурації агента
Уявімо, що в нас є умовний конструктор агента (псевдокод):
import { createAgent } from "@acme/agents-sdk";
import { tools } from "./gift-tools";
export const giftAgent = createAgent({
name: "gift-guru",
system: `
Ти — агент GiftGenius, допомагаєш підбирати подарунки.
Мета: запропонувати 3–5 варіантів, які реально можна придбати,
з урахуванням профілю отримувача, бюджету та обмежень на доставку.
Спершу уточни важливі деталі, потім використовуй інструменти пошуку й фільтрації.
Не викликай інструменти, якщо ще не знаєш бюджет або ключові інтереси.
Завершуй роботу, коли маєш чіткий список карток подарунків.
`,
tools, // тут будуть search_gifts, filter_and_score_gifts, format_gift_cards
maxSteps: 12,
timeoutMs: 15000,
});
Зверніть увагу на кілька деталей:
- У system-інструкції ми прямо кажемо, що агент має спочатку уточнити деталі, а вже потім викликати інструменти пошуку. Це зменшує ризик, що модель почне викликати інструменти із занадто розмитим контекстом.
- Ми обмежили maxSteps, щоб агент не потрапляв у нескінченні цикли.
- Тайм-аут timeoutMs потрібен, щоб увесь run не розтягувався для користувача надто довго.
5. Автооркестрація моделлю: що «віддати на волю моделі», а що зафіксувати жорстко
Агент — це баланс між свободою моделі та жорсткою структурою, яку ви закладаєте.
Якщо дати моделі надто багато свободи й не задати меж, ви отримаєте «творчий безлад»: зайві tool-виклики, повторювані кроки, неочевидні цикли. Якщо ж, навпаки, усе жорстко закодувати на бекенді як скінченний автомат, модель перетвориться на «декоратор тексту», а не на розумного виконавця завдань.
Що зазвичай лишають моделі
У контексті GiftGenius і схожих сценаріїв розумно довірити моделі:
- формулювання запитань користувачеві (як уточнити інтереси, як коректно запитати про бюджет);
- рішення, коли інформації достатньо, щоб запускати пошук;
- вибір, які саме інструменти використовувати в межах однієї фази (наприклад, який саме tool пошуку магазину використати, якщо їх кілька);
- генерацію текстів описів, пояснень і порівнянь.
Що краще «зашити» жорстко
Водночас варто заздалегідь зафіксувати:
- великі фази сценарію («Збір інформації» → «Пошук» → «Фільтрація» → «Форматування» → «Фінал»);
- ліміти на кроки та час;
- умови, за яких агент зобовʼязаний «зупинити процес» і чесно сказати користувачеві, що завдання нерозвʼязне (наприклад, якщо бюджет 5 доларів, але потрібен дорогий електронний ґаджет із доставкою на завтра);
- політику ідемпотентності інструментів і стратегії повторних спроб.
Приклад гібриду: фази як стан, деталізація — за моделлю
Можна завести в state агента поле phase, яке прийматиме значення "collect_profile" | "search" | "filter" | "format" | "done". Тоді ваш бекенд (або сам Agents SDK, якщо він підтримує користувацький state machine) контролюватиме, які інструменти доступні на якій фазі.
Псевдокод:
type Phase = "collect_profile" | "search" | "filter" | "format" | "done";
interface GiftAgentState {
phase: Phase;
profile?: UserProfile;
candidates?: GiftCandidate[];
finalGifts?: GiftCard[];
}
System-інструкція для агента може містити короткий опис фаз, а ви в коді обмежите список tools, який показуєте моделі, залежно від поточної фази. Це і є приклад tool gating, який детальніше розбирається в модулі про workflow.
6. Контроль нескінченних циклів і непотрібних повторів
Якщо дати агенту неконтрольований цикл run, він рано чи пізно почне поводитися як студент перед дедлайном: безкінечно «уточнювати й переписувати», аби лише не здавати роботу. Наше завдання — не дати йому зависнути.
Є три типові джерела нескінченних циклів:
- Модель не впевнена у відповіді й продовжує переформульовувати один і той самий запит до інструмента з незначними змінами.
- Інструмент стабільно повертає помилку або порожній результат, а агент уперто намагається «спробувати ще раз».
- Агент застрягає між двома інструментами: то викликає один, то інший — і не рухається до фінальної відповіді.
Ліміт кроків (maxSteps)
Найпростіший і обовʼязковий механізм — обмеження кількості кроків. У більшості реалізацій Agents SDK ви можете вказати maxSteps під час запуску run або в конфігурації агента. Щойно ліміт досягнуто, SDK завершує run з особливим статусом (наприклад, aborted_by_max_steps). Далі ви вирішуєте, як це показати користувачеві.
У GiftGenius ми можемо вважати, що адекватний підбір подарунків уміщується приблизно в 10 кроків (пара уточнень, пара пошуків, фільтрація, форматування). Тож ставимо, наприклад, 12–15 кроків із запасом і акуратно обробляємо ситуацію, коли ліміт досягнуто:
const run = await giftAgent.run({
input: userGoal,
maxSteps: 12, // перевизначаємо дефолт
});
if (run.status === "max_steps_exceeded") {
// Показуємо користувачеві чесне повідомлення
}
Ліміт часу (timeout)
Іноді проблема не в кількості кроків, а в сумарній тривалості. Інструменти можуть бути повільними, а мережа — нестабільною. Тому корисно вказувати timeoutMs і на рівні окремого tool-виклику, і на рівні всього run.
Наприклад, ви можете вирішити, що:
- кожен виклик зовнішнього API (пошук подарунків у партнера) не має тривати довше за 3–5 с;
- увесь run із підбору подарунків має вкластися в 15 с.
Якщо тайм-аут спрацьовує, ви акуратно завершуєте run і, можливо, показуєте користувачеві частковий результат та чесне пояснення: «частина джерел не відповіла вчасно».
Виявлення повторів
Більш просунутий (але корисний) підхід — виявляти повторювані виклики інструментів з однаковими аргументами. Якщо ви бачите, що агент уже тричі поспіль викликав search_gifts(profile, budget) з тими самими параметрами, це сигнал, що він застряг.
Ви можете додати в state лічильник викликів за ключем (toolName, argsHash) і, якщо лічильник перевалює за поріг, або:
- перервати run і повернути користувачеві зрозумілу помилку;
- або підкинути моделі додаткову інструкцію: «ви вже тричі намагалися викликати цей інструмент з тими самими параметрами — спробуйте змінити стратегію або поставте користувачеві уточнювальне запитання».
Псевдокод:
function shouldAbortToolCall(toolName: string, args: unknown, state: GiftAgentState) {
const key = `${toolName}:${hashArgs(args)}`;
const count = state.toolCallCounts[key] ?? 0;
if (count >= 3) return true;
state.toolCallCounts[key] = count + 1;
return false;
}
Де hashArgs — будь-яка детермінована функція серіалізації аргументів (наприклад, JSON.stringify із сортуванням ключів).
7. Чіткі критерії завершення завдання
Одна з ключових відмінностей «іграшкового» агента від продакшн-агента — наявність зрозумілих критеріїв завершення. Якщо їх немає, модель може або кинути завдання надто рано («ось якісь подарунки, далі самі розберетеся»), або, навпаки, безкінечно «допилювати» результат.
У GiftGenius можна сформулювати просте правило:
- Агент завершується, коли в нього є від 3 до 5 подарунків із заповненими полями: id, title, shortDescription, price, imageUrl, purchaseUrl, і вони пройшли фільтрацію за бюджетом і доставкою.
- Якщо після максимум N спроб пошуку й фільтрації відповідних подарунків менше ніж 3, агент чесно повідомляє користувачеві, що нічого гідного знайти не вдалося, і пропонує збільшити бюджет або послабити обмеження.
Ці критерії можна закодувати прямо в system-інструкції агента та/або в перевірці результату після run.
Приклад перевірки результату після run:
if (run.status === "completed") {
const gifts = run.output.gifts; // допустимо, наш агент повертає структурований JSON
if (!gifts || gifts.length < 3) {
// Агент "завершився", але результат слабкий — можна:
// 1) показати чесне пояснення,
// 2) запропонувати користувачеві змінити умови.
} else {
// Усе ок — показуємо віджет із подарунками
}
}
Важливо не чекати від моделі «магічного» розуміння бізнес-успіху. Ви як розробник маєте явно сформулювати умови «задовільного» результату й перевіряти їх.
8. Де саме реалізується оркестрація: агент, backend, віджет
Раніше ми вже говорили, що оркестрація може бути на різних рівнях: в агенті, на бекенді, у віджеті.
З погляду багатокрокових процесів логіка приблизно така.
Агент (Agents SDK) відповідає за «мисленнєвий» workflow:
- як розбити ціль на кроки;
- які інструменти викликати й у якому порядку;
- які додаткові запитання поставити користувачеві.
Backend зазвичай забезпечує:
- реалізацію інструментів (пошук, фільтр, commerce тощо);
- зберігання стану й чекпойнтів;
- жорсткі бізнес-обмеження (budget caps, права, доступність регіонів).
Віджет (Apps SDK) керує:
- відображенням прогресу (індикатор кроків, прогрес-бар, «крок 2 з 4»);
- формами введення;
- UX-дрібницями — на кшталт вимкнених кнопок, коли не всі дані заповнені.
Хороша практика — мислити так: агент режисує роботу інструментів і діалог, а UI-віджет — візуальний досвід користувача. Вони «домовляються» через структуровані дані (ToolOutput, agent run output).
9. Мініприклад коду: запуск багатокрокового агента GiftGenius з MCP-інструмента
Тепер, як і обіцяли на початку лекції, повʼяжемо нову концепцію з уже знайомим вам стеком Apps SDK → MCP → backend і покажемо невеликий приклад того, як MCP-інструмент може викликати агентний run.
Уявімо, що у вашому app/mcp/route.ts є tool run_gift_workflow, який:
- приймає текстовий запит користувача (його ціль);
- запускає агента giftAgent;
- повертає структурований результат для віджета.
Код спрощений і умовний, але дає уявлення про звʼязку:
// app/mcp/route.ts
import { server } from "@modelcontextprotocol/sdk/server";
import { z } from "zod";
import { giftAgent } from "@/agents/giftAgent";
server.registerTool(
"run_gift_workflow",
{
title: "Підібрати подарунки",
description: "Запускає багатокрокового агента для підбору подарунків",
inputSchema: {
userGoal: z
.string()
.describe("Завдання користувача, наприклад: хочу подарунок колезі до $50"),
},
},
async ({ userGoal }) => {
const run = await giftAgent.run({ // саме тут ми запускаємо агента на 12 кроків і тайм-аут 15 с
input: userGoal,
maxSteps: 12,
timeoutMs: 15000,
});
return {
status: run.status,
gifts: run.output?.gifts ?? [],
debug: run.debugInfo, // за потреби потім приберете
};
}
);
Далі ChatGPT App може викликати цей MCP-tool, як і будь-який інший, а ваш віджет GiftGenius — будувати UI на основі gifts. Ви отримали багатокроковий workflow «під капотом», при цьому зовні для ChatGPT усе виглядає як один охайний tool.
10. Типові помилки під час проєктування багатокрокових процесів
Помилка № 1: «Нехай модель сама розбереться, я просто дам їй усі інструменти».
Коли агенту доступний десяток інструментів, що перетинаються за змістом, без чіткої system-інструкції та фаз, модель починає метатися: викликати одне й те саме різними способами, дублювати запити, заходити в цикли. Краще витратити час на дизайн: розділити сценарій на фази, обмежити список інструментів у межах кожної фази й явно прописати стратегію в system-промпті.
Помилка № 2: Відсутність лімітів кроків і часу.
Якщо не задати maxSteps і timeout, у продакшні ви швидко отримаєте «блукаючі» запуски run, які споживають ресурси, а користувачі нічого не бачать. Ліміти — не «опція», а базова гігієна. Водночас важливо осмислено обробляти ситуації перевищення лімітів, а не просто падати мовчазним 500.
Помилка № 3: Немає явних критеріїв завершення.
Модель завершує run, коли їй здається, що вже «досить», але її уявлення про «досить» далеке від бізнес-вимог. Якщо не формалізувати критерії успіху (скільки подарунків, які поля, які фільтри пройдені) і не перевіряти їх, ви отримаєте нестабільний UX: сьогодні — пʼять відмінних варіантів, завтра — один «так собі» і ще три дублікати.
Помилка № 4: Невідстеження повторюваних викликів інструментів.
Агент може застрягти в патерні «отримав помилку → переформулював запит на два слова → знову викликав той самий інструмент». Якщо ви не відстежуєте повторювані виклики за (toolName, args), ці цикли залишаться невидимими, доки ви не зазирнете в логи й не жахнетеся. Прості лічильники та хеш аргументів дуже допомагають.
Помилка № 5: Змішування оркестрації та реалізації бізнес-логіки в одному інструменті.
Іноді намагаються сховати цілий workflow усередині одного MCP-tool або функції агента: і пошук, і фільтр, і форматування, і ухвалення рішення. У підсумку агент втрачає сенс: модель не може крок за кроком контролювати процес, а ви — прозорість і можливість повторно використовувати частини сценарію. Краще винести окремі етапи в самостійні tools і дати агенту можливість компонувати їх.
Помилка № 6: Відсутність звʼязку зі станом і чекпойнтами.
Багатокроковий процес без збереження проміжного стану й чекпойнтів перетворюється на крихкий моноліт: якщо щось упало посередині, користувач має починати все заново. Це особливо критично для сценаріїв, де користувач ходить туди-сюди між кроками або повертається з часом. Використовуйте state store, зберігайте фазу, профіль, кандидатів і давайте агенту можливість продовжувати з потрібного місця.
Помилка № 7: Ігнорування UX-шару.
Іноді розробники захоплюються внутрішнім workflow агента й забувають, що користувач бачить тільки віджет і повідомлення в чаті. Якщо в UI немає зрозумілого прогресу та статусів «шукаємо подарунки…», «фільтруємо варіанти…», користувач думатиме, що App «завис» або «нічого не робить», навіть якщо агент оркеструє складний процес. Плануючи багатокроковий run, відразу продумуйте, як він відобразиться в інтерфейсі.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ