Создайте агента для ежедневного обзора технологий с помощью управляемых агентов через API Gemini.

1. Обзор

В сфере искусственного интеллекта и технологий всё меняется быстрее, чем кто-либо может за этим уследить. Ежедневно появляются новые модели, статьи и продукты. Агент, который бы собирал сегодняшние заголовки, писал краткие резюме и генерировал PDF-файлы каждое утро, решил бы эту проблему, но раньше для его создания требовалось выбрать фреймворк, определить инструменты на Python, написать цикл оркестрации, упаковать контейнер и развернуть его в Cloud Run. И всё это ещё до того, как агент сделал хотя бы один веб-запрос.

Использование управляемых агентов через API Gemini меняет ситуацию. Вы пишете два конфигурационных файла в формате Markdown и предварительно созданный скрипт рендеринга, делаете один вызов API, и загружается настоящая песочница Ubuntu, просматривает веб-страницы, пишет ваши сводки и генерирует PDF-файл. Никаких контейнеров. Никакого развертывания. Никакого кода оркестрации.

В этом практическом занятии вы создадите именно такого агента: от пустой функции до работающего ежедневного дайджеста, шаг за шагом, рассматривая каждую концепцию.

Что вы построите

  • Создайте и запустите свой первый управляемый агент в реальной песочнице Linux.
  • Настройте агента, добавив его стиль изложения, веб-источники и навыки работы с PDF-файлами.
  • Добавьте механизм защиты, чтобы блокировать деструктивные команды до их выполнения.
  • Загрузите PDF-файл, сгенерированный агентом.
  • Уточняйте дайджест в многоэтапном диалоге, не перезагружая веб-страницу.
  • Сохраните конфигурацию агента и запускайте его по идентификатору в будущих запусках.
  • Отправьте дайджест на свой почтовый ящик через API Gmail.
  • Настройте агент на автоматический запуск и отправку сообщений каждый день.

Что вам понадобится

  • Python 3.10+
  • Ключ API Gemini: aistudio.google.com/api-keys (бесплатный тариф включен; для бесперебойной работы рекомендуется платная подписка)

2. Что такое управляемые агенты в API Gemini?

Три уровня систем искусственного интеллекта

Прежде чем углубляться в код, давайте рассмотрим, какое место занимают управляемые агенты по сравнению с двумя альтернативами:

Уровень

Что это такое

Кто управляет инфраструктурой?

Стандартная степень магистра права

Вы задаёте запрос, он отвечает текстом. Нет рук, нет памяти, нет интернета.

Н/Д: оно ничего не может сделать самостоятельно.

Агент, размещающий свои данные самостоятельно

Вы соединяете ADK/LangChain/AutoGen + Docker + инструменты + память.

Вы: всё это (или управляемая платформа, например, Agent Engine)

Управляемый агент

Вы задаёте ему цель. Google создаёт защищённую песочницу. Агент пишет код, запускает его, читает ошибки, ищет информацию в интернете и автоматически исправляет баги.

Google: всё это

Этот практический урок посвящен третьему ряду. Вы предоставляете задание и файлы конфигурации. Google занимается всем остальным.

Что бы вы создали с помощью ADK + Cloud Run

Для создания агента, генерирующего новостные сводки, который просматривает веб-страницы, запускает Python и создает PDF-файлы, вам потребуется все это с помощью ADK + 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

Это происходит до того, как агент хотя бы раз запустится. Вам по-прежнему необходима изоляция в песочнице (чтобы агент не мог повредить ваш сервер), установка пакетов, управление состоянием между вызовами инструментов и потоковая инфраструктура для передачи событий клиенту.

На что заменяет это управляемый агент?

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,
            },
        ],
    },
)

Что требуется для работы ADK + Cloud Run

Что для вас делает служба управляемых агентов

Образ контейнера + Dockerfile + CI/CD

Полностью управляемая песочница Ubuntu (Python 3.12, Node 22, 4 CPU / 16 ГБ ОЗУ)

