Przejdź do treści
Blog

ADK 2.0: kod się uruchomi i nie zrobi tego, co myślisz

Google przepisał Agent Development Kit z drzewa agentów na graf węzłów. Przeniosłem 24 moduły szkoleniowe i opisuję, co naprawdę się zmieniło oraz cztery miejsca, w których migracja psuje się po cichu.

Przemysław Zagórski7 min czytania

Najgorsze w migracji nie jest to, co się psuje głośno. Brakujący import rzuca wyjątkiem w pierwszej sekundzie i poprawiasz go, zanim zdążysz odejść po kawę.

Gorsze jest to, co dalej się uruchamia. Kod przechodzi, testy świecą na zielono, agent odpowiada. Tylko fragment logiki, który napisałeś, przestał być wykonywany, a nikt Ci o tym nie powiedział.

Google wydał ADK 2.0 dla Pythona 19 maja 2026, a wersja Go dogoniła go 30 czerwca. Od tego czasu wydania wychodzą mniej więcej co dwa tygodnie i jesteśmy przy 2.6.0. Przeniosłem na nowe API materiały szkoleniowe, które prowadzę: dwadzieścia kilka modułów, od „hello world” po agenta do audytu bezpieczeństwa. Poniżej to, co z tego wynikło.

Jedno zdanie o tym, co się zmieniło

ADK 1.x był drzewem agentów. Agent nadrzędny miał pod sobą podagentów, wywoływał je i zbierał wyniki. Hierarchia była w kodzie, ale przebieg wykonania zależał od tego, co model uznał za sensowne.

ADK 2.0 jest grafem węzłów. Agenci, narzędzia i zwykłe funkcje Pythona są równorzędnymi węzłami, a to, co dzieje się po czym, opisujesz krawędziami. Silnik wykonania nazywa się Workflow Runtime i to on decyduje o kolejności, ponowieniach i przekazywaniu stanu.

Brzmi jak kosmetyka. Nie jest, bo przenosi decyzję o przebiegu z modelu do kodu.

Trzy prymitywy dostały jedną alternatywę

W 1.x były trzy osobne klasy do orkiestracji. Chciałeś sekwencji, brałeś SequentialAgent. Chciałeś pętli, brałeś LoopAgent. Chciałeś równoległości, brałeś ParallelAgent.

Zanim pójdziemy dalej, jedna rzecz wbrew temu, co przeczytasz w większości omówień: te klasy nie zniknęły i nie są oznaczone jako przestarzałe. Sprawdziłem w źródłach pakietu. google.adk.agents nadal eksportuje SequentialAgent, LoopAgent, ParallelAgent i LlmAgent, bez żadnej adnotacji o wycofaniu. Twój kod z 1.x będzie się nadal kompilował.

Workflow jest alternatywą, nie zamiennikiem. To rozróżnienie jest ważniejsze, niż wygląda, bo zmienia migrację z „musimy przepisać wszystko” na „przepisujemy tam, gdzie to się opłaca”.

Tak wyglądał pipeline w moim module o sekwencjach:

from google.adk.agents import LlmAgent, SequentialAgent

scout = LlmAgent(name="zwiadowca", model=MODEL, output_key="raport")
strategist = LlmAgent(name="strateg", model=MODEL, output_key="plan")
captain = LlmAgent(name="kapitan", model=MODEL, output_key="decyzja")

pipeline = SequentialAgent(sub_agents=[scout, strategist, captain])

To samo zbudowane na grafie wygląda tak. Zwróć uwagę na import: korzeń pakietu google.adk eksportuje teraz wąski, uporządkowany zestaw nazw, czyli Agent, Context, Event, Runner i Workflow. Samo Agent nie jest przy tym nowością, tylko aliasem na LlmAgent, który istniał wcześniej.

from google.adk import Agent, Event, Workflow

def init_context(node_input: str):
    yield Event(state={"zapytanie": node_input})

scout = Agent(name="zwiadowca", model=MODEL, output_key="raport")
strategist = Agent(name="strateg", model=MODEL, output_key="plan")
captain = Agent(name="kapitan", model=MODEL, output_key="decyzja")

pipeline = Workflow(edges=[
    ("START", init_context, scout, strategist, captain),
])

Zwróć uwagę na init_context. To zwykła funkcja Pythona, która stała się pełnoprawnym węzłem grafu. W 1.x, żeby wstrzyknąć coś do stanu przed pierwszym agentem, trzeba było kombinować z callbackami albo pisać własną klasę agenta. Teraz to jest funkcja, która oddaje zdarzenie.

Pętla przestała być klasą, stała się krawędzią

To jest fragment, przy którym nowe API naprawdę zaczyna się bronić.

W 1.x pętla była osobnym bytem: opakowywałeś podagenta w LoopAgent, ustawiałeś max_iterations i wymyślałeś sposób, w jaki podagent zasygnalizuje, że można kończyć. Zwykle sprowadzało się to do szukania słowa kluczowego w jego odpowiedzi, co jest dokładnie tak kruche, jak brzmi.

