Стек агентов Google в действии: ADK, A2A, MCP в Google Cloud

1. Чему вы научитесь

Добро пожаловать! Сегодня мы отправляемся в довольно интересное путешествие. Давайте начнём с популярной платформы для социальных мероприятий InstaVibe. Несмотря на её успех, мы знаем, что для некоторых пользователей планирование групповых мероприятий может показаться рутиной. Представьте, что вы пытаетесь выяснить, чем интересуются все ваши друзья, затем просматриваете бесконечное количество вариантов мероприятий или площадок и, наконец, координируете всё. Это очень сложно! Именно здесь мы можем внедрить ИИ, а точнее, интеллектуальных агентов, чтобы добиться реальных изменений.

Идея заключается в создании системы, в которой эти агенты смогут взять на себя всю сложную работу, например, «слушая» пользователей и их друзей, чтобы понимать их предпочтения, а затем заблаговременно предлагать им интересные, персонализированные мероприятия. Наша цель — превратить социальное планирование в InstaVibe в нечто плавное и приятное. Чтобы начать создавать этих умных помощников, нам необходимо заложить прочную основу с помощью подходящих инструментов.

Вот концепция, которую вы увидите:

Титульный лист

Основы работы с Google ADK: освойте основы создания своего первого интеллектуального агента с помощью Google Agent Development Kit (ADK). Узнайте об основных компонентах, жизненном цикле агента и о том, как эффективно использовать встроенные инструменты фреймворка.

Расширение возможностей агентов с помощью протокола контекста модели (MCP): научитесь оснащать своих агентов специальными инструментами и контекстом, позволяя им выполнять специализированные задачи и получать доступ к определённой информации. Познакомьтесь с концепцией протокола контекста модели (MCP). Вы узнаете, как настроить сервер MCP для предоставления этого контекста.

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

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

Производственная реализация агентов в Google Cloud: перенесите свои агентские приложения из сред разработки в облако. Изучите передовые практики проектирования и развертывания масштабируемых, надежных многоагентных систем на Google Cloud Platform (GCP). Узнайте больше об использовании сервисов GCP, таких как Cloud Run, и ознакомьтесь с возможностями новейшей версии Google Agent Engine для размещения и управления вашими агентами.

2. Архитектура

Социальное планирование на основе искусственного интеллекта с помощью InstaVibe

Что такое социальное слушание?

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

Вы в команде InstaVibe

Представьте, что вы работаете в «InstaVibe» — успешном стартапе с популярной платформой для социальных мероприятий, ориентированной на молодёжь. Дела идут хорошо, но, как и во многих технологических компаниях, ваша команда сталкивается с давлением со стороны инвесторов, требующих внедрения инноваций с использованием ИИ. Внутри компании вы также заметили сегмент пользователей, которые менее активны, чем другие, — возможно, они менее склонны инициировать групповые мероприятия или считают процесс планирования сложным. Для вашей компании это означает снижение привязанности к платформе среди этой важной группы пользователей.

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

Решение на основе агентов (концепция прототипа)

Вы предлагаете разработать прототип функции на основе многоагентной системы. Вот концептуальное описание:

Вариант использования

  • Агент социального профилирования : этот агент использует методы мониторинга социальных сетей для анализа связей, взаимодействий пользователей и потенциально более широких общественных тенденций, связанных с предпочтениями пользователя. Его цель — выявить общие интересы и подходящие характеристики активности (например, предпочтение более тихих встреч, определённые хобби).
  • Агент по планированию мероприятий : используя информацию от агента по социальному профилированию, этот агент ищет в интернет-ресурсах конкретные мероприятия, места проведения или идеи, которые соответствуют заданным критериям (например, местоположение, интересы).
  • Агент взаимодействия с платформой (использующий MCP) : этот агент получает готовый план от агента планирования мероприятий. Его основная функция — непосредственное взаимодействие с платформой InstaVibe с помощью предустановленного инструмента MCP (Model Context Protocol). Этот инструмент предоставляет агенту возможность составить предложение по мероприятию и создать публикацию с описанием плана.
  • Агент-оркестратор : этот агент выступает в роли центрального координатора. Он получает первоначальный запрос пользователя от платформы InstaVibe, понимает общую цель (например, «спланировать мероприятие для меня и моих друзей»), а затем делегирует конкретные задачи соответствующим специализированным агентам в логической последовательности. Он управляет потоком информации между агентами и обеспечивает доставку конечного результата пользователю.

Ключевые архитектурные элементы и технологии

Архитектура

Облачная платформа Google (GCP):

  • Вершинный ИИ :
    • Модели Gemini: обеспечивает доступ к современным большим языковым моделям (LLM) от Google, таким как Gemini, которые обеспечивают возможности рассуждений и принятия решений нашими агентами.
    • Vertex AI Agent Engine: управляемая служба, используемая для развертывания, размещения и масштабирования нашего оркестратора, упрощающая производство и абстрагирующая сложности инфраструктуры.
  • Cloud Run : бессерверная платформа для развертывания контейнерных приложений. Мы используем её для:
    • Разместите основное веб-приложение InstaVibe.
    • Развертывайте отдельные агенты с поддержкой A2A (Планировщик, Социальное профилирование, Взаимодействие с платформой) как независимые микросервисы.
    • Запустите MCP Tool Server, сделав внутренние API InstaVibe доступными для агентов.
  • Spanner : полностью управляемая, глобально распределённая и строго согласованная реляционная база данных. В этом семинаре мы используем её возможности как графовой базы данных, используя функции GRAPH DDL и запросов для:
    • Моделируйте и храните сложные социальные отношения (пользователи, дружеские отношения, посещение мероприятий, посты).
    • Обеспечить эффективный запрос этих взаимосвязей для агентов социального профилирования.
  • Artifact Registry : полностью управляемая служба для хранения, управления и защиты образов контейнеров.
  • Cloud Build : сервис, который выполняет ваши сборки в Google Cloud. Мы используем его для автоматической сборки образов контейнеров Docker из нашего агента и исходного кода приложения.
  • Облачное хранилище : используется такими сервисами, как Cloud Build, для хранения артефактов сборки, а также Agent Engine для его эксплуатационных нужд.
  • Основные структуры и протоколы агентов :
    • Комплект разработки агентов Google (ADK) : основная платформа для:
      • Определение базовой логики, поведения и наборов инструкций для отдельных интеллектуальных агентов.
      • Управление жизненными циклами, состоянием и памятью агента (краткосрочное состояние сеанса и потенциально долгосрочные знания).
      • Интеграция инструментов (таких как Google Search или специально разработанные инструменты), которые агенты могут использовать для взаимодействия с миром.
      • Организация многоагентных рабочих процессов, включая последовательное, циклическое и параллельное выполнение подагентов.
    • Протокол связи «агент-агент» (A2A) : открытый стандарт, позволяющий:
      • Прямое, стандартизированное общение и сотрудничество между различными агентами ИИ, даже если они работают как отдельные службы или на разных машинах.
      • Агенты смогут узнавать о возможностях друг друга (через карты агентов) и делегировать задачи. Это крайне важно для взаимодействия нашего агента Orchestrator со специализированными агентами Planner, Social и Platform.
    • Библиотека A2A Python (a2a-python) : конкретная библиотека, используемая для поддержки протокола A2A в наших агентах ADK. Она предоставляет серверные компоненты, необходимые для:
      • Представить наших агентов как серверы, соответствующие стандарту A2A.
      • Автоматически обрабатывать выдачу «Агентской карты» для обнаружения.
      • Получайте и управляйте входящими запросами на выполнение задач от других агентов (например, Orchestrator).
    • Протокол контекста модели (MCP) : открытый стандарт, который позволяет агентам:
      • Подключайтесь и используйте внешние инструменты, источники данных и системы стандартизированным способом.
      • Наш агент взаимодействия с платформой использует клиент MCP для связи с сервером MCP, который, в свою очередь, предоставляет инструменты для взаимодействия с существующими API платформы InstaVibe.
  • Инструменты отладки :
    • A2A Inspector : A2A Inspector — это веб-инструмент отладки, используемый в ходе этого семинара для подключения к нашим агентам с поддержкой A2A, их проверки и взаимодействия с ними. Хотя он не входит в финальную производственную архитектуру, он является неотъемлемой частью нашего процесса разработки. Он обеспечивает:
      • Средство просмотра карточек агента: для извлечения и проверки публичных возможностей агента.
      • Интерфейс живого чата: для отправки сообщений непосредственно развернутому агенту для немедленного тестирования.
      • Консоль отладки: для просмотра необработанных сообщений JSON-RPC, которыми обмениваются инспектор и агент.
  • Языковые модели (LLM) : «мозги» системы:
    • Модели Gemini от Google: В частности, мы используем версии, такие как gemini-2.0-flash . Эти модели выбираются для:
      • Развитое мышление и следование инструкциям: их способность понимать сложные подсказки, следовать подробным инструкциям и рассуждать о задачах делает их подходящими для принятия решений в качестве активных агентов.
      • Использование инструментов (вызов функций): модели Gemini отлично справляются с определением того, когда и как использовать инструменты, предоставляемые через ADK, что позволяет агентам собирать информацию или выполнять действия.
      • Эффективность (модели Flash): варианты «Flash» обеспечивают хороший баланс производительности и экономической эффективности, подходят для многих интерактивных задач агентов, требующих быстрого реагирования.

Нужны кредиты Google Cloud?

3. Прежде чем начать

👉Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud (это значок в форме терминала в верхней части панели Cloud Shell), Cloud Shell

👉Нажмите кнопку «Открыть редактор » (она выглядит как открытая папка с карандашом). В окне откроется редактор кода Cloud Shell. Слева вы увидите проводник. Cloud Shell

👉Нажмите кнопку входа в Cloud Code в нижней строке состояния, как показано на рисунке. Авторизуйте плагин, следуя инструкциям. Если в строке состояния вы видите надпись «Cloud Code — нет проекта» , выберите её, затем в раскрывающемся списке «Выберите проект Google Cloud» выберите нужный проект Google Cloud из списка созданных вами проектов. Cloud Shell

👉 Найдите идентификатор вашего проекта Google Cloud :

  • Откройте консоль Google Cloud: https://console.cloud.google.com
  • Выберите проект, который вы хотите использовать для этого семинара, из раскрывающегося списка проектов в верхней части страницы.
  • Идентификатор вашего проекта отображается на карточке информации о проекте на панели инструментов.

Cloud Shell

👉Откройте терминал в облачной IDE, Cloud Shell

👉💻 В терминале убедитесь, что вы уже аутентифицированы и что проекту присвоен ваш идентификатор проекта, с помощью следующей команды:

gcloud auth list

👉💻 Клонируйте проект instavibe-bootstrap с GitHub:

git clone -b adk-1.2.1-a2a-0.2.7 https://github.com/weimeilin79/instavibe-bootstrap.git
chmod +x ~/instavibe-bootstrap/init.sh
chmod +x ~/instavibe-bootstrap/set_env.sh

Понимание структуры проекта

Прежде чем приступить к сборке, давайте уделим немного времени изучению структуры проекта instavibe-bootstrap который вы только что клонировали. Это поможет вам понять, где искать и редактировать файлы в ходе семинара.

instavibe-bootstrap/
├── agents/
   ├── orchestrate/
   ├── planner/
   ├── platform_mcp_client/
   └── social/
├── instavibe/
   ├── static/
   └── templates/
├── tools/
   └── instavibe/
├── utils/
├── init.sh
└── set_env.sh

Ниже приведена разбивка ключевых каталогов:

  • agents/ : Это сердце нашей системы искусственного интеллекта. Каждый подкаталог (planner/, social/ и т. д.) содержит исходный код для конкретного интеллектуального агента.
    • agent.py : внутри папки каждого агента находится основной файл, в котором хранится логика агента.
    • a2a_server.py : этот файл объединяет агент ADK с сервером Agent-to-Agent (A2A).
    • Dockerfile : определяет, как создать образ контейнера для развертывания агента в Cloud Run или Agent Engine.
  • instavibe/ : Этот каталог содержит весь исходный код веб-приложения InstaVibe.
  • tools/ : Этот каталог предназначен для создания внешних инструментов, которые могут использовать наши агенты.
    • instavibe/ содержит сервер Model Context Protocol (MCP).

Эта модульная структура отделяет веб-приложение от различных компонентов ИИ, что упрощает управление, тестирование и развертывание всей системы.

👉💻 Запускаем скрипт инициализации:

Этот скрипт предложит вам ввести идентификатор вашего проекта Google Cloud .

Введите идентификатор проекта Google Cloud, который вы нашли на последнем шаге, когда скрипт init.sh предложит это сделать:

cd ~/instavibe-bootstrap
./init.sh

👉💻 Установите необходимый идентификатор проекта:

gcloud config set project $(cat ~/project_id.txt) --quiet

👉💻 Выполните следующую команду, чтобы включить необходимые API Google Cloud:

gcloud services enable  run.googleapis.com \
                        cloudfunctions.googleapis.com \
                        cloudbuild.googleapis.com \
                        artifactregistry.googleapis.com \
                        spanner.googleapis.com \
                        apikeys.googleapis.com \
                        iam.googleapis.com \
                        compute.googleapis.com \
                        aiplatform.googleapis.com \
                        cloudresourcemanager.googleapis.com \
                        maps-backend.googleapis.com

👉💻 Установите все необходимые переменные среды:

export PROJECT_ID=$(gcloud config get project)
export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
export SPANNER_INSTANCE_ID="instavibe-graph-instance"
export SPANNER_DATABASE_ID="graphdb"
export GOOGLE_CLOUD_PROJECT=$(gcloud config get project)
export GOOGLE_GENAI_USE_VERTEXAI=TRUE
export GOOGLE_CLOUD_LOCATION="us-central1"

Настройка разрешения