Развертывание и масштабирование Cloud Run

Предоставляется за каждое взаимодействие, автоматически удаляется через 7 дней бездействия.

изоляция песочницы

Изолирован по взаимодействиям

Пользовательский инструмент для работы с PDF + pip install

Агент устанавливает пакеты в песочницу.

Инфраструктура потоковой передачи SSE

stream=True возвращает итерируемый объект события

Определения инструментов в Python

Встроенные инструменты: просмотр веб-страниц, выполнение кода, файловая система.

Управление состоянием между вызовами инструментов

Встроено в цикл рассуждений агента.

Вы пишете конфигурационные файлы ( AGENTS.md , SKILL.md , готовый скрипт) и делаете один вызов API. Google позаботится обо всем остальном.

Как работает песочница

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

Песочница сохраняется в течение 7 дней бездействия. Вы можете возобновить её работу, используя environment_id , чтобы уточнить результаты, запустить последующие задачи или создать форк в сохранённом именованном агенте.

3. Настройка

Нажмите кнопку ниже, чтобы открыть этот практический урок в Google Cloud Shell. Все необходимые зависимости уже установлены.

Открыть в Cloud Shell

Вариант B: Локальная настройка

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

При необходимости установите uv :

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

Настройте свой API-ключ

cp .env.example .env
cloudshell edit .env

Установите свой ключ:

GEMINI_API_KEY=your-key-here

Установите зависимости

uv sync

4. Сделайте свой первый звонок агенту.

Откройте стартовый файл

cloudshell edit run_digest.py

run_digest() содержит один пункт TODO, который нужно заполнить сейчас, и еще три для следующего шага. Два вспомогательных поля уже предварительно заполнены выше:

  • load_source(path) : считывает файл из .agents/ относительно скрипта. Вы будете использовать его в следующем упражнении для монтирования редакторского текста, сценария PDF и средства визуализации в изолированную среду.
  • run_stream(stream) : обрабатывает поток событий и возвращает (environment_id, interaction_id) . Вам не нужно писать цикл обработки событий самостоятельно.

Что добавить

TODO 1: заменить pass на (пока игнорируйте TODO 3 и 4; они будут на следующем шаге):

    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}")

Что делает каждая часть

genai.Client() считывает GEMINI_API_KEY из переменных окружения. Все остальное обрабатывается этим клиентом.

interactions.create() является ключевым вызовом. Для его работы требуется четыре параметра:

  • agent=BASE_AGENT : выбирает агент Antigravity ( antigravity-preview-05-2026 ), универсальный управляемый агент, работающий по умолчанию на Gemini 3.7 Flash. Вы можете настроить базовую модель с помощью agent_config (параметры: gemini-3.7-flash , gemini-3.6-flash , gemini-3.5-flash , gemini-3.5-flash-lite ). Он поставляется с тремя встроенными инструментами, включенными по умолчанию: code_execution (запуск Bash, Python, Node.js), google_search и url_context (получение и чтение веб-страниц). Инструменты файловой системы ( read_file , write_file , list_files ) включаются автоматически при передаче параметра environment . Один вызов инициализирует полностью управляемую среду Ubuntu с предустановленными Python 3.12, Node.js 22, git, pip и curl. Не нужно создавать контейнеры, не нужно запускать развертывание.
  • input : задача для данного запуска. Агент просматривает Hacker News и анализирует результаты.
  • environment="remote" : создает новую облачную песочницу для этого взаимодействия.
  • stream=True : возвращает итерируемый объект событий вместо блокировки. Без него вызов ожидает 30-90 секунд и возвращает весь вывод сразу в виде interaction.output_text . При потоковой передаче вы видите, как агент рассуждает и действует по мере развития событий. Потоковая передача здесь не является расширенной функцией: это правильный вариант по умолчанию, поскольку 90-секундный «черный ящик» не дает вам никакого сигнала о том, работает ли агент или завис.