W 2.0 pętla to krawędź wskazująca wstecz:

pipeline = Workflow(edges=[
    ("START", draft, evaluate, check_route),
    (check_route, {"retry": draft, "done": finalize}),
])

Węzeł check_route zwraca etykietę, a słownik mówi, dokąd iść dalej. „Retry” cofa do draft. To nie jest nowa funkcja frameworka, tylko konsekwencja tego, że graf potrafi mieć cykle. Rozgałęzienie warunkowe, ponowienie i pętla przestają być trzema mechanizmami, a stają się jednym.

Co przetrwało, czyli dlaczego to nie jest przepisywanie od zera

Zanim przejdziemy do rzeczy, które bolą: mechanizm przepływu danych został ten sam i to jest najlepsza wiadomość w całej migracji.

output_key nadal zapisuje wynik agenta do stanu sesji. Składnia {klucz} w instrukcji nadal go stamtąd czyta. Doszła drobna, ale przyjemna rzecz: {klucz?} oznacza odczyt opcjonalny, który nie wysadza wykonania, gdy klucza jeszcze nie ma.

Znaczy to tyle, że instrukcje agentów przenoszą się bez zmian. W moim przypadku to była zdecydowana większość objętości kodu. Migracja dotknęła orkiestracji, nie treści.

Praktyczny wniosek do planowania

Jeśli szacujesz koszt migracji, licz węzły orkiestracji, a nie linie kodu. Pipeline z ośmioma agentami i dwiema pętlami to kilka godzin. Osiem tysięcy linii instrukcji w tych agentach to zero godzin, bo przechodzą bez zmian.

Cztery miejsca, w których psuje się po cichu

Tu wracamy do zdania z początku. Wszystkie cztery pochodzą z oficjalnej dokumentacji migracyjnej i wszystkie mają wspólną cechę: kod działa dalej.

Nadpisany _run_async_impl() przestaje być wywoływany. Jeśli w 1.x pisałeś własną klasę agenta i nadpisywałeś tę metodę albo generate_content(), silnik grafowy ją ignoruje. Nie ostrzega, nie rzuca wyjątkiem. Po prostu Twoja logika znika z wykonania. Oficjalne zalecenie to przeniesienie jej do BeforeAgentCallback i AfterAgentCallback.

Szerokie except Exception: wyłącza ponowienia. W 1.x łapanie wszystkiego w narzędziu było defensywne i rozsądne. W 2.0 maskuje błąd przed frameworkiem i trwale unieruchamia mechanizm automatycznych ponowień. Nie dostaniesz ostrzeżenia, że RetryConfig, który skonfigurowałeś, nigdy się nie uruchomi. Wyjątki mają się propagować, żeby silnik mógł je ocenić.

Łapanie BaseException rozbraja human-in-the-loop. To ta sama pułapka o poziom wyżej i groźniejsza. Framework zatrzymuje graf na wejście człowieka, rzucając NodeInterruptedError. Twój except BaseException go połyka, a wraz z nim całą możliwość pauzowania workflow.

context.session.events.append() omija silnik. Bezpośrednie dopisywanie zdarzeń do sesji obchodzi graf i, jak to ujmuje dokumentacja, łamie determinizm. Zdarzenia trzeba oddawać przez yield z węzła, żeby framework zajął się ich trwałością, routingiem i strumieniowaniem.

Widać tu wspólny mianownik. Wszystkie cztery nawyki z 1.x były przejawem ostrożności: przejmij kontrolę, złap wszystko, zapisz sam. W 2.0 ta ostrożność wyłącza mechanizmy, które silnik chce wykonać za Ciebie.

Rzeczy nudne, które oszczędzają dzień

Dwie na koniec listy technicznej.

Python 3.10 albo nowszy. Krąży po blogach wersja z 3.11, ale oficjalna dokumentacja mówi 3.10. Warto sprawdzić, zanim przepiszesz środowisko.

Schemat sesji. Zdarzenia dostały dwa nowe pola, node_info i output. Jeśli masz własną implementację przechowywania sesji z kolumnami odwzorowującymi pola zdarzenia, trzeba ją zaktualizować. Jeśli trzymasz zdarzenia jako blob JSON, nie musisz robić nic. To rzadki przypadek, w którym leniwsze rozwiązanie wygrywa.

W Go zmienia się ścieżka importu na google.golang.org/adk/v2.

Czego graf nie naprawia

Teraz część, której nie znajdziesz w materiałach marketingowych.

Workflow Runtime daje determinizm orkiestracji, a nie determinizm systemu. Kolejność węzłów jest gwarantowana przez kod. To, co dzieje się wewnątrz węzła z modelem językowym, pozostaje probabilistyczne. Agent na trzeciej pozycji w grafie nadal potrafi zwrócić trzy różne odpowiedzi na to samo wejście.

