Tworzenie agenta Daily Tech Digest za pomocą zarządzanych agentów w Gemini API

1. Przegląd

Świat AI i technologii rozwija się szybciej, niż ktokolwiek jest w stanie za nim nadążyć. Codziennie pojawiają się nowe modele, artykuły i usługi. Agent podsumowujący, który pobiera dzisiejsze nagłówki, pisze zwięzłe podsumowania i codziennie rano generuje plik PDF, rozwiązałby ten problem, ale jego utworzenie wymagało wybrania platformy, zdefiniowania narzędzi w Pythonie, napisania pętli orkiestracji, spakowania kontenera i wdrożenia w Cloud Run. Wszystko to zanim agent wysłał jakiekolwiek żądanie internetowe.

Zarządzane agenty w Gemini API zmieniają tę sytuację. Piszesz 2 pliki konfiguracyjne w formacie Markdown i gotowy skrypt renderujący, wykonujesz 1 wywołanie interfejsu API, a prawdziwy sandbox Ubuntu uruchamia się, przegląda internet, pisze podsumowania i generuje plik PDF. Brak kontenerów. Brak wdrożenia. Brak kodu orkiestracji.

W tym laboratorium kodu stworzysz dokładnie takiego agenta – od pustej funkcji po działające codzienne podsumowanie.

Co utworzysz

  • Tworzenie i uruchamianie pierwszego agenta zarządzanego w prawdziwej piaskownicy Linux
  • Dostosowywanie agenta za pomocą stylu dziennikarskiego, źródeł internetowych i umiejętności związanych z plikami PDF
  • Dodawanie zabezpieczeń blokujących destrukcyjne polecenia przed ich uruchomieniem
  • Pobieranie wygenerowanego przez agenta pliku PDF
  • Ulepszanie podsumowania w wieloetapowej rozmowie bez ponownego pobierania informacji z internetu
  • Zapisywanie konfiguracji agenta i wywoływanie jej w przyszłych uruchomieniach za pomocą identyfikatora
  • Wysyłanie podsumowania do skrzynki odbiorczej za pomocą interfejsu Gmail API
  • Zaplanuj codzienne automatyczne uruchamianie i wysyłanie agenta

Czego potrzebujesz

  • Python 3.10 lub nowszy
  • Klucz interfejsu Gemini API: aistudio.google.com/api-keys (obejmuje poziom bezpłatny; zalecane rozliczenia w celu zapewnienia nieprzerwanego działania)

2. Czym są zarządzane agenty w interfejsie Gemini API?

3 poziomy systemów AI

Zanim przejdziemy do kodu, zobaczmy, gdzie na tle 2 alternatywnych rozwiązań plasują się zarządzane agenty:

Poziom

Co to jest?

Kto zarządza infrastrukturą?

Standardowy LLM

Ty podajesz prompt, a ona odpowiada tekstem. Bez rąk, bez pamięci, bez internetu.

Nie dotyczy: nie może niczego robić samodzielnie

Agent hostowany samodzielnie

Łączysz ADK/LangChain/AutoGen z Dockerem, narzędziami i pamięcią.

Ty: wszystko (lub zarządzana platforma, np. Agent Engine)

Zarządzany agent

Nadajesz mu cel. Google udostępnia bezpieczną piaskownicę. Agent pisze kod, uruchamia go, odczytuje błędy, przeszukuje internet i samodzielnie naprawia błędy.

Google: wszystkie usługi

To ćwiczenie dotyczy trzeciego wiersza. Podajesz zadanie i pliki konfiguracji. Google zajmie się resztą.

Co możesz utworzyć za pomocą pakietu ADK i Cloud Run

Aby utworzyć agenta do podsumowywania wiadomości, który przegląda internet, uruchamia Pythona i generuje plik PDF, potrzebujesz tego wszystkiego w połączeniu z pakietem ADK i Cloud Run:

# agent.py: define tools and wire up the agent
from google.adk.agents import LlmAgent
from google.adk.tools import google_search, built_in_code_execution

agent = LlmAgent(
    name="digest-agent",
    model=MODEL,
    instruction=AGENTS_MD,          # your editorial voice and rules
    tools=[google_search, built_in_code_execution],
)
# app.py: serve the agent over HTTP
from google.adk.runners import FastApiRunner
runner = FastApiRunner(agent=agent)
app = runner.app
# pdf_tool.py: custom tool, install reportlab, render PDF
# scraper.py: custom tool, fetch each news source
# streaming.py: wire agent events to your SSE endpoint
# Dockerfile: package everything
FROM python:3.12
COPY . /app
RUN pip install google-adk reportlab requests
CMD ["uvicorn", "app:app", "--host", "0.0.0.0"]
# Deploy to Cloud Run
gcloud run deploy digest-agent \
  --image gcr.io/your-project/digest-agent \
  --set-secrets GEMINI_API_KEY=gemini-key:latest \
  --memory 2Gi

Dzieje się to przed pierwszym uruchomieniem agenta. Nadal masz kontrolę nad izolacją piaskownicy (dzięki czemu agent nie może uszkodzić serwera), instalacją pakietów, zarządzaniem stanem między wywołaniami narzędzi i infrastrukturą przesyłania strumieniowego, która umożliwia dostarczanie zdarzeń do klienta.