environment_id — это идентификатор только что запущенной песочницы. После interaction.completed песочница не завершает работу: она остается активной до 7 дней. environment_id — это способ вернуться к ней. Передайте его во второй вызов interactions.create() , и агент возобновит работу на той же файловой системе, с теми же файлами и установленными пакетами, как если бы он и не покидал её. На следующем шаге он используется для загрузки PDF-файла без повторного запуска агента, а на шаге после этого — для продолжения диалога.

interaction_id — это идентификатор только что завершившегося диалога. Передайте его в качестве previous_interaction_id в следующем звонке, и агент будет иметь полную память о том, что он сказал и сделал в этом диалоге.

Проверять

uv run python run_digest.py

Вы должны видеть вывод в реальном времени во время работы агента:

[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

API возвращает реальный environment_id даже при environment="remote" . Песочница запустилась. Отсутствует конфигурация: нет голосового ввода, нет навыков, нет генератора PDF. Агент просто распечатал истории в текстовом виде и остановился. На следующем шаге они будут добавлены.

Каждая строка вывода соответствует событию из run_stream() :

step.type

Что это такое

Что выводит функция run_stream()

"url_context_call"

агент получает URL-адрес

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

"code_execution_call"

агент запускает код в песочнице

[tool] run_code

"google_search_call"

агент ищет в интернете

[tool] google_search

"function_call"

файловые инструменты и другие

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

step.delta where delta.type == "text"

агент пишет текст

транслируется напрямую в стандартный вывод

5. Настройка агента

У агента не было никаких инструкций: ни голоса, ни навыков, ни генератора PDF-файлов. На этом этапе вы загружаете файлы конфигурации из папки .agents/ и монтируете их в песочницу.

Что изменить

Внесите четыре изменения в run_digest.py :

TODO 2: После функции load_source() добавьте три константы уровня модуля (они находятся вне run_digest() , в верхней части файла):

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")

Откройте каждый файл, чтобы увидеть, что вы загружаете: AGENTS.md задает стиль редактирования и правила рабочего процесса; SKILL.md — это пошаговое руководство по созданию PDF-файлов; generate_pdf.py — это предварительно созданный рендерер, который будет запускать агент.

Теперь внесите еще два изменения в run_digest() :

TODO 3: изменить environment с "remote" на словарь источников и установить input на "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: добавить эту строку сразу после print(f"\nDone. environment_id={environment_id}") :

    save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)

save_env уже определен в run_digest.py . Он записывает оба идентификатора в .env , чтобы на следующем шаге можно было загрузить PDF-файл без повторного запуска агента.

Что делает каждый источник

Каждый исходный файл монтируется в файловую систему песочницы при запуске, до начала работы агента. target пути соответствуют тем местам, где, по мнению Antigravity, они должны находиться:

.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

target путь

Переменная

Для чего используется ремень безопасности?

.agents/AGENTS.md

AGENTS_MD

Автоматически загружаются в виде постоянных инструкций: редакционный стиль, рабочий процесс, правила выполнения.

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

SKILL_MD

Автоматически обнаружен и зарегистрирован как именованный навык; агент вызывает его по имени.

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

GENERATE_PDF_PY

Встроенный инструмент для рендеринга PDF-файлов; агент записывает файл summaries.json а затем запускает этот скрипт.

Проверять

uv run python run_digest.py

Теперь выполнение занимает 1-3 минуты. Вы должны увидеть, как агент считывает файлы конфигурации, записывает сводки и сохраняет 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 — это реальное значение: песочница работала с вашими конфигурационными файлами, и агент создал digest.pdf . На следующем шаге добавляется механизм защиты перед загрузкой.

6. Добавьте предохранительный крюк.

Хуки позволяют запускать скрипт внутри песочницы до или после каждого вызова инструмента. Агент обработки дайджестов использует code_execution для запуска скриптов Python, поэтому хук pre_tool_execution может перехватывать эти вызовы и блокировать деструктивные команды оболочки до их выполнения.

