Przejdź do treści
Blog

Od czatu do systemu agentowego w sześciu krokach po jednej linii

Dystans między chatbotem a systemem wieloagentowym z narzędziami i MCP mierzy się w liniach kodu i jest krótszy, niż się wydaje. Przechodzę tę drogę krok po kroku i pokazuję, co rośnie szybciej niż kod.

Przemysław Zagórski6 min czytania

„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.

Czym w ogóle jest ADK

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.

Krok 0: agent to jeden obiekt

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.

Krok 1: jedna linia i ma internet

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.

Krok 2: własna funkcja, czyli docstring jako interfejs

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.

Krok 3: twoje dokumenty zamiast internetu

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ć.

Krok 4: topologia, czyli kilku agentów zamiast jednego

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.

Krok 5: MCP, czyli koniec pisania integracji od zera

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.

Co rośnie szybciej niż liczba linii

Teraz część, którą uważam za ważniejszą od całej reszty.

Kod rośnie liniowo i łagodnie. Ryzyko nie.

KrokIle koduCo może pójść nie tak
0. Czatkilka liniiZmyślona odpowiedź, którą ktoś weźmie za prawdę
1. Wyszukiwarkajedna liniaTo samo plus zaufanie do przypadkowego źródła z sieci
2. Własne narzędziekilkanaście liniiAgent wywołuje funkcję w złym momencie albo ze złym argumentem
3. Dokumentykilka liniiOdpowiedź z nieaktualnej wersji procedury, wyglądająca na udokumentowaną
4. Wiele agentówkilkanaście liniiBłąd jednego etapu propaguje się dalej jako fakt
5. MCPkilka liniiAgent 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ść.

Gdzie zacząć

Trzy kroki na pierwszy wieczór.

  1. 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.
  2. 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.
  3. 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.

Co dalej w tej serii

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.

Źródła

  1. [1]Agent Development Kit: dokumentacjaGoogle
  2. [2]Kod źródłowy ADK dla Pythonagoogle/adk-python (GitHub)
  3. [3]Model Context Protocol: specyfikacjaAnthropic
  4. [4]Vertex AI Search: dokumentacjaGoogle Cloud
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.