Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik11 min czytania

Agenci w LangChain 1, create_agent i middleware

LangChain 1 wycofał AgentExecutor na rzecz create_agent opartego na LangGraph. Co to zmienia, czym jest middleware i jak migrować stary kod.

Agenci w LangChain 1, create_agent i middleware

W pierwszej dużej wersji LangChaina sposób budowania agentów zmienił się całkowicie. Dotychczasowe podejście oparte o wykonawcę agenta zostało wycofane, a jego miejsce zajęła jedna funkcja tworząca agenta, działająca na środowisku uruchomieniowym LangGraph. Bieżące wydanie to 1.3.14, licencja MIT.

To ma bezpośrednią konsekwencję przy nauce: praktycznie każdy poradnik starszy niż ta wersja pokazuje kod, który dziś jest wycofany. Jeśli kopiujesz przykład z artykułu i dostajesz ostrzeżenie albo błąd importu, to jest właśnie ta sytuacja.

Co dokładnie zostało wycofane

Trzy rzeczy, które trzeba rozpoznać w starym kodzie.

Wykonawca agenta, czyli klasa spinająca model z narzędziami i prowadząca pętlę. Funkcja inicjalizująca agenta na podstawie nazwy typu. Oraz funkcja tworząca agenta w schemacie rozumowania i działania, w tej postaci, w jakiej istniała w pakiecie głównym.

Wszystkie trzy zastępuje jedna funkcja tworząca agenta, dostępna w module agentów pakietu głównego. Przyjmuje model, listę narzędzi i prompt, a zwraca coś, co da się wywołać.

Warto od razu wiedzieć, że wycofanie znaczy tutaj usunięcie, a nie samo oznaczenie jako przestarzałe. Pakiet główny został odchudzony do kilku modułów, a dawny wykonawca i funkcja inicjalizująca przeniosły się do osobnego pakietu ze starym kodem, o nazwie langchain-classic, który instaluje się oddzielnie. Niezmieniony stary import kończy się więc błędem importu, a nie ostrzeżeniem. Ostrzeżenia zobaczysz dopiero po doinstalowaniu tamtego pakietu i przepisaniu ścieżek importu na jego przestrzeń nazw.

Powód wycofania jest techniczny i wart zrozumienia, bo tłumaczy resztę zmian. Dawny wykonawca był pętlą: wywołaj model, sprawdź czy chce narzędzia, wywołaj narzędzie, powtórz. Ta pętla nie miała miejsca na rozgałęzienia, na zatrzymanie się i poczekanie na decyzję człowieka ani na wznowienie po awarii. Wszystko, co wykraczało poza prostą pętlę, wymagało obchodzenia biblioteki.

Nowe podejście stawia agenta na grafie, który te rzeczy ma wbudowane. Agent jest grafem z konkretnym układem węzłów, a Ty dostajesz wygodny konstruktor zamiast składania go ręcznie.

Podstawowy układ

Code
Python
from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def sprawdz_status(numer: str) -> str:
    """Zwraca status zamowienia po jego numerze."""
    return baza.status(numer)

agent = create_agent(
    model="anthropic:claude-sonnet-5",
    tools=[sprawdz_status],
    prompt="Odpowiadasz na pytania o zamowienia. Uzywaj narzedzia, nie zgaduj.",
)

wynik = agent.invoke({"messages": [{"role": "user", "content": "gdzie jest 4471"}]})

Trzy rzeczy warto zauważyć.

Model podaje się łańcuchem znaków z przedrostkiem dostawcy, co upraszcza przełączanie między dostawcami do zmiany jednego napisu. Można też przekazać gotowy obiekt modelu, gdy potrzebujesz konkretnych ustawień.

Narzędzie to zwykła funkcja z opisem w dokumentacji. Ten opis trafia do modelu i jest jedyną informacją, na podstawie której model decyduje o wywołaniu, więc zdanie mówiące wprost, kiedy narzędzie wywołać, działa lepiej niż samo stwierdzenie, co ono robi.

Wynik jest strukturą z historią wiadomości, a nie łańcuchem znaków. To drobiazg, o który potyka się większość osób przechodzących ze starego podejścia.

Middleware, czyli sedno nowego podejścia

To jest element, który odróżnia obecne rozwiązanie od poprzedniego najbardziej i którego warto się nauczyć, bo zastępuje większość obejść stosowanych dawniej.