Среда выполнения считывает файл .agents/hooks.json из песочницы. Перед каждым соответствующим вызовом инструмента она передает детали вызова вашему скрипту шлюза через stdin . Скрипт выводит в стандартный вывод {"decision": "allow"} или {"decision": "deny", "reason": "..."} . Отказ в stdout отменяет вызов инструмента, и агент видит вашу причину и самостоятельно исправляет ошибку.

Что добавить

TODO 5: В run_digest.py добавьте эти две константы в начало, после существующих вызовов 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: добавить еще две записи в список sources внутри interactions.create() :

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

Как срабатывают крючки в процессе переваривания

Каждый раз, когда агент вызывает code_execution для выполнения скрипта Python или команды оболочки, среда выполнения сначала передает детали вызова в gate.py Если команда содержит rm -rf , обработчик возвращает deny , агент получает причину отклонения и повторяет попытку с безопасной альтернативой. Все остальные вызовы выполнения кода проходят без изменений.

Проверять

uv run python run_digest.py

Результат идентичен предыдущему: защитный механизм разрешает все обычные команды генерации PDF. Чтобы подтвердить срабатывание перехватчика, временно измените входные данные агента, запросив выполнение команды rm -rf /tmp/test — вы увидите, как агент сообщит о блокировке команды и выберет альтернативный вариант.

7. Скачайте PDF-файл

Агент записал digest.pdf в папку /workspace/digest.pdf внутри песочницы. Снимок среды доступен в виде архива tar через API файлов Gemini.

При необходимости установите requests :

uv pip install requests

Что нужно заполнить

Откройте download_pdf.py . В нём два пункта TODO.

TODO 1: Заполнить вызов 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()

URL-адрес обращается к снимку песочницы. params={"alt": "media"} возвращает необработанные байты вместо метаданных. Ваш существующий GEMINI_API_KEY также используется для аутентификации в Files API.

TODO 2: найти и извлечь PDF-файл из архива tar:

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

Префикс пути к архиву tar меняется в зависимости от запуска, поэтому выполняйте поиск по суффиксу, а не указывайте точный путь вручную. filter="data" подавляет предупреждение об устаревании Python 3.13, связанное с небезопасным извлечением архива tar.

Проверять

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

Откройте digest.pdf в той же директории. Он содержит отформатированный дайджест, сгенерированный агентом на основе данных с работающих веб-страниц.

8. Продолжите разговор

У вас уже есть digest.pdf . Если вам нужен был только этот файл, то всё готово. Этот шаг предназначен для другого: вы просите агента изменить дайджест без повторной загрузки веб-страницы.

Песочница всё ещё активна. У агента по-прежнему есть /workspace/digest.pdf , и он помнит каждую историю, которую он резюмировал. Второй вызов interactions.create() отправляет последующее сообщение в ту же песочницу. Здесь вы просите его добавить примечание «Почему это важно» под каждой историей, и он обновляет PDF-файл на месте, без повторной загрузки и повторного составления резюме.

Что нужно заполнить

Откройте refine_digest.py . В нём три пункта TODO.

Задачи 1 и 2: заполните два параметра для многошагового режима внутри interactions.create() :

    environment=environment_id,
    previous_interaction_id=interaction_id,

environment=environment_id возобновляет работу в той же песочнице с ее файлами и пакетами. previous_interaction_id=interaction_id предоставляет агенту историю разговоров. Ничего больше не меняется по сравнению с первым звонком.

TODO 3: сохранить новый interaction_id обратно в файл .env после завершения цикла обработки событий:

save_env(INTERACTION_ID=interaction_id)

Каждый вызов interactions.create() создает новый interaction_id . Запись его обратно означает, что при следующем запуске это уточнение передается как previous_interaction_id , и цепочка взаимодействий выполняется корректно. Идентификатор песочницы никогда не меняется, поэтому ENVIRONMENT_ID обновлять не нужно.

