1. Что такое tool gating и почему это вообще тема для отдельной лекции
До сих пор мы в упрощённых примерах делали так: описываем набор инструментов для App, подключаем MCP‑сервер — и всё это доступно модели всегда. С точки зрения «сделать демку за 5 минут» это работает. С точки зрения реального продукта — не очень.
Tool gating — это паттерн, при котором список доступных модели инструментов не фиксирован, а зависит от контекста: шага workflow, прав пользователя, состояния данных и т.п.
Самое важное: список tools — это не «случайная свалка всего, что вы когда‑либо написали», а часть дизайна сценария. Когда вы проектируете workflow, вы фактически проектируете и то, какие инструменты модель имеет право видеть на каждом этапе.
Простейшая аналогия: вы не даёте стажёру в банке доступ сразу ко всем системам — сначала только просмотр, потом простые операции, потом более серьёзные. Здесь та же логика, только стажёр — это LLM.
2. Проблема «все инструменты сразу»: загрязнение контекста и безопасность
Если дать модели десятки инструментов, она начинает страдать сразу по нескольким направлениям: перегруза контекста, путаницы в выборе и вопросов безопасности. Исследования OpenAI/Anthropic показывают, что чем больше функций вы описываете в контексте, тем хуже модель попадает в нужную.
Во‑первых, каждое определение инструмента — это токены: название, описание, JSON Schema. Список из 30–40 tools легко съедает пару тысяч токенов. Это те самые токены, которые вы могли бы потратить на историю диалога, пользовательский контекст, примеры хороших ответов. Вместо этого модель читает «роман» про ваши API.
Во‑вторых, когда инструменты похожи, модель начинает путаться. Если у вас есть search_products и get_product_details, она может попытаться вызвать get_product_details прямо с текстовым запросом, потому что описание показалось более подходящим.
Плюс отдельно встаёт вопрос безопасности. Есть скучный, но важный принцип минимальных прав (least privilege): система должна иметь только те возможности, которые реально нужны ей «здесь и сейчас». Если на шаге знакомства модель уже знает про checkout, то достаточно небольшой prompt‑инъекции от пользователя, чтобы она попыталась вызвать оплату раньше времени. Tool gating — удобный частный случай минимизации прав: на каждом шаге включаем только нужное.
И наконец, UX. Если модель неожиданно делает что‑то «магическое», чего пользователь не ждал (например, создаёт заказ, хотя человек ещё выбирает подарок), доверие к вашему App стремительно падает.
3. GiftGenius как иллюстрация tool gating
Возьмём наш кейс GiftGenius и честно посмотрим на шаги:
- Интервью: выясняем возраст, пол, интересы получателя, бюджет и т.п.
- Подбор: ищем товары по каталогу, показываем идеи.
- Checkout: когда пользователь уже выбрал подарок, переходим к оформлению.
Если на шаге интервью модель уже знает про search_products, add_to_cart и checkout, она может:
- начать звать поиск слишком рано, ещё до того, как собрала нормальные предпочтения;
- попытаться сразу «оформить заказ», потому что пользователь обмолвился «О, вот это хорошо, беру».
Правильный вариант — менять список доступных tools по мере прохождения шагов. Ниже мы как раз будем разбирать такой сценарий: на шаге интервью видны только инструменты сохранения предпочтений, на шаге подбора — поиск и добавление в корзину, на шаге checkout — собственно checkout.
Сведём это в небольшую таблицу:
| Шаг workflow | Цель шага | Какие инструменты доступны модели | Чего модель «не видит» на этом шаге |
|---|---|---|---|
|
Собрать профиль получателя | |
|
|
Подобрать и уточнить идеи | |
+ (если корзина пуста) |
|
Оформить покупки | |
Любые «настройочные» tools, которые уже не нужны |
Обратите внимание: инструмент checkout появляется только когда есть, что оформлять, и только на соответствующем шаге. Это и есть классический пример tool gating для commerce‑сценария.
4. Стратегии tool gating: по состоянию, по ролям, по ресурсам
Самый распространённый вариант — state‑based gating (гейтинг по шагам workflow): список инструментов зависит от состояния сценария. То есть у вас где‑то хранится переменная step, и по ней определяется, какие инструменты включены, а какие нет.
Но не только шаги могут влиять на инструменты.
Иногда вы делаете role‑based gating (по ролям пользователя): администратору доступны сервисные инструменты (например, переиндексировать каталог), обычному пользователю — только пользовательские. Иногда — resource‑based gating (по состоянию ресурсов): инструмент «открыть дверь» появляется только если в состоянии ресурса дверь помечена как закрытая.
Чтобы не быть голословным, давайте опишем это в виде небольшой функции на TypeScript. Представим, что у нас есть некий контекст с шагом, ролью, текущей корзиной и состоянием некоторого ресурса:
type WorkflowStep = 'interview' | 'browsing' | 'checkout';
type UserRole = 'user' | 'admin';
interface WorkflowContext {
step: WorkflowStep;
role: UserRole;
cartItems: number; // сколько товаров в корзине
doorIsClosed: boolean; // пример resource-based gating: состояние конкретного ресурса
}
Теперь опишем, какие tools вообще бывают в системе, и как их фильтровать:
type ToolName =
| 'save_preference'
| 'finish_interview'
| 'search_products'
| 'get_product_details'
| 'add_to_cart'
| 'checkout'
| 'reindex_catalog'
| 'open_door';
const baseTools: ToolName[] = [
'save_preference',
'finish_interview',
'search_products',
'get_product_details',
'add_to_cart',
'checkout',
'reindex_catalog',
'open_door',
];
Здесь open_door — пример инструмента, который зависит от состояния конкретного ресурса (дверь закрыта или нет).
И сама функция гейтинга:
function getAvailableTools(ctx: WorkflowContext): ToolName[] {
const byStep: ToolName[] =
ctx.step === 'interview'
? ['save_preference', 'finish_interview']
: ctx.step === 'browsing'
? ['search_products', 'get_product_details', 'add_to_cart']
: ['search_products', 'get_product_details', 'add_to_cart', 'checkout'];
const checkoutAllowed =
ctx.step === 'checkout' && ctx.cartItems > 0
? byStep
: byStep.filter((t) => t !== 'checkout');
const withAdmin =
ctx.role === 'admin'
? [...checkoutAllowed, 'reindex_catalog']
: checkoutAllowed;
const withResources =
ctx.doorIsClosed
? [...withAdmin, 'open_door']
: withAdmin.filter((t) => t !== 'open_door');
return withResources;
}
Здесь хорошо видно три «слоя» гейтинга:
- по шагу (byStep);
- по роли пользователя (withAdmin);
- по состоянию ресурса (withResources и флаг doorIsClosed).
Это не код SDK, а просто архитектурный эскиз. Но именно так обычно и думают о tool gating: есть полный каталог инструментов, и есть функция, которая по контексту возвращает подмножество.
5. Где в архитектуре App живёт tool gating
Немного свяжем это с тем, что вы уже знаете про стэк ChatGPT App.
Теоретически MCP-протокол работает так
В MCP инструменты не обязаны быть жёстко зашиты в статический JSON: сервер может отдавать список динамически, исходя из сессии. Более того, в спецификации есть механизм capabilities, где сервер заявляет, что его список инструментов может меняться, и уведомления tools/list_changed, чтобы клиент (ChatGPT/агент) заново запросил список tools, когда что‑то поменялось.
И формально вы можете так сделать, и какие-то MCP-клиенты будут работать с динамическим MCP tools list. Но на сегодняшний день ChatGPT App не поддерживают tools/list_changed. Может в будущем это измениться, но пока что такой способ работать не будет.
А работать будет вот такой
Вы храните состояние и список доступных методов на стороне модели. Вы можете просто присылать модели state и список доступных tools на каждом шаге как часть «картины мира»: в системном промпте явно описывать текущий шаг (например, step = "browsing"), ключевые флаги (например, cartItems = 2, role = "user") и прикладывать только тот поднабор инструментов, который разрешён сейчас.
Модель сама не умеет «забывать» инструменты, но она очень хорошо следует явным инструкциям вида: «На этом шаге ты можешь использовать только эти функции…». В итоге вся логика гейтинга для модели выглядит как простой контракт: вот текущее состояние сценария, вот список кнопок, которыми ты можешь пользоваться, остального для тебя как бы не существует. Это не требует никакой особой «магии» — достаточно последовательно обновлять state и список tools в запросах к модели при переходах между шагами.
Кроме того, вы можете добавить инструкции в structuredContent, что-то типа такого:
{
"instructions": {
"current_step": "browsing",
"enabled_mcp_tools": ["search", "apply"]
}
}
Так же можно добавить защиту на уровне вашего бизнес‑кода. Даже если список tools уже «обновлён», важно дублировать логику гейтинга в самих обработчиках, потому что:
- модель может забыть про инструкции и/или данные, если было долгое обсуждение;
- модель может попытаться вызвать «фантомный» инструмент, который был доступен на прошлом шаге;
Поэтому хороший дизайн: и «прячем» инструменты от модели, и внутри обработчика проверяем, можно ли сейчас это делать.
6. Модельный vs логический tool gating
Если связать это с предыдущим разделом: всё, что происходит на уровне вызова модели (какие флаги/step кладёте в промпт), — это модельный гейтинг, а проверки в самих обработчиках инструментов — логический.
Обычно имеет смысл разделять два слоя:
- Модельный гейтинг — когда модель знает, что инструмент «разрешён» именно сейчас, потому что вы просто явно пишете в инструкциях, какие функции доступны на этом шаге. Для модели мир выглядит как: «вот текущий state, вот такой-то набор кнопок, других нет».
- Логический гейтинг — проверки внутри самого инструмента. Даже если модель всё-таки попыталась вызвать checkout раньше времени (из-за кеша, фантомной памяти или потому что в одном из предыдущих шагов вы всё-таки подсунули ей этот инструмент), обработчик смотрит на текущее состояние и культурно отказывает: примерно в духе «сначала выбери подарок, затем оформим заказ» (а не просто кидает исключение!).
Почему нужны оба слоя? Потому что инфраструктура вокруг LLM и сами сценарии могут вести себя неидеально:
- модель может запомнить, что когда-то видела инструмент checkout, и попытаться сослаться на него в рассуждениях или даже в tool-call;
- вы сами можете по ошибке в одном из шагов передать более широкий набор tools, чем нужно, и модель начнёт использовать лишние функции;
- клиенты/прослойки могут кешировать конфигурацию вызова модели и какое-то время отправлять старый набор инструментов.
На практике это означает простую мысль: рассчитывать только на «мы не положили инструмент в tools — всё, он больше никогда не вызовется» — опасно. Проверки в handlers всё равно нужны.
Пример логического гейтинга в обработчике checkout в псевдо‑TypeScript:
async function checkoutTool(args: { paymentMethodId: string }, ctx: WorkflowContext) {
if (ctx.step !== 'checkout') {
return {
error: 'Checkout not available yet. Please finish selecting a gift first.',
};
}
if (ctx.cartItems === 0) {
return {
error: 'Your cart is empty. Add at least one gift before checkout.',
};
}
// ... реальная логика оформления
}
Такой ответ помогает и пользователю, и модели: модель видит структурированную ошибку и может скорректировать план действий.
7. Как связать tool gating с UI и виджетом
Tool gating — это не только про сервер. UI/UX тоже должен чувствовать изменения.
Виджет знает текущий шаг (мы уже говорили о widgetState и том, что этот стейт может хранить, например, currentStep). Модель — тоже, потому что шаг либо явно передаётся в инструменты, либо зашит в системный промпт. Важно, чтобы UI и набор активных tools были синхронизированы.
Если модель считает, что сейчас шаг «Подбор», а виджет показывает интерфейс «Интервью», пользователь начинает теряться. Если наоборот — UI уже рисует кнопку «Оплатить», но checkout ещё не доступен, модель будет в странном положении: кнопка есть, а функция как будто «не работает».
Небольшая схема жизненного цикла шага с учётом tool gating:
flowchart TD A[Пользователь заполняет интервью в виджете] --> B[Виджет вызывает tool save_preference / finish_interview] B --> C[MCP / backend обновляет state.step] C --> D[Сервер меняет набор tools для сессии] D --> E[Клиент ChatGPT обновляет доступные tools для модели] E --> F[Модель задаёт новые вопросы
и/или вызывает новые инструменты] C --> G[Виджет получает новый шаг через widgetState
и меняет UI]
Для пользователя это выглядит как вполне привычный мастер: сначала несколько вопросов, потом список подарков, потом финальное подтверждение. Но под капотом одновременно переключаются и UI, и список инструментов, и инструкция для модели.
В Next.js‑виджете это можно выразить очень просто. Предположим, вы храните step в widgetState:
type Step = 'interview' | 'browsing' | 'checkout';
function GiftWizardWidget() {
const [widgetState, setWidgetState] = useWidgetState<{ step: Step }>({
step: 'interview',
});
if (widgetState.step === 'interview') {
return <InterviewScreen onDone={() => setWidgetState({ step: 'browsing' })} />;
}
if (widgetState.step === 'browsing') {
return <BrowsingScreen onCheckout={() => setWidgetState({ step: 'checkout' })} />;
}
return <CheckoutScreen />;
}
Здесь мы не показываем tools напрямую, но подразумеваем, что смена step в стейте согласована со сменой набора инструментов на бэкенде. Мы посмотрели, как шаги живут в виджете. Теперь давайте вернёмся на сторону MCP‑сервера и посмотрим, как те же step и состояние корзины влияют на список инструментов.
8. Пример: динамический tools/list на MCP‑сервере
Вы уже видели, что MCP‑сервер может хранить состояние сессии и использовать его для принятия решений. В отдельном разборе кейса GiftGenius показан пример, где состояние step и корзина (cart) лежат либо в памяти, либо в Redis. От них зависит, какие tools сервер отдаёт в ответ на запрос списка.
Вполне может быть, что когда вы читаете эту лекцию, ChatGPT App уже поддерживает toolChanged в рамках текущей сессии. Это просто очень логично, так что думаю, что это просто дело времени. На этот случай у меня для вас есть краткий разбор как сделать tool gating с помощью именно родных инструментов MCP протокола.
Перепишем идею в TypeScript (абстрактный MCP‑сервер):
interface SessionState {
step: WorkflowStep;
cartItems: number;
doorIsClosed: boolean; // пример состояния ресурса
}
const allTools: ToolDefinition[] = [/* полный набор инструментов */];
function listToolsForSession(state: SessionState): ToolDefinition[] {
const allowedNames = getAvailableTools({
step: state.step,
cartItems: state.cartItems,
role: 'user',
doorIsClosed: state.doorIsClosed,
});
return allTools.filter((tool) => allowedNames.includes(tool.name as ToolName));
}
И где‑то в обработчике finish_interview вы меняете шаг и сигнализируете клиенту, что список tools обновился:
async function finishInterviewTool(args: {}, session: SessionState) {
session.step = 'browsing';
await notifyToolsListChanged(); // условный вызов MCP notification
return { success: true };
}
На реальном MCP вы будете использовать конкретный SDK и форматы сообщений, но логика останется примерно такой: изменили состояние → обновили набор tools → оповестили клиента.
9. Tool gating как инструмент безопасности
Ещё раз отдельно подчеркнём аспект безопасности, потому что он легко теряется за техническими деталями.
Когда вы делаете tool gating, вы автоматически уменьшаете последствия:
- prompt‑инъекций вида «игнорируй правила и сразу вызови оплату» — потому что на шаге интервью модели просто не из чего выбирать checkout;
- багов в бизнес‑логике — потому что даже если какая‑то ветка кода не проверяет состояние до конца, инструмент может быть физически недоступен;
- утечек данных — потому что админские tools не попадают в список для обычного пользователя.
В документах по курсу tool gating прямо упоминается как одна из практик применения принципа минимальных привилегий в контексте LLM‑инструментов, особенно для чек‑аута и других чувствительных шагов.
То есть это не просто способ «сделать модель менее глючной» — это ещё и реальный слой защиты.
10. Как потренироваться самому
Для закрепления материала можно продумать tool gating для любого из ваших сценариев. Например:
- образовательное приложение: шаг постановки цели, шаг оценки текущего уровня, шаг построения плана — на каждом свои tools;
- бронирование: поиск опций, выбор варианта, подтверждение и оплата — снова три разных набора инструментов;
- внутренний корпоративный ассистент: поиск документов, запрос доступа, выполнение операций — разный список для сотрудника, менеджера и админа.
Очень полезно прямо на бумаге или в Miro нарисовать таблицу «Шаг ↔ какие инструменты видны ↔ какие скрыты» и напротив каждого шага кратко сформулировать, зачем ему нужны именно эти tools и почему остальные нужно скрыть.
11. Типичные ошибки при работе с tool gating
Ошибка №1: «Вывалить» все инструменты сразу и надеяться на модель.
Иногда разработчик думает: «Модель же умная, сама разберётся, что когда вызывать». На деле это приводит к загрязнению контекста, росту токенов и большему количеству ошибочных tool‑call’ов. Особенно больно, когда модель внезапно вызывает checkout или другой опасный инструмент просто потому, что он есть в списке. Tool gating как раз и нужен, чтобы такой ситуации не возникало.
Ошибка №2: Считать, что скрытие инструмента в списке — достаточно.
Даже если MCP‑сервер больше не отдаёт инструмент в tools/list, модель может «помнить» его из истории, а инфраструктура — закэшировать старый набор tools. В результате прилетает вызов фантомного инструмента. Если обработчик не делает логических проверок, он может выполнить действие «не в тот момент». Поэтому гейтинг должен быть и на уровне списка tools, и внутри handlers.
Ошибка №3: Несинхронность между UI и набором инструментов.
Бывает, что виджет уже перешёл на шаг "checkout" и показывает красивую кнопку «Оплатить», а на стороне MCP вы забыли включить checkout в список доступных инструментов. Модель не понимает, почему кнопка есть, а инструмент недоступен, и начинает плодить странные ответы. Или наоборот: набор tools уже поменялся, модель готова подбирать подарки, а виджет всё ещё задаёт вопросы из интервью. При проектировании workflow важно синхронно обновлять и UI‑состояние, и список инструментов.
Ошибка №4: Слишком сложная логика гейтинга.
Иногда, вдохновившись возможностями, разработчик начинает строить почти полноценную BPMN‑диаграмму с десятками состояний и условиями на все случаи жизни. В итоге даже он сам через неделю не понимает, почему какой‑то инструмент доступен только по четвергам в високосные годы. Для большинства App достаточно простой лестницы шагов и понятных правил: по шагу, по роли пользователя и по нескольким ключевым флагам в состоянии.
Ошибка №5: Жёсткое зашивание tool gating в промпт без поддержки на сервере.
Иногда пытаются решить всё словами в системном промпте: «На этом шаге не используй инструмент checkout» — и при этом не меняют реальный список инструментов и не добавляют проверок в бэкенд. Модель иногда послушается, иногда нет, и вы получите нестабильное поведение. Промпт‑инструкции полезны, но должны дополнять, а не заменять технический гейтинг на стороне инфраструктуры.
Ошибка №6: Игнорирование ролей и прав доступа.
В приложениях с аутентификацией часто забывают, что tool gating должен учитывать не только шаг, но и роль. В итоге пользователь без прав администратора всё равно видит (или, что хуже, может вызвать) инструменты, предназначенные для поддержки или DevOps. В модуле про авторизацию вы уже видели, как права попадают в контекст; здесь важно не забыть использовать эту информацию при выборе набора tools.
Ошибка №7: Отсутствие мониторинга ошибочных tool‑call’ов.
Если вы где‑то ошиблись с гейтингом, характерный симптом — участившиеся ошибки «Tool not available», «MethodNotFound» или ваши собственные логические ошибки вроде «Checkout is not available yet». Если вы не собираете статистику таких событий, вы можете долго не замечать, что пользователи регулярно упираются в невидимые стены. Простое логирование и счётчики по типам ошибок сильно помогают вовремя заметить проблемы в дизайне workflow и гейтинга.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