Middleware to warstwa wpinana w konkretny moment pracy agenta. Możesz zmienić to, co idzie do modelu, przejrzeć decyzję o wywołaniu narzędzia zanim ono się wykona, zmodyfikować wynik narzędzia zanim wróci do modelu albo zatrzymać przebieg.

Cztery zastosowania pojawiają się najczęściej.

Zatwierdzanie przez człowieka: agent zamierza wywołać narzędzie wykonujące operację nieodwracalną, warstwa zatrzymuje przebieg i czeka na potwierdzenie. Dawniej wymagało to rozbicia pętli i napisania własnej.

Przycinanie historii: przy długiej rozmowie kontekst przestaje się mieścić, więc warstwa wycina starsze wiadomości albo je streszcza przed wysłaniem do modelu.

Ograniczanie uprawnień: warstwa sprawdza, czy w bieżącym kontekście wolno wywołać dane narzędzie, i odrzuca próbę, zanim cokolwiek się wykona. To jest właściwe miejsce na taką kontrolę, bo nie zależy od tego, co model przeczytał w rozmowie.

Zapisywanie i pomiar: warstwa widzi każde wywołanie, więc jest naturalnym miejscem na dopisanie identyfikatorów, zliczanie kosztu albo wysyłanie zdarzeń do narzędzia śledzącego, na przykład Arize Phoenix.

Zaleta tego układu polega na tym, że warstwy są niezależne i składalne. Dodanie zatwierdzania nie wymaga dotykania kodu przycinającego historię, a wyłączenie jednej z nich w środowisku testowym jest usunięciem elementu z listy.

Relacja do LangGraph

Warto to rozstrzygnąć, bo pytanie „LangChain czy LangGraph" pada często i przestało mieć sens w dawnej postaci.

Agent utworzony tą funkcją jest grafem. Konstruktor składa graf o ustalonym kształcie: węzeł wywołujący model, węzeł wykonujący narzędzia i krawędź warunkowa decydująca, czy wracamy do modelu, czy kończymy.

Znaczy to, że wszystko, co daje niższa warstwa, jest dostępne: punkty kontrolne zapisujące stan po każdym kroku, wznawianie przerwanego przebiegu, podróż w czasie i przeglądanie historii stanów. Opisałem to szerzej w tekście o pamięci w LangGraph.

Praktyczna rada dotycząca wyboru poziomu. Zacznij od konstruktora, bo pokrywa typowy przypadek: model plus narzędzia plus prompt. Zejdź do składania grafu ręcznie, gdy potrzebujesz kształtu, którego konstruktor nie daje, na przykład dwóch modeli w różnych rolach albo rozgałęzienia zależnego od kategorii pytania.

Przejście między poziomami nie jest przepisaniem, bo pracujesz w tym samym środowisku uruchomieniowym. To jest zresztą główna korzyść z tej zmiany: dawniej wyjście poza możliwości wykonawcy oznaczało porzucenie biblioteki.

Migracja starego kodu

Kolejność, która działa, gdy masz działającego agenta w starym podejściu.

Zacznij od inwentaryzacji. Wypisz, co Twój obecny kod robi poza prostą pętlą: przycinanie historii, obsługę błędów narzędzi, zatwierdzanie, logowanie. To jest lista rzeczy, które staną się warstwami.

Potem zamień samo tworzenie agenta. Model, narzędzia i prompt przenoszą się prawie jeden do jednego, więc pierwsza wersja działa szybko.

Trzeci krok to przeniesienie obejść na warstwy. Tu zwykle usuwa się najwięcej kodu, bo rzeczy pisane ręcznie wokół dawnej pętli mają teraz właściwe miejsce.

Czwarty to obsługa wyniku. Stary wykonawca zwracał słownik z kluczem zawierającym odpowiedź jako tekst, nowy zwraca strukturę z historią wiadomości, więc miejsca odczytujące wynik trzeba poprawić.

Nie migruj wszystkiego naraz, jeśli masz kilku agentów. Jeden na początek, przez tydzień na produkcji, potem reszta. Starsze podejście da się utrzymać przy życiu przez pakiet ze starym kodem, więc presja czasu jest mniejsza, niż sugerują komunikaty, ale kosztuje to doinstalowanie tego pakietu i poprawienie importów wszędzie, gdzie stary wykonawca się pojawia.

Narzędzia i to, co decyduje o trafności

