1. Что такое многошаговый run и чем он отличается от «разового» запроса
Когда вы работали только с ChatGPT App и MCP tools, привычная картина была довольно линейной: пришёл пользовательский запрос → GPT решил вызвать один или несколько инструментов → вы дали ответ пользователю. Это всё еще можно считать «одним логическим шагом», даже если внутри инструмента вы делали что-то посложнее.
У агента run — это цель + серия шагов. Мы больше не думаем в категориях «один промпт — один ответ», а воспринимаем задачу как мини-проект, который агент ведёт от начала до конца.
Можно представить разницу так:
| Тип взаимодействия | Что делает модель | Где логика |
|---|---|---|
| Обычный вызов инструмента в ChatGPT App | Определяет, вызывать ли инструмент, подставляет аргументы, формирует ответ на основе результата | Основная бизнес-логика и последовательность действий — в одном инструменте или в backend |
| Агентный 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.
Основные шаги
Опишем минимальный вариант из 4 шагов:
- Уточнение контекста получателя
Цель: собрать информацию о том, кому дарят подарок (возраст, пол, интересы, отношение к дарителю), а также бюджет и дату события.
Инструменты: возможно, вообще без инструментов, чисто диалог модель ↔ пользователь. - Поиск и первичный отбор подарков
Цель: получить «сырую» выборку подарков.
Инструменты: 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-вызовы, повторяющиеся шаги, неочевидные циклы. Если наоборот всё закодировать жёстко в backend как конечный автомат, модель превратится в декоратор текста, а не в умного исполнителя задач.
Что обычно оставляют за моделью
В контексте GiftGenius и похожих сценариев разумно доверять модели:
- формулировку вопросов пользователю (как уточнить интересы, как корректно спросить про бюджет);
- решение, когда информации достаточно, чтобы запускать поиск;
- выбор, какие именно инструменты использовать в рамках одной фазы (например, какой именно tool поиска магазина использовать, если их несколько);
- генерацию текстов описаний, объяснений, сравнений.
Что лучше зашить жёстко
При этом стоит заранее зафиксировать:
- крупные фазы сценария («Сбор информации» → «Поиск» → «Фильтрация» → «Форматирование» → «Финал»);
- лимиты на шаги и время;
- условия, когда агент обязан «стопнуть» и честно сказать пользователю, что задача неразрешима (например, если бюджет 5 долларов, но нужен дорогой электронный гаджет с доставкой завтра);
- idempotency-политику инструментов и retry-стратегии.
Пример гибрида: фазы как состояние, детализация — за моделью
Можно завести в state агента поле phase, которое будет принимать значения "collect_profile" | "search" | "filter" | "format" | "done". Тогда ваш backend (или сам 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 секунд.
Если timeout срабатывает, вы аккуратно завершаете 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, виджет
Раньше мы уже говорили, что оркестрация может жить на разных уровнях: в агенте, в backend, в виджете.
С точки зрения многошаговых процессов логика примерно такая.
Агент (Agents SDK) отвечает за «мысленный» workflow:
- как разбить цель на шаги;
- какие инструменты вызывать и в каком порядке;
- какие дополнительные вопросы задать пользователю.
Backend обычно обеспечивает:
- реализацию инструментов (поиск, фильтр, commerce и т.п.);
- хранение состояния и чекпоинтов;
- жёсткие бизнес-ограничения (budget caps, права, доступность регионов).
Виджет (Apps SDK) управляет:
- отображением прогресса (степпер, прогресс-бар, «шаг 2 из 4»);
- формами ввода;
- UX-мелочами, вроде disable-кнопок, когда не все данные заполнены.
Хорошая практика — мыслить так: агент режиссирует работу инструментов и диалог, а 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 sec timeout
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: «Пусть модель сама разберётся, я просто дам ей все инструменты».
Когда агенту доступен десяток пересекающихся по смыслу tools без чёткой system-инструкции и фаз, модель начинает метаться: вызывать одно и то же разными способами, дублировать запросы, заходить в циклы. Лучше потратить время на дизайн: разделить сценарий на фазы, ограничить список инструментов внутри каждой фазы и явно прописать стратегию в system-промпте.
Ошибка №2: Отсутствие лимитов шагов и времени.
Если не задать maxSteps и timeout, в продакшене вы быстро получите «блуждающие» run’ы, которые жрут ресурсы, а пользователи ничего не видят. Лимиты — не «опция», а базовая гигиена. При этом важно осмысленно обрабатывать ситуации превышения лимитов, а не просто падать немым 500.
Ошибка №3: Нет явных критериев завершения.
Модель завершает run, когда ей кажется, что уже «хватит», но её представление о «хватит» далеко от бизнес-требований. Если не формализовать критерии успеха (сколько подарков, какие поля, какие фильтры пройдены) и не проверять их, вы получите нестабильный UX: сегодня пять отличных вариантов, завтра один «так себе» и ещё три дубликата.
Ошибка №4: Неотслеживание повторяющихся вызовов инструментов.
Агент может застрять в паттерне «получил ошибку → переформулировал запрос на 2 слова → снова вызвал тот же инструмент». Если вы не отслеживаете повторяющиеся вызовы по (toolName, args), эти циклы окажутся невидимыми, пока вы не посмотрите в логи и не ужаснётесь. Простые счётчики и hash аргументов сильно помогают.
Ошибка №5: Смешивание оркестрации и реализации бизнес-логики в одном инструменте.
Иногда пытаются спрятать целый workflow внутрь одного MCP-tool или функции агента: и поиск, и фильтр, и форматирование, и принятие решения. В итоге агент теряет смысл — модель не может по шагам контролировать процесс, вы теряете прозрачность и возможность переиспользовать части сценария. Лучше вынести отдельные этапы в самостоятельные tools и дать агенту их композицию.
Ошибка №6: Отсутствие связи с состоянием и чекпоинтами.
Многошаговый процесс без сохранения промежуточного состояния и чекпоинтов превращается в хрупкий монолит: если что-то упало посередине, пользователь должен начинать всё заново. Это особенно критично для сценариев, где пользователь ходит туда-сюда между шагами или возвращается спустя время. Используйте state store, храните фазу, профиль, кандидатов и давайте агенту возможность продолжать с нужного места.
Ошибка №7: Игнорирование UX-слоя.
Иногда разработчики увлекаются внутренним workflow агента и забывают, что пользователь видит только виджет и сообщения в чате. Если в UI нет понятного прогресса, статусов «ищем подарки…», «фильтруем варианты…», пользователь будет думать, что App «завис» или «ничего не делает», даже если агент оркестрирует сложный процесс. Планируя многошаговый run, сразу думайте, как он отразится в интерфейсе.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