Agent to pętla while: gdzie LangChain create_agent i LangGraph Fit
Agent do wywoływania narzędzi w dwudziestu liniach, cztery problemy produkcyjne występujące w pętlach oraz kiedy przejść od LangChain create_agent do grafów stanowych LangGraph.
Źródła: An Agent Is a While Loop
Użycie narzędzi przyciąga dużo uwagi, ale to, co decyduje o tym, kiedy wezwać narzędzie, co zrobić z wynikiem i kiedy zakończyć działanie, to pętla otaczająca model. Ludzie nazywają tę pętlę orkiestracją, co sprawia, że brzmi to poważniej, niż w rzeczywistości jest. Najprostszym sposobem na zrozumienie tego jest najpierw przyjrzenie się całej strukturze, a następnie zastanowienie się, dlaczego nie jest ona wystarczająca do użycia w produkcji.
Celowy agent w około dwudziestu liniach kodu
Weźmy proces wywoływania narzędzi, w którym model żąda użycia narzędzia, twój kod je uruchamia, a wynik wraca do modelu, i otoczmy to pętlą while. Najpierw należy zainicjować tę konwersację:
messages = [system_prompt, user_question]
Następnie sam pętla:
while True:
reply = model(messages, tools=TOOLS) if not reply.tool_calls:
return reply.text # the model answered; done for call in reply.tool_calls:
result = TOOLS[call.name](**call.args) # your code runs it
messages.append(tool_result(call.id, result))
To jest prawdziwy agent, a nie jego wersja z kreskówek. Model wybiera, który narzędzie użyć. Twój kod je uruchamia i dodaje wynik. Proces ten powtarza się, aż model zwróci tekst zamiast żądania narzędzia.
Jeśli wydaje ci się to trochę rozczarowujące ze względu na małe rozmiary, to właśnie taka reakcja jest zamierzona. Wszystko, co dostarcza framework, to elementy, które dodajesz po tym, jak ten pętla trafi do środowiska produkcyjnego.
Jeden przebieg, krok po kroku
Załóżmy, że agent do triażu sytuacji awaryjnych otrzymuje pytanie, dlaczego synchronizacje z dostawcami w nocy kończą się błędem ERR_4412 od czasu wersji z grudnia. Odpowiedź znajduje on w ciągu trzech prób, w ramach jednej pętli bez użycia frameworka.
Krok 1. Model jeszcze nie odpowiada. Wysyła żądanie search_runbooks("ERR_4412"), a pętla je wykonuje. Powracają trzy wyniki, z których pierwszy wskazuje, że błąd oznacza wygasły token autoryzacji dostawcy. Informacja ta jest dodawana do wiadomości.
Krok 2. Po przeczytaniu tego model żąda get_release_notes("december"). Zwraca jeden dokument: interwał odnowy tokena został skrócony z 24 godzin do 12. Dodano to ponownie.
Krok 3. Mając oba wyniki, model odpowiada w formacie prostego tekstu bez żadnego żądania narzędzia. Synchronizacje przeprowadzane w nocy trwają dłużej niż dwanaście godzin, więc token wygasa w trakcie ich trwania. Ponieważ nie ma żadnego żądania wykonania, pętla się zamyka.
Model wybrał narzędzia i uruchomił je w ramach pętli. Lista wiadomości powiększyła się o dwa wyniki, a nic więcej się nie wydarzyło.
Co psuje się po tygodniu pracy
W środowisku produkcyjnym zazwyczaj pojawiają się cztery problemy, zwykle w takiej kolejności. Żaden z nich nie dotyczy modelu – wszystkie dotyczą środowiska wokół niego.
Ponowne uruchomienie. Na drugim kroku system napotyka powolny incydent – po ośmiu minutach kontener jest recyklingowany. Ponieważ messages znajdowały się wyłącznie w pamięci, znikają i proces rozpoczyna się od nowa od tego samego pytania. Rozwiązaniem jest zapisywanie rozmowy po każdym kroku i kontynuowanie pracy na podstawie tego zapisu.
Niebezpieczne narzędzie. Innego wieczoru, na czwartym kroku model dochodzi do wniosku, że rozwiązaniem jest rollback_release("dec-2025"). Pozwolenie modelowi na samodzielne wykonanie tego działania o 3 rano nie jest czymś, czego ktoś by chciał. Pętla musi się zatrzymać, zapytać człowieka, poczekać – być może aż do rana – a następnie kontynuować dokładnie tam, gdzie się zatrzymała. Zwykła pętla while nie może czekać do rana.
Niezła decyzja. Na etapie 5 wybierany jest kiepski zapytanie wyszukiwawcze, a etapy od 6 do 16 powtarzają to wyszukiwanie z niewielkimi modyfikacjami sformułowania, przy czym za każdą z nich pobierana jest opłata. Wpatrywanie się w listę szesnastu wiadomości nie pokaże, gdzie doszło do błędu. Pomaga tu ograniczenie liczby kroków oraz zapisany ślad każdego kroku, który można odtworzyć.
Którą drogą iść. Błędy autoryzacji wymagają jednej sekwencji kroków, a błędy danych innej; gdy dwa narzędzia się nie zgadzają, sprawa powinna trafić do człowieka. Model radzi sobie nieźle z wyborem narzędzia, ale nie powinien za każdym razem improwizować struktury sterowania. Gałęzie powinny być zapisane tak, aby za każdym razem zachowywały się tak samo.
Spójrz ponownie na te cztery elementy. Nikt nie obwinia ani sprawnego, ani niezdarnego modelu – wszystko wynika z otaczającego kodu, czyli z tego, co on przechowuje, kiedy się zatrzymuje, co loguje oraz kto wybiera trasę działania. Orkiestracja to właśnie ta otaczająca warstwa, dlatego pętla składająca się z dwudziestu linijek może być w pełni poprawna, a mimo to wymagać dalszych zmian.
Zmień kroki na pola
Narysuj ponownie ten sam proces, przy czym każdy krok będzie przedstawiony jako pole połączone strzałkami: klasyfikacja, wyszukiwanie instrukcji, pobieranie notatek o nowościach, zaproponowanie poprawki, zatwierdzenie, a następnie jej wdrożenie lub odpowiedź. Gdy kroki są przedstawione jako pola, pojawiają się trzy nowe możliwości.
- Każde pole kończy się zapisaniem stanu do trwałego magazynowania, dzięki czemu po ponownym uruchomieniu proces kontynuuje się od ostatniego ukończonego pola, a nie od początku.
- Każde pole może wstrzymać cały proces i czekać na osobę tak długo, jak to konieczne, przy jednoczesnym bezpiecznym przechowywaniu stanu.
Rozgałęzienia znajdują się w polu klasyfikacji – istnieje tam ścieżka autoryzacji, ścieżka danych oraz mechanizm eskalacji. Narysowałeś te trzy strzałki. Model nadal wybiera narzędzia w ramach danej ścieżki, ale sama ścieżka należy do ciebie.
Model się nie zmienił. Pętla nabrała formy, którą można zobaczyć, zapisać i przerwać.
Czym jest LangChain
LangChain to biblioteka w Pythonie, z odpowiednikiem w JavaScriptzie, która dostarcza gotowe elementy do aplikacji opartych na LLM, dzięki czemu nie trzeba co raz ponownie pisać tych samych elementów. Istotne są trzy rodzaje takich elementów.
Opakowania modeli. Jedna interfejs dla wszystkich dostawców. ChatAnthropic, ChatOpenAI oraz ChatGoogleGenerativeAI oferują tę samą funkcję .invoke(messages), więc zmiana dostawcy polega jedynie na edycji jednej linii kodu, a nie na przepisywaniu całego kodu żądania.
Integracje. Adaptery do elementów otaczających model: magazyny wektorowe, narzędzia do ładowania dokumentów, mechanizmy wyszukiwania, modele embeddingowe oraz setki zewnętrznych narzędzi. To właśnie tutaj kryje się większość zaoszczędzonego wysiłku, ponieważ w przeciwnym razie każdy adapter wymagałby osobnego napisania i konserwacji przez użytkownika.
Pakietowy cykl agenta. Od wersji 1.0 z października 2025 roku główną funkcją jest create_agent, importowana w ten sposób:
from langchain.agents import create_agent
i używa się go w ten sposób:
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[search_runbooks, get_release_notes],
system_prompt="You are the on-call triage assistant.",
)
result = agent.invoke({"messages": [("user", "Why is ERR_4412 failing?")]})
W praktyce pętla z wcześniejszego przykładu jest dostarczana już w pełni napisana. Obsługiwane są również narzędzia pośredniczące: małe elementy, które uruchamiają się przy wywołaniu modelu lub narzędzia. Istniejące rozwiązania umożliwiają pauzę w oczekiwaniu na zatwierdzenie przez człowieka przed uruchomieniem narzędzia, usuwanie danych osobowych oraz skracanie długich rozmów.
To, czego nie obejmuje funkcja create_agent, to kontrola nad wnętrzem pętli: brak możliwości sprawdzania stanu między poszczególnymi krokami, brak pauzy trwającej cały dzień, a awaria procesu powoduje przerwanie całego działania.
Czym jest LangGraph
Pod funkcją create_agent kryje się LangGraph – inna biblioteka stworzona przez tego samego twórcę. LangChain dostarcza gotową pętlę, natomiast LangGraph oferuje elementy niezbędne do stworzenia własnej pętli, ukształtowanej zgodnie z grafem przedstawionym powyżej. Musisz zdefiniować trzy elementy.
Stan. Obiekt typowany, który przemieszcza się wzdłuż ścieżki wykonywania. Do celów triażu zawiera dotychczasowe komunikaty, klasifikację błędów, proponowane rozwiązanie oraz informację o tym, czy zostało ono zatwierdzone.
Węzły. Zwykłe funkcje w Pythonie, które otrzymują stan i zwracają aktualizacje. classify, search_runbooks, propose_fix oraz ask_human to po prostu funkcje.
Krawędzie. Kolejność wykonywania. Zwykłe krawędzie to stałe strzałki, natomiast krawędzie warunkowe to funkcje, które odczytują stan i wybierają następny węzeł, dzięki czemu tam wyrażony jest podział na autoryzację i dane.
Import:
from langgraph.graph import StateGraph
A wykres utworzony z tych elementów:
g = StateGraph(TriageState)
g.add_node("classify", classify)
g.add_node("search", search_runbooks)
g.add_node("propose", propose_fix)
g.add_node("approve", ask_human)
g.add_edge("classify", "search")
g.add_conditional_edges("search", route_by_error_type)
g.add_edge("propose", "approve")app = g.compile(checkpointer=PostgresSaver(...))
Jedna linia pod koniec ma kluczowe znaczenie: jest to wskaźnik stanu, czyli miejsce do przechowywania informacji o stanie, które jest przekazywane podczas kompilacji grafu. Dzięki niemu stan jest zapisywany po każdym węźle. Funkcje umożliwiające kontynuację działania po restartie, interrupt() służący do oczekiwania na dany element, a także możliwość cofnięcia się do węzła w celu ponownego odtworzenia procesu – wszystko to pochodzi z tego jednego argumentu, a nie z ręcznie napisanego kodu. Twoim wkładem są ramki i strzałki.
Zatem kontrast jest następujący: LangChain dostarcza pętlę, do której nie można zajrzeć. LangGraph wymaga od Ciebie narysowania tej pętli, a w zamian możesz ją badać, przechowywać, zatrzymywać i ponownie odtwarzać. Cztery wymienione powyżej problemy to momenty, gdy brak możliwości zajrzenia do środka przestaje być akceptowalny.
Jedna pila, dwoje drzwi
Ponieważ create_agent jest zbudowany na grafie LangGraph, wybór między LangChain a LangGraph nie jest decyzją dotyczącą frameworku. Chodzi o to, który „drzwi” użyć, a oba te rozwiązania się łączą. Agent utworzony za pomocą create_agent sam w sobie stanowi graf, co oznacza, że może być umieszczony jako pojedynczy węzeł w większym StateGraph. Większy graf zajmuje się klasyfikacją, zatwierdzaniem i przechowywaniem danych, natomiast wewnętrzny agent odpowiada za te etapy, w których model musi podjąć decyzję.
Użyj create_agent, gdy potrzebny jest natychmiast działający agent, a jego wewnętrzna struktura nie ma znaczenia. Wiele agentów nigdy nie wymaga czegoś więcej. Zwróć się do StateGraph, gdy musisz sprawdzić stan w trakcie działania, czekać na osobę, odzyskać się po awarii lub cofnąć proces – to te same cztery problemy wymienione wcześniej.
SDK dostawców od Anthropic i OpenAI znajdują się obok tej architektury, a nie w jej obrębie. Pracują według tego samego schematu, powiązanego z jedną rodziną modeli, przy najmniejszych trudnościach, jeśli korzystasz z jednego dostawcy. Ceną za to jest możliwość przenoszenia rozwiązania.
Porady dla zespołu
Najpierw napisz ten dwudziestolinijowy schemat, nawet jeśli zostanie później wyrzucony. Zajmie to godzinę, a potem będzie jasne, co każda platforma robi za ciebie – to jedyny sposób na jej debugowanie.
Przejdź na LangGraph w dniu, w którym naprawdę pojawi się jeden z tych czterech problemów, i nie wcześniej. Bot rozmówczy, który korzysta z trzech narzędzi i udziela odpowiedzi, może obyć się bez grafu. Agent, który ma przetrwać restart, czekać na zatwierdzenie lub być odtwarzany, nie może tego zrobić, a budowa trwałej implementacji od zera to samodzielny projekt.
Zespoły często od razu używają grafów do rozwiązywania problemów, które można załatwić prostym pętlą, a potem tracą czas na naukę frameworka, podczas gdy ten czas powinien być przeznaczony na sam problem.