Czym zastępuje je funkcja Zarządzani agenci

from google import genai
client = genai.Client()

stream = client.interactions.create(
    agent="antigravity-preview-05-2026",
    input="Generate the digest.",
    stream=True,
    environment={
        "type": "remote",
        "sources": [          # your config files, mounted at startup
            {
                "type": "inline",
                "target": ".agents/AGENTS.md",
                "content": AGENTS_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/SKILL.md",
                "content": SKILL_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                "content": GENERATE_PDF_PY,
            },
        ],
    },
)

Wymagania dotyczące ADK i Cloud Run

Co robią za Ciebie zarządzane agenty

Obraz kontenera + Dockerfile + CI/CD

W pełni zarządzane środowisko testowe Ubuntu (Python 3.12, Node 22, 4 rdzenie CPU / 16 GB pamięci RAM)

Wdrożenie i skalowanie Cloud Run

Dostępny w przypadku interakcji, automatycznie wygasa po 7 dniach nieaktywności

Izolacja piaskownicy

Izolowane na każdą interakcję

Narzędzie do tworzenia niestandardowych plików PDF + pip install

Agent instaluje pakiety w piaskownicy

Infrastruktura strumieniowania SSE

stream=True zwraca iterowalny obiekt wydarzenia

Definicje narzędzi w Pythonie

Wbudowane narzędzia: przeglądanie internetu, wykonywanie kodu, system plików

Zarządzanie stanem między wywołaniami narzędzi

Wbudowane w pętlę rozumowania agenta

Tworzysz pliki konfiguracyjne (AGENTS.md, SKILL.md, gotowy skrypt) i wykonujesz jedno wywołanie interfejsu API. Google zajmie się resztą.

Jak działa piaskownica

interactions.create() call
        │
        ▼
Google provisions Ubuntu sandbox (Python 3.12, Node 22, 4 CPU / 16 GB RAM)
        │
        ▼
Agent reasoning loop:
  plan → fetch URLs → run Python → write files → reason → repeat
        │
        ▼
Events stream back in real time: tool calls, text chunks, completion
        │
        ▼
interaction.completed → environment_id + interaction_id

Piaskownica jest dostępna przez 7 dni nieaktywności. Możesz ją wznowić za pomocą environment_id, aby dopracować wynik, uruchomić kolejne zadania lub rozwidlić ją w zapisany agent o określonej nazwie.

3. Skonfiguruj

Kliknij przycisk poniżej, aby otworzyć ten codelab w Google Cloud Shell. Wszystkie zależności są wstępnie zainstalowane.

Otwórz w Cloud Shell

Opcja B. Konfiguracja lokalna

git clone https://github.com/Saoussen-CH/tech-digest-managed-agent.git
cd tech-digest-managed-agent

W razie potrzeby zainstaluj uv:

curl -LsSf https://astral.sh/uv/install.sh | sh

Konfigurowanie klucza interfejsu API

cp .env.example .env
cloudshell edit .env

Ustaw klucz:

GEMINI_API_KEY=your-key-here

Instalowanie zależności

uv sync

4. Wykonaj pierwsze połączenie z agentem

Otwieranie pliku początkowego

cloudshell edit run_digest.py

run_digest() ma teraz 1 element do zrobienia i 3 kolejne na następny krok. Powyżej są już wstępnie wypełnione 2 pomocnicze teksty:

  • load_source(path): odczytuje plik z lokalizacji .agents/ względem skryptu. Użyjesz go w następnym ćwiczeniu, aby zamontować w sandboxie styl dziennikarski, scenariusz w formacie PDF i mechanizm renderowania.
  • run_stream(stream): przetwarza strumień zdarzeń i zwraca wartość (environment_id, interaction_id). Nie musisz samodzielnie pisać pętli zdarzeń.

Co dodać

DO ZROBIENIA 1: zastąp pass tym kodem (na razie zignoruj DO ZROBIENIA 3 i 4, które będą potrzebne w następnym kroku):

    from google import genai
    client = genai.Client()

    stream = client.interactions.create(
        agent=BASE_AGENT,
        agent_config={"type": "antigravity", "model": "gemini-3.7-flash"},
        input="Fetch the Hacker News front page and list the top 5 stories.",
        stream=True,
        environment="remote",
    )

    environment_id, interaction_id = run_stream(stream)
    print(f"\nDone. environment_id={environment_id}")

Działanie poszczególnych części

genai.Client() odczytuje GEMINI_API_KEY ze środowiska. Wszystkie inne działania są wykonywane przez tego klienta.

