Specjalni agenci, router słów kluczowych i funkcja interrupt(): Trener LangGraph
Zaprojektuj asystenta LangGraph z dwoma specjalistami, wyposażonego w deterministycznego routera, wspólny stan przetrwający przenoszenie kontroli oraz punkt kontrolny dla człowieka oparty na przerwach i poleceniach.
Jedno pytanie do asystenta, które ma obejmować dwa niespowiązane obszary, zazwyczaj źle radzi sobie z oboma. W tym artykule zamiast tego tworzymy mały system LangGraph w postaci trenera fitness i dietetyka: dwa specjalistyczne agenty, deterministyczny router decydujący, kto ma odpowiadać, jeden wspólny stan przenoszący ograniczenia pomiędzy etapami rozmowy oraz punkt kontrolny dla człowieka, który wstrzymuje działanie systemu po każdej odpowiedzi. Pod koniec dowiesz się, jak funkcjonuje każda z tych części, dlaczego system ma taką strukturę oraz jak można ponownie wykorzystać tę samą strukturę dla dowolnej pary specjalistów.
Sytuacja i rozmowa testowa
Celem jest asystent w aplikacji fitness, który pomaga zarówno w treningu, jak i w diecie. Projekt został sprawdzony na podstawie trójkrotnej rozmowy: prośby o plan treningowy, pytania dotyczącego diety oraz ograniczenia żywieniowego, które wymagają zmiany zaleceń dotyczących posiłków:
Scenario: A fitness app wants an assistant that helps with workouts and nutrition.
Test conversation flow (3 turns):
"Build muscle, lose fat — suggest a workout plan"
"What should I eat to support this workout plan?"
"I'm vegetarian, adjust the meal suggestion"
Każda z poniższych decyzji projektowych ma na celu zapewnienie prawidłowego i przewidywalnego funkcjonowania tych trzech etapów rozmowy.
Dlaczego zwykły chatbot nie wystarcza
Gdy poprosimy zwykłego chatbota o plan ćwiczeń, a następnie o odpowiednią dietę, odpowiedzi są zazwyczaj powierzchowne. Model musi obsługiwać wszystkie role w ramach jednego promptu, przez co rady dotyczące treningu tracą precyzję, a szczegóły diety stają się zbyt uproszczone. Ta słabość wynika z faktu, że trzy różne wymagania oddziałują jednocześnie na ten sam prompt:
- Rozdzielenie dziedzin. Planowanie treningów i dietetyka to różne obszary wiedzy, więc rady z jednej dziedziny nie powinny przenikać do drugiej ani się z nią sprzeciwiać.
- Zachowanie kontekstu między etapami rozmowy. Ograniczenie podane wcześniej, na przykład kontuzja kolana wspomniana trzy wiadomości temu, musi nadal wpływać na plan posiłków lub treningów tworzony później.
Zamiast prosić jeden prompt o wykonywanie trzech zadań, architektura zapewnia każdemu wymaganiu własny mechanizm: specjalistów do separacji dziedzin, wspólny stan dla kontekstu oraz węzeł ludzki do nadzoru.
Pięć elementów budulcowych
System składa się z pięciu części, z których każda ma jedną konkretną funkcję:
- Poradnik ćwiczeń: agent odpowiedzialny za plany treningowe, podział ćwiczeń oraz modyfikacje z uwzględnieniem kontuzji.
- Poradnik żywieniowy: agent odpowiedzialny za plany posiłków, makroskładniki odżywcze oraz ograniczenia dietetyczne, takie jak dieta wegetariańska, wegańska lub związana z alergiami.
- Router: lżeka procedura decyzyjna, która analizuje najnowsze wiadomość użytkownika i wybiera, który doradca ma odpowiedzieć następnie.
- Węzeł ludzki: celowe zatrzymanie, podczas którego graf czeka na osobę zamiast generować dodatkowe wyniki.
- Wspólny stan: jedyny autorytatywny zapis dotychczasowej rozmowy oraz informacja o tym, który doradca odpowiedział ostatnio.
To wzorzec hierarchiczny, czyli orkiestracyjny. Koordynator deleguje zadania na specjalistyczne podagenty zamiast polegać na jednym agentze, który próbuje pełnić wszystkie role jednocześnie.
Zasada zapewniająca przewidywalność przenoszenia zadań
Jedna zasada strukturalna sprawia, że cały system jest godny zaufania: doradca nigdy nie przekazuje kontroli bezpośrednio drugiemu doradcy. Każdy wynik generowany przez agenta przechodzi przez węzeł ludzki, a następnie przez router. Dlatego dwa agenci nigdy nie mogą w tajemnicy przekazywać sobie kontroli wzajemnie, a każda zmiana jest zarówno widoczna, jak i możliwa do przerwania.
Jak wiadomość przemieszcza się po grafie
Śledzi się pojedynczą wiadomość użytkownika. Najpierw trafia ona do węzła ludzkiego, a następnie jest przekazywana routerowi, który odczytuje tekst wiadomości i, jeśli sam tekst nie jest wystarczający, sprawdza pole last_active_agent w wspólnym stanie, aby wybrać między Doradcą ds. Treningu a Doradcą ds. Odżywiania. Wybrany doradca może wywołać narzędzie należące do jego domeny, a następnie zwraca kontrolę do węzła ludzkiego, który ponownie się zawiesza i czeka na kolejną wiadomość. Pętla składa się zatem zawsze z węzła ludzkiego, routera, doradcy, opcjonalnego narzędzia i ponownie węzła ludzkiego.
Model: otwarty model LLM o dużej mocy obliczeniowej na Groq
Oba doradcy wykorzystują otwarty model openai/gpt-oss-120b dostarczany przez Groq, skonfigurowany z parametrem temperature=0:
- Dlaczego ten model: posiada solidne możliwości bezpośredniego wywoływania narzędzi, od których zależy ten oparty na narzędziach model pracy.
W tym wykresie nic nie zależy konkretnie od Groq. Może to być dowolny model czatowy z możliwością wywoływania narzędzi, na przykład Google Gemini lub Anthropic Claude, pod warunkiem dostarczenia odpowiedniego klucza API (takiego jak GEMINI_API_KEY lub ANTHROPIC_API_KEY) oraz skierowania narzędzia do tego dostawcy. Dostępność modeli na platformach hostowanych zmienia się z czasem, dlatego należy sprawdzić identyfikator modelu w aktualnym katalogu dostawcy.
Stan współdzielony: gdzie faktycznie znajduje się pamięć
Każdy węzeł czyta i zapisuje dane do jednego obiektu stanu typowanego, MultiAgentState, który zawiera dwa pola:
messages: pełna rozmowa w trakcie, odziedziczona odMessagesStatez LangGraph; ten obiekt dostarcza również funkcję redukcyjną, która dodaje nowe wiadomości zamiast nadpisywać całą listę.
last_active_agent: ustawiony na "workout_advisor" lub "nutrition_advisor"; router korzysta z tego wartości, gdy nie może określić, do którego domeny należy dalsze zapytanie typu „powiedz mi więcej”. Dzięki temu stanowi ograniczenie określone w jednej turze nadal obowiązuje w kolejnych turach, nawet jeśli odpowiedź generuje inny węzeł. Model nie otrzymuje żadnego podsumowania ani ponownego wyjaśnienia. Każdy doradca po prostu otrzymuje tę samą, zgromadzoną listę messages przy każdym uruchomieniu, a mechanizm checkpointer przechowuje stan pomiędzy turami tej samej rozmowy.
Kluczową decyzją projektową jest to, że pamięć jest centralizowana, a nie przypisana do konkretnego agenta. Gdyby każdy doradca miał własną prywatną historię, Doradca ds. Odżywiania nigdy nie dowiedziałby się o celu, który użytkownik podał Doradcy ds. Ćwiczeń.
Jeden wąski narzędzie na doradcę
Każdy agent może użyć dokładnie jednego narzędzia, które należy do jego dziedziny. W tej wersji narzędzia opierają się na regułach i dopasowują kluczowe słowa, co sprawia, że ich zachowanie jest przewidywalne i łatwe do zademonstrowania. W środowisku produkcyjnym można je zastąpić rzeczywistym API dotyczącym kondycji fizycznej lub żywienia albo pozwolić modelowi w pełni generować odpowiedzi. Ponieważ reszta grafu zależy wyłącznie od interfejsu narzędzia, zmiana implementacji nie wymaga żadnych innych modyfikacji.
Zapewnienie, by każdy doradca pracował we własnej dziedzinie
Samodzielne narzędzie nie wystarcza, by agent trzymał się tematu; model musi również wiedzieć, na co nie powinien odpowiadać. Dlatego każde polecenie systemowe określa wyraźną granicę:
- Doradcy ds. ćwiczeń poleca się, aby nie udzielał porad dotyczących posiłków czy żywienia, ponieważ graf skieruje takie zapytania do Doradcy ds. żywienia.
Zasada leżąca u podstaw tego rozwiązania polega na tym, że model sam siebie nie kieruje. Wskazówki dotyczące granic sprawiają, że każdy agent decyduje się na czekanie zamiast improwizować poza zakresem swojej wiedzy, a to graf, a nie LLM, wyraźnie podejmuje każdą decyzję dotyczącą kierowania.
Kierownik oparty na słowach kluczowych, a nie kolejne wezwanie LLM
Gdy doradcy są ograniczeni, kierownik musi jedynie wybrać następnego mówcę, a robi to poprzez proste dopasowanie słów kluczowych:
- Słowa takie jak gym, reps, cardio lub muscle kierują do Doradcy Treningowego.
- Słowa takie jak diet, food, eat, protein lub vegetarian kierują do Doradcy ds. żywienia.
- Wszystko, co nie pasuje do żadnej z list, trafia do doradcy przechowywanego w
last_active_agent.
Routing deterministyczny jest przewidywalny i łatwy do sprawdzenia: ta sama wiadomość zawsze powoduje ten sam krok w procesie przekazywania, a router można przetestować w formie testów jednostkowych bez wywoływania modelu. Wadą jest jednak jego krucha struktura. Listy słów kluczowych pomijają synonimy, więc wiadomość odnosząca się do obu dziedzin (np. pytanie o jedzenie przed treningiem) wymaga wyraźnej zasady rozstrzygania. Gdy sformułowania stają się zbyt zróżnicowane dla słów kluczowych, rozsądnym ulepszeniem jest krótkie przekazanie do systemu klasyfikacyjnego, choć to pozbawia rozwiązanie części przewidywalności.
Człowiek w łańcuchu z możliwością przerwania i poleceń
Najważniejszą decyzją projektową nie jest router, lecz węzeł ludzki. Po każdej turze doradcy graf celowo się zatrzymuje. Nie zgaduje on następnego pytania użytkownika ani nie kontynuuje generowania treści. Dwie prymitywy LangGraph służą do realizacji tej pauzy:
interrupt(...)zatrzymuje działanie w tym miejscu, gdzie został wywołany, i przekazuje kontrolę z powrotem do osoby, która go wywołała, wraz z wartością, którą mu przekazano.Command(resume=...)kontynuuje zatrzymany graf, przekazując dane wprowadzone przez człowieka jako wartość zwracaną po wywołaniuinterruptw tym samym węźle.
Wywołania interrupt opierają się na tworzeniu punktów kontrolnych: graf musi zapisać swój stan w momencie pauzy, aby móc później kontynuować pracę, dlatego skompilowany graf wymaga punktu kontrolnego oraz identyfikatora wątku.
Dla asystenta ds. fitnessu i żywienia ta pauza to coś więcej niż tylko wygoda interfejsu. To moment, w którym użytkownik może dodać ograniczenie istotne dla bezpieczeństwa, takie jak kontuzja, alergia lub ograniczenie dietetyczne, zanim rozmowa będzie kontynuowana, a nie po tym, jak system już wydał poradę, która to ignoruje.
Przebieg trzech etapów rozmowy
Etap 1: rozpoczęcie rozmowy
Użytkownik określa dwa cele – budowanie mięśni i utrata tłuszczu – oraz prosi o plan treningowy. Nowa rozmowa zawsze rozpoczyna się od Poradnika Treningowego. Ten bierze pod uwagę informacje o „mięśniach” i „tłuszczu”, uruchamia odpowiedni narzędzie i zwraca ustrukturyzowany plan treningowy, na przykład czterodniowy podział na ćwiczenia górnych i dolnych partii ciała, składający się z 8 do 12 powtórzeń w 3 do 4 serii, łączący trzy dni na siłownię z dwoma sesjami kardio.
Etap 2: przekazanie tematu ds. żywienia
Następnie użytkownik chce wiedzieć, jakie produkty żywnościowe mogą wspierać ten plan. Węzeł ludzki przekazuje wiadomość, router dopasowuje opcję „jeść”, a realizacja przechodzi do Doradcy ds. Odżywiania. Ten zwraca dzienny plan o wartości około 2500 kcal, skupiony na białku o niskiej zawartości tłuszczu i złożonych węglowodanach, z posiłkami co trzy do czterech godzin.
Krok 3: zastosowanie ograniczenia z wspólnego stanu
Ostatecznie użytkownik wspomina, że jest wegetarianinem, i prosi o dostosowanie sugestii posiłku poprzez dodanie przekąski. Wiadomość trafia ponownie do Doradcy ds. Odżywiania, a tutaj działają dwa mechanizmy: „wegetarianin” sam w sobie jest słowem kluczowym związanych z odżywianiem, a last_active_agent nadal wskazuje na tematy odżywianiowe, więc nawet bardziej niejasna prośba typu „dostosuj to i dodaj przekąskę” trafiłaby w to samo miejsce. Doradca ponownie czyta całą historię, nadal realizuje cel ustalony już w pierwszym kroku, dodaje ograniczenie wegetariańskie i tworzy plan budowania mięśni bez mięsa, oparty na tofu, paneerze i soczewicy, wraz z przekąskami takimi jak edamame i miseczki hummusu. Nikt nie musiał ponownie formułować celu, ponieważ stan już go zawierał.
To kroku jest wynikiem centralizowanego państwa. Odpowiedź generuje inny węzeł niż ten, który otrzymał początkowy cel, a mimo to posiada pełny kontekst.
Konstruowanie maszyny stanów
Strukturalnie otrzymujemy mały graf stanów składający się z trzech węzłów (dwóch doradców i węzła ludzkiego) oraz routera pełniącego rolę logiki warunkowej pomiędzy nimi, przy czym istnieje jeden stały punkt wejścia:
- Każdy nowy wątek rozpoczyna się od Doradcy ds. Treningów. Ten standard odzwierciedla charakter produktu: osoby otwierające aplikację fitness zazwyczaj najpierw pytają o treningi, a dopiero potem o żywność.
- Po tej pierwszej odpowiedzi węzeł ludzki i router decydują o każdym kolejnym kroku.
MemorySaver, a każda rozmowa jest identyfikowana za pomocą thread_id. Gdy użytkownik ponownie używa tego samego thread_id, graf automatycznie przywraca wiadomości oraz wartość last_active_agent; użytkownik nigdy nie musi przekazywać historii ponownie.MemorySaver przechowuje punkty kontrolne w pamięci procesu, co jest idealne dla notatników, ale wszystko zostaje utracone po ponownym uruchomieniu. Wersja wdrożona powinna wykorzystywać trwały wskaźnik kontrolny wspierany przez bazę danych.
Pełna, działająca implementacja jest dostępna jako notatnik towarzyszący. Aby zapoznać się z innymi sposobami strukturyzowania kroków routingu i zatwierdzania, sprawdź pięć wzorców LangGraph obejmujących routing, fan-out, krytykę i zatwierdzenie.
Ponowne wykorzystanie wzorca poza obszarem fitness
Nic tutaj nie jest specyficzne dla ćwiczeń lub posiłków. Te same trzy elementy – specjalistyczni agenci, router sterowany wspólnym stanem oraz punkt kontrolny dla człowieka wykorzystujący funkcje przerwania i kontynuacji – nadają się do każdego systemu, który posiada:
- więcej niż jedną odrębną dziedzinę specjalizacji, której może potrzebować rozmowa,
Zastąpienie tych dwóch doradców innym parami ekspertów z danej dziedziny nie zmienia kształtu grafu – zmieniają się jedynie narzędzia, pytania i kluczowe słowa routera.
Zasady projektowania
- Orkiestracja zamiast monolitów. Wykorzystaj graf do zarządzania oddzielnymi ścieżkami specjalistycznej wiedzy zamiast polegania na jednym agencie, który robi wszystko.
- Zcentralizowanie stanu. Jeden obiekt
MultiAgentStateprzechowywany przez checkpointer sprawia, że przekazywanie obowiązków przebiega płynnie, a ograniczenia pozostają aktualne.
interrupt(), aby zapewnić udział człowieka w kluczowych momentach.Główne wnioski
- Rozdziel pracę według domen i powierz decyzje o routingu deterministycznej logice grafu zamiast modelowi.
- Zachowaj jeden wspólny historial
messageswraz zlast_active_agent, aby wcześnie wprowadzone ograniczenia wpływały na odpowiedzi generowane później przez innego agenta. - Traktuj
interrupt()iCommand(resume=...)jako mechanizm umożliwiający osobie dodanie ograniczeń bezpieczeństwa przed następną odpowiedzią, pamiętając przy tym, że potrzebny jest checkpointer oraz identyfikator wątku. - Zachowaj ograniczone narzędzia za stabilną interfejsem, aby logika oparta na regułach mogła później stać się prawdziwą API lub pełnym rozumowaniem modelu bez ingerencji w graf.
- Naj szybszym sposobem na wdrożenie tego wzorca jest wybranie dwóch dobrze zdefiniowanych specjalistów we własnym zespole i stworzenie kompletnego, działającego grafu; topologia pozostaje niezmieniona, natomiast prompty i narzędzia to elementy, które dostosowujesz według potrzeb.
Literatura pokrewna
- Agenci z akceptacją w LangGraph: interrupt(), punkty checkpoint i magazyn — Budowanie agenta w LangGraph krok po kroku: wyraźny graf ReAct, akceptacja przez człowieka z użyciem funkcji interrupt(), pamięć między wątkami za pomocą magazynu, a na końcu asystent poczty, który najpierw zadaje pytania.
- Tworzenie agenta badawczego ReAct w LangGraph: Mózg, ręce, router — Dowiedz się, jak zaimplementować pętlę ReAct (reason-act-observe) jako podgraf LangGraph, z wymuszanym refleksją, budżetami iteracji oraz równoległymi metodami badawczymi typu scatter-gather.
- Bramki zatwierdzenia w LangGraph.js: Pauzowanie agentów za pomocą interrupt() i Command — Stwórz minimalną bramkę zatwierdzenia w LangGraph.js, która pauzuje przed wywołaniem efektu ubocznego, zbiera decyzję człowieka w terminalu i bezpiecznie wznowia działanie od punktu kontrolnego.
- Pauzowanie i wznowienie agentów LangGraph za pomocą interrupt() i Command — Jak funkcje interrupt() i Command w LangGraph wykorzystują punkty kontrolne oraz wątki, aby zapauzować agenta w celu uzyskania zatwierdzenia, edycji lub wprowadzenia danych przez człowieka, a następnie bezpiecznie go wznowić później.