👉💻 Предоставьте разрешения. В терминале выполните:

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.admin"

# Spanner Database User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.databaseUser"

# Artifact Registry Admin
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/artifactregistry.admin"

# Cloud Build Editor
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/cloudbuild.builds.editor"

# Cloud Run Admin
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/run.admin"

# IAM Service Account User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/iam.serviceAccountUser"

# Vertex AI User
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/aiplatform.user"

# Logging Writer (to allow writing logs)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.logWriter"


gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.viewer"


👉 Проверьте результат в консоли IAM Cloud Shell

👉💻 Выполните следующие команды в терминале, чтобы создать репозиторий реестра артефактов . Все образы Docker для наших агентов, сервера MCP и приложения InstaVibe хранятся здесь до развертывания в Cloud Run или Agent Engine.

export REPO_NAME="introveally-repo"
gcloud artifacts repositories create $REPO_NAME \
  --repository-format=docker \
  --location=us-central1 \
  --description="Docker repository for InstaVibe workshop"

Настройка платформы Map для ключей API

Чтобы использовать сервисы Google Maps в приложении InstaVibe, вам необходимо создать ключ API и соответствующим образом ограничить его.

👉 В новой вкладке перейдите в раздел API и сервисы > Учётные данные . На странице «Учётные данные» нажмите кнопку + СОЗДАТЬ УЧЕТНЫЕ ДАННЫЕ вверху. Выберите «Ключ API» в раскрывающемся меню. альтернативный текст

👉 Появится диалоговое окно с вашим только что созданным ключом API. Он понадобится вам позже для настройки приложения.

👉 Нажмите ЗАКРЫТЬ в диалоговом окне «Ключ API создан».

👉 Вы увидите свой новый ключ API (например, «API key 1»). Нажмите на три точки справа и выберите «Изменить ключ API» , чтобы открыть страницу «Ограничить и переименовать ключ API». альтернативный текст

👉 В поле «Имя» вверху измените имя по умолчанию на: «Ключ API платформы Карт» (🚨🚨ВАЖНО🚨🚨 Пожалуйста, используйте это имя!)

Maps Platform API Key

👉 В разделе «Ограничения приложения» убедитесь, что выбрано значение «Нет» .

👉 В разделе «Ограничения API» выберите переключатель Ограничить ключ.

👉 Нажмите раскрывающееся меню «Выбрать API». В появившемся поле поиска введите Maps JavaScript API и выберите его из списка. альтернативный текст

👉 Нажмите «ОК».

👉 Нажмите кнопку СОХРАНИТЬ внизу страницы.

Ключевой результат

Вы успешно создали ключ API с названием «Ключ API платформы Карт», ограничили его использование только «API JavaScript Карт» и обеспечили включение API для вашего проекта.

4. Настройка графовой базы данных

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

Мы реализуем эту графовую базу данных с помощью Google Cloud Spanner. Хотя Spanner в первую очередь известен как глобально распределённая, строго согласованная реляционная база данных, он также позволяет нам определять графовые структуры и выполнять запросы к ним непосредственно на основе наших реляционных таблиц.

Это дает нам объединенные преимущества масштабируемости Spanner, транзакционной согласованности и знакомого интерфейса SQL, а также выразительную мощь графовых запросов для анализа сложной социальной динамики, имеющей решающее значение для наших функций на базе ИИ.

👉💻 В терминале Cloud Shell IDE. Подготовьте необходимую инфраструктуру в Google Cloud. Начнём с создания экземпляра Spanner, который будет служить выделенным контейнером для наших баз данных. После того, как экземпляр будет готов, мы создадим в нём саму базу данных Spanner, которая будет содержать все наши таблицы и графические данные для InstaVibe.

. ~/instavibe-bootstrap/set_env.sh

gcloud spanner instances create $SPANNER_INSTANCE_ID \
  --config=regional-us-central1 \
  --description="GraphDB Instance InstaVibe" \
  --processing-units=100 \
  --edition=ENTERPRISE

gcloud spanner databases create $SPANNER_DATABASE_ID \
  --instance=$SPANNER_INSTANCE_ID \
  --database-dialect=GOOGLE_STANDARD_SQL

👉💻 Предоставьте Spanner доступ на чтение и запись к учетной записи службы по умолчанию.

echo "Granting Spanner read/write access to ${SERVICE_ACCOUNT_NAME} for database ${SPANNER_DATABASE_ID}..."

gcloud spanner databases add-iam-policy-binding ${SPANNER_DATABASE_ID} \
  --instance=${SPANNER_INSTANCE_ID} \
  --member="serviceAccount:${SERVICE_ACCOUNT_NAME}" \
  --role="roles/spanner.databaseUser" \
  --project=${PROJECT_ID}

👉💻 Теперь мы настроим виртуальную среду Python, установим необходимые пакеты Python, а затем настроим схему базы данных Graph в Spanner, загрузим в неё начальные данные и запустим скрипт setup.py .

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap
python -m venv env
source env/bin/activate
pip install -r requirements.txt
cd instavibe
python setup.py

👉 В новой вкладке браузера откройте Google Cloud Console и выберите Spanner . Вы увидите список экземпляров Spanner. Нажмите на instavibe-graph-instance . экземпляр гаечного ключа 👉 На странице обзора экземпляра вы увидите список баз данных, входящих в него. Нажмите на graphdb гаечный ключ db

👉 В левой навигационной панели вашей базы данных нажмите Spanner Studio. студия гаечного ключа

👉 В редакторе запросов (вкладка «Запрос без названия») вставьте следующий SQL-запрос Graph. Этот запрос найдёт все узлы Person и их прямые дружеские связи с другими узлами Person. Нажмите кнопку «Выполнить» , чтобы увидеть результат.

Graph SocialGraph
MATCH result_paths = ((p:Person)-[f:Friendship]-(friend:Person))
RETURN SAFE_TO_JSON(result_paths) AS result_paths

гаечный граф

👉 В том же редакторе запросов замените предыдущий DDL, чтобы найти людей, посетивших одно и то же мероприятие, что подразумевает косвенную связь через общую активность.

Graph SocialGraph
MATCH result_paths =  (p1:Person)-[:Attended]->(e:Event)<-[:Attended]-(p2:Person)
WHERE p1.person_id < p2.person_id
RETURN SAFE_TO_JSON(result_paths) AS result_paths

гаечный граф

👉 Этот запрос исследует другой тип связи, где люди, упомянутые в сообщениях, написанных друзьями определенного человека, запускают следующий запрос в редакторе запросов.

Graph SocialGraph
MATCH result_paths =  (user:Person {name: "Alice"})-[:Friendship]-(friend:Person)-[:Wrote]->(post:Post)-[:Mentioned]->(mentioned_person:Person)
WHERE user <> mentioned_person AND friend <> mentioned_person -- Avoid self-mentions or friend mentioning themselves in their own post if not intended
RETURN SAFE_TO_JSON(result_paths) AS result_paths

гаечный граф

Эти запросы лишь отчасти демонстрируют возможности использования Spanner в качестве графовой базы данных для нашего приложения InstaVibe. Моделируя наши социальные данные как взаимосвязанный граф, мы обеспечиваем сложный анализ взаимосвязей и действий, который будет иметь основополагающее значение для наших ИИ-агентов: они смогут понимать контекст пользователя, выявлять его интересы и, в конечном итоге, предоставлять интеллектуальную помощь в социальном планировании.

Теперь, когда наша базовая структура данных создана и протестирована, давайте обратим внимание на существующее приложение InstaVibe, с которым будут взаимодействовать наши агенты.

5. Текущее состояние InstaVibe

Чтобы понять, какое место займут наши ИИ-агенты, нам сначала нужно развернуть и запустить существующее веб-приложение InstaVibe. Это приложение предоставляет пользовательский интерфейс и базовые функции для подключения к базе данных графа Spanner, которую мы уже настроили.

домашняя страница

Приложение InstaVibe использует Google Карты для визуального отображения мест проведения мероприятий на страницах с подробной информацией о них. Для включения этой функции приложению требуется API-ключ, который мы создали ранее. Следующий скрипт извлечет фактическую строку ключа, используя назначенное нами отображаемое имя («API-ключ платформы Карт»).

страница события

👉💻 Вернитесь в IDE Cloud Shell . Запустите скрипт ниже. После этого внимательно проверьте вывод, чтобы убедиться, что отображаемый ключ GOOGLE_MAPS_API_KEY совпадает с ключом, который вы ранее создали и скопировали из консоли Google Cloud.

. ~/instavibe-bootstrap/set_env.sh
export KEY_DISPLAY_NAME="Maps Platform API Key"

GOOGLE_MAPS_KEY_ID=$(gcloud services api-keys list \
  --project="${PROJECT_ID}" \
  --filter="displayName='${KEY_DISPLAY_NAME}'" \
  --format="value(uid)" \
  --limit=1)

GOOGLE_MAPS_API_KEY=$(gcloud services api-keys get-key-string "${GOOGLE_MAPS_KEY_ID}" \
    --project="${PROJECT_ID}" \
    --format="value(keyString)")

echo "${GOOGLE_MAPS_API_KEY}" > ~/mapkey.txt

echo "Retrieved GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY}"

ключевой результат

👉💻 Теперь давайте создадим образ контейнера для веб-приложения InstaVibe и отправим его в наш репозиторий Artifact Registry.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/
export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

👉💻 Разверните новый образ веб-приложения InstaVibe в Cloud Run

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/
export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="SPANNER_INSTANCE_ID=${SPANNER_INSTANCE_ID}" \
  --set-env-vars="SPANNER_DATABASE_ID=${SPANNER_DATABASE_ID}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}" \
  --project=${PROJECT_ID} \
  --min-instances=1

После успешного завершения развертывания в журналах Cloud Run должен отобразиться общедоступный URL-адрес вашего работающего приложения InstaVibe.

URL

Вы также можете найти этот URL, перейдя в раздел Cloud Run в Google Cloud Console и выбрав службу instavibe. СписокURL

Откройте этот URL в браузере прямо сейчас, чтобы изучить базовую платформу InstaVibe. Ознакомьтесь с публикациями, событиями и связями пользователей, основанными на созданной нами графовой базе данных.

Теперь, когда наше целевое приложение запущено, давайте начнем создавать первый интеллектуальный агент, чтобы расширить его возможности.

6. Базовый агент, планировщик мероприятий с ADK

Рамки ADK

Введение в инфраструктуру Google ADK Теперь, когда наша основа (приложение и база данных InstaVibe) готова, мы можем приступить к созданию нашего первого интеллектуального агента с использованием комплекта разработки агентов (ADK) от Google.

Комплект разработки агентов (ADK) — это гибкая модульная платформа, специально разработанная для разработки и развертывания агентов ИИ. Принцип её разработки заключается в том, чтобы максимально приблизить разработку агентов к традиционной разработке программного обеспечения, значительно упростив разработчикам создание, развертывание и организацию архитектур агентов, способных решать любые задачи — от простых одноцелевых задач до сложных многоагентных рабочих процессов.

По своей сути ADK вращается вокруг концепции Agent , который инкапсулирует инструкции, конфигурацию (например, выбранную языковую модель, например, Gemini) и набор Tools он может использовать для выполнения действий или сбора информации.

06-агент.png

Нашим первым агентом станет «Планировщик мероприятий». Его основная задача — принимать запросы пользователей на встречи (с указанием места, даты и интересов) и генерировать креативные, персонализированные предложения. Чтобы предложения были релевантными и основывались на актуальной информации (например, о конкретных мероприятиях, которые пройдут в эти выходные), мы будем использовать один из встроенных инструментов ADK: Google Поиск . Это позволяет агенту основывать свои ответы на результатах поиска в режиме реального времени, получая актуальную информацию о местах проведения мероприятий, мероприятиях и мероприятиях, соответствующих критериям пользователя.

👉📝 Вернитесь в Cloud Shell IDE , в ~/instavibe-bootstrap/agents/planner/agent.py добавьте следующую подсказку и инструкцию для создания агента

from google.adk.agents import Agent
from google.adk.tools import google_search

root_agent = Agent(
    name="planner_agent",
    model="gemini-2.0-flash",
    description="Agent tasked with generating creative and fun dating plan suggestions",
    instruction="""

        You are a specialized AI assistant tasked with generating creative and fun plan suggestions.

        Request:
        For the upcoming weekend, specifically from **[START_DATE_YYYY-MM-DD]** to **[END_DATE_YYYY-MM-DD]**, in the location specified as **[TARGET_LOCATION_NAME_OR_CITY_STATE]** (if latitude/longitude are provided, use these: Lat: **[TARGET_LATITUDE]**, Lon: **[TARGET_LONGITUDE]**), please generate a distinct dating plan suggestions.

        Constraints and Guidelines for Suggestions:
        1.  Creativity & Fun: Plans should be engaging, memorable, and offer a good experience for a date.
        2.  Budget: All generated plans should aim for a moderate budget (conceptually "$$"), meaning they should be affordable yet offer good value, without being overly cheap or extravagant. This budget level should be *reflected in the choice of activities and venues*, but **do not** explicitly state "Budget: $$" in the `plan_description`.
        3.  Interest Alignment:
               Consider the following user interests: **[COMMA_SEPARATED_LIST_OF_INTERESTS, e.g., outdoors, arts & culture, foodie, nightlife, unique local events, live music, active/sports]**. Tailor suggestions specifically to these where possible. The plan should *embody* these interests.
               Fallback: If specific events or venues perfectly matching all listed user interests cannot be found for the specified weekend, you should create a creative and fun generic dating plan that is still appealing, suitable for the location, and adheres to the moderate budget. This plan should still sound exciting and fun, even if it's more general.
        4.  Current & Specific: Prioritize finding specific, current events, festivals, pop-ups, or unique local venues operating or happening during the specified weekend dates. If exact current events cannot be found, suggest appealing evergreen options or implement the fallback generic plan.
        5.  Location Details: For each place or event mentioned within a plan, you MUST provide its name, precise latitude, precise longitude, and a brief, helpful description.
        6.  Maximum Activities: The plan must contain a maximum of 3 distinct activities.

        RETURN PLAN in MARKDOWN FORMAT 
    """,
    tools=[google_search]
)

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