Skoro agent sprowadza się do modelu wywołującego narzędzia, jakość narzędzi decyduje o wyniku bardziej niż cokolwiek innego.

Opis jest najważniejszy i najczęściej pisany źle. Model widzi wyłącznie nazwę funkcji, tekst dokumentacji i schemat argumentów, więc to jest cała informacja, na podstawie której podejmuje decyzję. Zdanie „Sprawdza status zamówienia" mówi, co narzędzie robi. Zdanie „Wywołaj, gdy użytkownik pyta o stan zamówienia i podał jego numer" mówi, kiedy je wywołać, i to jest różnica widoczna w statystykach trafności.

Argumenty warto opisać typami możliwie wąskimi. Pole przyjmujące dowolny łańcuch znaków zostanie wypełnione czymkolwiek, a pole przyjmujące jedną z trzech wartości ogranicza pomyłki u źródła. Walidacja schematem, na przykład w Zodzie po stronie interfejsu albo jej odpowiedniku w Pythonie, pełni tu podwójną rolę: opisuje kształt modelowi i odrzuca niepoprawne wywołanie.

Obsługa błędów zasługuje na osobne przemyślenie. Narzędzie, które rzuca wyjątkiem, zwraca agentowi komunikat, a agent na tej podstawie decyduje, co dalej. Komunikat mówiący, co poszło nie tak i co można zrobić zamiast tego, zamienia awarię w krok pośredni. Komunikat techniczny zwykle prowadzi do powtarzania tej samej próby.

Ostatnia rzecz to liczba narzędzi. Agent z pięcioma narzędziami wybiera trafnie, agent z trzydziestoma zaczyna się gubić, bo opisy konkurują ze sobą w kontekście. Przy większej liczbie warto rozważyć podział na kilku agentów wyspecjalizowanych albo warstwę wybierającą podzbiór narzędzi na podstawie pytania.

Koszty i ograniczenia przebiegu

Agent różni się od pojedynczego wywołania modelu tym, że sam decyduje, ile razy się wywoła, a to ma bezpośrednie przełożenie na rachunek.

Każdy krok niesie ze sobą całą dotychczasową rozmowę razem z wynikami narzędzi. Przebieg z sześcioma krokami to nie sześć wywołań o podobnym koszcie, tylko sześć wywołań o rosnącym koszcie, bo kontekst przyrasta.

Stąd dwie rzeczy warto ustawić od początku. Limit kroków, po którym przebieg się zatrzymuje, chroni przed pętlą, w której agent wywołuje narzędzie zwracające błąd i próbuje w kółko. Oraz przycinanie historii, bo bez niego długi przebieg przestaje mieścić się w oknie kontekstu.

Warto też dobrać model do roli. Agent podejmujący decyzje o wywołaniu narzędzi nie musi być modelem najmocniejszym, jeśli zestaw narzędzi jest wyraźnie opisany. Mocniejszy model warto zostawić na kroki wymagające rozumowania, a przy dwóch modelach w różnych rolach schodzi się już na poziom grafu budowanego ręcznie.

Ostatnia rzecz to obserwowanie. Bez zapisu przebiegów nie wiesz, ile kroków wykonuje typowe zapytanie ani które narzędzie wywołuje się najczęściej bez potrzeby. To są liczby, które zwykle zaskakują przy pierwszym spojrzeniu i które prowadzą do najprostszych oszczędności.

Zapis zdobywa się dwiema drogami i warto je rozróżnić, bo różnią się kosztem wdrożenia. Pierwsza to warstwa w kodzie agenta, opisana wyżej. Druga to pośrednik między aplikacją a dostawcą modelu, liczący tokeny i czasy bez dotykania kodu, przy czym najbardziej znany przedstawiciel tego podejścia, Helicone, od marca 2026 roku należy do Mintlify i działa w trybie utrzymania, więc przy nowym wdrożeniu warstwa w kodzie jest bezpieczniejszym wyborem.

LangChain kontra alternatywy

RozwiązaniePoziomZależnośćKiedy wybrać
create_agentKonstruktor nad grafemLangChain plus LangGraphTypowy agent z narzędziami
Graf budowany ręczniePełna kontrola przebieguLangGraphNietypowy kształt przepływu
OpenAI Agents SDKKonstruktor u dostawcyPowiązany z dostawcąPracujesz w jednym ekosystemie
Własna pętlaZero abstrakcjiSam klient modeluProsty przypadek, chcesz rozumieć całość