interactions.create() to główna rozmowa. W tym celu musisz uwzględnić 4 parametry:

  • agent=BASE_AGENT: wybiera agenta Antigravity (antigravity-preview-05-2026), czyli ogólnego agenta zarządzanego, który domyślnie korzysta z Gemini 3.7 Flash. Model bazowy możesz skonfigurować za pomocą parametru agent_config (opcje: gemini-3.7-flash, gemini-3.6-flash, gemini-3.5-flash, gemini-3.5-flash-lite). Domyślnie ma on włączone 3 wbudowane narzędzia: code_execution (uruchamianie skryptów Bash, Python i Node.js), google_search i url_context (pobieranie i odczytywanie stron internetowych). Narzędzia systemu plików (read_file, write_file, list_files) są włączane automatycznie po przekazaniu parametru environment. Jedno wywołanie udostępnia w pełni zarządzane środowisko Ubuntu z fabrycznie zainstalowanymi Pythonem 3.12, Node.js 22, git, pip i curl. Nie musisz tworzyć kontenera ani uruchamiać wdrożenia.
  • input: zadanie dla tego uruchomienia. Agent przegląda Hacker News i analizuje wyniki.
  • environment="remote": udostępnia nową piaskownicę w chmurze na potrzeby tej interakcji.
  • stream=True: zwraca iterowalny obiekt zdarzeń zamiast blokować. Bez niego połączenie czeka 30–90 sekund i zwraca wszystkie dane wyjściowe naraz jako interaction.output_text. Dzięki przesyłaniu strumieniowemu możesz zobaczyć przyczynę działania agenta i podjąć odpowiednie kroki. Strumieniowanie nie jest tu funkcją zaawansowaną: to właściwe ustawienie domyślne, ponieważ 90-sekundowe czarne pole nie daje żadnego sygnału, czy agent działa, czy się zawiesił.

environment_id to uchwyt do właśnie uruchomionej piaskownicy. Po interaction.completed piaskownica nie zostanie wyłączona: będzie działać jeszcze przez maksymalnie 7 dni. Aby wrócić do tego miejsca, kliknij environment_id. Przekaż go do drugiego interactions.create() połączenia, a agent wznowi pracę w tym samym systemie plików, z tymi samymi plikami i zainstalowanymi pakietami, jakby nigdy nie przestał działać. W następnym kroku używa go do pobrania pliku PDF bez ponownego uruchamiania agenta, a w kolejnym kroku używa go do kontynuowania rozmowy.

interaction_id to uchwyt do zakończonej właśnie tury rozmowy. Przekaż go jako previous_interaction_id w następnym wywołaniu, a agent będzie pamiętać wszystko, co powiedział i zrobił w tej turze.

Zweryfikuj

uv run python run_digest.py

W trakcie pracy agenta powinny być widoczne dane wyjściowe na żywo:

[agent started]
  [tool] run_code
Here are the top 5 stories currently on the Hacker News front page, retrieved via the official Hacker News API:

1. **Qwen 3.6 27B is the sweet spot for local development** (471 points)
2. **.self: A new top-level domain designed to support self-hosting** (116 points)
...
Done. environment_id=e3de58774073f75a6ef42924c6ce2e88

Interfejs API zwraca wartość rzeczywistą environment_id nawet w przypadku wartości environment="remote". Piaskownica została uruchomiona. Brak konfiguracji: brak głosu, umiejętności i generatora plików PDF. Agent wydrukował tylko opowiadania w formie tekstu i przestał działać. W następnym kroku dodasz te uprawnienia.

Każdy wiersz danych wyjściowych jest mapowany na zdarzenie z run_stream():

step.type

Co to jest?

Co drukuje run_stream()

"url_context_call"

agent pobierający adres URL,

[tool] url_context (https://...)

"code_execution_call"

agent uruchamiający kod w piaskownicy,

[tool] run_code

"google_search_call"

Agent wyszukuje informacje w internecie

[tool] google_search

"function_call"

narzędzia do obsługi plików i inne.

[tool] read_file (/workspace/...)

step.delta, gdzie delta.type == "text"

agent pisze tekst,

przesyłane strumieniowo bezpośrednio do stdout,

5. Dostosowywanie agenta

Agent nie miał żadnych instrukcji: nie miał głosu, umiejętności ani generatora plików PDF. W tym kroku załadujesz pliki konfiguracyjne z .agents/ i zamontujesz je w środowisku testowym.

Co zmienić

Wprowadź 4 zmiany w run_digest.py:

TODO 2: poniżej load_source() dodaj 3 stałe na poziomie modułu (znajdują się one poza run_digest(), u góry pliku):

AGENTS_MD       = load_source(".agents/AGENTS.md")
SKILL_MD        = load_source(".agents/skills/digest-pdf/SKILL.md")
GENERATE_PDF_PY = load_source(".agents/skills/digest-pdf/scripts/generate_pdf.py")

Otwórz każdy plik, aby zobaczyć, co ładujesz: AGENTS.md określa styl dziennikarski i reguły przepływu pracy; SKILL.md to szczegółowy przewodnik w formacie PDF; generate_pdf.py to gotowy moduł renderujący, który będzie uruchamiany przez agenta.

Wprowadź jeszcze 2 zmiany w sekcji run_digest():

TODO 3: zmień environment z "remote" na słownik źródeł i ustaw input na "Generate the digest.":

        environment={
            "type": "remote",
            "sources": [
                {
                    "type": "inline",
                    "target": ".agents/AGENTS.md",
                    "content": AGENTS_MD,
                },
                {
                    "type": "inline",
                    "target": ".agents/skills/digest-pdf/SKILL.md",
                    "content": SKILL_MD,
                },
                {
                    "type": "inline",
                    "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                    "content": GENERATE_PDF_PY,
                },
            ],
        },

TODO 4: dodaj ten wiersz bezpośrednio po print(f"\nDone. environment_id={environment_id}"):

    save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)