👉💻 Давайте запустим его. Следующие команды запустят ADK DEV UI:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/planner/.env
adk web

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

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://localhost:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

👉 Далее, чтобы получить доступ к интерфейсу разработчика ADK из браузера:

На значке веб-предпросмотра (часто он выглядит как глаз или квадрат со стрелкой) на панели инструментов Cloud Shell (обычно справа вверху) выберите «Изменить порт ». Во всплывающем окне установите порт 8000 и нажмите «Изменить и просмотреть». Cloud Shell откроет новую вкладку или окно браузера с интерфейсом ADK Dev.

веб-превью

После открытия интерфейса ADK Dev в браузере: в раскрывающемся меню в правом верхнем углу выберите «Планировщик» в качестве агента, с которым вы хотите взаимодействовать. Теперь в диалоговом окне чата справа попробуйте дать агенту задание. Например, пообщайтесь с ним:

Search and plan something in Seattle for me this weekend
This weekend and I enjoy food and anime

Предложить дату (по вашему предпочтению)

July 12 2025

Вы увидите, как агент обработает ваш запрос и предоставит план на основе результатов поиска Google.

adk dev ui

Конечно, взаимодействие с агентом — это одно, но как мы узнаем, что он всегда ведет себя так, как ожидается, особенно когда мы вносим изменения?

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

Оценка

👉 В интерфейсе разработчика ADK нажмите на вкладку «Eval» в левой навигационной панели. Вы увидите предварительно загруженный тестовый файл plan_eval . Этот файл содержит предопределённые входные данные и критерии для тестирования нашего агента-планировщика.

👉 Выберите сценарий, например, «Бостон», и нажмите кнопку «Запустить оценку ». В появившемся всплывающем окне уменьшите оценку соответствия до 0,3 и нажмите «Старт».

Счет матча

Это запустит агент с тестовыми входными данными и проверит, соответствует ли его вывод заданным ожиданиям. Это даёт вам возможность систематически тестировать производительность вашего агента.

оценка adk dev ui

👉 Теперь посмотрим, что произойдёт при более строгом пороге. Выберите сценарий «nyc» и снова нажмите «Запустить оценку» . На этот раз оставьте оценку соответствия по умолчанию (оценка соответствия ответа: 0,7) и нажмите «Старт». Вы заметите, что результат — «Не пройдено». Это ожидаемо, поскольку креативный результат агента не полностью соответствует предопределённому «золотому» ответу.

Ошибка оценки adk dev ui

👉 Чтобы понять причину сбоя, нажмите на значок сбоя в строке «nyc». В пользовательском интерфейсе теперь отображается параллельное сравнение фактического ответа агента и ожидаемого ответа тестового случая. Это представление важно для отладки, позволяя точно увидеть, где выходные данные агента расходятся, и соответствующим образом уточнить инструкции.

После завершения изучения пользовательского интерфейса и оценки вернитесь в терминал Cloud Shell Editor и нажмите Ctrl+C , чтобы остановить ADK Dev UI.

Хотя вывод текста в свободной форме — это неплохое начало, для таких приложений, как InstaVibe, чтобы легко использовать предложения агента, структурированные данные (например, JSON) гораздо практичнее. Давайте модифицируем наш агент так, чтобы он возвращал свой план в согласованном формате JSON.

👉📝 В файле ~/instavibe-bootstrap/agents/planner/agent.py найдите строку с текущим текстом RETURN PLAN in MARKDOWN FORMAT в строке инструкции агента. Замените эту строку следующей подробной структурой JSON:

Return your response *exclusively* as a single JSON object. This object should contain a top-level key, "fun_plans", which holds a plan objects. Each plan object in the list must strictly adhere to the following structure:

        --json--
        {
          "plan_description": "A summary of the overall plan, consisting of **exactly three sentences**. Craft these sentences in a friendly, enthusiastic, and conversational tone, as if you're suggesting this awesome idea to a close friend. Make it sound exciting and personal, highlighting the positive aspects and appeal of the plan without explicitly mentioning budget or listing interest categories.",
          "locations_and_activities": [
              {
              "name": "Name of the specific place or event",
              "latitude": 0.000000,  // Replace with actual latitude
              "longitude": 0.000000, // Replace with actual longitude
              "description": "A brief description of this place/event, why it's suitable for the date, and any specific details for the weekend (e.g., opening hours, event time)."
              }
              // Add more location/activity objects here if the plan involves multiple stops/parts
          ]
        }

Теперь, когда вы обновили инструкции агента для запроса JSON-вывода, давайте проверим изменение.

👉💻 Перезапустите ADK Dev UI, используя ту же команду, что и раньше:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
adk web

Обновите вкладку, если она уже открыта. Или выполните те же действия, что и ранее, чтобы открыть интерфейс ADK Dev в браузере (через веб-превью Cloud Shell через порт 8000). После загрузки интерфейса убедитесь, что выбран агент планировщика.

👉 На этот раз давайте попробуем задать другой запрос. В диалоговом окне введите:

Plan an event Boston this weekend with art and coffee

Внимательно изучите ответ агента. Вместо чисто текстового ответа вы должны увидеть ответ, отформатированный строго как JSON-объект, соответствующий структуре, определённой в инструкциях (содержащей fun_plans, plan_description, location_and_activities и т. д.). Это подтверждает, что агент теперь может генерировать структурированный вывод, подходящий для программного использования в нашем приложении InstaVibe.

adk dev ui json

После подтверждения вывода JSON вернитесь в терминал Cloud Shell и нажмите Ctrl+C , чтобы остановить ADK Dev UI.

Компоненты АДК

Хотя интерфейс разработчика ADK отлично подходит для интерактивного тестирования, нам часто приходится запускать агенты программно, возможно, как часть более крупного приложения или бэкенд-сервиса. Чтобы понять, как это работает, давайте рассмотрим некоторые основные концепции ADK, связанные со средой выполнения и управлением контекстом.

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

  • Сеанс: когда пользователь начинает взаимодействие с агентом, создаётся сеанс. Представьте его как контейнер для одной конкретной ветки чата. Он содержит уникальный идентификатор, историю взаимодействий (события), текущие рабочие данные (состояние) и метаданные, например, время последнего обновления.
  • Состояние: это кратковременная рабочая память агента в рамках одного сеанса. Это изменяемый словарь, в котором агент может хранить временную информацию, необходимую для выполнения текущей задачи (например, собранные на данный момент пользовательские настройки, промежуточные результаты вызовов инструментов).
  • Память: отражает потенциал агента для долгосрочного запоминания информации в разных сеансах или доступа к внешним базам знаний. В то время как Сеанс и Состояние отвечают за непосредственный диалог, Память (часто управляемая службой памяти) позволяет агенту извлекать информацию из прошлых взаимодействий или структурированных источников данных, предоставляя ему более широкий контекст знаний. (Примечание: наш простой клиент использует службы памяти для простоты, то есть память/состояние сохраняются только во время выполнения скрипта).
  • Событие: каждое взаимодействие в рамках сеанса (сообщение пользователя, ответ агента, запрос на использование инструмента, результат использования инструмента, изменение состояния, ошибка) регистрируется как неизменяемое событие. Это создаёт хронологический журнал, по сути, содержащий расшифровку и историю действий в ходе разговора.

Итак, как же этим управляют, когда агент работает? Это работа Бегуна .

  • Runner : Runner — это основной механизм выполнения, предоставляемый ADK. Вы определяете своего агента и используемые им инструменты, а Runner координирует процесс выполнения запроса пользователя. Он управляет сеансом, обрабатывает поток событий, обновляет состояние, вызывает базовую языковую модель, координирует вызовы инструментов и, возможно, взаимодействует с MemoryService. Представьте его как дирижера, обеспечивающего корректную совместную работу всех компонентов.

Мы можем использовать Runner для запуска нашего агента как автономного приложения Python, полностью независимого от Dev UI.

Давайте создадим простой клиентский скрипт для программного вызова нашего агента планировщика.

👉📝 В файле ~/instavibe-bootstrap/agents/planner/planner_client.py добавьте следующий код Python под существующими импортами. В planner_client.py под импортами добавьте следующее:

async def async_main():
  session_service = InMemorySessionService()

  session = await session_service.create_session(
      state={}, app_name='planner_app', user_id='user_dc'
  )

  query = "Plan Something for me in San Francisco this weekend on wine and fashion "
  print(f"User Query: '{query}'")
  content = types.Content(role='user', parts=[types.Part(text=query)])

  root_agent = agent.root_agent
  runner = Runner(
        app_name='planner_app',
        agent=root_agent,
        session_service=session_service,
  )
  print("Running agent...")
  events_async =  runner.run_async(
    session_id=session.id, user_id=session.user_id, new_message=content
  )

  async for event in events_async:
    print(f"Event received: {event}")


if __name__ == '__main__':
  try:
    asyncio.run(async_main())
  except Exception as e:
    print(f"An error occurred: {e}")

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

👉💻 Теперь выполните этот клиентский скрипт из вашего терминала:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
python -m planner.planner_client

👀 Наблюдайте за выходом. Вместо последнего плана JSON вы увидите подробную структуру каждого объекта события, сгенерированной во время потока выполнения агента. Это включает в себя начальное событие сообщений пользователя, потенциальные события, связанные с инструментами (например, поиск Google), и, наконец, событие ответа модели, содержащее план JSON. Этот подробный поток событий очень полезен для отладки и понимания пошаговой обработки, происходящей во время выполнения ADK.