Ostatni wiersz warto rozważyć uczciwie, bo bywa pomijany. Pętla wywołująca model, sprawdzająca czy chce narzędzia i wywołująca je to około czterdziestu linii kodu. Przy jednym agencie z trzema narzędziami napisanie tego samodzielnie daje pełne zrozumienie i zero zależności.

Wartość biblioteki pojawia się przy rzeczach, które przy własnej pętli trzeba dopisać: trwałość stanu, wznawianie, zatwierdzanie przez człowieka, strumieniowanie zdarzeń pośrednich, obsługa równoległych wywołań narzędzi. Jeśli żadnej z nich nie potrzebujesz, biblioteka jest kosztem bez zwrotu.

Trzeci wiersz to wybór na innej osi: wiąże Cię z jednym dostawcą modeli w zamian za mniej elementów do złożenia. Przy pewności co do dostawcy bywa to rozsądne, przy potrzebie porównywania modeli mniej.

Typowe błędy

Pierwszy to uczenie się z poradników starszych niż pierwsza duża wersja. Pokazany tam kod jest wycofany, a różnice są na tyle duże, że nie da się go poprawić drobną zmianą.

Drugi to opis narzędzia mówiący, co ono robi, zamiast kiedy je wywołać. Model widzi wyłącznie nazwę, opis i schemat argumentów, więc to zdanie decyduje o trafności bardziej niż prompt systemowy.

Trzeci to brak ograniczenia liczby kroków. Agent, który wywołuje narzędzie zwracające błąd, potrafi próbować w kółko, a rachunek rośnie w tle.

Czwarty to zatwierdzanie operacji nieodwracalnych oparte na prompcie zamiast na warstwie. Prompt jest sugestią, którą model może zignorować, a warstwa jest kodem, którego nie ominie.

Piąty to odczytywanie wyniku jak w starym podejściu. Nowy zwraca strukturę z historią wiadomości, a nie tekst pod ustalonym kluczem.

Szósty to sięganie po bibliotekę przy przypadku, który obsłuży czterdzieści linii własnej pętli. Abstrakcja opłaca się przy trwałości i wznawianiu, a nie przy jednym agencie z trzema narzędziami.

FAQ

Czy AgentExecutor jest wycofany?

Tak, razem z funkcją inicjalizującą agenta i dotychczasową funkcją tworzącą agenta w schemacie rozumowania i działania. Zastępuje je jedna funkcja tworząca agenta w module agentów, zbudowana na środowisku uruchomieniowym LangGraph. Wycofane klasy zniknęły z pakietu głównego i żyją dalej w osobnym pakiecie ze starym kodem.

Dlaczego zmieniono podejście?

Bo dawny wykonawca był prostą pętlą bez miejsca na rozgałęzienia, zatrzymanie na decyzję człowieka i wznowienie po awarii. Wszystko, co wykraczało poza tę pętlę, wymagało obchodzenia biblioteki, a nowe podejście ma te rzeczy wbudowane.

Czym jest middleware w tym kontekście?

Warstwą wpinaną w konkretny moment pracy agenta: przed wywołaniem modelu, przed wykonaniem narzędzia albo po nim. Służy do zatwierdzania operacji, przycinania historii, ograniczania uprawnień i pomiaru, czyli rzeczy pisanych dawniej ręcznie wokół pętli.

Czy muszę uczyć się LangGraph?

Do typowego agenta nie, bo konstruktor składa graf za Ciebie. Warto natomiast wiedzieć, że agent jest grafem, bo dzięki temu punkty kontrolne, wznawianie i przeglądanie historii stanów są dostępne bez zmiany biblioteki.

Jak szybko trzeba migrować?

Bez pośpiechu, choć zupełnie nic nie zrobić się nie da. Wykonawca agenta zniknął z pakietu głównego, więc żeby zostawić stary kod na jakiś czas, trzeba doinstalować pakiet ze starym kodem i poprawić w nim ścieżki importu; dopiero wtedy działa dalej, z ostrzeżeniami o wycofaniu. Rozsądna droga to migracja jednego agenta, tydzień obserwacji na produkcji i dopiero potem reszta. Największa część pracy to przeniesienie własnych obejść na warstwy.

Dokumentacja agentów stoi na stronie projektu, a kod i wydania w repozytorium na GitHubie.