save_env jest już zdefiniowany w pliku run_digest.py. Zapisuje oba identyfikatory w .env, aby w następnym kroku można było pobrać plik PDF bez ponownego uruchamiania agenta.

Co robi każde źródło

Każde źródło to plik zamontowany w systemie plików piaskownicy podczas uruchamiania, zanim zostanie uruchomiony agent. Ścieżki target pasują do miejsc, w których uprząż Antigravity oczekuje ich znalezienia:

.agents/
├── AGENTS.md                              ← auto-loaded as global instructions
└── skills/
    └── digest-pdf/
        ├── SKILL.md                       ← auto-discovered and registered as a skill
        └── scripts/
            └── generate_pdf.py            ← pre-built renderer the agent can run

Ścieżka elementu „target”

Zmienna

Jak uprząż współpracuje z tym urządzeniem

.agents/AGENTS.md

AGENTS_MD

Automatycznie wczytywane jako trwałe instrukcje: styl dziennikarski, przepływ pracy, reguły wykonywania

.agents/skills/digest-pdf/SKILL.md

SKILL_MD

Automatycznie wykryta i zarejestrowana jako umiejętność o nazwie. Agent wywołuje ją po nazwie.

.agents/skills/digest-pdf/scripts/generate_pdf.py

GENERATE_PDF_PY

Gotowy mechanizm renderowania plików PDF; agent pisze summaries.json, a potem uruchamia ten skrypt

Zweryfikuj

uv run python run_digest.py

Wykonanie zajmuje teraz 1–3 minuty. Powinien być widoczny agent odczytujący pliki konfiguracyjne, tworzący podsumowania i zapisujący plik PDF:

[agent started]
  [tool] read_file (/.agents/skills/digest-pdf/SKILL.md)
  [tool] list_files (/.agents/skills/digest-pdf/scripts)
  [tool] read_file (/.agents/skills/digest-pdf/scripts/generate_pdf.py)
  [tool] run_code
  [tool] write_file (/workspace/summaries.json)
  [tool] run_code
  [tool] delete_file (/tmp/test_scrape.py)
I have successfully generated today's tech news digest and saved the formatted document to /workspace/digest.pdf.
Done. environment_id=4129ffd75574e308748e9425d7ec828f

environment_id to teraz rzeczywista wartość: piaskownica została uruchomiona z Twoimi plikami konfiguracyjnymi, a agent utworzył digest.pdf. Kolejny krok to dodanie zabezpieczenia przed pobraniem.

6. Dodawanie zaczepu zabezpieczającego

Haczyki umożliwiają uruchamianie skryptu w piaskownicy przed lub po każdym wywołaniu narzędzia. Agent podsumowujący używa code_execution do uruchamiania skryptów w języku Python, więc wywołanie pre_tool_execution może przechwytywać te wywołania i blokować destrukcyjne polecenia powłoki przed ich wykonaniem.

Środowisko wykonawcze odczytuje .agents/hooks.json z piaskownicy. Przed każdym wywołaniem narzędzia do dopasowywania przekazuje szczegóły wywołania do skryptu bramy na stdin. Skrypt wysyła SMS-a o treści {"decision": "allow"} lub {"decision": "deny", "reason": "..."} na numer stdout. Odmowa powoduje anulowanie wywołania narzędzia, a agent widzi Twój powód i samodzielnie koryguje błąd.

Co dodać

TODO 5: w run_digest.py dodaj te 2 stałe w górnej części, po istniejących wywołaniach load_source:

import json

HOOKS_JSON = json.dumps({
    "safety-gate": {
        "pre_tool_execution": [
            {
                "matcher": "code_execution",
                "hooks": [
                    {
                        "type": "command",
                        "command": "python3 /.agents/hooks-scripts/gate.py",
                        "timeout": 10,
                    }
                ],
            }
        ]
    }
}, indent=2)

GATE_PY = """\
#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
cmd = str(data.get("tool_call", {}).get("args", {}))
if "rm -rf" in cmd:
    print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by safety gate."}))
else:
    print(json.dumps({"decision": "allow"}))
"""

TODO 6: dodaj 2 kolejne pozycje do listy sources w interactions.create():

{"type": "inline", "target": ".agents/hooks.json",            "content": HOOKS_JSON},
{"type": "inline", "target": ".agents/hooks-scripts/gate.py", "content": GATE_PY},

Jak działają hooki podczas wykonywania funkcji digest

Za każdym razem, gdy agent wywołuje code_execution, aby uruchomić skrypt w Pythonie lub polecenie powłoki, środowisko wykonawcze najpierw przekazuje szczegóły wywołania do gate.py. Jeśli polecenie zawiera rm -rf, hook zwraca deny, a agent otrzymuje powód odrzucenia i ponawia próbę z bezpieczną alternatywą. Wszystkie inne wywołania wykonywania kodu przechodzą bez zmian.

Zweryfikuj

uv run python run_digest.py

Wynik jest taki sam jak wcześniej: mechanizm bezpieczeństwa zezwala na wszystkie normalne polecenia generowania plików PDF. Aby potwierdzić, że wywołanie działa, tymczasowo zmień dane wejściowe agenta, aby poprosić go o uruchomienie rm -rf /tmp/test – zobaczysz, że agent zgłosi zablokowanie polecenia i wybierze alternatywę.

7. Pobierz plik PDF