Running agent...
Event received: content=Content(parts=[Part(video_metadata=None, thought=None, code_execution_result=None, executable_code=None, file_data=None, function_call=None, function_response=None, inline_data=None, text='```json\n{\n "fun_plans": [\n  {\n   "plan_description": "Embark on a stylish adventure through Hayes Valley, 
...(turncated)
, offering a variety of fashion styles to browse and enjoy."\n    }\n   ]\n  }\n ]\n}\n```')], role='model') grounding_metadata=GroundingMetadata(grounding_chunks=[GroundingChunk(retrieved_context=None, web=GroundingChunkWeb(domain='islands.com', title='islands.com', uri='http
...(turncated)
QyTpPV7jS6wUt-Ix7GuP2mC9J4eY_8Km6Vv44liF9cb2VSs='))], grounding_supports=[GroundingSupport(confide
...(turncated)
>\n', sdk_blob=None), web_search_queries=['..e']) partial=None turn_complete=None error_code=None error_message=None interrupted=None custom_metadata=None invocation_id='e-04d97b8b-9021-47a5-ab41-17b5cbb4bf03' author='location_search_agent' actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={}, transfer_to_agent=None, escalate=None, requested_auth_configs={}) long_running_tool_ids=None branch=None id='CInHdkKw' timestamp=1746978846.232674

Если сценарий работает непрерывно или висит, вам может потребоваться вручную остановить его, нажав Ctrl+C .

7. Агент взаимодействия платформы - взаимодействовать с MCP -сервером

В то время как ADK помогает структурировать наши агенты, им часто нужно взаимодействовать с внешними системами или API для выполнения реальных действий.

Протокол контекста модели (MCP)

Протокол контекста модели (MCP) - это открытый стандарт, разработанный для стандартизации того, как приложения AI, такие как агенты, соединяются с внешними источниками данных, инструментами и системами. Он стремится решить проблему необходимости пользовательской интеграции для каждой комбинации приложения и источника данных, предоставляя универсальный интерфейс. MCP использует архитектуру клиента-сервер, где клиенты MCP, проживающие в приложениях AI (хосты), управляют подключениями к серверам MCP. Эти серверы являются внешними программами, которые раскрывают конкретные функциональные возможности, такие как доступ к локальным данным, взаимодействие с удаленными услугами через API или предоставление предопределенных подсказок, позволяя моделям искусственного интеллекта доступ к текущей информации и выполняют задачи за пределами их первоначальной подготовки. Эта структура позволяет стандартизированным образом открывать и взаимодействовать с внешними возможностями, что делает интеграции более простыми и масштабируемыми.

Создайте и разверните сервер Instavibe MCP

07-MCP-server.png

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

Enpoint

URL

HTTP Метод

Описание

Создать пост

API/посты

ПОЧТА

API конечная точка, чтобы добавить новый пост. Ожидает тело JSON:
{"author_name": "...", "text": "...", "sentiment": "..." (optional)}

Создать событие

API/события

ПОЧТА

Конечная точка API, чтобы добавить новое мероприятие и его посетителей (упрощенная схема).
Ожидает тело JSON: { "event_name": "...", "description": "...", "event_date": "YYYY-MM-DDTHH:MM:SSZ", "locations": [ {"name": "...", "description": "...", "latitude": 0.0, "longitude": 0.0, "address": "..."} ], "attendee_names": ["...", "..."] }

Чтобы сделать эти возможности доступными для наших агентов через MCP, нам сначала необходимо создать простые функции Python, которые действуют как обертки вокруг этих вызовов API. Эти функции будут обрабатывать логику HTTP -запроса.

👉 Во -первых, давайте реализуем функцию обертки для создания поста. Откройте файл ~/instavibe-bootstrap/tools/instavibe/instavibe.py и замените #REPLACE ME CREATE POST Comment.

def create_post(author_name: str, text: str, sentiment: str, base_url: str = BASE_URL):
    """
    Sends a POST request to the /posts endpoint to create a new post.

    Args:
        author_name (str): The name of the post's author.
        text (str): The content of the post.
        sentiment (str): The sentiment associated with the post (e.g., 'positive', 'negative', 'neutral').
        base_url (str, optional): The base URL of the API. Defaults to BASE_URL.

    Returns:
        dict: The JSON response from the API if the request is successful.
              Returns None if an error occurs.

    Raises:
        requests.exceptions.RequestException: If there's an issue with the network request (e.g., connection error, timeout).
    """
    url = f"{base_url}/posts"
    headers = {"Content-Type": "application/json"}
    payload = {
        "author_name": author_name,
        "text": text,
        "sentiment": sentiment
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)
        print(f"Successfully created post. Status Code: {response.status_code}")
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error creating post: {e}")
        # Optionally re-raise the exception if the caller needs to handle it
        # raise e
        return None
    except json.JSONDecodeError:
        print(f"Error decoding JSON response from {url}. Response text: {response.text}")
        return None

👉📝 Далее мы создадим функцию обертки для API создания событий. В том же файле ~/instavibe-bootstrap/tools/instavibe/instavibe.py замените комментарий #REPLACE ME CREATE EVENTS с этим кодом:

def create_event(event_name: str, description: str, event_date: str, locations: list, attendee_names: list[str], base_url: str = BASE_URL):
    """
    Sends a POST request to the /events endpoint to create a new event registration.

    Args:
        event_name (str): The name of the event.
        description (str): The detailed description of the event.
        event_date (str): The date and time of the event (ISO 8601 format recommended, e.g., "2025-06-10T09:00:00Z").
        locations (list): A list of location dictionaries. Each dictionary should contain:
                          'name' (str), 'description' (str, optional),
                          'latitude' (float), 'longitude' (float),
                          'address' (str, optional).
        attendee_names (list[str]): A list of names of the people attending the event.
        base_url (str, optional): The base URL of the API. Defaults to BASE_URL.

    Returns:
        dict: The JSON response from the API if the request is successful.
              Returns None if an error occurs.

    Raises:
        requests.exceptions.RequestException: If there's an issue with the network request (e.g., connection error, timeout).
    """
    url = f"{base_url}/events"
    headers = {"Content-Type": "application/json"}
    payload = {
        "event_name": event_name,
        "description": description,
        "event_date": event_date,
        "locations": locations,
        "attendee_names": attendee_names,
    }

    try:
        response = requests.post(url, headers=headers, json=payload)
        response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)
        print(f"Successfully created event registration. Status Code: {response.status_code}")
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Error creating event registration: {e}")
        # Optionally re-raise the exception if the caller needs to handle it
        # raise e
        return None
    except json.JSONDecodeError:
        print(f"Error decoding JSON response from {url}. Response text: {response.text}")
        return None

Как видите, эти функции являются простыми обертками вокруг существующих APIS Instavibe. Этот шаблон полезен, если у вас уже есть API для ваших услуг, вы можете легко разоблачить их функциональность в качестве инструментов для агентов, создавая такие обертки.

Реализация MCP Server

Теперь, когда у нас есть функции Python, которые выполняют действия (вызывая API Instavibe), нам нужно создать компонент сервера MCP. Этот сервер выявит эти функции как «инструменты» в соответствии с стандартом MCP, что позволит клиентам MCP (например, наши агенты) открывать и вызывать их.

Сервер MCP обычно реализует две ключевые функции:

  • List_tools : Отвечает за то, чтобы клиент обнаружил доступные инструменты на сервере, предоставляя метаданные, такие как их имена, описания и необходимые параметры, часто определяемые с помощью схемы JSON
  • call_tool : обрабатывает выполнение конкретного инструмента, запрашиваемого клиентом, получая имя и аргументы инструмента и выполняя соответствующее действие, например, в нашем случае взаимодействие с API

Серверы MCP используются для предоставления моделей искусственного интеллекта с доступом к данным и действиям в реальном мире, включают такие задачи, как отправка электронных писем, создание задач в системах управления проектами, поиск баз данных или взаимодействие с различными программными и веб-службами. В то время как первоначальные реализации часто были сосредоточены на локальных серверах, передавающихся через стандартный ввод/вывод (STDIO) для простоты, особенно в условиях разработки или «студийных», перемещение в направлении удаленных серверов с использованием протоколов, таких как HTTP с серверными событиями (SSE) (SSE), имеет больше смысла для более широких вариантов принятия и предприятий.

The remote architecture, despite the added network communication layer, offers significant advantages: it allows multiple AI clients to share access to a single server, centralizes management and updates of tools, enhances security by keeping sensitive data and API keys on the server side rather than distributed across potentially many client machines, and decouples the AI model from the specifics of the external system integration, making the entire ecosystem more scalable, secure, and maintainable than requiring every AI instance to Управлять своими собственными прямыми интеграциями.

07-MCP-server.png

Мы будем реализовать наш сервер MCP с использованием HTTP и серверных событий (SSE) для общения, что хорошо подходит для потенциально длительных выполнений инструментов и сценариев предприятия.

👉📝 Во -первых, давайте реализуем конечную точку List_tools. Откройте файл ~/instavibe-bootstrap/tools/instavibe/mcp_server.py и замените комментарий #REPLACE ME - LIST TOOLS на следующий код. :

@app.list_tools()
async def list_tools() -> list[mcp_types.Tool]:
  """MCP handler to list available tools."""
  # Convert the ADK tool's definition to MCP format
  mcp_tool_schema_event = adk_to_mcp_tool_type(event_tool)
  mcp_tool_schema_post = adk_to_mcp_tool_type(post_tool)
  print(f"MCP Server: Received list_tools request. \n MCP Server: Advertising tool: {mcp_tool_schema_event.name} and {mcp_tool_schema_post}")
  return [mcp_tool_schema_event,mcp_tool_schema_post]

Эта функция определяет инструменты (create_event, create_post) и рассказывает об подключении клиентов о них.

👉📝 Далее реализуйте конечную точку call_tool , которая обрабатывает фактические запросы выполнения от клиентов. В том же файле ~/instavibe-bootstrap/tools/instavibe/mcp_server.py замените комментарий #REPLACE ME - CALL TOOLS с помощью этого кода.

@app.call_tool()
async def call_tool(
    name: str, arguments: dict
) -> list[mcp_types.TextContent | mcp_types.ImageContent | mcp_types.EmbeddedResource]:
  """MCP handler to execute a tool call."""
  print(f"MCP Server: Received call_tool request for '{name}' with args: {arguments}")

  # Look up the tool by name in our dictionary
  tool_to_call = available_tools.get(name)
  if tool_to_call:
    try:
      adk_response = await tool_to_call.run_async(
          args=arguments,
          tool_context=None, # No ADK context available here
      )
      print(f"MCP Server: ADK tool '{name}' executed successfully.")
      
      response_text = json.dumps(adk_response, indent=2)
      return [mcp_types.TextContent(type="text", text=response_text)]

    except Exception as e:
      print(f"MCP Server: Error executing ADK tool '{name}': {e}")
      # Creating a proper MCP error response might be more robust
      error_text = json.dumps({"error": f"Failed to execute tool '{name}': {str(e)}"})
      return [mcp_types.TextContent(type="text", text=error_text)]
  else:
      # Handle calls to unknown tools
      print(f"MCP Server: Tool '{name}' not found.")
      error_text = json.dumps({"error": f"Tool '{name}' not implemented."})
      return [mcp_types.TextContent(type="text", text=error_text)]

Эта функция получает имя и аргументы инструмента, находит соответствующую функцию обертки Python, которую мы определили ранее, выполняют его и возвращает результат

👉💻 С определенной логикой сервера MCP нам теперь нужно упаковать его в качестве контейнера, в терминале запустите следующий скрипт, чтобы создать изображение Docker, используя облачную сборку:

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/tools/instavibe

export IMAGE_TAG="latest"
export MCP_IMAGE_NAME="mcp-tool-server"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${MCP_IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="mcp-tool-server"
export INSTAVIBE_BASE_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe)/api

gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

👉💻 И развернуть изображение в качестве службы в Google Cloud Run.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/tools/instavibe

export IMAGE_TAG="latest"
export MCP_IMAGE_NAME="mcp-tool-server"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${MCP_IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="mcp-tool-server"
export INSTAVIBE_BASE_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe)/api

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="INSTAVIBE_BASE_URL=${INSTAVIBE_BASE_URL}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --project=${PROJECT_ID} \
  --min-instances=1

👉💻 После успешного завершения развертывания сервер MCP будет запущен и доступен через публичный URL. Нам нужно захватить этот URL, чтобы наш агент (действующий в качестве клиента MCP) знал, где подключиться.

export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse

Теперь вы также должны увидеть службу MCP-Tool-Server, указанную как «запуск» в разделе Cloud Run в вашей консоли Google Cloud Console.

Облачный запуск

При развертывании MCP -сервера и его URL -адреса мы теперь можем реализовать агента, который будет действовать в качестве клиента MCP и использовать инструменты, выявленные этим сервером.

8. Агент взаимодействия платформы (с помощью MCP)

Клиент MCP Клиент MCP - это компонент, который находится в приложении или агенте AI, действуя как интерфейс между моделью ИИ и одним или несколькими серверами MCP; В нашей реализации этот клиент будет интегрирован непосредственно в наш агент. Основная функция этого клиента состоит в том, чтобы общаться с серверами MCP для обнаружения доступных инструментов с помощью функции list_tools и впоследствии запросить выполнение конкретных инструментов с использованием функции call_tool , передавая необходимые аргументы, предоставленные моделью ИИ или агентом, организующим вызов.

MCP клиент

Теперь мы создадим агента, который выступает в качестве клиента MCP. Этот агент, работающий в рамках ADK, будет отвечать за связь с mcp-tool-server мы только что развернули.

👉 Во -первых, нам нужно изменить определение агента, чтобы динамически извлечь инструменты с нашего работающего сервера MCP. В agents/platform_mcp_client/agent.py замените #REPLACE ME - FETCH TOOLS на следующее:

"""Gets tools from the File System MCP Server."""
  tools =  MCPToolset(
      connection_params=SseServerParams(url=MCP_SERVER_URL, headers={})
  )

В этом коде используется метод mcptoolset.from_server для подключения к MCP_SERVER_URL (который мы устанавливаем в качестве переменной среды ранее) и извлечь список доступных инструментов.

Затем нам нужно сообщить определению агента ADK, чтобы фактически использовать эти динамически извлеченные инструменты.

👉 В agents/platform_mcp_client/agent.py , замените #REPLACE ME - SET TOOLs на следующие:

  tools=[tools],

👉💻 Теперь давайте протестируем этот агент локально, используя пользовательский интерфейс ADK Dev, чтобы увидеть, может ли он правильно подключиться к серверу MCP и использовать инструменты для взаимодействия с нашим запущенным приложением Instavibe.

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse

cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/platform_mcp_client/.env
sed -i "s|^\(O\?MCP_SERVER_URL\)=.*|MCP_SERVER_URL=${MCP_SERVER_URL}|" ~/instavibe-bootstrap/agents/platform_mcp_client/.env
adk web

Откройте пользовательский интерфейс ADK Dev в своем браузере (используя веб -предварительный просмотр Cloud Shell на порту 8000). На этот раз, в правом спине, выберите агент platform_mcp_client .

Давайте протестируем инструмент create_post. В диалоговом окне чата введите следующий запрос:

Create a post saying "Y'all I just got the cutest lil void baby 😭✨ Naming him Abyss bc he's deep, mysterious, and lowkey chaotic 🔥🖤 #VoidCat #NewRoomie" I'm Julia

ADK Dev UI Post

Агент должен обработать это, определить необходимость использования инструмента create_post, общаться с сервером MCP, который, в свою очередь, вызывает API Instavibe.

👉 Шаг проверки: после того, как агент подтверждает действие, откройте вкладку, на которой работает ваше приложение Instavibe (или обновить его). Вы должны увидеть новый пост из «Джулии», который появляется на главном корме!

Instavibe Post

👉💻 Запустите этот скрипт в отдельном терминале, чтобы получить ссылку на Instavibe, если это необходимо:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep instavibe

👉📝 Теперь давайте проверим инструмент Create_event. Введите следующий многострочный запрос в диалог в чате:

Hey, can you set up an event for Hannah and George and me, and I'm Julia? Let's call it 'Mexico City Culinary & Art Day'.
here are more info
  {"event_name": "Mexico City Culinary & Art Day",
  "description": "A vibrant day in Mexico City for Hannah and George, starting with lunch at one of the city's best taco spots in the hip Condesa neighborhood, followed by an inspiring afternoon exploring the Museo Soumaya's stunning art collection.",
  "event_date": "2025-10-17T12:00:00-06:00",
  "locations": [
    {
      "name": "El Tizoncito",
      "description": "Considered one of the original creators of tacos al pastor, El Tizoncito offers a legendary taco experience in the heart of Condesa. Their flavorful meats, house salsas, and casual vibe make it a must-visit for foodies.",
      "latitude": 19.412179,
      "longitude": -99.171308,
      "address": "Av. Tamaulipas 122, Hipódromo, Cuauhtémoc, 06100 Ciudad de México, CDMX, Mexico"
    },
    {
      "name": "Museo Soumaya",
      "description": "An architectural icon in Mexico City, Museo Soumaya houses over 66,000 works of art, including pieces by Rodin, Dalí, and Rivera. The striking silver structure is a cultural landmark and a visual feast inside and out.",
      "latitude": 19.440056,
      "longitude": -99.204281,
      "address": "Plaza Carso, Blvd. Miguel de Cervantes Saavedra 303, Granada, Miguel Hidalgo, 11529 Ciudad de México, CDMX, Mexico"
    }
  ],
  "attendee_names": ["Hannah", "George", Julia],
}

Опять же, агент должен использовать соответствующий инструмент через сервер MCP. На вкладке «События» не стесняйтесь щелкнуть о недивиальном событии, вы увидите подробный, пошаговый след выполнения.

ADK Dev UI мероприятие

👉 Шаг проверки: вернитесь к своему приложению Instavibe и перейдите в раздел «События» (или эквивалент). Теперь вы должны увидеть недавно созданное событие «Кулинарное и искусство и искусство» в Мехико.

Событие Instavibe

Это успешно демонстрирует, как MCP позволяет нашему агенту использовать внешние инструменты (в данном случае API Instavibe) стандартизированным образом.

После того, как вы проверили оба действия, вернитесь к своему терминалу облачной оболочки и нажмите Ctrl+C чтобы остановить пользовательский интерфейс ADK.

9. Агент рабочих процессов и мультиагенты в ADK

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

Агент социального профилирования

Во -первых, нам нужны инструменты для этого агента, чтобы получить доступ к графическим данным.

👉📝 Добавьте следующие функции Python в конце файла ~/instavibe-bootstrap/agents/social/instavibe.py :

def get_person_attended_events(person_id: str)-> list[dict]:
    """
    Fetches events attended by a specific person using Graph Query.
    Args:
       person_id (str): The ID of the person whose posts to fetch.
    Returns: list[dict] or None.
    """
    if not db_instance: return None

    graph_sql = """
        Graph SocialGraph
        MATCH (p:Person)-[att:Attended]->(e:Event)
        WHERE p.person_id = @person_id
        RETURN e.event_id, e.name, e.event_date, att.attendance_time
        ORDER BY e.event_date DESC
    """
    params = {"person_id": person_id}
    param_types_map = {"person_id": param_types.STRING}
    fields = ["event_id", "name", "event_date", "attendance_time"]

    results = run_graph_query( graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results is None: return None

    for event in results:
        if isinstance(event.get('event_date'), datetime):
            event['event_date'] = event['event_date'].isoformat()
        if isinstance(event.get('attendance_time'), datetime):
            event['attendance_time'] = event['attendance_time'].isoformat()
    return results

def get_person_id_by_name( name: str) -> str:
    """
    Fetches the person_id for a given name using SQL.

    Args:
       name (str): The name of the person to search for.

    Returns:
        str or None: The person_id if found, otherwise None.
                     Returns the ID of the *first* match if names are duplicated.
    """
    if not db_instance: return None

    sql = """
        SELECT person_id
        FROM Person
        WHERE name = @name
        LIMIT 1 -- Return only the first match in case of duplicate names
    """
    params = {"name": name}
    param_types_map = {"name": param_types.STRING}
    fields = ["person_id"]

    # Use the standard SQL query helper
    results = run_sql_query( sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results: # Check if the list is not empty
        return results[0].get('person_id') # Return the ID from the first dictionary
    else:
        return None # Name not found


def get_person_posts( person_id: str)-> list[dict]:
    """
    Fetches posts written by a specific person using Graph Query.

    Args:
        person_id (str): The ID of the person whose posts to fetch.


    Returns:
        list[dict] or None: List of post dictionaries with ISO date strings,
                           or None if an error occurs.
    """
    if not db_instance: return None

    # Graph Query: Find the specific Person node, follow 'Wrote' edge to Post nodes
    graph_sql = """
        Graph SocialGraph
        MATCH (author:Person)-[w:Wrote]->(post:Post)
        WHERE author.person_id = @person_id
        RETURN post.post_id, post.author_id, post.text, post.sentiment, post.post_timestamp, author.name AS author_name
        ORDER BY post.post_timestamp DESC
    """
    # Parameters now include person_id and limit
    params = {
        "person_id": person_id
    }
    param_types_map = {
        "person_id": param_types.STRING
    }
    # Fields returned remain the same
    fields = ["post_id", "author_id", "text", "sentiment", "post_timestamp", "author_name"]

    results = run_graph_query(graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    if results is None:
        return None

    # Convert datetime objects to ISO format strings
    for post in results:
        if isinstance(post.get('post_timestamp'), datetime):
            post['post_timestamp'] = post['post_timestamp'].isoformat()

    return results


def get_person_friends( person_id: str)-> list[dict]:
    """
    Fetches friends for a specific person using Graph Query.
    Args:
        person_id (str): The ID of the person whose posts to fetch.
    Returns: list[dict] or None.
    """
    if not db_instance: return None

    graph_sql = """
        Graph SocialGraph
        MATCH (p:Person {person_id: @person_id})-[f:Friendship]-(friend:Person)
        RETURN DISTINCT friend.person_id, friend.name
        ORDER BY friend.name
    """
    params = {"person_id": person_id}
    param_types_map = {"person_id": param_types.STRING}
    fields = ["person_id", "name"]

    results = run_graph_query( graph_sql, params=params, param_types=param_types_map, expected_fields=fields)

    return results

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

В ADK Google агент рабочего процесса не выполняет задачи, но организует другие агенты, называемые суб-агентами . Это обеспечивает модульную конструкцию, разбивая сложные проблемы на специализированные компоненты. ADK предоставляет встроенные типы рабочих процессов, такие как

  • Последовательный (шаг за шагом)
  • Параллель (одновременное исполнение)
  • и петля (повторное исполнение)

Агент социального профилирования

Для нашей задачи социального профилирования в нашем дизайне используется агент с цикла для создания итерационного рабочего процесса. Намерение состоит в том, чтобы обрабатывать одного человека за раз: profile_agent собирает данные, обновления summary_agent Анализ, а check_agent определяет, следует ли нам снова зациклена.

Давайте определим суб-агенты, необходимые для этого рабочего процесса.

👉📝 В ~/instavibe-bootstrap/agents/social/agent.py замените #REPLACE FOR profile_agent на следующее:

profile_agent = LlmAgent(
    name="profile_agent",
    model="gemini-2.5-flash",
    description=(
        "Agent to answer questions about the this person social profile. Provide the person's profile using their name, make sure to fetch the id before getting other data."
    ),
    instruction=(
        "You are a helpful agent to answer questions about the this person social profile. You'll be given a list of names, provide the person's profile using their name, make sure to fetch the id before getting other data. Get one person at a time, start with the first one on the list, and skip if already provided. return this person's result"
    ),
    tools=[get_person_posts,get_person_friends,get_person_id_by_name,get_person_attended_events],
)

Затем, агент, который получает собранную информацию о профиле (накапливается через итерации цикла) и генерирует окончательное резюме, определяя общее основание, если было проанализировано несколько человек.

👉📝 В то же время ~/instavibe-bootstrap/agents/social/agent.py , замените #REPLACE FOR summary_agent на следующее:

summary_agent = LlmAgent(
    name="summary_agent",
    model="gemini-2.5-flash",
    description=(
        "Generate a comprehensive social summary as a single, cohesive paragraph. This summary should cover the activities, posts, friend networks, and event participation of one or more individuals. If multiple profiles are analyzed, the paragraph must also identify and integrate any common ground found between them."
    ),
    instruction=(
        """
        Your primary task is to synthesize social profile information into a single, comprehensive paragraph.

            **Input Scope & Default Behavior:**
            *   If specific individuals are named by the user, focus your analysis on them.
            *   **If no individuals are specified, or if the request is general, assume the user wants an analysis of *all relevant profiles available in the current dataset/context*.**

            **For each profile (whether specified or determined by default), you must analyze:**

            1.  **Post Analysis:**
                *   Systematically review their posts (e.g., content, topics, frequency, engagement).
                *   Identify recurring themes, primary interests, and expressed sentiments.

            2.  **Friendship Relationship Analysis:**
                *   Examine their connections/friends list.
                *   Identify key relationships, mutual friends (especially if comparing multiple profiles), and the general structure of their social network.

            3.  **Event Participation Analysis:**
                *   Investigate their past (and if available, upcoming) event participation.
                *   Note the types of events, frequency of attendance, and any notable roles (e.g., organizer, speaker).

            **Output Generation (Single Paragraph):**

            *   **Your entire output must be a single, cohesive summary paragraph.**
                *   **If analyzing a single profile:** This paragraph will detail their activities, interests, and social connections based on the post, friend, and event analysis.
                *   **If analyzing multiple profiles:** This paragraph will synthesize the key findings regarding posts, friends, and events for each individual. Crucially, it must then seamlessly integrate or conclude with an identification and description of the common ground found between them (e.g., shared interests from posts, overlapping event attendance, mutual friends). The aim is a unified narrative within this single paragraph.

            **Key Considerations:**
            *   Base your summary strictly on the available data.
            *   If data for a specific category (posts, friends, events) is missing or sparse for a profile, you may briefly acknowledge this within the narrative if relevant.
                """
        ),
    output_key="summary"
)

Нам нужен способ определить, когда петля должна остановиться (т.е., когда все запрошенные профили были обобщены)

👉📝 В то же время ~/instavibe-bootstrap/agents/social/agent.py , замените #REPLACE FOR check_agent на следующее:

check_agent = LlmAgent(
    name="check_agent",
    model="gemini-2.5-flash",
    description=(
        "Check if everyone's social profile are summarized and has been generated. Output 'completed' or 'pending'."
    ),
    output_key="summary_status"
)

Мы добавляем простую программную проверку (CheckCondition), которая явно рассматривает summary_status , хранящийся в состоянии , которые возвращаются check_agent , и сообщает агенту цикла, продолжать (ESCALATE = FALSE) или Stop (ESCALATE = TRUE).

👉📝 В то же время ~/instavibe-bootstrap/agents/social/agent.py замените #REPLACE FOR CheckCondition расположенную в верхней части файла следующим образом:

class CheckCondition(BaseAgent):
    async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:
        #log.info(f"Checking status: {ctx.session.state.get("summary_status", "fail")}")
        log.info(f"Summary: {ctx.session.state.get("summary")}")

        status = ctx.session.state.get("summary_status", "fail").strip()
        is_done = (status == "completed")

        yield Event(author=self.name, actions=EventActions(escalate=is_done))

Состояние и обратные вызовы для результатов цикла

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

В нашем сценарии, как итерат цикла, summary_agent и check_agent хранят свои выходы (Summary и Summary_status) в состоянии агента. Это позволяет информации сохранять итерации. Тем не менее, сам агент Loop не автоматически возвращает окончательное резюме из состояния, когда он заканчивается.

Агент социального профилирования

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

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

Перезвонить

👉📝 В то же время ~/instavibe-bootstrap/agents/social/agent.py , замените #REPLACE FOR modify_output_after_agent с следующим:

def modify_output_after_agent(callback_context: CallbackContext) -> Optional[types.Content]:

    agent_name = callback_context.agent_name
    invocation_id = callback_context.invocation_id
    current_state = callback_context.state.to_dict()
    current_user_content = callback_context.user_content
    print(f"[Callback] Exiting agent: {agent_name} (Inv: {invocation_id})")
    print(f"[Callback] Current summary_status: {current_state.get("summary_status")}")
    print(f"[Callback] Current Content: {current_user_content}")

    status = current_state.get("summary_status").strip()
    is_done = (status == "completed")
    # Retrieve the final summary from the state

    final_summary = current_state.get("summary")
    print(f"[Callback] final_summary: {final_summary}")
    if final_summary and is_done and isinstance(final_summary, str):
        log.info(f"[Callback] Found final summary, constructing output Content.")
        # Construct the final output Content object to be sent back
        return types.Content(role="model", parts=[types.Part(text=final_summary.strip())])
    else:
        log.warning("[Callback] No final summary found in state or it's not a string.")
        # Optionally return a default message or None if no summary was generated
        return None

Определение агента корневой петли

Наконец, мы определяем основной плюшевый. Он организует суб -агенты в последовательности в каждой итерации цикла (profile_agent -> summary_agent -> check_agent -> checkcondition). Он повторит эту последовательность до максимального времени или до завершения сигналов проверки. After_agent_callback гарантирует, что окончательное резюме возвращается.

👉📝 В то же время ~/instavibe-bootstrap/agents/social/agent.py замените #REPLACE FOR root_agent на следующее:

root_agent = LoopAgent(
    name="InteractivePipeline",
    sub_agents=[
        profile_agent,
        summary_agent,
        check_agent,
        CheckCondition(name="Checker")
    ],
    description="Find everyone's social profile on events, post and friends",
    max_iterations=10,
    after_agent_callback=modify_output_after_agent
)

Давайте проверим этот многоагентный рабочий процесс, используя ADK Dev UI.

👉💻 Запустите веб -сервер ADK:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?GOOGLE_CLOUD_PROJECT\)=.*|GOOGLE_CLOUD_PROJECT=${PROJECT_ID}|" ~/instavibe-bootstrap/agents/social/.env
adk web

Откройте пользовательский интерфейс ADK Dev (порт 8000 через веб -предварительный просмотр). В выпадающем меню агента (вверху) выберите социального агента.

👉 Теперь дайте ему задачу, чтобы профилировать нескольких людей. В диалоговом окне чата введите:

Tell me about Mike and Bob

После того, как агент отвечает (что может занять немного больше времени из -за цикла и нескольких вызовов LLM), не просто смотрите на окончательный результат чата. Перейдите на вкладку «События» на левой панели ADK Dev UI.

👉 Шаг проверки: На вкладке «События» вы увидите подробный, пошаговый след выполнения. 09-01-ADK-DEV-UI.PNG

Наблюдая за тем, как агент вызывает каждое поджарие, где вы ожидаете, что поток переходит от profile_agent -> summary_agent -> check_agent, проверка в каждой итерации. Но на практике, однако, мы видим мощную «самооптимизацию» агента в действии.

Поскольку базовая модель видит весь запрос (например, «профиль Майк и Боб»), она часто выбирает наиболее эффективный путь, собирая все необходимые данные в одном консолидированном повороте, а не итерации несколько раз. Вы можете увидеть входы, выходы и состояния для каждого шага, включая вызовы инструментов, сделанные Profile_agent

09-02-UI-Graph.png

и обновления статуса от check_agent и checkcondition. 09-03-UI-государство.png

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

После того, как вы изучили ответ чата и трассировку события, вернитесь к терминалу облачной оболочки и нажмите Ctrl+C , чтобы остановить U -UI ADK.

10. Агент к агенту (A2A) общение

До сих пор мы создали специализированных агентов, но они работают в изоляции или в рамках предопределенного рабочего процесса на той же машине. Чтобы построить по-настоящему распределенные и совместные многоагентные системы, нам нужен способ для агентов, потенциально работающих как отдельные услуги, открыть друг друга и эффективно общаться. Именно здесь появляется протокол агента-агента (A2A).

Протокол A2A является открытым стандартом, специально разработанным для совместимой связи между агентами искусственного интеллекта. В то время как MCP фокусируется на взаимодействии с агентом-инструментом, A2A фокусируется на взаимодействии агента с агентом. Это позволяет агентам:

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

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

10-05-агент-карта

A2A использует знакомые веб-стандарты (HTTP, SSE, JSON-RPC) и часто использует модель клиента-сервер, где один агент (клиент) отправляет задачи другому (удаленный агент/сервер). Эта стандартизация является ключом к созданию модульных, масштабируемых систем, в которых агенты, разработанные независимо, могут работать вместе.

Включение A2A для агентов Instavibe

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

  • Раскрыть карту агента : Обратите стандартное описание возможностей агента через конечную точку HTTP.
  • Слушайте задачи (запросы сообщений) : принять входящие запросы на задачу от других агентов (клиенты A2A) в соответствии с протоколом A2A.
  • Управление задачи (запросы сообщений). Выполнение : отдача полученных задач в базовую логику агента ADK для обработки.

Агент планировщика (A2A включен)

Все-агентный планировщик

Давайте начнем с добавления уровня сервера A2A к нашему агенту планировщика.

Определите логику запуска сервера A2A. Этот код определяет AgentCard (публичное описание агента), настраивает A2Aserver и запускает ее, связывая ее с платформой .

👉📝 Добавьте следующий код в конце ~/instavibe-bootstrap/agents/planner/a2a_server.py :

class PlannerAgent:
    """An agent to help user planning a event with its desire location."""
    SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

    def __init__(self):
        self._agent = self._build_agent()
        self.runner = Runner(
            app_name=self._agent.name,
            agent=self._agent,
            artifact_service=InMemoryArtifactService(),
            session_service=InMemorySessionService(),
            memory_service=InMemoryMemoryService(),
        )
        capabilities = AgentCapabilities(streaming=True)
        skill = AgentSkill(
            id="event_planner",
            name="Event planner",
            description="""
            This agent generates multiple fun plan suggestions tailored to your specified location, dates, and interests,
            all designed for a moderate budget. It delivers detailed itineraries,
            including precise venue information (name, latitude, longitude, and description), in a structured JSON format.
            """,
            tags=["instavibe"],
            examples=["What about Bostona MA this weekend?"],
        )
        self.agent_card = AgentCard(
            name="Event Planner Agent",
            description="""
            This agent generates multiple fun plan suggestions tailored to your specified location, dates, and interests,
            all designed for a moderate budget. It delivers detailed itineraries,
            including precise venue information (name, latitude, longitude, and description), in a structured JSON format.
            """,
            url=f"{PUBLIC_URL}",
            version="1.0.0",
            defaultInputModes=PlannerAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=PlannerAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )

    def get_processing_message(self) -> str:
        return "Processing the planning request..."

    def _build_agent(self) -> LlmAgent:
        """Builds the LLM agent for the night out planning agent."""
        return agent.root_agent


if __name__ == '__main__':
    try:
        plannerAgent = PlannerAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=PlannerAgentExecutor(plannerAgent.runner,plannerAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=plannerAgent.agent_card,
            http_handler=request_handler,
        )
        logger.info(f"Attempting to start server with Agent Card: {plannerAgent.agent_card.name}")
        logger.info(f"Server object created: {server}")

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

👉💻 Давайте быстро проверим, если сервер A2A запускается правильно локально и обслуживает свою карту агента. Запустите следующую команду в вашем первом терминале:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate
cd ~/instavibe-bootstrap/agents/
python -m planner.a2a_server

👉 Теперь откройте еще одно окно терминала. (Нажмите на подпись + на панели терминала) два терминала

👉💻 Используйте Curl, чтобы запросить карту агента с локально запущенного сервера:

curl http://localhost:10003/.well-known/agent.json | jq

Вы должны увидеть представление JSON AgentCard, которую мы определили, подтверждая, что сервер работает и рекламирует агента планировщика.

10-02-Planner-A2A.PNG

Вернитесь к первому терминалу (где работает сервер) и нажмите Ctrl+C , чтобы остановить его.

👉💻 С добавленной логикой сервера A2A мы теперь можем создать изображение контейнера.

Создайте и разверните агент планировщика

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/agents

# Set variables specific to the PLANNER agent
export IMAGE_TAG="latest"
export AGENT_NAME="planner"
export IMAGE_NAME="planner-agent"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="planner-agent"
export PUBLIC_URL="https://planner-agent-${PROJECT_NUMBER}.${REGION}.run.app"

echo "Building ${AGENT_NAME} agent..."
gcloud builds submit . \
  --config=cloudbuild-build.yaml \
  --project=${PROJECT_ID} \
  --region=${REGION} \
  --substitutions=_AGENT_NAME=${AGENT_NAME},_IMAGE_PATH=${IMAGE_PATH}

echo "Image built and pushed to: ${IMAGE_PATH}"

👉💻 И развернуть нашего агента планировщика в облачном забеге.

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/agents

# Set variables specific to the PLANNER agent
export IMAGE_TAG="latest"
export AGENT_NAME="planner"
export IMAGE_NAME="planner-agent"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="planner-agent"
export PUBLIC_URL="https://planner-agent-${PROJECT_NUMBER}.${REGION}.run.app"


gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --set-env-vars="A2A_HOST=0.0.0.0" \
  --set-env-vars="A2A_PORT=8080" \
  --set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="PUBLIC_URL=${PUBLIC_URL}" \
  --allow-unauthenticated \
  --project=${PROJECT_ID} \
  --min-instances=1

Давайте убедимся, что развернутая служба работает и правильно обслуживает свою агентскую карту из облака с помощью инспектора A2A .

👉 Из значка веб -предварительного просмотра на панели инструментов Cloud Shell выберите порт изменения. Установите порт на 8081 и нажмите «Изменить и предварительный просмотр». Новая вкладка браузера откроется с помощью интерфейса инспектора A2A.

10-08-Web-preview.png

👉💻 В терминале получите URL вашего развернутого агента планировщика:

export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
echo ${PLANNER_AGENT_URL}

👉💻 Скопировать выходной URL.

👉 В пользовательском интерфейсе A2A вставьте URL в поле URL агента и нажмите Connect.

👀 Информация о карте агента и JSON должен появиться на вкладке Agent Card, подтверждая успешное соединение.

10-03-Planner-A2A.PNG

👉 Нажмите на вкладку Chat в инспекторе A2A. Здесь вы можете напрямую взаимодействовать с развернутым Agen, отправить ему сообщение для проверки его возможности планирования. Например:

Plan something for me in Boston MA this weekend, and I enjoy classical music

👀 Чтобы осмотреть необработанную связь, нажмите на пузырь вашего сообщения, а затем на пузырь ответа агента в окне чата. Когда вы нажимаете на каждый из них, он отобразит полное сообщение JSON-RPC 2.0, которое было отправлено или получено, которое неоценимо для отладки.

Давайте оставим под рукой вкладку Инспектора A2A. Не закрывайте! Мы будем использовать его снова через мгновение, чтобы проверить два других наших агентов.

10-06-A2A-INSPEPOR.PNG

Агент взаимодействия платформы (A2A включен)

Все-агентная платформа

Затем мы повторим процесс для агента взаимодействия с платформой (тот, который использует MCP).

👉📝 Определите настройку сервера A2A, включая его уникальную AgentCard, в конце ~/instavibe-bootstrap/agents/platform_mcp_client/a2a_server.py :

class PlatformAgent:
  """An agent that post event and post to instavibe."""

  SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

  def __init__(self):
    self._agent = self._build_agent()
    self.runner = Runner(
        app_name=self._agent.name,
        agent=self._agent,
        artifact_service=InMemoryArtifactService(),
        session_service=InMemorySessionService(),
        memory_service=InMemoryMemoryService(),
    )
    capabilities = AgentCapabilities(streaming=True)
    skill = AgentSkill(
            id="instavibe_posting",
            name="Post social post and events on instavibe",
            description="""
            This "Instavibe" agent helps you create posts (identifying author, text, and sentiment – inferred if unspecified) and register
            for events (gathering name, date, attendee). It efficiently collects required information and utilizes dedicated tools
            to perform these actions on your behalf, ensuring a smooth sharing experience.
            """,
            tags=["instavibe"],
            examples=["Create a post for me, the post is about my cute cat and make it positive, and I'm Alice"],
        )
    self.agent_card = AgentCard(
            name="Instavibe Posting Agent",
            description="""
            This "Instavibe" agent helps you create posts (identifying author, text, and sentiment – inferred if unspecified) and register
            for events (gathering name, date, attendee). It efficiently collects required information and utilizes dedicated tools
            to perform these actions on your behalf, ensuring a smooth sharing experience.
            """,
            url=f"{PUBLIC_URL}",
            version="1.0.0",
            defaultInputModes=PlatformAgent.SUPPORTED_CONTENT_TYPES,
            defaultOutputModes=PlatformAgent.SUPPORTED_CONTENT_TYPES,
            capabilities=capabilities,
            skills=[skill],
        )


  def get_processing_message(self) -> str:
      return "Processing the social post and event request..."

  def _build_agent(self) -> LlmAgent:
    """Builds the LLM agent for the Processing the social post and event request."""
    return agent.root_agent


if __name__ == '__main__':
    try:
        platformAgent = PlatformAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=PlatformAgentExecutor(platformAgent.runner,platformAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=platformAgent.agent_card,
            http_handler=request_handler,
        )

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

Социальный агент (A2A включен)

Все-агент-социальный

Наконец, давайте включим A2A для нашего агента социального профилирования.

👉📝 Определите настройку сервера A2A и AgentCard в конце ~/instavibe-bootstrap/agents/social/a2a_server.py :

class SocialAgent:
  """An agent that handles social profile analysis."""

  SUPPORTED_CONTENT_TYPES = ["text", "text/plain"]

  def __init__(self):
    self._agent = self._build_agent()
    self.runner = Runner(
        app_name=self._agent.name,
        agent=self._agent,
        artifact_service=InMemoryArtifactService(),
        session_service=InMemorySessionService(),
        memory_service=InMemoryMemoryService(),
    )
    capabilities = AgentCapabilities(streaming=True)
    skill = AgentSkill(
                id="social_profile_analysis",
                name="Analyze Instavibe social profile",
                description="""
                Using a provided list of names, this agent synthesizes Instavibe social profile information by analyzing posts, friends, and events.
                It delivers a comprehensive single-paragraph summary for individuals, and for groups, identifies commonalities in their social activities
                and connections based on profile data.
                """,
                tags=["instavibe"],
                examples=["Can you tell me about Bob and Alice?"],
    )
    self.agent_card = AgentCard(
                name="Social Profile Agent",
                description="""
                Using a provided list of names, this agent synthesizes Instavibe social profile information by analyzing posts, friends, and events.
                It delivers a comprehensive single-paragraph summary for individuals, and for groups, identifies commonalities in their social activities
                and connections based on profile data.
                """,
                url=f"{PUBLIC_URL}",
                version="1.0.0",
                defaultInputModes=self.SUPPORTED_CONTENT_TYPES,
                defaultOutputModes=self.SUPPORTED_CONTENT_TYPES,
                capabilities=capabilities,
                skills=[skill],
    )

  def get_processing_message(self) -> str:
      return "Processing the social profile analysis request..."

  def _build_agent(self) -> LoopAgent:
    """Builds the LLM agent for the social profile analysis agent."""
    return agent.root_agent

if __name__ == '__main__':
    try:
        socialAgent = SocialAgent()

        request_handler = DefaultRequestHandler(
            agent_executor=SocialAgentExecutor(socialAgent.runner,socialAgent.agent_card),
            task_store=InMemoryTaskStore(),
        )

        server = A2AStarletteApplication(
            agent_card=socialAgent.agent_card,
            http_handler=request_handler,
        )

        uvicorn.run(server.build(), host='0.0.0.0', port=port)
    except Exception as e:
        logger.error(f"An error occurred during server startup: {e}")
        exit(1)

Создайте и разверните взаимодействие платформы и социальные агенты

Эти агенты нуждаются в доступе к Spanner, поэтому убедитесь, что переменные среды SPANNER_INSTANCE_ID , SPANNER_DATABASE_ID и MCP_SERVER_URL Правильно передаются во время развертывания.

👉💻 Стройте и разверните в облаке с помощью облачной сборки :

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap/agents
export MCP_SERVER_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep mcp-tool-server)/sse


gcloud builds submit . \
  --config=cloudbuild.yaml \
  --project="${PROJECT_ID}" \
  --region="${REGION}" \
  --substitutions=\
_PROJECT_ID="${PROJECT_ID}",\
_PROJECT_NUMBER="${PROJECT_NUMBER}",\
_REGION="${REGION}",\
_REPO_NAME="${REPO_NAME}",\
_SPANNER_INSTANCE_ID="${SPANNER_INSTANCE_ID}",\
_SPANNER_DATABASE_ID="${SPANNER_DATABASE_ID}",\
_MCP_SERVER_URL="${MCP_SERVER_URL}"

👉💻 В терминале получите URL вашего развернутого агента платформы:

export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
echo $PLATFORM_MPC_CLIENT_URL

👉💻 Скопировать выходной URL.

👉 В пользовательском интерфейсе A2A вставьте URL в поле URL агента и нажмите Connect.

👀 Информация о карте агента и JSON должен появиться на вкладке Agent Card, подтверждая успешное соединение.

10-05-платформ-A2A.PNG

👉 Нажмите на вкладку Chat в инспекторе A2A. Здесь вы можете напрямую взаимодействовать с развернутым агентом, отправьте ему тест сообщения. Способность агента создавать сообщения:

Create a post for me, the post says 'Paws, purrs, and ocean views 🐾☕🌊. Spent my morning at the Morning Seaside Cat Café, where every sip comes with a side of snuggles and sea breeze.' and make it positive, and I'm Oscar.

👀 Чтобы осмотреть необработанную связь, нажмите на пузырь вашего сообщения, а затем на пузырь ответа агента в окне чата. Когда вы нажимаете на каждый из них, он отобразит полное сообщение JSON-RPC 2.0, которое было отправлено или получено, которое неоценимо для отладки.

👉💻 В терминале получите URL вашего развернутого социального агента:

export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)
echo $SOCIAL_AGENT_URL

👉💻 Скопировать выходной URL.

👉 В пользовательском интерфейсе A2A вставьте URL в поле URL агента и нажмите Connect.

👀 Информация о карте агента и JSON должен появиться на вкладке Agent Card, подтверждая успешное соединение.

10-04-Social-A2A.PNG

👉 Нажмите на вкладку Chat в инспекторе A2A. Здесь вы можете напрямую взаимодействовать с развернутым AGEN, отправьте ему сообщение для анализа профилей пользователей из вашей базы данных:

Can you tell me about both Ian and Kevin's profile, what are their common interests?

👀 Чтобы осмотреть необработанную связь, нажмите на пузырь вашего сообщения, а затем на пузырь ответа агента в окне чата. Когда вы нажимаете на каждый из них, он отобразит полное сообщение JSON-RPC 2.0, которое было отправлено или получено, которое неоценимо для отладки.

👉 Отлично, мы закончили осматривать всех наших агентов. Теперь вы можете закрыть вкладку A2A Inspector.

11. Агент оркестратора (клиент A2A)

Теперь у нас есть три специализированных агента (планировщик, платформа, социальная), работающие в качестве независимых сервисов с поддержкой A2A в облаке. Последняя часть - агент оркестратора. Этот агент будет выступать в качестве центрального координатора или клиента A2A. Он будет получать запросы пользователей, выяснить, какие удаленные агенты необходимы для выполнения запроса (потенциально в последовательности), а затем использовать протокол A2A для делегирования задач этим удаленным агентам. Для этого семинара мы будем запускать агент оркестратора локально, используя U -UI ADK Dev.

Все-агент-Орхестратор

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

👉📝 В ~/instavibe-bootstrap/agents/orchestrate/agent.py замените #REPLACE ME REG AGENT CARD :

async with httpx.AsyncClient(timeout=30) as client:
            for i, address in enumerate(REMOTE_AGENT_ADDRESSES):
                log.info(f"--- STEP 3.{i}: Attempting connection to: {address} ---")
                try:
                    card_resolver = A2ACardResolver(client, address)
                    card = await card_resolver.get_agent_card()
                    
                    remote_connection = RemoteAgentConnections(agent_card=card, agent_url=address)
                    self.remote_agent_connections[card.name] = remote_connection
                    self.cards[card.name] = card
                    log.info(f"--- STEP 5.{i}: Successfully stored connection for {card.name} ---")

                except Exception as e:
                    log.error(f"--- CRITICAL FAILURE at STEP 4.{i} for address: {address} ---")
                    log.error(f"--- The hidden exception type is: {type(e).__name__} ---")
                    log.error(f"--- Full exception details and traceback: ---", exc_info=True)

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

  • send_message (функция A2A для делегирования работы).

👉📝 Замените #REPLACE ME CREATE AGENT в ~/instavibe-bootstrap/agents/orchestrate/agent.py с:

def create_agent(self) -> Agent:
        """Synchronously creates the ADK Agent object."""
        return Agent(
            model="gemini-2.5-flash",
            name="orchestrate_agent",
            instruction=self.root_instruction,
            before_agent_callback=self.before_agent_callback,
            description=("Orchestrates tasks for child agents."),
            tools=[self.send_message], 
        )

Основная логика оркестратора заключается в его инструкциях, в которых рассказывается, как использовать A2A.

👉📝 Замените #REPLACE ME INSTRUCTIONS в ~/instavibe-bootstrap/agents/orchestrate/agent.py с помощью этого метода, сгенерирующего инструкции:

def root_instruction(self, context: ReadonlyContext) -> str:
        current_agent = self.check_active_agent(context)
        return f"""
                You are an expert AI Orchestrator. Your primary responsibility is to intelligently interpret user requests, break them down into a logical plan of discrete actions, and delegate each action to the most appropriate specialized remote agent using the send_message function. You do not perform the tasks yourself but manage their assignment, sequence, and critically, their outcomes.
                    **Core Directives & Decision Making:**

                    *   **Understand User Intent & Complexity:**
                        *   Carefully analyze the user's request to determine the core task(s) they want to achieve. Pay close attention to keywords and the overall goal.
                        *   Identify if the request requires a single agent or a sequence of actions from multiple agents. For example, "Analyze John Doe's profile and then create a positive post about his recent event attendance" would require two agents in sequence.

                    *   **Task Planning & Sequencing (for Multi-Step Requests):**
                        *   Before delegating, outline the clear sequence of agent tasks.
                        *   Identify dependencies. If Task B requires output from Task A, execute them sequentially. If tasks are independent (like creating a post and then creating an event), execute them one after the other as separate delegations.
                        *   Agent Reusability: An agent's completion of one task does not make it unavailable. If a user's plan involves multiple, distinct actions that fall under the same agent's expertise (e.g., create a post, then create an event), you must call that same agent again for the subsequent task.

                    *   **Task Delegation & Management (using `send_message`):**
                        *   **Delegation:** Use `send_message` to assign actionable tasks to the selected remote agent. Your `send_message` call MUST include:
                            *   The `remote_agent_name` you've selected.
                            *   The `user_request` or all necessary parameters extracted from the user's input, formatted in a way the target agent will understand.
                        *   **Contextual Awareness for Remote Agents:** If a remote agent repeatedly requests user confirmation or seems to lack context, assume it lacks access to the full conversation history. In such cases, enrich your `send_message` with all necessary contextual information relevant to that specific agent from the conversation history.
                        *   **Sequential Task Execution:**
                            *   After a preceding task completes (indicated by the agent's response or a success signal), gather any necessary output from it.
                            *   Then, use `send_message` for the next agent in the sequence, providing it with the user's original relevant intent and any necessary data obtained from the previous agent's task.
                        *   **Active Agent Prioritization:** If an active agent is already engaged and the user's request is related to its current task, route subsequent related requests directly to that agent by providing updated context via `send_message`.
                    
                    
                    **Critical Success Verification:**

                    *   You **MUST** wait for the tool_output after every send_message call before taking any further action.
                    *   Your decision to proceed to the next task in a sequence **MUST** be based entirely on a confirmation of success from the tool_output of the previous task.
                    *   If a tool call fails, returns an error, or the tool_output is ambiguous, you MUST STOP the sequence. Your next action is to report the exact failure or ambiguity to the user.
                    *   DO NOT assume a task was successful. Do not invent success messages like "The event has been created." Only state that a task is complete if the tool's response explicitly says so.
                    
                    **Communication with User:**

                    *   **Transparent Communication:** Always present the complete and detailed response from the remote agent to the user. Do not summarize or filter unless explicitly instructed.
                    *   When you delegate a task (or the first task in a sequence), clearly inform the user which remote agent is handling it.
                    *   For multi-step requests, you can optionally inform the user of the planned sequence (e.g., "Okay, first I'll ask the 'Social Profile Agent' to analyze the profile, and then I'll have the 'Instavibe Posting Agent' create the post.").
                    *   If waiting for a task in a sequence to complete, you can inform the user (e.g., "The 'Social Profile Agent' is currently processing. I'll proceed with the post once that's done.").
                    *   **User Confirmation Relay:** If a remote agent asks for confirmation, and the user has not already provided it, just make up something.
                    *   If the user's request is ambiguous, if necessary information is missing for any agent in the sequence, or if you are unsure about the plan, just make up something.

                    **Important Reminders:**

                    *   **Autonomous Agent Engagement:** Never seek user permission before engaging with remote agents. If multiple agents are required to fulfill a request, connect with them directly without requesting user preference or confirmation.
                    *   **Focused Information Sharing:** Provide remote agents with only relevant contextual information. Avoid extraneous details that are not directly pertinent to their task.
                    *   **No Redundant Confirmations:** Do not ask remote agents for confirmation of information or actions they have already processed or committed to.
                    *   **Tool Reliance:** Strictly rely on your available tools, primarily `send_message`, to address user requests. Do not generate responses based on assumptions. If information is insufficient, request clarification from the user.
                    *   **Prioritize Recent Interaction:** Focus primarily on the most recent parts of the conversation when processing requests, while maintaining awareness of the overall goal for multi-step tasks.
                    *   Always prioritize selecting the correct agent(s) based on their documented purpose.
                    *   Ensure all information required by the chosen remote agent is included in the `send_message` call, including outputs from previous agents if it's a sequential task.

                    Agents:
                    {self.agents}

                    Current agent: {current_agent['active_agent']}`
                """

Тестирование оркестратора и полной системы A2A

Теперь давайте проверим всю систему. Мы запустим Orchestrator локально, используя ADK Dev UI, и он будет общаться с планировщиком, платформой и социальными агентами, работающими удаленно в Cloud Run.

👉💻 Во-первых, убедитесь, что переменная среды REMOTE_AGENT_ADDRESSES содержит развернутые URL-адреса ваших развернутых агентов с поддержкой A2A. Затем установите необходимые переменные среды для агента оркестратора и запустите пользовательский интерфейс ADK Dev:

. ~/instavibe-bootstrap/set_env.sh
source ~/instavibe-bootstrap/env/bin/activate

export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)

export REMOTE_AGENT_ADDRESSES=${PLANNER_AGENT_URL},${PLATFORM_MPC_CLIENT_URL},${SOCIAL_AGENT_URL}

cd  ~/instavibe-bootstrap/agents
sed -i "s|^\(O\?REMOTE_AGENT_ADDRESSES\)=.*|REMOTE_AGENT_ADDRESSES=${REMOTE_AGENT_ADDRESSES}|" ~/instavibe-bootstrap/agents/orchestrate/.env
adk web

👉 Откройте пользовательский интерфейс ADK Dev (измените порт на 8000 через веб -предварительный просмотр).

10-08-Web-preview.png

👉 В выпадении агента выберите агент оркестровки .

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

You are an expert event planner for a user named  Diana.
    Your task is to design a fun and personalized event.

    Here are the details for the plan:
    - Friends to invite: Ian, Nora
    - Desired date: "2025-10-15"
    - Location idea or general preference: "Chicago"

    Your process should be:
    1. Analyze the provided friend names. If you have access to a tool to get their InstaVibe profiles or summarized interests, please use it.
    2. Based on their potential interests (or general good taste if profiles are unavailable), create a tailored plan for the outing, check if you have access to any event planner tools.
    3. Ensure the plan includes the original `planned_date`.

    The user wants a comprehensive plan that includes:
    - The list of invited friends.
    - A catchy and descriptive name for the event.
    - The exact planned date for the event.
    - A summary of what the group will do.
    - Specific recommended spots (e.g., restaurants, bars, activity venues) with their names, (if possible, approximate latitude/longitude for mapping, and address), and a brief description of why it fits the plan.
    - A short, exciting message that {Diana} can send to {Ian, Nora} to get them excited about the event.

Оркестровать

Наблюдайте за взаимодействием в окне чата ADK Dev UI. Обратите пристальное внимание на ответы оркестратора - он должен указать, какой отдаленный агент является делегирующим задачами (например, «Хорошо, я спрошу агента социального профиля о Яне и Норе первым ...»).

Кроме того, проверьте вкладку «События» в пользовательском интерфейсе, чтобы увидеть базовые вызовы инструментов (send_message) на URL -адреса удаленных агентов.

Отправить задание

👉 Теперь попробуйте второй пример, который должен привлекать агент интеграции платформы напрямую:

Hey, can you register an event on Instavibe for Laura and Charlie? Let's call it 'Vienna Concert & Castles Day'.
here are more info
"event_name": "Vienna Concert & Castles Day",
  "description": "A refined and unforgettable day in Vienna with Laura and Charlie. The day begins with a guided tour of the magnificent Schönbrunn Palace, showcasing imperial architecture and history. In the evening, enjoy a classical music concert in one of Vienna's most iconic concert halls.",
  "event_date": "2025-10-14T10:00:00+02:00",
  "locations": [
    {
      "name": "Schönbrunn Palace",
      "description": "A UNESCO World Heritage Site and former imperial summer residence, Schönbrunn Palace offers opulent rooms, beautiful baroque gardens, and a glimpse into the life of the Habsburg monarchy. Visitors can stroll the grounds or take a guided historical tour.",
      "latitude": 48.184516,
      "longitude": 16.312222,
      "address": "Schönbrunner Schloßstraße 47, 1130 Wien, Austria"
    },
    {
      "name": "Musikverein Vienna",
      "description": "Home to the world-renowned Vienna Philharmonic, the Musikverein is one of the finest concert halls in the world. Its 'Golden Hall' is famous for its acoustics and ornate design. Attendees can enjoy a powerful classical concert in an unforgettable setting.",
      "latitude": 48.200132,
      "longitude": 16.373777,
      "address": "Musikvereinsplatz 1, 1010 Wien, Austria"
    }
  ],
  "attendee_names": ["Laura", "Charlie", "Oscar"] And I am Oscar

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

Затем вы можете проверить, что событие появляется в веб -приложении Instavibe. Событие Instavibe

Это демонстрирует успешную реализацию многоагентной системы с использованием ADK и протокола A2A, где центральный оркестратор делегирует задачи специализированным отдаленным агентам.

Не забудьте остановить adk dev UI ( Ctrl+C в терминале), когда вы закончите тестирование.

12. Agent Engine и удаленный вызов из Instavibe

До сих пор мы запускаем наших специализированных агентов на облачном запуска и протестировали оркестратор локально, используя пользовательский интерфейс ADK Dev. Для производственного сценария нам нужна надежная, масштабируемая и управляемая среда для размещения наших агентов. Именно здесь появляется двигатель Google Vertex Ag Agent.

Agent Engine - это полностью управляемый сервис на вершине AI, разработанном специально для развертывания и масштабирования агентов ИИ. Он абстрагирует управление инфраструктурой, безопасность и операционные накладные расходы, позволяя разработчикам (особенно тем, кто менее знаком со сложными облачными средами) сосредоточиться на логике и возможностях агента, а не на управлении серверами. Он обеспечивает выделенную среду выполнения, оптимизированную для агентских рабочих нагрузок.

Теперь мы развертываем нашего агента оркестратора в Agent Engine. (Примечание. Механизм развертывания, показанный ниже, использует пользовательский скрипт (Agent_Engine_App.py), предоставляемый в материалах мастерской, поскольку официальные инструменты развертывания прямого агента-двигателя могут все еще развиваться. Этот скрипт обрабатывает упаковку и развертывание агента оркестратора, настроенных с необходимыми адресами удаленного агента).

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

👉💻 Мы развертываем агент оркестровки в Agent Engine (Примечание: это моя собственная реализация развертывания, ADK имеет CLI для развертывания, я обновлю это после реализации Byo-Sa.)

cd ~/instavibe-bootstrap/agents/
. ~/instavibe-bootstrap/set_env.sh

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:service-$PROJECT_NUMBER@gcp-sa-aiplatform-re.iam.gserviceaccount.com" \
    --role="roles/viewer"


source ~/instavibe-bootstrap/env/bin/activate
export PLATFORM_MPC_CLIENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep platform-mcp-client)
export PLANNER_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner-agent)
export SOCIAL_AGENT_URL=$(gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep social-agent)