Два параметра, обеспечивающие работу многооборотного механизма.

ИДЕНТИФИКАТОР

Что оно сохраняет

Аналогия

environment=environment_id

Файлы, установленные пакеты, состояние системы: всё в файловой системе Linux.

Сохранение одного и того же рабочего стола между совещаниями.

previous_interaction_id=interaction_id

История разговора: что агент говорил и делал в предыдущих диалогах.

Вспоминая, что обсуждалось на последнем совещании

Вы можете передать каждый из этих идентификаторов независимо:

  • Только для environment_id : повторное использование файлов и пакетов, но начало нового диалога. Полезно для новой задачи в том же рабочем пространстве.
  • Только previous_interaction_id : продолжить контекст беседы, но в новой песочнице (файлы удалены).
  • Оба варианта: полная непрерывность, которая используется на этом этапе.

Без environment_id : пустая песочница, PDF-файл недоступен. Без previous_interaction_id : отсутствует контекст, агент не может уточнить конкретный раздел.

Проверять

uv run python refine_digest.py

Поток должен быть быстрым; агент ничего не запрашивает повторно. После завершения:

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

Откройте digest_v2.pdf и сравните его с digest.pdf . В каждой статье теперь должна быть добавлена ​​строка "Почему это важно".

9. Сохранение конфигурации управляемого агента.

До сих пор каждый вызов передавал AGENTS.md , SKILL.md и generate_pdf.py в режиме inline. Это работает, но ваш вызывающий код передаёт полное содержимое файлов при каждом запуске. agents.create() встраивает конфигурацию в сохранённый именованный агент на стороне Google. Следующий вызов просто передаёт идентификатор агента:

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

Что нужно заполнить

Откройте save_agent.py . У него есть одно TODO (TODO 1).

Обратите внимание, что константы импортируются непосредственно из run_digest.py (без дублирования):

from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY

TODO 1: Заполнить вызов 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 задает базовую модель. gemini-3.7-flash является моделью по умолчанию и наилучшим выбором для этого рабочего процесса; gemini-3.6-flash , gemini-3.5-flash и gemini-3.5-flash-lite доступны, если вам нужен более легкий или экономичный вариант.

Ключевое отличие от встроенного вызова на предыдущем шаге заключается в base_environment (а не environment ): исходные файлы хранятся на стороне Google и автоматически монтируются при каждом последующем вызове. Запускайте его один раз, а не при каждом запуске дайджеста.

Проверить: сохранить агента

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

Запустите сохраненный агент

Откройте invoke_agent.py . Он вызывает сохраненный агент по ID без указания источников:

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

Сравните это с встроенным вызовом: agent=BASE_AGENT заменяется на "my-digest" , а полный блок environment с тремя встроенными источниками заменяется на environment="remote" . Конфигурация уже встроена на стороне Google.

Проверка: вызовите сохраненный агент.

uv run python invoke_agent.py

Вы должны увидеть ту же прямую трансляцию, что и при запуске скрипта непосредственно в программе, но вызов не будет содержать исходных файлов. После запуска значения ENVIRONMENT_ID и INTERACTION_ID в .env будут обновлены, поэтому вы можете продолжить работу с refine_digest.py как и раньше.