Pracownik obsługi klienta napisał wiadomość digest.pdf do /workspace/digest.pdf w środowisku piaskownicy. Zrzut środowiska jest dostępny jako archiwum tar za pomocą interfejsu Gemini Files API.

W razie potrzeby zainstaluj requests:

uv pip install requests

Co należy wypełnić

Otwórz pokój download_pdf.py. Zawiera 2 elementy do zrobienia.

DO ZROBIENIA 1: wypełnij wywołanie requests.get():

    r = requests.get(
        f"https://generativelanguage.googleapis.com/v1beta/files/environment-{environment_id}:download",
        params={"alt": "media"},
        headers={"x-goog-api-key": api_key},
        allow_redirects=True,
    )
    r.raise_for_status()

Adres URL odnosi się do migawki środowiska piaskownicy. params={"alt": "media"} zwraca nieprzetworzone bajty zamiast metadanych. Twój dotychczasowy GEMINI_API_KEY uwierzytelnia też interfejs Files API.

TODO 2: znajdź i wyodrębnij plik PDF z archiwum tar:

            member = next(m for m in tar.getmembers() if m.name.endswith("workspace/digest.pdf"))
            tar.extract(member, path=tmp, filter="data")

Prefiks ścieżki tar różni się w zależności od uruchomienia, więc zamiast wpisywać na stałe dokładną ścieżkę, wyszukuj według sufiksu. filter="data" tłumi ostrzeżenie o wycofaniu Pythona 3.13 dotyczące niebezpiecznego wyodrębniania plików tar.

Zweryfikuj

uv run python download_pdf.py
Saved digest.pdf (48,231 bytes)

Otwórz plik digest.pdf w tym samym katalogu. Zawiera sformatowane podsumowanie wygenerowane przez agenta na podstawie aktywnych stron internetowych.

8. Kontynuowanie rozmowy

Masz już digest.pdf. Jeśli zależało Ci tylko na pliku, to wszystko. Ten krok dotyczy czegoś innego: poproś agenta o zmianę podsumowania bez ponownego pobierania informacji z internetu.

Piaskownica nadal działa. Agent nadal ma /workspace/digest.pdf i pamięta każdą podsumowaną historię. Drugie wywołanie interactions.create() wysyła wiadomość uzupełniającą do tego samego sandboxa. W tym przypadku prosisz o dodanie notatki „Dlaczego to jest ważne” pod każdą historią, a usługa aktualizuje plik PDF w miejscu, bez ponownego pobierania i podsumowywania.

Co należy wypełnić

Otwórz pokój refine_digest.py. Ma 3 zadania do wykonania.

TODO 1 i 2: wypełnij 2 parametry wieloetapowe w interactions.create():

    environment=environment_id,
    previous_interaction_id=interaction_id,

environment=environment_id wznawia tę samą piaskownicę z jej plikami i pakietami. previous_interaction_id=interaction_id przekazuje agentowi historię rozmów, Nic innego się nie zmienia w porównaniu z pierwszym połączeniem.

TODO 3: po zakończeniu pętli zdarzeń zapisz nową wartość interaction_id z powrotem w .env:

save_env(INTERACTION_ID=interaction_id)

Każde wywołanie interactions.create() powoduje utworzenie nowego elementu interaction_id. Zapisanie go z powrotem oznacza, że następne uruchomienie przekaże to ulepszenie jako previous_interaction_id, prawidłowo łącząc kolejne tury. Identyfikator piaskownicy nigdy się nie zmienia, więc ENVIRONMENT_ID nie musisz go aktualizować.

2 parametry, które umożliwiają działanie wieloetapowych odpowiedzi

Identyfikator

Co jest zachowywane

Analogie

environment=environment_id

Pliki, zainstalowane pakiety, stan systemu: wszystko w systemie plików Linux

Utrzymywanie tego samego biurka między spotkaniami

previous_interaction_id=interaction_id

Historia rozmowy: co agent powiedział i zrobił w poprzednich turach.

przypominanie sobie, o czym rozmawiano na ostatnim spotkaniu;

Możesz przekazywać każdy identyfikator osobno:

  • environment_id: ponownie używaj plików i pakietów, ale rozpoczynaj nową rozmowę. Przydatne w przypadku nowego zadania w tym samym obszarze roboczym.
  • previous_interaction_id: kontynuuj kontekst rozmowy, ale w nowym środowisku testowym (pliki znikną).
  • Oba: pełna ciągłość, która jest używana w tym kroku.

Bez environment_id: pusta piaskownica bez pliku PDF. Bez previous_interaction_id: brak kontekstu, pracownik obsługi klienta nie może doprecyzować konkretnej sekcji.

Zweryfikuj

uv run python refine_digest.py

Strumień powinien być szybki, ponieważ agent nie pobiera ponownie żadnych informacji. Po zakończeniu:

Refinement done.
Saved digest_v2.pdf (52,418 bytes)

Otwórz digest_v2.pdf i porównaj go z digest.pdf. Do każdej historii powinna zostać dodana linia „Dlaczego to jest ważne”.

9. Zapisywanie konfiguracji zarządzanego agenta