export REMOTE_AGENT_ADDRESSES=${PLANNER_AGENT_URL},${PLATFORM_MPC_CLIENT_URL},${SOCIAL_AGENT_URL}
sed -i "s|^\(O\?REMOTE_AGENT_ADDRESSES\)=.*|REMOTE_AGENT_ADDRESSES=${REMOTE_AGENT_ADDRESSES}|" ~/instavibe-bootstrap/agents/orchestrate/.env

adk deploy agent_engine \
--display_name "orchestrate-agent" \
--project $GOOGLE_CLOUD_PROJECT \
--region $GOOGLE_CLOUD_LOCATION \
--staging_bucket gs://$GOOGLE_CLOUD_PROJECT-agent-engine \
--trace_to_cloud \
--requirements_file orchestrate/requirements.txt \
orchestrate

Теперь, когда оркестратор размещен на платформе управляемого агента, наше веб -приложение Instavibe должно связаться с ним. Вместо того, чтобы взаимодействовать с помощью пользовательского интерфейса ADK Dev, веб -приложение будет делать удаленные вызовы в конечную точку двигателя агента.

10-агент-ремот.png

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

👉📝 Открыть ~/instavibe-bootstrap/instavibe/introvertally.py и замените #REPLACE ME initiate agent_engine следующим кодом. Это извлекает идентификатор двигателя агента из переменной среды (которую мы вскоре установим) и получит клиент -объект:

