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 + | Agent instaluje pakiety w piaskownicy |
Infrastruktura strumieniowania SSE |
|
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
Opcja A. Cloud Shell (zalecane)
Kliknij przycisk poniżej, aby otworzyć ten codelab w Google Cloud Shell. Wszystkie zależności są wstępnie zainstalowane.
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ą parametruagent_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_searchiurl_context(pobieranie i odczytywanie stron internetowych). Narzędzia systemu plików (read_file,write_file,list_files) są włączane automatycznie po przekazaniu parametruenvironment. 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 jakointeraction.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():
| Co to jest? | Co drukuje |
| agent pobierający adres URL, |
|
| agent uruchamiający kod w piaskownicy, |
|
| Agent wyszukuje informacje w internecie |
|
| narzędzia do obsługi plików i inne. |
|
| 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 „ | Zmienna | Jak uprząż współpracuje z tym urządzeniem |
|
| Automatycznie wczytywane jako trwałe instrukcje: styl dziennikarski, przepływ pracy, reguły wykonywania |
|
| Automatycznie wykryta i zarejestrowana jako umiejętność o nazwie. Agent wywołuje ją po nazwie. |
|
| Gotowy mechanizm renderowania plików PDF; agent pisze |
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 |
| Pliki, zainstalowane pakiety, stan systemu: wszystko w systemie plików Linux | Utrzymywanie tego samego biurka między spotkaniami |
| 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:
- Otwórz stronę console.cloud.google.com/apis/library/gmail.googleapis.com i włącz interfejs Gmail API.
- Kliknij Interfejsy API i usługi > Dane logowania > Utwórz dane logowania > Identyfikator klienta OAuth 2.0.
- Typ aplikacji: Aplikacja na komputer. Pobierz plik JSON i zapisz go jako
credentials.jsonw 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ń |
|
Dostosowywanie agenta | Montowanie plików konfiguracyjnych; utrwalanie identyfikatorów w |
|
Dodawanie haczyka zabezpieczającego | Przechwytywanie wywołań narzędzi przed ich wykonaniem; odrzucanie poleceń destrukcyjnych |
|
Pobierz plik PDF | Pobieranie pliku PDF bez ponownego uruchamiania agenta | Gemini Files API |
Kontynuuj rozmowę | Kontynuowanie rozmowy bez ponownego pobierania informacji z internetu |
|
Zachowywanie konfiguracji agenta | Utrwalanie konfiguracji agenta; wywoływanie według identyfikatora, bez konieczności podawania źródeł |
|
Wysyłanie przez Gmaila | Uzyskaj lokalnie token OAuth i przekaż go agentowi, który wywołuje interfejs Gmail REST API za pomocą | OAuth 2.0, |
Planowanie codziennych uruchomień | Automatyczne uruchamianie agenta zgodnie z harmonogramem cron |
|
Wzory kluczy
- Jedno wywołanie, jedna piaskownica:
interactions.create()obsługuje całą infrastrukturę (nie trzeba wdrażać kontenerów ani instalować lokalnie pakietów). - Strumieniowanie progresywne:
stream=Truezamienia 90-sekundowe czarne pole w aktywny kanał wywołań narzędzi i fragmentów tekstu. - Źródła wbudowane: montowanie
AGENTS.md,SKILL.mdi gotowych skryptów w piaskownicy bez przesyłania ani wdrażania. - Automatyczne wykrywanie uprzęży: pliki umieszczone w
.agents/są wybierane automatycznie (nie jest wymagana konfiguracja pakietu SDK). - Stan dwuwymiarowy:
environment_idśledzi pliki i paczki;previous_interaction_idśledzi kontekst rozmowy; oba mogą być przekazywane niezależnie. - 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.
- Agenci z nazwą:
agents.create()trwale zapisuje konfigurację; przyszłe wywołania przekazują tylko identyfikator agenta ienvironment="remote"bez źródeł. - Haki:
hooks.json+ skrypt bramy przechwytuje wywołania narzędzi przed ich wykonaniem; odpowiedźdenyanuluje wywołanie, a agent samodzielnie koryguje błędy. - Wywołania zewnętrznego interfejsu API: przekazywanie danych logowania w prompcie
input; agent pisze i uruchamia kod integracji w piaskownicy za pomocącode_execution. - 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 |
|
|
Definiowanie narzędzi | Funkcje Pythona zarejestrowane w agencie | Wbudowane: przeglądanie internetu, wykonywanie kodu, system plików |
Instalowanie pakietów |
| Agent działa w piaskownicy |
Strumieniowanie wydarzeń | Infrastruktura niestandardowego SSE |
|
Kontynuowanie sesji | Baza danych sesji + wstrzykiwanie kontekstu |
|
Pliki konfiguracyjne | Zakodowane na stałe w agencie lub wstrzykiwane podczas uruchamiania | Zamontowano za pomocą |
infrastrukturą do zarządzania, | Kontener, Cloud Run, IAM, klucze tajne | Brak |
Dalsze kroki
- Zapoznaj się z dokumentacją dotyczącą zarządzanych agentów w Gemini API