Każde połączenie przeszło do tej pory weryfikację AGENTS.md, SKILL.md i generate_pdf.py. To działa, ale kod wywołujący przenosi pełną zawartość pliku przy każdym uruchomieniu. agents.create() zapisuje konfigurację w postaci nazwanego agenta po stronie Google. Następne wywołanie przekazuje tylko identyfikator agenta:

Inline calls:   send sources on every call
Named agent:    bake once → invoke by ID, no sources

Co należy wypełnić

Otwórz pokój save_agent.py. Ma 1 element TODO (TODO 1).

Zwróć uwagę, że stałe są importowane bezpośrednio z run_digest.py (bez duplikowania):

from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY

DO ZROBIENIA 1: wypełnij wywołanie agents.create():

agent = client.agents.create(
    id="my-digest",
    base_agent=BASE_AGENT,
    agent_config={
        "type": "antigravity",
        "model": "gemini-3.7-flash",
    },
    description="Daily tech digest with editorial voice and PDF generation.",
    base_environment={
        "type": "remote",
        "sources": [
            {
                "type": "inline",
                "target": ".agents/AGENTS.md",
                "content": AGENTS_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/SKILL.md",
                "content": SKILL_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                "content": GENERATE_PDF_PY,
            },
        ],
    },
)

agent_config ustawia model bazowy. gemini-3.7-flash to domyślna i najlepsza opcja w tym przepływie pracy. Opcje gemini-3.6-flash, gemini-3.5-flash i gemini-3.5-flash-lite są dostępne, jeśli chcesz przeprowadzić lżejsze lub tańsze uruchomienie.

base_environment (nie environment) to kluczowa różnica w porównaniu z wywołaniem wbudowanym z poprzedniego kroku: źródła są przechowywane po stronie Google i montowane automatycznie przy każdym kolejnym wywołaniu. Uruchom go raz, a nie przy każdym uruchomieniu podsumowania.

Weryfikacja: zapisz agenta

uv run python save_agent.py
Saved: my-digest
my-digest: Daily tech digest with editorial voice and PDF generation.

Wywoływanie zapisanego agenta

Otwórz pokój invoke_agent.py. Wywołuje zapisanego agenta według identyfikatora bez źródeł:

stream = client.interactions.create(
    agent="my-digest",
    input="Generate the digest.",
    stream=True,
    environment="remote",
)

Porównaj to z wywołaniem w wierszu: agent=BASE_AGENT jest zastępowane przez "my-digest", a cały blok environment z 3 źródłami w wierszu jest zastępowany przez environment="remote". Konfiguracja jest już wbudowana po stronie Google.

Weryfikacja: wywołanie zapisanego agenta

uv run python invoke_agent.py

Powinna pojawić się ta sama transmisja na żywo co w przypadku uruchomienia wbudowanego, ale wywołanie nie zawiera plików źródłowych. Po zakończeniu biegu wartości ENVIRONMENT_ID i INTERACTION_ID w .env zostaną zaktualizowane, dzięki czemu możesz kontynuować refine_digest.py jak wcześniej.

[agent started]
  [tool] read_file
  [tool] write_file
  [tool] run_code
I have successfully created today's tech news digest.
Done. environment_id=9a1c3e02-...

10. Wysyłanie przez Gmaila

Agent wygenerował podsumowanie i zapisał je w /workspace/digest.pdf. Do tej pory została pobrana lokalnie. W tym celu agent wywołuje interfejs Gmail REST API w środowisku piaskownicy, aby dostarczyć e-maila bezpośrednio do skrzynki odbiorczej.

Podejście: lokalnie uzyskujesz token dostępu OAuth 2.0 i przekazujesz go do agenta w input prompty. Agent używa code_execution do utworzenia e-maila MIME z załączonym plikiem PDF i przesłania go do interfejsu Gmail API. Brak niestandardowych narzędzi i rejestracji serwera MCP.

Wymagania wstępne

Włącz interfejs Gmail API w projekcie GCP i utwórz identyfikator klienta OAuth 2.0:

  1. Otwórz stronę console.cloud.google.com/apis/library/gmail.googleapis.com i włącz interfejs Gmail API.
  2. Kliknij Interfejsy API i usługi > Dane logowania > Utwórz dane logowania > Identyfikator klienta OAuth 2.0.
  3. Typ aplikacji: Aplikacja na komputer. Pobierz plik JSON i zapisz go jako credentials.json w katalogu głównym projektu.

Dodaj adres e-mail odbiorcy do .env:

RECIPIENT_EMAIL=you@gmail.com

W razie potrzeby zainstaluj biblioteki uwierzytelniania:

uv sync

Co należy wypełnić

Otwórz pokój send_digest.py. Zawiera 2 elementy do zrobienia.

TODO 1: wczytaj lub odśwież token dostępu OAuth 2.0:

creds = None
if TOKEN_FILE.exists():
    creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
        TOKEN_FILE.write_text(creds.to_json())
    else:
        flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
        creds = flow.run_local_server(port=8080, open_browser=False)
        TOKEN_FILE.write_text(creds.to_json())

Po dodaniu linii raise NotImplementedError usuń ją. Przy pierwszym uruchomieniu otworzy się przeglądarka z ekranem zgody OAuth. Token jest przechowywany w pamięci podręcznej w .gmail_token.json na potrzeby przyszłych uruchomień.

