Strona główna / Artykuły / Specjalni agenci, router słów kluczowych i funkcja interrupt(): Trener LangGraph

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.

2462 słów

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.
  • Zachowanie kontroli przez człowieka. System nie powinien działać w pełni autonomicznie; potrzebuje wyraźnego punktu, w którym osoba może zatwierdzić działanie, dodać szczegóły lub zmienić kierunek, zanim zostanie wygenerowane cokolwiek innego.
  • 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.
  • Dlaczego temperatura 0: sprawia, że wyniki są jak najbardziej powtarzalne, dzięki czemu wykres przestrzega ustalonych zasad zamiast zmieniać się przy każdym uruchomieniu. Jest to istotne, gdy ktoś wielokrotnie testuje lub ocenia tę samą rozmowę. W praktyce nawet temperatura 0 nie gwarantuje ścisłego determinizmu w modelach hostowanych, więc testy powinny sprawdzać routing i strukturę, a nie dokładne sformułowania.
  • Dlaczego Groq: jego sprzęt do inferencji LPU utrzymuje niską opóźnioność, co ma znaczenie, gdy jeden krok użytkownika wywołuje kilka etapów przetwarzania (węzeł ludzki, router, doradca, narzędzie, węzeł ludzki), zanim pojawi się odpowiedź.
  • 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 od MessagesState z 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.
  • Doradcy ds. żywienia otrzymali polecenie, aby nie projektować planów treningowych, ponieważ to należy do Doradcy Treningowego.
  • 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łaniu interrupt w 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.
  • Graf jest kompilowany z użyciem wskaźnika kontrolnego 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,
  • potrzeba zatrzymania procesu w połowie, aby ktoś mógł się wypowiedzieć lub zatwierdzić działania, czy to ze względu na wymogi prawne, bezpieczeństwo czy personalizację,
  • kontekst ustalony w jednej części rozmowy, który musi prawidłowo wpłynąć na działania innego specjalisty później.
  • 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 MultiAgentState przechowywany przez checkpointer sprawia, że przekazywanie obowiązków przebiega płynnie, a ograniczenia pozostają aktualne.
  • Kontrola jest kluczowa. Unikaj całkowicie autonomicznych pętli w środowisku produkcyjnym; używaj 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 messages wraz z last_active_agent, aby wcześnie wprowadzone ograniczenia wpływały na odpowiedzi generowane później przez innego agenta.
    • Traktuj interrupt() i Command(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