„Chatbot” i „system agentowy” brzmią jak dwie różne kategorie produktu. W rozmowach o budżecie traktuje się je jak dwa osobne projekty, z których drugi jest wielokrotnie droższy.
W kodzie to jest jedna oś, a przesuwanie się po niej kosztuje po kilka linii na krok. Poniżej przechodzę ją całą, od agenta bez żadnych narzędzi do systemu z wieloma agentami, własnymi funkcjami, wyszukiwarką dokumentów i połączeniem przez MCP. Wszystkie fragmenty pochodzą z modułów, na których prowadzę szkolenia, więc to nie jest kod z prezentacji, tylko taki, który się uruchamia.
Na końcu jest część, której nie ma w materiałach dostawców: co rośnie szybciej niż liczba linii.
Agent Development Kit to otwarty framework Google do budowania agentów. Dostępny dla Pythona, TypeScriptu, Go, Javy i Kotlina, z wdrożeniem na Cloud Run albo Vertex AI Agent Engine.
Najkrótsza definicja, jaką znam: ADK jest do agentów tym, czym framework webowy jest do serwera HTTP. Żądanie obsłużysz samemu, ale routing, sesje, testy i wdrożenie ktoś już rozwiązał. Ty chcesz pisać logikę, nie infrastrukturę.
W praktyce dostajesz cztery rzeczy: definicję agenta, mechanizm narzędzi, zarządzanie stanem sesji oraz uruchamianie i podgląd tego, co się dzieje w środku.
Punkt wyjścia. Model, instrukcja, nazwa. Nic więcej.
from google.adk.agents import LlmAgent
root_agent = LlmAgent(
name="asystent_podstawowy",
model="gemini-2.5-flash",
instruction="""Jesteś pomocnym asystentem.
Odpowiadasz konkretnie. Jeśli czegoś nie wiesz, mówisz o tym wprost.""",
description="Asystent odpowiadający na pytania techniczne.",
)
To jest chatbot. Odpowiada z tego, czego nauczył się model, i nie ma dostępu do niczego poza własną pamięcią.
Warto od razu zwrócić uwagę na description. Wygląda na pole opisowe dla człowieka, a jest interfejsem: przy wielu agentach to na jego podstawie inni decydują, komu przekazać zadanie. Wrócę do tego w kroku czwartym.
Pierwszy skok jest dosłownie jednolinijkowy. W moim module wprowadzającym ta linia leży zakomentowana, jako zapowiedź tego, co dalej.
from google.adk.tools import google_search
root_agent = LlmAgent(
name="asystent_z_wyszukiwarka",
model="gemini-2.5-flash",
instruction="...",
tools=[google_search],
)
Od tego momentu agent przestaje być zamknięty w swojej wiedzy z treningu. Dostaje możliwość sprawdzenia czegoś, zanim odpowie, i sam decyduje, kiedy z niej skorzystać.
To jest pierwsza rzecz, która zmienia charakter systemu, a nie tylko jego możliwości. Odpowiedź przestaje zależeć wyłącznie od modelu, a zaczyna zależeć od tego, co akurat jest w sieci.
Narzędzie w ADK to zwykła funkcja Pythona. Nie ma dekoratora, nie ma rejestracji, nie ma pliku konfiguracyjnego.
TREASURE_INVENTORY = {"zlote_dublony": 1500, "rubiny": 45}
def get_treasure_count(item_name: str) -> str:
"""
Pobiera aktualną liczbę określonego przedmiotu ze skarbca.
Args:
item_name: Nazwa przedmiotu do wyszukania (np. 'zlote_dublony').
Returns:
Wiadomość z liczbą przedmiotów albo informacja o braku.
"""
count = TREASURE_INVENTORY.get(item_name.lower().replace(" ", "_"))
if count is None:
return f"Nie znaleziono '{item_name}'."
return f"Mamy {count} sztuk '{item_name}'."
root_agent = LlmAgent(
name="zarzadca_skarbca",
model="gemini-2.5-flash",
instruction="Zarządzasz inwentarzem. Używaj narzędzi zamiast zgadywać.",
tools=[get_treasure_count],
)
Tu jest rzecz, którą uważam za najbardziej niedocenianą w całym frameworku. Docstring nie jest dokumentacją dla człowieka. Jest specyfikacją, na podstawie której model decyduje, czy i kiedy wywołać tę funkcję.
Adnotacje typów mówią mu, co ma podstawić. Opis mówi, do czego to służy. Jeśli docstring jest niejasny, agent będzie wywoływał narzędzie w złych momentach albo nie będzie go wywoływał wcale, a Ty będziesz szukał problemu w instrukcji agenta, gdzie go nie ma.
Praktyczny wniosek: przy pisaniu narzędzi dla agenta poświęć na docstring tyle uwagi, ile poświęciłbyś na sygnaturę publicznego API. Bo to jest publiczne API, tylko konsumentem jest model.
Wyszukiwarka ogólna jest przydatna do faktów ze świata. Do procedur firmowych potrzebujesz czegoś innego: wyszukiwania po Waszych dokumentach.
from google.adk.tools import VertexAiSearchTool
narzedzie_wyszukiwania = VertexAiSearchTool(
search_engine_id=os.getenv("SEARCH_ENGINE_ID"),
max_results=10,
)
root_agent = LlmAgent(
name="asystent_dokumentacji",
model="gemini-2.5-flash",
instruction="""Odpowiadasz na podstawie dokumentacji organizacji.
Zawsze używaj wyszukiwania, zanim odpowiesz na pytanie o procedury.
Cytuj źródło. Jeśli nie znajdziesz odpowiedzi, powiedz to wprost.""",
tools=[narzedzie_wyszukiwania],
)
Konstrukcja jest taka sama jak w kroku pierwszym. Zmienia się narzędzie na liście, reszta zostaje.
Zwróć uwagę na dwa zdania w instrukcji. „Zawsze używaj wyszukiwania” zapobiega odpowiadaniu z pamięci modelu tam, gdzie liczy się Wasz dokument. „Jeśli nie znajdziesz, powiedz to wprost” to jedyna linia, która odróżnia system przyznający się do niewiedzy od takiego, który zawsze coś powie. O tym, dlaczego to nie wystarcza i co jeszcze trzeba sprawdzać, pisałem osobno przy okazji wierności cytowania w RAG.
Wybór między lokalnym indeksem a zarządzaną wyszukiwarką to osobna decyzja i rozebrałem ją w tekście o tym, kiedy przeskoczyć.
Do tej pory był jeden agent z rosnącym zestawem narzędzi. Teraz zmienia się kształt systemu.
from google.adk.agents import LlmAgent, SequentialAgent
scout = LlmAgent(
name="zwiadowca",
model=MODEL,
instruction="Zbierz informacje o celu. Zakończ oceną pewności.",
output_key="raport_wywiadu",
)
strategist = LlmAgent(
name="strateg",
model=MODEL,
instruction="Na podstawie raportu: {raport_wywiadu} przygotuj plan.",
output_key="plan",
)
captain = LlmAgent(
name="kapitan",
model=MODEL,
instruction="Oceń plan: {plan}. Zatwierdź albo odrzuć z uzasadnieniem.",
output_key="decyzja",
)
root_agent = SequentialAgent(
name="pipeline_planowania",
sub_agents=[scout, strategist, captain],
)
Mechanizm jest prosty i wart zapamiętania, bo powtarza się wszędzie. output_key zapisuje wynik agenta do stanu sesji. Składnia {klucz} w instrukcji następnego agenta odczytuje go stamtąd. Dane płyną przez stan, nie przez parametry funkcji.
Zysk z podziału nie polega na tym, że trzy modele są mądrzejsze od jednego. Polega na tym, że każdy etap ma jedno zadanie i da się go osobno poprawić, przetestować i podmienić. Jeden agent z instrukcją na trzy strony jest nie do zdiagnozowania, gdy zacznie odpowiadać dziwnie.
W ADK 2.0 doszedł do tego drugi sposób opisywania topologii, oparty na grafie, który dodaje rozgałęzienia i pętle. Opisałem go w tekście o migracji na graf.
Ostatni krok na osi. Zamiast pisać własną funkcję do każdego systemu, agent podłącza się do serwerów mówiących wspólnym protokołem.
from google.adk.tools.mcp_tool.mcp_toolset import (
MCPToolset,
StdioConnectionParams,
StreamableHTTPConnectionParams,
)
# Serwer lokalny, uruchamiany jako proces
api_tools = MCPToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command=sys.executable,
args=["mcp_servers/api_server.py"],
),
timeout=30,
)
)
# Serwer zdalny, po HTTP
remote_tools = MCPToolset(
connection_params=StreamableHTTPConnectionParams(url=REMOTE_MCP_URL)
)
root_agent = LlmAgent(
name="agent_wielonarzedziowy",
model=MODEL,
instruction="...",
tools=[api_tools, remote_tools],
)
Kilka rzeczy dzieje się tu naraz. Agent jest podłączony jednocześnie do serwera uruchamianego lokalnie i do zdalnego wystawionego pod adresem HTTP. Nie wie, ile narzędzi dostanie od każdego z nich, bo pyta o to przy starcie. I nie wymaga to napisania ani jednej funkcji integracyjnej, jeśli serwer po drugiej stronie już istnieje.
To jest moment, w którym zestaw możliwości przestaje być czymś, co piszesz, a staje się czymś, co konfigurujesz.
Teraz część, którą uważam za ważniejszą od całej reszty.
Kod rośnie liniowo i łagodnie. Ryzyko nie.
| Krok | Ile kodu | Co może pójść nie tak |
|---|
| 0. Czat | kilka linii | Zmyślona odpowiedź, którą ktoś weźmie za prawdę |
| 1. Wyszukiwarka | jedna linia | To samo plus zaufanie do przypadkowego źródła z sieci |
| 2. Własne narzędzie | kilkanaście linii | Agent wywołuje funkcję w złym momencie albo ze złym argumentem |
| 3. Dokumenty | kilka linii | Odpowiedź z nieaktualnej wersji procedury, wyglądająca na udokumentowaną |
| 4. Wiele agentów | kilkanaście linii | Błąd jednego etapu propaguje się dalej jako fakt |
| 5. MCP | kilka linii | Agent działa na systemach, do których ma dostęp, a Ty nie wiesz kiedy |
Każdy wiersz jest tańszy w napisaniu od poprzedniego i droższy w weryfikacji.
Przy kroku zerowym sprawdzasz odpowiedź, czytając ją. Przy kroku piątym musisz wiedzieć, jakie narzędzia agent dostał, w jakiej kolejności je wywołał i co zrobiły po drugiej stronie. To już nie jest kwestia przeczytania odpowiedzi, tylko posiadania śladu wykonania.
Wniosek praktyczny: nie decyduj o kroku na osi na podstawie tego, ile kodu wymaga. Decyduj na podstawie tego, czy potrafisz zweryfikować, co się stało. Jeśli nie potrafisz, cofnij się o jeden krok i najpierw dołóż obserwowalność.
Trzy kroki na pierwszy wieczór.
- Zbuduj krok zerowy i pogadaj z nim. Kilkanaście minut razem z instalacją. Celem jest zobaczyć, jak wygląda podgląd wykonania, a nie napisać coś użytecznego.
- Dodaj jedną własną funkcję i celowo napisz zły docstring. Potem popraw i porównaj zachowanie. To najszybszy sposób, żeby zrozumieć, czym w tym frameworku naprawdę jest narzędzie.
- Zatrzymaj się przed krokiem czwartym, dopóki jeden agent z narzędziami robi to, czego potrzebujesz. Topologia jest przyjemna do budowania i rzadko potrzebna wcześniej, niż się wydaje.
Ten tekst jest wstępem. Kolejne części schodzą głębiej w miejsca, w których to przestaje być proste:
W planie są jeszcze: człowiek w pętli decyzyjnej, agenci rozmawiający z agentami przez protokół A2A oraz pamięć agenta między sesjami. Podstawowe pojęcia zebrałem w słowniczku.