DO ZROBIENIA 2: zastąp input="" instrukcjami z e-maila. Token jest już w zakresie jako creds.token:

    input=(
        "Use the Gmail REST API to send an email:\n"
        f"  To: {recipient}\n"
        "  Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
        "  Attachment: /workspace/digest.pdf attached as digest.pdf\n\n"
        "For the body, read /workspace/summaries.json and format it as a "
        "human-readable newsletter, NOT raw JSON. Use this structure:\n"
        "  Tech Digest - <date>\n\n"
        "  === <source name> ===\n"
        "  1. <title>\n"
        "     <summary>\n\n"
        "Steps:\n"
        "1. Parse /workspace/summaries.json and build the formatted body text above.\n"
        "2. Read /workspace/digest.pdf as bytes.\n"
        "3. Build a MIME multipart message using Python's email library.\n"
        "4. Base64url-encode the raw message.\n"
        "5. POST to https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
        "with Authorization header using this token: "
        f"{creds.token}"
    ),

Działanie poszczególnych części

Interakcja jest wznawiana w tym samym środowisku testowym, w którym agent wygenerował już digest.pdf i summaries.json. previous_interaction_id przekazuje agentowi historię rozmów,

Token dostępu jest przekazywany w ciągu input. Agent odczytuje go z promptu i używa go w nagłówku Authorization: Bearer podczas wywoływania interfejsu Gmail API. Nigdy nie ma dostępu do Twojego komputera ani systemu plików.

Agent używa code_execution do pisania i uruchamiania skryptu w Pythonie w piaskownicy: odczytuje summaries.json, formatuje go jako newsletter, odczytuje digest.pdf, tworzy wieloczęściową wiadomość MIME, koduje ją w standardzie base64url i wysyła za pomocą żądania POST do https://gmail.googleapis.com/gmail/v1/users/me/messages/send.

Zweryfikuj

uv run python send_digest.py
Sending digest...
[agent started]
  [tool] read_file (/workspace/summaries.json)
  [tool] run_code
  [tool] run_code
Email sent successfully.
Email sent. Check your inbox.

Sprawdź skrzynkę odbiorczą. E-mail przychodzi z treścią w formacie newslettera i digest.pdf w załączniku.

11. Planowanie codziennych uruchomień

Każdy krok został do tej pory wywołany ręcznie. Aktywatory umożliwiają zaplanowanie automatycznego uruchamiania nazwanego agenta zgodnie z wyrażeniem cron. Agent uruchamia się o zaplanowanej godzinie, wykonuje cały przepływ pracy podsumowania, a środowisko jest zachowywane między wykonaniami, więc pakiety zainstalowane podczas pierwszego uruchomienia są dostępne przy każdym kolejnym uruchomieniu.

Manual:     python run_digest.py     → runs once, now
Trigger:    client.triggers.create() → runs every morning, automatically

Co należy wypełnić

Otwórz pokój create_trigger.py. Jest 1 element do zrobienia.

DO ZROBIENIA 1: wypełnij wywołanie triggers.create(). Aktywator uruchamia cały przepływ pracy każdego dnia: generuje podsumowanie I wysyła je do Twojej skrzynki odbiorczej. Tokeny dostępu wygasają po godzinie, więc wstrzykuje token odświeżania z .gmail_token.json jako źródło wbudowane, aby agent mógł wymieniać go na nowy token przy każdym uruchomieniu.

trigger = client.triggers.create(
    schedule="0 9 * * *",
    time_zone="UTC",
    display_name="daily-tech-digest",
    max_consecutive_failures=3,
    execution_timeout_seconds=600,
    interaction={
        "agent": "my-digest",
        "input": (
            f"Generate the daily tech digest following AGENTS.md instructions. "
            f"Then send an email to {recipient}:\n"
            "- Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
            "- Body: the content of /workspace/summaries.json formatted as a readable "
            "newsletter (NOT raw JSON).\n"
            "- Attachment: /workspace/digest.pdf\n\n"
            "For Gmail auth: read /workspace/.gmail_creds.json, POST to "
            "https://oauth2.googleapis.com/token with grant_type=refresh_token "
            "and the client_id, client_secret, refresh_token from the file to get an "
            "access_token. Then POST to "
            "https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
            "with Authorization: Bearer <access_token>."
        ),
        "environment": {
            "type": "remote",
            "sources": [
                {
                    "type": "inline",
                    "target": "/workspace/.gmail_creds.json",
                    "content": gmail_creds,
                }
            ],
        },
    },
)

Domyślny limit czasu to execution_timeout_seconds=600. max_consecutive_failures=3 automatycznie wstrzymuje wyzwalacz po 3 nieudanych uruchomieniach z rzędu (domyślna wartość w interfejsie API to 5; 3 to bardziej konserwatywna wartość w przypadku warsztatów).

Lista sources wstrzykuje .gmail_creds.json do piaskownicy w miejscu /workspace/.gmail_creds.json. Agent odczytuje go, wymienia token odświeżania na nowy token dostępu i wywołuje interfejs Gmail API. Tokeny odświeżania nie wygasają, więc ta metoda działa przy każdym zaplanowanym uruchomieniu bez konieczności ręcznego odświeżania tokena.

Po dodaniu połączenia usuń wiersz raise NotImplementedError.

Zweryfikuj