ORCHESTRATE_AGENT_ID = os.environ.get('ORCHESTRATE_AGENT_ID')
agent_engine = agent_engines.get(ORCHESTRATE_AGENT_ID)

Наш запланированный пользовательский поток в Instavibe включает в себя два взаимодействия с агентом: сначала генерируя рекомендуемый план, а во -вторых, просят пользователя подтвердить до того, как агент фактически опубликовал событие на платформу.

Поскольку веб -приложение Instavibe (работающее на Cloud Run) и агент Orchestrator (работающий на Agent Engine) теперь являются отдельными службами, веб -приложение необходимо для удаленных вызовов в конечную точку двигателя агента для взаимодействия с агентом.

👉📝 Давайте обновим код, который делает первоначальный вызов, чтобы генерировать рекомендацию плана. В том же файле introvertally.py замените #REPLACE ME Query remote agent get plan со следующим фрагментом, который использует клиент Agent_Engine для отправки запроса пользователя:

agent_engine.stream_query(
                user_id=user_id,
                message=prompt_message,
            )

👉📝 Далее, обновите код, который обрабатывает подтверждение пользователя (например, когда пользователь нажимает «Подтвердить план»). Это посылает последующее сообщение в тот же разговор на Agent Engine, инструктируя оркестратор, чтобы продолжить публикацию события (которое оно передаст агенту интеграции платформы). Замените #REPLACE ME Query remote agent for confirmation для подтверждения в introvertally.py .