[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. Отправить через Gmail

Агент сгенерировал дайджест и сохранил его в /workspace/digest.pdf . До сих пор вы скачивали его локально. На этом этапе он будет доставлен непосредственно в ваш почтовый ящик, поскольку агент вызовет REST API Gmail из изолированной среды.

Подход: вы получаете токен доступа OAuth 2.0 локально и передаете его агенту в поле input . Агент использует code_execution для создания MIME-письма с прикрепленным PDF-файлом и отправляет его методом POST в API Gmail. Никаких пользовательских инструментов, никакой регистрации на сервере MCP.

Предварительные требования

Включите API Gmail в своем проекте GCP и создайте идентификатор клиента OAuth 2.0:

  1. Перейдите в консоль по адресу console.cloud.google.com/apis/library/gmail.googleapis.com и включите API Gmail.
  2. Перейдите в раздел API и сервисы > Учетные данные > Создать учетные данные > Идентификатор клиента OAuth 2.0 .
  3. Тип приложения: Настольное приложение . Загрузите JSON-файл и сохраните его как credentials.json в корневой папке проекта.

Добавьте адрес электронной почты получателя в файл .env :

RECIPIENT_EMAIL=you@gmail.com

При необходимости установите библиотеки аутентификации:

uv sync

Что нужно заполнить

Откройте send_digest.py . В нём два пункта TODO.

TODO 1: загрузить или обновить токен доступа 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())

Удалите строку raise NotImplementedError после её добавления. При первом запуске откроется браузер для экрана согласия OAuth. Токен будет кэширован в файле .gmail_token.json для последующих запусков.

TODO 2: заменить input="" на инструкции для электронной почты. Токен уже находится в области действия как 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}"
    ),

Что делает каждая часть

Взаимодействие возобновляется в той же песочнице, где агент уже сгенерировал digest.pdf и summaries.json . previous_interaction_id предоставляет агенту историю его разговоров.

Токен доступа передается во input строке. Агент считывает его из запроса и использует в заголовке Authorization: Bearer при вызове API Gmail. Он никогда не затрагивает ваш локальный компьютер или файловую систему.

Агент использует code_execution для написания и запуска скрипта Python внутри песочницы: он считывает файл summaries.json , форматирует его как новостную рассылку, считывает digest.pdf , формирует MIME-сообщение multipart, кодирует его в base64url и отправляет POST-запрос на https://gmail.googleapis.com/gmail/v1/users/me/messages/send .

Проверять

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.

Проверьте свою почту. Пришло письмо с текстом письма в формате новостной рассылки и прикрепленным digest.pdf .

11. Составьте расписание ежедневных пробежек.

До сих пор каждый шаг запускался вручную. Триггеры позволяют запланировать автоматический запуск указанного агента по заданному cron-выражению. Агент запускается в запланированное время, выполняет полный рабочий процесс обработки дайджеста, и среда сохраняется между запусками, поэтому пакеты, установленные при первом запуске, будут доступны при каждом последующем запуске.

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

Что нужно заполнить

Откройте create_trigger.py . В нём есть один пункт TODO.

TODO 1: Заполнить вызов triggers.create() . Триггер запускает полный рабочий процесс каждый день: генерирует дайджест И отправляет его в ваш почтовый ящик. Поскольку токены доступа истекают через час, он внедряет токен обновления из .gmail_token.json в качестве встроенного источника, чтобы агент мог обменивать его на новый токен при каждом запуске.

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,
                }
            ],
        },
    },
)

execution_timeout_seconds=600 — это значение по умолчанию для таймаута. max_consecutive_failures=3 автоматически приостанавливает запуск триггера после 3 неудачных запусков подряд (значение по умолчанию в API — 5; 3 — более консервативное значение для семинара).

Список sources внедряет файл .gmail_creds.json в песочницу по адресу /workspace/.gmail_creds.json . Агент считывает его, обменивает токен обновления на новый токен доступа и вызывает API Gmail. Токены обновления не истекают, поэтому это работает при каждом запланированном запуске без необходимости ручного обновления токенов.

Удалите строку raise NotImplementedError после добавления вызова.

Проверять

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

create_trigger.py автоматически сохраняет идентификатор триггера в файл .env .

Чтобы проверить историю выполнения после запуска:

uv run python check_trigger.py

Чтобы сработал триггер немедленно, без ожидания следующего запланированного времени:

uv run python fire_trigger.py

Чтобы приостановить или удалить триггер:

uv run python pause_trigger.py

12. Уборка