uv run python create_trigger.py
Trigger created: trig_abc123
Next run:        2026-07-23T09:00:00Z

create_trigger.py automatycznie zapisuje identyfikator aktywatora w .env.

Aby sprawdzić historię wykonania po uruchomieniu:

uv run python check_trigger.py

Aby uruchomić wyzwalacz od razu bez czekania na następny zaplanowany czas:

uv run python fire_trigger.py

Aby wstrzymać lub usunąć aktywator:

uv run python pause_trigger.py

12. Czyszczenie

Piaskownica automatycznie wygasa po 7 dniach nieaktywności. Brak serwerów do zatrzymania. Brak kontenerów do usunięcia.

Jeśli masz zapisaną konfigurację agenta, usuń ją:

uv run python delete_agent.py

13. Podsumowanie

Zbudowano agenta zarządzanego od podstaw, po jednym koncepcie. Oto czego uczyło każde z tych ćwiczeń:

Ćwiczenia

Pomysł

Key API

Wykonaj pierwsze połączenie

Udostępnianie prawdziwego środowiska testowego Linuksa i transmitowanie na żywo jego wydarzeń

interactions.create(agent, input, environment, stream=True), event.event_type

Dostosowywanie agenta

Montowanie plików konfiguracyjnych; utrwalanie identyfikatorów w .env w ramach tego samego uruchomienia

environment.sources, save_env

Dodawanie haczyka zabezpieczającego

Przechwytywanie wywołań narzędzi przed ich wykonaniem; odrzucanie poleceń destrukcyjnych

hooks.json, pre_tool_execution, gate.py

Pobierz plik PDF

Pobieranie pliku PDF bez ponownego uruchamiania agenta

Gemini Files API :download w download_pdf.py

Kontynuuj rozmowę

Kontynuowanie rozmowy bez ponownego pobierania informacji z internetu

environment=environment_id, previous_interaction_id=interaction_id

Zachowywanie konfiguracji agenta

Utrwalanie konfiguracji agenta; wywoływanie według identyfikatora, bez konieczności podawania źródeł

agents.create(), agents.list()

Wysyłanie przez Gmaila

Uzyskaj lokalnie token OAuth i przekaż go agentowi, który wywołuje interfejs Gmail REST API za pomocą code_execution.

OAuth 2.0, client.interactions.create(input=...)

Planowanie codziennych uruchomień

Automatyczne uruchamianie agenta zgodnie z harmonogramem cron

client.triggers.create(schedule, time_zone, interaction)

Wzory kluczy

  1. Jedno wywołanie, jedna piaskownica: interactions.create() obsługuje całą infrastrukturę (nie trzeba wdrażać kontenerów ani instalować lokalnie pakietów).
  2. Strumieniowanie progresywne: stream=True zamienia 90-sekundowe czarne pole w aktywny kanał wywołań narzędzi i fragmentów tekstu.
  3. Źródła wbudowane: montowanie AGENTS.md, SKILL.md i gotowych skryptów w piaskownicy bez przesyłania ani wdrażania.
  4. Automatyczne wykrywanie uprzęży: pliki umieszczone w .agents/ są wybierane automatycznie (nie jest wymagana konfiguracja pakietu SDK).
  5. Stan dwuwymiarowy: environment_id śledzi pliki i paczki; previous_interaction_id śledzi kontekst rozmowy; oba mogą być przekazywane niezależnie.
  6. Pobieranie stanu: środowisko to pełny plik tar systemu plików, do którego można uzyskać dostęp za pomocą interfejsu Gemini Files API.
  7. Agenci z nazwą: agents.create() trwale zapisuje konfigurację; przyszłe wywołania przekazują tylko identyfikator agenta i environment="remote" bez źródeł.
  8. Haki: hooks.json + skrypt bramy przechwytuje wywołania narzędzi przed ich wykonaniem; odpowiedź deny anuluje wywołanie, a agent samodzielnie koryguje błędy.
  9. Wywołania zewnętrznego interfejsu API: przekazywanie danych logowania w prompcie input; agent pisze i uruchamia kod integracji w piaskownicy za pomocą code_execution.
  10. Reguły: zaplanuj uruchamianie agenta za pomocą wyrażenia cron z parametrem client.triggers.create(); środowisko jest zachowywane między wykonaniami.

ADK + Cloud Run a zarządzani agenci: krótkie porównanie różnic

Możliwości

ADK + Cloud Run

Zarządzane agenty w Gemini API

Udostępnianie piaskownicy

docker build + gcloud run deploy

interactions.create()

Definiowanie narzędzi

Funkcje Pythona zarejestrowane w agencie

Wbudowane: przeglądanie internetu, wykonywanie kodu, system plików

Instalowanie pakietów

pip install w pliku Dockerfile

Agent działa w piaskownicy pip install

Strumieniowanie wydarzeń

Infrastruktura niestandardowego SSE

stream=True

Kontynuowanie sesji

Baza danych sesji + wstrzykiwanie kontekstu

environment_id + previous_interaction_id

Pliki konfiguracyjne

Zakodowane na stałe w agencie lub wstrzykiwane podczas uruchamiania

Zamontowano za pomocą environment.sources

infrastrukturą do zarządzania,

Kontener, Cloud Run, IAM, klucze tajne

Brak

Dalsze kroki