agent_engine.stream_query(
            user_id=agent_session_user_id,
            message=prompt_message,
        )

Маршруты веб -приложения нуждаются в доступе к этим функциям. Убедитесь, что необходимые функции от интроверталлического.

👉📝 В cd ~/instavibe-bootstrap/instavibe/ally_routes.py мы сначала указываем на экземпляр заменить # REPLACE ME TO ADD IMPORT с следующим:

from introvertally import call_agent_for_plan, post_plan_event

👉📝 Добавьте функцию прототипа в Instavibe, в ~/instavibe-bootstrap/instavibe/templates/base.html , замените <!-replace_me_link_to_introvert_ally–> с следующим:

            <li class="nav-item">
              <a class="nav-link" href="{{ url_for('ally.introvert_ally_page') }}">Introvert Ally</a>
            </li>

Прежде чем мы сможем передать приложение Instavibe, нам нужен конкретный Resource ID агента оркестратора, который мы развернули в Agent Engine.

В настоящее время извлечение этого программного программного обеспечения через gcloud может быть ограничено, поэтому мы будем использовать вспомогательный сценарий Python ( temp-endpoint.py , предоставленный в семинаре), чтобы получить идентификатор и сохранить его в переменной среды.

👉💻 Запустите следующие команды, чтобы выполнить скрипт. The script will capture the Agent Engine Endpoint ID and grant the necessary permissions to the agent engine's default service account (Note: The script is configured to use the default service account as it is currently not user-modifiable).