Песочница автоматически закрывается через 7 дней бездействия. Нет необходимости останавливать серверы. Нет необходимости удалять контейнеры.

Если вы сохранили конфигурацию агента, удалите её:

uv run python delete_agent.py

13. Резюме

Вы создали управляемого агента с нуля, шаг за шагом, рассматривая каждую концепцию. Вот чему вас научило каждое упражнение:

Упражнение

Концепция

Ключ API

Сделайте свой первый звонок

Создайте реальную песочницу Linux и транслируйте её события в прямом эфире.

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

Настройте агента

Смонтируйте конфигурационные файлы; сохраните идентификаторы в файле .env в ходе того же запуска.

environment.sources , save_env

Добавьте страховочный крюк

Перехватывайте вызовы инструментов до их выполнения; запрещайте деструктивные команды.

hooks.json , pre_tool_execution , gate.py

Скачать PDF

Загрузите PDF-файл, не запуская агент повторно.

API файлов Gemini :download в download_pdf.py

Продолжите разговор

Продолжите разговор, не перезагружая веб-страницу.

environment=environment_id , previous_interaction_id=interaction_id

Сохранение конфигурации агента

Сохранять конфигурацию агента; запускать по идентификатору, источники не требуются.

agents.create() , agents.list()

Отправить через Gmail

Получите локальный токен OAuth; передайте его агенту, который вызовет REST API Gmail через code_execution

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

Расписание ежедневных рейсов

Запускать агента автоматически по расписанию cron

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

Ключевые шаблоны

  1. Один вызов, одна песочница : interactions.create() обрабатывает всю инфраструктуру (не нужно развертывать контейнеры, не нужно устанавливать пакеты локально).
  2. Прогрессивная потоковая передача : stream=True превращает 90-секундный «черный ящик» в прямую трансляцию вызовов инструментов и фрагментов текста.
  3. Встроенные источники : монтируются AGENTS.md , SKILL.md и предварительно скомпилированные скрипты в песочницу без каких-либо шагов загрузки или развертывания.
  4. Автоматическое обнаружение файлов : файлы, размещенные в папке .agents/ автоматически распознаются (настройка SDK не требуется).
  5. Двумерное состояние : environment_id отслеживает файлы и пакеты; previous_interaction_id отслеживает контекст разговора; любой из этих параметров может передаваться независимо.
  6. Загрузка снимка : среда представляет собой полный архив файловой системы в формате tar, доступный через API файлов Gemini.
  7. Именованные агенты : agents.create() сохраняет конфигурацию навсегда; последующие вызовы передают только идентификатор агента и environment="remote" , без указания источников.
  8. Хуки : hooks.json + скрипт-шлюз перехватывают вызовы инструментов до их выполнения; ответ deny отменяет вызов, и агент самостоятельно исправляет ошибку.
  9. Вызовы внешнего API : передайте учетные данные в поле input ; агент пишет и запускает код интеграции внутри песочницы с помощью code_execution
  10. Триггеры : запланируйте запуск агента по расписанию cron с помощью client.triggers.create() ; среда сохраняется между запусками.

ADK + Cloud Run против управляемых агентов: разница вкратце

Возможности

ADK + Cloud Run

Управляемые агенты в API Gemini

Создайте песочницу.

docker build + gcloud run deploy

interactions.create()

Определить инструменты

Функции Python, зарегистрированные в агенте.

Встроенные функции: просмотр веб-страниц, выполнение кода, файловая система.

Установить пакеты

pip install в Dockerfile

Агент запускает pip install в изолированной среде.

Трансляция событий

Пользовательская инфраструктура SSE

stream=True

Продолжить сессию

База данных сессий + внедрение контекста

environment_id + previous_interaction_id

Файлы конфигурации

Задается жестко в агенте или внедряется при запуске.

Смонтировано через environment.sources

Инфраструктура для управления

Контейнеры, Cloud Run, IAM, секреты

Никто

Следующие шаги