1. Czym jest tool gating i dlaczego to temat na osobny wykład
Do tej pory w uproszczonych przykładach robiliśmy tak: opisujemy zestaw narzędzi dla App, podłączamy serwer MCP — i wszystko to jest zawsze dostępne dla modelu. Z punktu widzenia „zrobić demo w 5 minut” to działa. Z punktu widzenia realnego produktu — już niekoniecznie.
Tool gating to wzorzec, w którym lista narzędzi dostępnych dla modelu nie jest stała, lecz zależy od kontekstu: kroku workflow, uprawnień użytkownika, stanu danych itp.
Najważniejsze: lista tools to nie „przypadkowy śmietnik wszystkiego, co kiedykolwiek napisaliście”, ale część projektu scenariusza. Projektując workflow, w praktyce projektujecie też to, jakie narzędzia model ma prawo widzieć na każdym etapie.
Najprostsza analogia: nie dajecie stażyście w banku dostępu od razu do wszystkich systemów — najpierw tylko podgląd, potem proste operacje, potem poważniejsze. Tu jest ta sama logika, tylko stażystą jest LLM.
2. Problem „wszystkie narzędzia naraz”: zanieczyszczenie kontekstu i bezpieczeństwo
Jeśli dać modelowi dziesiątki narzędzi, zaczyna on cierpieć jednocześnie na kilku frontach: przeciążenia kontekstu, pomyłek w wyborze i kwestii bezpieczeństwa. Badania OpenAI/Anthropic pokazują, że im więcej funkcji opiszecie w kontekście, tym gorzej model trafia z wyborem właściwej funkcji.
Po pierwsze, każda definicja narzędzia to tokeny: nazwa, opis, JSON Schema. Lista 30–40 tools bez trudu zjada parę tysięcy tokenów. To dokładnie te tokeny, które moglibyście przeznaczyć na historię dialogu, kontekst użytkownika, przykłady dobrych odpowiedzi. Zamiast tego model czyta „powieść” o waszych API.
Po drugie, gdy narzędzia są podobne, model zaczyna się gubić. Jeśli macie search_products i get_product_details, może spróbować wywołać get_product_details bezpośrednio z tekstowym zapytaniem, bo opis wydał mu się bardziej pasujący.
Dochodzi jeszcze kwestia bezpieczeństwa. Istnieje nudna, ale ważna zasada minimalnych uprawnień (least privilege): system powinien mieć tylko te możliwości, które są naprawdę potrzebne „tu i teraz”. Jeśli na etapie zapoznania model już zna checkout, wystarczy niewielka prompt‑iniekcja od użytkownika, by spróbował wywołać płatność przed czasem. Tool gating to wygodny szczególny przypadek minimalizacji uprawnień: na każdym kroku włączamy tylko to, co potrzebne.
I wreszcie UX. Jeśli model niespodziewanie robi coś „magicznego”, czego użytkownik się nie spodziewał (np. tworzy zamówienie, chociaż człowiek wciąż wybiera prezent), zaufanie do waszej aplikacji szybko spada.
3. GiftGenius jako ilustracja tool gating
Weźmy nasz przypadek GiftGenius i spójrzmy uczciwie na kroki:
- Wywiad: ustalamy wiek, płeć, zainteresowania obdarowywanego, budżet itp.
- Przeglądanie: szukamy produktów w katalogu, pokazujemy pomysły.
- Checkout: gdy użytkownik wybrał już prezent, przechodzimy do finalizacji.
Jeśli na etapie wywiadu model już zna search_products, add_to_cart i checkout, może:
- zacząć uruchamiać wyszukiwanie zbyt wcześnie, zanim zbierze sensowne preferencje;
- spróbować od razu „sfinalizować zamówienie”, ponieważ użytkownik rzucił mimochodem: „O, to dobre, biorę”.
Właściwy wariant to zmieniać listę dostępnych tools w miarę przechodzenia między krokami. Poniżej rozbierzemy taki scenariusz: na etapie wywiadu widoczne są tylko narzędzia zapisywania preferencji, na etapie przeglądania — wyszukiwanie i dodawanie do koszyka, a na etapie checkout — samo checkout.
Zbierzmy to w krótkiej tabeli:
| Krok workflow | Cel kroku | Które narzędzia są dostępne dla modelu | Czego model „nie widzi” na tym kroku |
|---|---|---|---|
|
Zebrać profil obdarowywanego | |
|
|
Dobrać i doprecyzować pomysły | |
+ (jeśli koszyk jest pusty) |
|
Sfinalizować zakupy | |
Wszelkie „konfiguracyjne” tools, które nie są już potrzebne |
Zwróć uwagę: narzędzie checkout pojawia się tylko wtedy, gdy jest co finalizować, i tylko na odpowiednim kroku. To klasyczny przykład tool gating dla scenariusza commerce.
4. Strategie tool gating: według stanu, ról i zasobów
Najczęstsza opcja to state‑based gating (gating po krokach workflow): lista narzędzi zależy od stanu scenariusza. To znaczy, gdzieś przechowujecie zmienną step i na jej podstawie określacie, które narzędzia są włączone, a które nie.
Ale nie tylko kroki mogą wpływać na narzędzia.
Czasem robicie role‑based gating (według ról użytkownika): administratorowi dostępne są serwisowe narzędzia (np. przeindeksowanie katalogu), a zwykłemu użytkownikowi — tylko użytkowe. Czasem — resource‑based gating (według stanu zasobów): narzędzie „otwórz drzwi” pojawia się tylko wtedy, gdy w stanie zasobu drzwi są oznaczone jako zamknięte.
Aby nie być gołosłownym, opiszmy to w postaci krótkiej funkcji w TypeScript. Załóżmy, że mamy jakiś kontekst z krokiem, rolą, bieżącym koszykiem i stanem pewnego zasobu:
type WorkflowStep = 'interview' | 'browsing' | 'checkout';
type UserRole = 'user' | 'admin';
interface WorkflowContext {
step: WorkflowStep;
role: UserRole;
cartItems: number; // ile produktów w koszyku
doorIsClosed: boolean; // przykład resource-based gating: stan konkretnego zasobu
}
Teraz opiszmy, jakie tools w ogóle istnieją w systemie i jak je filtrować:
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',
];
Tutaj open_door to przykład narzędzia, które zależy od stanu konkretnego zasobu (drzwi są zamknięte czy nie).
I sama funkcja gatingu:
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;
}
Widać tu wyraźnie trzy „warstwy” gatingu:
- po kroku (byStep);
- po roli użytkownika (withAdmin);
- po stanie zasobu (withResources i flaga doorIsClosed).
To nie jest kod SDK, lecz po prostu szkic architektoniczny. Ale właśnie tak zwykle myśli się o tool gating: jest pełny katalog narzędzi i jest funkcja, która na podstawie kontekstu zwraca podzbiór.
5. Gdzie w architekturze App żyje tool gating
Połączmy to trochę z tym, co już wiecie o stacku ChatGPT App.
Teoretycznie protokół MCP działa tak
W MCP narzędzia nie muszą być na sztywno wszyte w statyczny JSON: serwer może zwracać listę dynamicznie, zależnie od sesji. Co więcej, w specyfikacji jest mechanizm capabilities, w którym serwer deklaruje, że jego lista narzędzi może się zmieniać, oraz powiadomienia tools/list_changed, aby klient (ChatGPT/agent) ponownie pobrał listę tools, gdy coś się zmieniło.
Formalnie możecie tak zrobić i niektórzy klienci MCP będą działać z dynamiczną listą narzędzi MCP. Jednak na dziś ChatGPT App nie wspierają tools/list_changed. Może to się zmieni w przyszłości, ale na razie taki sposób nie zadziała.
A zadziała taki
Przechowujecie stan i listę dostępnych metod po stronie modelu. Po prostu wysyłacie modelowi state i listę dostępnych tools na każdym kroku jako część „obrazu świata”: w promcie systemowym jawnie opisujecie bieżący krok (np. step = "browsing"), kluczowe flagi (np. cartItems = 2, role = "user") i dołączacie tylko ten podzbiór narzędzi, który jest teraz dozwolony.
Model sam nie potrafi „zapominać” narzędzi, ale bardzo dobrze podąża za jawnymi instrukcjami typu: „Na tym kroku możesz używać tylko tych funkcji…”. W efekcie cała logika gatingu dla modelu wygląda jak prosty kontrakt: oto bieżący stan scenariusza, oto lista przycisków, których możesz używać, reszta dla ciebie jakby nie istniała. Nie wymaga to żadnej szczególnej „magii” — wystarczy konsekwentnie aktualizować state i listę tools w zapytaniach do modelu przy przejściach między krokami.
Dodatkowo możecie dodać instrukcje w structuredContent, coś w tym stylu:
{
"instructions": {
"current_step": "browsing",
"enabled_mcp_tools": ["search", "apply"]
}
}
Można też dodać zabezpieczenie na poziomie waszego kodu biznesowego. Nawet jeśli lista tools jest już „zaktualizowana”, ważne jest duplikować logikę gatingu wewnątrz samych handlerów, ponieważ:
- model może zapomnieć o instrukcjach i/lub danych, jeśli dyskusja była długa;
- model może spróbować wywołać „widmowe” narzędzie, które było dostępne na poprzednim kroku;
Dlatego dobry projekt to: zarówno „ukrywamy” narzędzia przed modelem, jak i sprawdzamy wewnątrz handlera, czy teraz można to zrobić.
6. Modelowy vs logiczny tool gating
Łącząc to z poprzednią sekcją: wszystko, co dzieje się na poziomie wywołania modelu (jakie flagi/step wkładacie w prompt), to gating modelowy, a sprawdzenia wewnątrz samych handlerów narzędzi — logiczny.
Zwykle warto rozdzielać dwie warstwy:
- Gating modelowy — gdy model wie, że narzędzie jest „dozwolone” właśnie teraz, ponieważ po prostu jawnie piszecie w instrukcjach, jakie funkcje są dostępne na tym kroku. Dla modelu świat wygląda tak: „oto bieżący state, oto taki zestaw przycisków, innych nie ma”.
- Gating logiczny — sprawdzenia wewnątrz samego narzędzia. Nawet jeśli model mimo wszystko spróbuje wywołać checkout przed czasem (z powodu cache, „widmowej” pamięci lub dlatego, że na jednym z poprzednich kroków jednak podsunięto mu to narzędzie), handler patrzy na bieżący stan i grzecznie odmawia: w stylu „najpierw wybierz prezent, potem sfinalizujemy zamówienie” (a nie po prostu rzuca wyjątek!).
Dlaczego potrzebne są obie warstwy? Bo infrastruktura wokół LLM i same scenariusze mogą zachowywać się nieidealnie:
- model może zapamiętać, że kiedyś widział narzędzie checkout, i próbować się do niego odwołać w rozumowaniach lub nawet w tool‑call;
- możecie sami przez pomyłkę na którymś kroku przekazać szerszy zestaw tools, niż potrzeba, i model zacznie używać zbędnych funkcji;
- klienci/warstwy pośrednie mogą keszować konfigurację wywołania modelu i przez jakiś czas wysyłać stary zestaw narzędzi.
W praktyce oznacza to prostą myśl: poleganie tylko na „nie daliśmy narzędzia w tools — więc nigdy się nie wywoła” jest ryzykowne. Sprawdzenia w handlers i tak są potrzebne.
Przykład logicznego gatingu w handlerze checkout w pseudo‑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.',
};
}
// ... rzeczywista logika finalizacji
}
Taka odpowiedź pomaga zarówno użytkownikowi, jak i modelowi: model widzi ustrukturyzowany błąd i może skorygować plan działania.
7. Jak powiązać tool gating z UI i widżetem
Tool gating to nie tylko serwer. UI/UX też musi odczuwać zmiany.
Widżet zna bieżący krok (mówiliśmy już o widgetState i o tym, że ten stan może przechowywać np. currentStep). Model — również, ponieważ krok albo jest jawnie przekazywany do narzędzi, albo wpisany w prompt systemowy. Ważne, aby UI i zestaw aktywnych tools były zsynchronizowane.
Jeśli model uważa, że teraz jest krok „Przeglądanie”, a widżet pokazuje interfejs „Wywiad”, użytkownik zaczyna się gubić. Jeśli odwrotnie — UI już rysuje przycisk „Zapłać”, ale checkout nie jest jeszcze dostępny, model znajdzie się w dziwnej sytuacji: przycisk jest, a funkcja jakby „nie działa”.
Mały schemat cyklu życia kroku z uwzględnieniem tool gating:
flowchart TD A[Użytkownik wypełnia wywiad w widżecie] --> B[Widżet wywołuje tool save_preference / finish_interview] B --> C[MCP / backend aktualizuje state.step] C --> D[Serwer zmienia zestaw tools dla sesji] D --> E[Klient ChatGPT aktualizuje dostępne tools dla modelu] E --> F[Model zadaje nowe pytania
i/lub wywołuje nowe narzędzia] C --> G[Widżet otrzymuje nowy krok przez widgetState
i zmienia UI]
Dla użytkownika wygląda to jak zwykły kreator: najpierw kilka pytań, potem lista prezentów, a potem finalne potwierdzenie. Pod spodem jednocześnie przełączają się i UI, i lista narzędzi, i instrukcja dla modelu.
W widżecie Next.js można to wyrazić bardzo prosto. Załóżmy, że przechowujecie step w 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 />;
}
Tutaj nie pokazujemy tools bezpośrednio, ale zakładamy, że zmiana step w stanie jest uzgodniona ze zmianą zestawu narzędzi po stronie backendu. Obejrzeliśmy, jak kroki żyją w widżecie. Teraz wróćmy na stronę serwera MCP i zobaczmy, jak te same step i stan koszyka wpływają na listę narzędzi.
8. Przykład: dynamiczne tools/list na serwerze MCP
Widzieliście już, że serwer MCP może przechowywać stan sesji i używać go do podejmowania decyzji. W osobnym omówieniu przypadku GiftGenius pokazano przykład, gdzie stan step i koszyk (cart) leżą albo w pamięci, albo w Redisie. Od nich zależy, jakie tools serwer zwraca w odpowiedzi na zapytanie o listę.
Całkiem możliwe, że gdy czytacie ten wykład, ChatGPT App już obsługuje toolChanged w ramach bieżącej sesji. To po prostu bardzo logiczne, więc myślę, że to kwestia czasu. Na tę okazję mam krótki opis, jak zrobić tool gating za pomocą natywnych mechanizmów protokołu MCP.
Przepiszmy ideę w TypeScript (abstrakcyjny serwer MCP):
interface SessionState {
step: WorkflowStep;
cartItems: number;
doorIsClosed: boolean; // przykład stanu zasobu
}
const allTools: ToolDefinition[] = [/* pełny zestaw narzędzi */];
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));
}
I gdzieś w handlerze finish_interview zmieniacie krok i sygnalizujecie klientowi, że lista tools się zaktualizowała:
async function finishInterviewTool(args: {}, session: SessionState) {
session.step = 'browsing';
await notifyToolsListChanged(); // przykładowe wywołanie powiadomienia MCP
return { success: true };
}
Na prawdziwym MCP użyjecie konkretnego SDK i formatów wiadomości, ale logika pozostanie podobna: zmieniliśmy stan → zaktualizowaliśmy zestaw tools → powiadomiliśmy klienta.
9. Tool gating jako narzędzie bezpieczeństwa
Jeszcze raz podkreślmy aspekt bezpieczeństwa, bo łatwo ginie w natłoku szczegółów technicznych.
Gdy robicie tool gating, automatycznie ograniczacie skutki:
- prompt‑iniekcji typu „ignoruj zasady i od razu wywołaj płatność” — bo na etapie wywiadu model po prostu nie ma z czego wybrać checkout;
- błędów w logice biznesowej — bo nawet jeśli jakaś gałąź kodu nie sprawdza stanu do końca, narzędzie może być fizycznie niedostępne;
- wycieków danych — bo narzędzia administratorskie nie trafiają na listę dla zwykłego użytkownika.
W materiałach kursu tool gating jest wprost wymieniany jako jedna z praktyk stosowania zasady minimalnych przywilejów w kontekście narzędzi LLM, zwłaszcza dla checkoutu i innych wrażliwych kroków.
A więc to nie tylko sposób na „mniej zawodny model” — to także realna warstwa ochrony.
10. Jak poćwiczyć samodzielnie
Aby utrwalić materiał, możecie przemyśleć tool gating dla dowolnego z waszych scenariuszy. Na przykład:
- aplikacja edukacyjna: krok wyznaczania celu, krok oceny bieżącego poziomu, krok budowy planu — na każdym własne tools;
- rezerwacja: wyszukiwanie opcji, wybór wariantu, potwierdzenie i płatność — znów trzy różne zestawy narzędzi;
- wewnętrzny asystent korporacyjny: wyszukiwanie dokumentów, wniosek o dostęp, wykonywanie operacji — różna lista dla pracownika, menedżera i admina.
Bardzo przydatne jest dosłownie na papierze lub w Miro narysować tabelę „Krok ↔ jakie narzędzia są widoczne ↔ jakie są ukryte” i naprzeciw każdego kroku krótko sformułować, po co są mu potrzebne właśnie te tools i dlaczego pozostałe należy ukryć.
11. Typowe błędy przy pracy z tool gating
Błąd nr 1: „Wyrzucić” wszystkie narzędzia naraz i liczyć na model.
Czasem deweloper myśli: „Model jest mądry, sam zrozumie, co kiedy wywołać”. W praktyce prowadzi to do zanieczyszczenia kontekstu, wzrostu liczby tokenów i większej liczby błędnych tool‑callów. Szczególnie boli, gdy model nagle wywołuje checkout lub inne niebezpieczne narzędzie tylko dlatego, że jest na liście. Tool gating właśnie po to jest, by do takiej sytuacji nie dochodziło.
Błąd nr 2: Uważać, że ukrycie narzędzia na liście wystarczy.
Nawet jeśli serwer MCP przestaje zwracać narzędzie w tools/list, model może „pamiętać” je z historii, a infrastruktura — zakeszować stary zestaw tools. W rezultacie przylatuje wywołanie widmowego narzędzia. Jeśli handler nie wykonuje logicznych sprawdzeń, może wykonać działanie „nie w tym momencie”. Dlatego gating powinien być i na poziomie listy tools, i wewnątrz handlers.
Błąd nr 3: Niesynchronizacja między UI a zestawem narzędzi.
Zdarza się, że widżet przeszedł już na krok "checkout" i pokazuje ładny przycisk „Zapłać”, a po stronie MCP zapomnieliście włączyć checkout do listy dostępnych narzędzi. Model nie rozumie, dlaczego przycisk jest, a narzędzie niedostępne, i zaczyna produkować dziwne odpowiedzi. Albo odwrotnie: zestaw tools już się zmienił, model jest gotów dobierać prezenty, a widżet nadal zadaje pytania z wywiadu. Przy projektowaniu workflow ważne jest synchronicznie aktualizować i stan UI, i listę narzędzi.
Błąd nr 4: Zbyt skomplikowana logika gatingu.
Czasem, pod wpływem możliwości, deweloper zaczyna budować niemal pełny diagram BPMN z dziesiątkami stanów i warunkami na każdą okazję. W efekcie nawet on sam po tygodniu nie rozumie, dlaczego jakieś narzędzie jest dostępne tylko w czwartki w lata przestępne. Dla większości aplikacji wystarczy prosta drabinka kroków i zrozumiałe zasady: po kroku, po roli użytkownika i po kilku kluczowych flagach stanu.
Błąd nr 5: Twarde zaszycie tool gating w promcie bez wsparcia na serwerze.
Czasem próbuje się wszystko rozwiązać słowami w promcie systemowym: „Na tym kroku nie używaj narzędzia checkout” — i jednocześnie nie zmienia się realnej listy narzędzi ani nie dodaje kontroli po stronie backendu. Model czasem posłucha, czasem nie, i otrzymacie niestabilne zachowanie. Instrukcje w promcie są przydatne, ale powinny uzupełniać, a nie zastępować techniczny gating po stronie infrastruktury.
Błąd nr 6: Ignorowanie ról i uprawnień.
W aplikacjach z uwierzytelnianiem często zapomina się, że tool gating powinien uwzględniać nie tylko krok, ale i rolę. W rezultacie użytkownik bez uprawnień administratora wciąż widzi (albo, co gorsza, może wywołać) narzędzia przeznaczone dla wsparcia lub DevOps. W module o autoryzacji widzieliście już, jak uprawnienia trafiają do kontekstu; tutaj ważne jest, by nie zapomnieć wykorzystać tych informacji przy wyborze zestawu tools.
Błąd nr 7: Brak monitoringu błędnych tool‑callów.
Jeśli gdzieś pomyliliście się z gatingiem, charakterystycznym symptomem będzie wzrost liczby błędów „Tool not available”, „MethodNotFound” lub waszych własnych błędów logicznych w rodzaju „Checkout is not available yet”. Jeśli nie zbieracie statystyk takich zdarzeń, możecie długo nie zauważać, że użytkownicy regularnie uderzają w niewidzialne ściany. Proste logowanie i liczniki według typów błędów bardzo pomagają w porę zauważyć problemy w projekcie workflow i gatingu.
GO TO FULL VERSION