. ~/instavibe-bootstrap/set_env.sh
cd ~/instavibe-bootstrap/instavibe/
source ~/instavibe-bootstrap/env/bin/activate
python temp-endpoint.py
export ORCHESTRATE_AGENT_ID=$(cat temp_endpoint.txt)
echo "ORCHESTRATE_AGENT_ID set to: ${ORCHESTRATE_AGENT_ID}"

Agent Engine Endpoint ID

Finally, we need to redeploy the InstaVibe web application with the updated code and the new ORCHESTRATE_AGENT_ID environment variable so it knows how to connect to our agent running on Agent Engine.

👉💻 The following commands rebuild the InstaVibe application image and deploy the new version to Cloud Run:

. ~/instavibe-bootstrap/set_env.sh

cd ~/instavibe-bootstrap/instavibe/

export IMAGE_TAG="latest"
export APP_FOLDER_NAME="instavibe"
export IMAGE_NAME="instavibe-webapp"
export IMAGE_PATH="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"
export SERVICE_NAME="instavibe"

echo "Building ${APP_FOLDER_NAME} webapp image..."
gcloud builds submit . \
  --tag=${IMAGE_PATH} \
  --project=${PROJECT_ID}

echo "Deploying ${SERVICE_NAME} to Cloud Run..."

gcloud run deploy ${SERVICE_NAME} \
  --image=${IMAGE_PATH} \
  --platform=managed \
  --region=${REGION} \
  --allow-unauthenticated \
  --set-env-vars="SPANNER_INSTANCE_ID=${SPANNER_INSTANCE_ID}" \
  --set-env-vars="SPANNER_DATABASE_ID=${SPANNER_DATABASE_ID}" \
  --set-env-vars="APP_HOST=0.0.0.0" \
  --set-env-vars="APP_PORT=8080" \
  --set-env-vars="GOOGLE_CLOUD_LOCATION=${REGION}" \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=${PROJECT_ID}" \
  --set-env-vars="GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}" \
  --set-env-vars="ORCHESTRATE_AGENT_ID=${ORCHESTRATE_AGENT_ID}" \
  --project=${PROJECT_ID} \
  --min-instances=1 \
  --cpu=2 \
  --memory=2Gi

With the final deployment complete, navigate to your InstaVibe application URL in a different browser tab.

Testing the Full AI-Powered InstaVibe Experience

The "InstaVibe Ally" feature is now live, powered by our multi-agent system orchestrated via Vertex AI Agent Engine and communicating through A2A.

12-02-new.png

Click into "InstaVibe Ally" and ask it to plan an event.

12-03-introvertally.png

Observe the activity log on the right while the agents work (it may take 90-120 seconds). Once the plan appears, review it and click "Confirm This Plan" to proceed with posting.

12-04-confirm.png

The orchestrator will now instruct the Platform agent to create the post and event within InstaVibe. 12-05-posting.png

Check the InstaVibe home page for the new post and event. 12-06-instavibe.png

The event page will reflect the details generated by the agent.

12-07-event.png

Analyzing Performance with Cloud Trace

You might notice the process takes some time. Vertex AI Agent Engine integrates with Cloud Trace, allowing us to analyze the latency of our multi-agent system.

Go to the Traces in the google cloud console, select agent_run[orchestrate_agent] in the Span, you should see a couple of Spans, click into it

12-08-trace.png

Within the trace details, you can identify which parts took longer. For example, calls to the Planner agent might show higher latency due to search grounding and complex generation. 12-09-plan.png

Similarly, when creating the post and event, you might see time spent by the Orchestrator processing data and preparing tool calls for the Platform agent. 12-10-post.png

Exploring these traces helps understand and optimize the performance of your agent system.

celebrate.png

Поздравляю! You've successfully built, deployed, and tested a sophisticated multi-agent AI system using Google's ADK, A2A, MCP, and Google Cloud services. You've tackled agent orchestration, tool usage, state management, and cloud deployment, creating a functional AI-powered feature for InstaVibe. Well done on completing the workshop!

13. Clean Up

To avoid ongoing charges to your Google Cloud account, it's important to delete the resources we created during this workshop. The following commands will help you remove the Spanner instance, Cloud Run services, Artifact Registry repository, API Key, Vertex AI Agent Engine, and associated IAM permissions.

Важный:

  • Ensure you are running these commands in the same Google Cloud project used for the workshop.
  • If you've closed your Cloud Shell terminal, some environment variables like $PROJECT_ID, $SPANNER_INSTANCE_ID, etc., might not be set. You'll need to either re-export them as you did during the workshop setup or replace the variables in the commands below with their actual values.
  • These commands will permanently delete your resources. Double-check before running if you have other important data in this project.

👉💻 Run the following scripts to clean up.

Reset environment variables

. ~/instavibe-bootstrap/set_env.sh

Delete Agent Engine:

cd ~/instavibe-bootstrap/utils
source ~/instavibe-bootstrap/env/bin/activate
export ORCHESTRATE_AGENT_ID=$(cat ~/instavibe-bootstrap/instavibe/temp_endpoint.txt)
echo "ORCHESTRATE_AGENT_ID set to: ${ORCHESTRATE_AGENT_ID}"
python remote_delete.py
deactivate
echo "Vertex AI Agent Engine deletion initiated."

Delete Cloud Run Services:

# InstaVibe Web Application
gcloud run services delete instavibe --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# MCP Tool Server
gcloud run services delete mcp-tool-server --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Planner Agent (A2A Server)
gcloud run services delete planner-agent --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Platform MCP Client Agent (A2A Server)
gcloud run services delete platform-mcp-client --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

# Social Agent (A2A Server)
gcloud run services delete social-agent --platform=managed --region=${REGION} --project=${PROJECT_ID} --quiet

echo "Cloud Run services deletion initiated."

Stop and Remove the A2A Inspector Docker Container

docker rm --force a2a-inspector

Delete Spanner Instance:

echo "Deleting Spanner instance: ${SPANNER_INSTANCE_ID}..."
gcloud spanner instances delete ${SPANNER_INSTANCE_ID} --project=${PROJECT_ID} --quiet
echo "Spanner instance deletion initiated."

Delete Artifact Registry Repository:

echo "Deleting Artifact Registry repository: ${REPO_NAME}..."
gcloud artifacts repositories delete ${REPO_NAME} --location=${REGION} --project=${PROJECT_ID} --quiet
echo "Artifact Registry repository deletion initiated."

Remove Roles from Service Account:

echo "Removing roles from service account: $SERVICE_ACCOUNT_NAME in project $PROJECT_ID"

# Remove Project-level roles for default service account
gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/spanner.databaseUser"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/artifactregistry.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/cloudbuild.builds.editor"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/run.admin"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/iam.serviceAccountUser"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/aiplatform.user"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.logWriter"

gcloud projects remove-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/logging.viewer"


echo "All specified roles have been removed."

Delete Local Workshop Files:

echo "Removing local workshop directory ~/instavibe-bootstrap..."
rm -rf ~/instavibe-bootstrap
rm -rf ~/a2a-inspector
rm -f ~/mapkey.txt
rm -f ~/project_id.txt
echo "Local directory removed."

Уборка