To rozróżnienie ma konsekwencje praktyczne. Przejście na graf nie zwalnia z testowania jakości odpowiedzi, tylko przenosi część problemu z „czy w ogóle wywoła właściwe kroki” do „czy krok zrobił to, co miał zrobić”. Pierwsze rozwiązuje framework. Drugie nadal Ty.

Druga rzecz: graf robi architekturę bardziej jawną, a nie prostszą. Pipeline z rozgałęzieniami, pętlami i ponowieniami to nadal skomplikowany system. Różnica polega na tym, że teraz ta złożoność jest napisana w jednym miejscu, zamiast być rozproszona po instrukcjach i mieć nadzieję, że model ją odtworzy.

Jak podejść do migracji

Kolejność, którą uznałem za najmniej bolesną, po przejściu przez to na własnych modułach.

  1. Zacznij od inwentarza wzorców, nie plików. Policz, ile masz miejsc z SequentialAgent, LoopAgent, ParallelAgent i własnymi klasami agentów. To jest realny zakres pracy. Liczba plików nic nie mówi. Pamiętaj przy tym, że prosta sekwencja bez rozgałęzień działa na starych klasach dalej i nie musi być przepisywana wcale.
  2. Najpierw wyszukaj cztery ciche awarie, zanim cokolwiek przepiszesz. Grep po _run_async_impl, except Exception, except BaseException i events.append. To pięć minut i od razu wiesz, czy czeka Cię migracja, czy śledztwo.
  3. Przenoś jeden pipeline i porównaj wyjście. Nie całość naraz. Weź najprostszą sekwencję, przepisz na Workflow(edges=[...]) i sprawdź, czy stan płynie tak samo.
  4. Nie kieruj 2.0 na bazę sesji z 1.x. Zrób nową, choćby lokalną, do czasu aż migracja się ustabilizuje.
  5. Zostaw pętle na koniec. Są najprzyjemniejsze do przepisania, ale wymagają decyzji o warunku wyjścia, którą wcześniej zwykle podejmował model. To dobry moment, żeby ją wreszcie zapisać w kodzie.

Czy warto

Tak, ale nie z powodów, które podaje changelog.

Argumenty w rodzaju „mniej tokenów” i „lepsza wydajność” są prawdziwe i drugorzędne. Rzecz, która realnie zmienia pracę zespołu, jest inna: kiedy przebieg jest w kodzie, da się go przetestować w kawałkach. Możesz uruchomić trzeci węzeł w izolacji, podstawić mu stan i sprawdzić, co zwróci. W dispatcherze opartym o jeden model to nie było możliwe, bo pojedynczego kroku nie dawało się wyjąć.

To samo dotyczy diagnozy awarii. Graf daje osobny ślad na węzeł zamiast jednego długiego przebiegu, w którym trzeba zgadywać, gdzie coś poszło nie tak.

Cena jest taka, że trzeba usiąść i zdecydować, co po czym następuje i kiedy się kończy pętla. Framework przestał zgadywać za Ciebie. Dla prototypu to strata, dla systemu, który ma stać na produkcji, to jedyny sensowny kierunek.

Dlatego moja rekomendacja jest węższa niż entuzjazm z ogłoszeń: przepisz na graf to, co ma rozgałęzienia, pętle albo ponowienia. Resztę zostaw. Prosta sekwencja trzech agentów działa na starym API tak samo dobrze i nie ma powodu ruszać czegoś, co nie sprawia problemów. Za to cztery ciche awarie z sekcji wyżej sprawdź wszędzie, niezależnie od tego, czy migrujesz orkiestrację, bo one dotyczą także kodu, którego nie zamierzasz dotykać.

To pierwszy tekst z serii o ADK. Kolejne dotyczą testowania agentów, w tym pułapki, w którą wpadłem osobiście: dlaczego AgentEvaluator wywala się na pipeline'ach i co z tym zrobić. Podstawowe pojęcia zebrałem w słowniczku.

Źródła

  1. [1]Welcome to ADK 2.0Google / adk.dev
  2. [2]ADK 2.0: breaking changes i wskazówki migracyjneGoogle, adk-docs (GitHub)
  3. [3]google-adk: historia wydańPyPI
  4. [4]Agent Development Kit: dokumentacjaGoogle
Przemysław Zagórski

Przemysław Zagórski

Programista i architekt, 14+ lat w telco i enterprise IT. Prowadzi warsztaty z inżynierii kontekstu, GitHub Copilota, Google ADK oraz ekosystemu Gemini dla zespołów, które muszą dowozić produkcyjnie.

Jeśli widzisz te problemy u siebie, zwykle da się je rozbroić w kilka dni roboczych. Nie kolejnym narzędziem, tylko zmianą sposobu pracy zespołu. Chętnie omówię Wasz przypadek.

6 min czytania

A2A: agent przestaje być biblioteką, staje się usługą

Protokół agent-to-agent zamienia agenta w mikrousługę wywoływalną po HTTP, niezależnie od języka i frameworka. Pokazuję, jak to wygląda w kodzie i kiedy naprawdę warto rozdzielać agentów na osobne procesy.

  • ADK
  • Agenci
Czytaj