ADK с мультимодальным взаимодействием инструментов: Часть 2 (набор инструментов MCP с обратными вызовами инструментов)

1. 📖 Введение

В предыдущем практическом занятии вы узнали, как разработать многомодальное взаимодействие с данными в ADK. Теперь мы сделаем следующий шаг и рассмотрим, как разработать многомодальное взаимодействие с данными с помощью MCP Server, используя MCP Toolset. Мы расширим возможности ранее разработанного агента для редактирования фотографий, добавив возможность создания коротких видеороликов с использованием модели Veo и Veo MCP Server.

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

  1. Подготовьте проект Google Cloud и базовый каталог агентов.
  2. Настройте MCP-сервер, которому требуются файловые данные в качестве входных данных.
  3. Подготовка агента ADK к подключению к серверу MCP.
  4. Разработайте стратегию обработки запросов и функцию обратного вызова для изменения запроса на вызов функции к набору инструментов MCP.
  5. Разработайте функцию обратного вызова для обработки многомодальных данных, поступающих от инструментария MCP.

Обзор архитектуры

Общее взаимодействие в данной практической работе показано на следующей диаграмме.

93fe3107e0946ddd.jpeg

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

  • Уверенно работаю с Python.
  • (Необязательно) Базовые практические занятия по работе с комплектом разработки агентов (ADK)
  1. goo.gle/adk-foundation
  2. goo.gle/adk-using-tools
  • (Необязательно) Практический урок по использованию мультимодального инструмента ADK, часть 1: goo.gle/adk-multimodal-tool-1

Что вы узнаете

  • Как создать короткое видео с помощью Veo 3.1, используя подсказку и изображение в качестве отправной точки.
  • Как разработать многомодальный MCP-сервер с использованием FastMCP
  • Как настроить ADK для использования MCP Toolset
  • Как изменить вызов инструмента MCP Toolset через функцию обратного вызова инструмента?
  • Как изменить ответ инструмента от MCP Toolset через функцию обратного вызова инструмента.

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

  • Веб-браузер Chrome
  • Аккаунт Gmail
  • Облачный проект с включенным платежным аккаунтом.

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

2. 🚀 (Необязательно) Подготовка к настройке процесса разработки.

Шаг 1: Выберите активный проект в облачной консоли.

В консоли Google Cloud на странице выбора проекта выберите или создайте проект Google Cloud (см. раздел в левом верхнем углу консоли).

6069be756af6452b.png

Нажмите на него, и вы увидите список всех ваших проектов, как в этом примере.

dd8fcf0428ab868f.png

Значение, обозначенное красной рамкой, — это идентификатор проекта , и это значение будет использоваться на протяжении всего урока.

Убедитесь, что для вашего облачного проекта включена оплата. Для этого нажмите на значок меню (гамбургер) ☰ в верхнем левом углу панели, где отображается меню навигации, и найдите пункт «Оплата».

db07810b26fc61d6.png

Если в разделе «Биллинг / Обзор » ( в верхнем левом углу консоли облачных сервисов ) вы видите пункт «Пробный платёжный аккаунт Google Cloud Platform» , значит, ваш проект готов к использованию в этом руководстве. В противном случае вернитесь к началу руководства и активируйте пробный платёжный аккаунт.

45539d4ac57dd995.png

Шаг 2: Ознакомьтесь с Cloud Shell.

В большинстве руководств вы будете использовать Cloud Shell . Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud. Если система запросит авторизацию, нажмите «Авторизовать».

26f20e837ff06119.png

79b06cc89a99f840.png

После подключения к Cloud Shell нам потребуется проверить, авторизована ли оболочка (или терминал) уже с использованием нашей учетной записи.

gcloud auth list

Если вы видите в своей личной почте Gmail результат, как в приведенном ниже примере, значит, все в порядке.

Credentialed Accounts

ACTIVE: *
ACCOUNT: alvinprayuda@gmail.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

Если это не поможет, попробуйте обновить страницу в браузере и обязательно нажмите кнопку « Авторизовать» , когда появится соответствующий запрос (возможно, авторизация прервалась из-за проблем с подключением).

Далее нам также необходимо проверить, настроена ли оболочка уже для правильного идентификатора проекта (PROJECT ID ). Если вы видите значение внутри скобок ( ) перед значком $ в терминале (на скриншоте ниже значение — "adk-multimodal-tool" ), это значение показывает настроенный проект для вашей активной сессии оболочки.

10a99ff80839b635.png

Если отображаемое значение уже верное , вы можете пропустить следующую команду . Однако, если оно неверно или отсутствует, выполните следующую команду.

gcloud config set project <YOUR_PROJECT_ID>

Затем клонируйте рабочую директорию шаблона для этого практического занятия из Github и выполните следующую команду. Она создаст рабочую директорию в каталоге adk-multimodal-tool.

git clone https://github.com/alphinside/adk-mcp-multimodal.git adk-multimodal-tool

Шаг 3: Ознакомьтесь с редактором Cloud Shell и настройте рабочий каталог приложения.

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

Нажмите кнопку «Открыть редактор» , это откроет редактор Cloud Shell. 168eacea651b086c.png

После этого перейдите в верхнюю часть редактора Cloud Shell и нажмите «Файл» -> «Открыть папку», найдите каталог с вашим именем пользователя и найдите каталог adk-multimodal-tool, затем нажмите кнопку «ОК». Это сделает выбранный каталог основным рабочим каталогом. В этом примере имя пользователя — alvinprayuda , поэтому путь к каталогу показан ниже.

8eb3f593141dbcbf.png

a4860f6be228d864.png

Теперь ваша рабочая директория в Cloud Shell Editor должна выглядеть следующим образом (внутри adk-multimodal-tool ):

aa2edaf29303167f.png

Теперь откройте терминал для редактора. Это можно сделать, щелкнув «Терминал» -> «Новый терминал» в строке меню, или используя Ctrl + Shift + C — это откроет окно терминала в нижней части браузера.

74d314f6ff34965b.png

Ваш текущий активный терминал должен находиться в рабочей директории adk-multimodal-tool . В этом практическом занятии мы будем использовать Python 3.12 и менеджер проектов Python uv для упрощения создания и управления версиями Python и виртуальными средами. Этот пакет uv уже предустановлен в Cloud Shell.

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

uv sync --frozen

Проверьте файл pyproject.toml , чтобы увидеть объявленные зависимости для этого руководства: google-adk, and python-dotenv .

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

gcloud services enable aiplatform.googleapis.com

После успешного выполнения команды вы должны увидеть сообщение, похожее на показанное ниже:

Operation "operations/..." finished successfully.

Структура шаблона агента уже предоставлена ​​вам в каталоге part2_starter_agent в клонированном репозитории. Теперь нам нужно сначала переименовать его, чтобы он был готов к этому руководству.

mv part1_ckpt_agent product_photo_editor

После этого скопируйте файл product_photo_editor/.env.example в файл product_photo_editor/.env.

cp product_photo_editor/.env.example product_photo_editor/.env

Открыв файл product_photo_editor/.env , вы увидите содержимое, показанное ниже.

GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=global

Затем вам потребуется обновить значение your-project-id , указав правильный идентификатор вашего проекта. Теперь мы готовы к следующему шагу.

3. 🚀 Инициализируйте сервер Veo MCP.

Для начала создадим каталог службы MCP с помощью этой команды.

mkdir veo_mcp

Затем создайте файл veo_mcp/main.py , используя эту команду.

touch veo_mcp/main.py

После этого скопируйте следующий код в файл veo_mcp/main.py.

from fastmcp import FastMCP
from typing import Annotated
from pydantic import Field
import base64
import asyncio
import os
from google import genai
from google.genai import types
from dotenv import load_dotenv
import logging

# Load environment variables from .env file
load_dotenv()

mcp = FastMCP("Veo MCP Server")


@mcp.tool
async def generate_video_with_image(
    prompt: Annotated[
        str, Field(description="Text description of the video to generate")
    ],
    image_data: Annotated[
        str, Field(description="Base64-encoded image data to use as starting frame")
    ],
    negative_prompt: Annotated[
        str | None,
        Field(description="Things to avoid in the generated video"),
    ] = None,
) -> dict:
    """Generates a professional product marketing video from text prompt and starting image using Google's Veo API.

    This function uses an image as the first frame of the generated video and automatically
    enriches your prompt with professional video production quality guidelines to create
    high-quality marketing assets suitable for commercial use.

    AUTOMATIC ENHANCEMENTS APPLIED:
    - 4K cinematic quality with professional color grading
    - Smooth, stabilized camera movements
    - Professional studio lighting setup
    - Shallow depth of field for product focus
    - Commercial-grade production quality
    - Marketing-focused visual style

    PROMPT WRITING TIPS:
    Describe what you want to see in the video. Focus on:
    - Product actions/movements (e.g., "rotating slowly", "zooming into details")
    - Desired camera angles (e.g., "close-up of the product", "wide shot")
    - Background/environment (e.g., "minimalist white backdrop", "lifestyle setting")
    - Any specific details about the product presentation

    The system will automatically enhance your prompt with professional production quality.

    Args:
        prompt: Description of the video to generate. Focus on the core product presentation
                you want. The system will automatically add professional quality enhancements.
        image_data: Base64-encoded image data to use as the starting frame
        negative_prompt: Optional prompt describing what to avoid in the video

    Returns:
        dict: A dictionary containing:
            - status: 'success' or 'error'
            - message: Description of the result
            - video_data: Base64-encoded video data (on success only)
    """
    try:
        # Initialize the Gemini client
        client = genai.Client(
            vertexai=True,
            project=os.getenv("GOOGLE_CLOUD_PROJECT"),
            location=os.getenv("GOOGLE_CLOUD_LOCATION"),
        )

        # Decode the image
        image_bytes = base64.b64decode(image_data)
        print(f"Successfully decoded image data: {len(image_bytes)} bytes")

        # Create image object
        image = types.Image(image_bytes=image_bytes, mime_type="image/png")

        # Prepare the config
        config = types.GenerateVideosConfig(
            duration_seconds=8,
            number_of_videos=1,
        )

        if negative_prompt:
            config.negative_prompt = negative_prompt

        # Enrich the prompt for professional marketing quality
        enriched_prompt = enrich_prompt_for_marketing(prompt)

        # Generate the video (async operation)
        operation = client.models.generate_videos(
            model="veo-3.1-generate-preview",
            prompt=enriched_prompt,
            image=image,
            config=config,
        )

        # Poll until the operation is complete
        poll_count = 0
        while not operation.done:
            poll_count += 1
            print(f"Waiting for video generation to complete... (poll {poll_count})")
            await asyncio.sleep(5)
            operation = client.operations.get(operation)

        # Download the video and convert to base64
        video = operation.response.generated_videos[0]

        # Get video bytes and encode to base64
        video_bytes = video.video.video_bytes
        video_base64 = base64.b64encode(video_bytes).decode("utf-8")

        print(f"Video generated successfully: {len(video_bytes)} bytes")

        return {
            "status": "success",
            "message": f"Video with image generated successfully after {poll_count * 5} seconds",
            "complete_prompt": enriched_prompt,
            "video_data": video_base64,
        }
    except Exception as e:
        logging.error(e)
        return {
            "status": "error",
            "message": f"Error generating video with image: {str(e)}",
        }


def enrich_prompt_for_marketing(user_prompt: str) -> str:
    """Enriches user prompt with professional video production quality enhancements.

    Adds cinematic quality, professional lighting, smooth camera work, and marketing-focused
    elements to ensure high-quality product marketing videos.
    """
    enhancement_prefix = """Create a high-quality, professional product marketing video with the following characteristics:

TECHNICAL SPECIFICATIONS:
- 4K cinematic quality with professional color grading
- Smooth, stabilized camera movements
- Professional studio lighting setup with soft, even illumination
- Shallow depth of field for product focus
- High dynamic range (HDR) for vibrant colors

VISUAL STYLE:
- Clean, minimalist aesthetic suitable for premium brand marketing
- Elegant and sophisticated presentation
- Commercial-grade production quality
- Attention to detail in product showcase

USER'S SPECIFIC REQUIREMENTS:
"""

    enhancement_suffix = """

ADDITIONAL QUALITY GUIDELINES:
- Ensure smooth transitions and natural motion
- Maintain consistent lighting throughout
- Keep the product as the clear focal point
- Use professional camera techniques (slow pans, tracking shots, or dolly movements)
- Apply subtle motion blur for cinematic feel
- Ensure brand-appropriate tone and style"""

    return f"{enhancement_prefix}{user_prompt}{enhancement_suffix}"


if __name__ == "__main__":
    mcp.run()

Следующий код выполняет следующие действия:

  1. Создает сервер FastMCP, который предоставляет агентам ADK доступ к инструменту генерации видео Veo 3.1.
  2. Принимает в качестве входных данных изображения, текстовые подсказки и отрицательные подсказки, закодированные в base64.
  3. Генерирует 8-секундные видеоролики асинхронно, отправляя запросы к API Veo 3.1 и опрашивая систему каждые 5 секунд до завершения процесса.
  4. Возвращает видеоданные в формате base64 вместе с расширенным запросом.

Для работы этого инструмента Veo MCP потребуется та же переменная среды, что и для нашего агента, поэтому мы можем просто скопировать и вставить файл .env . Для этого выполните следующую команду.

cp product_photo_editor/.env veo_mcp/

Теперь мы можем проверить корректность работы сервера MCP, выполнив следующую команду.

uv run veo_mcp/main.py

И в консоли отобразится примерно следующее:

╭────────────────────────────────────────────────────────────────────────────╮
│                                                                            │
│        _ __ ___  _____           __  __  _____________    ____    ____     │
│       _ __ ___ .'____/___ ______/ /_/  |/  / ____/ __ \  |___ \  / __ \    │
│      _ __ ___ / /_  / __ `/ ___/ __/ /|_/ / /   / /_/ /  ___/ / / / / /    │
│     _ __ ___ / __/ / /_/ (__  ) /_/ /  / / /___/ ____/  /  __/_/ /_/ /     │
│    _ __ ___ /_/    \____/____/\__/_/  /_/\____/_/      /_____(*)____/      │
│                                                                            │
│                                                                            │
│                                FastMCP  2.0                                │
│                                                                            │
│                                                                            │
│                 🖥️  Server name:     Veo MCP Server                         │
│                 📦 Transport:       STDIO                                  │
│                                                                            │
│                 🏎️  FastMCP version: 2.12.5                                 │
│                 🤝 MCP SDK version: 1.16.0                                 │
│                                                                            │
│                 📚 Docs:            https://gofastmcp.com                  │
│                 🚀 Deploy:          https://fastmcp.cloud                  │
│                                                                            │
╰────────────────────────────────────────────────────────────────────────────╯


[10/22/25 08:28:53] INFO     Starting MCP server 'Veo MCP Server' with          server.py:1502
                             transport 'stdio'

Теперь завершите процесс службы MCP, используя CTRL+C. Эта команда будет вызвана из набора инструментов ADK MCP позже. Мы можем перейти к следующему шагу, чтобы разрешить нашему агенту использовать эти инструменты MCP.

4. 🚀 Подключите сервер Veo MCP к агенту ADK.

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

touch product_photo_editor/mcp_tools.py

Затем скопируйте следующий код в файл product_photo_editor/mcp_tools.py.

from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters


mcp_toolset = MCPToolset(
    connection_params=StdioConnectionParams(
        server_params=StdioServerParameters(
            command="uv",
            args=[
                "run",
                "veo_mcp/main.py",
            ],
        ),
        timeout=120, # seconds
    ),
)

# Option to connect to remote MCP server

# from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams

# mcp_toolset = MCPToolset(
#     connection_params=StreamableHTTPConnectionParams(
#         url="http://localhost:8000/mcp",
#         timeout=120,
#     ),
# )

Приведённый выше код демонстрирует, как можно подключиться к MCP-серверу с помощью ADK MCPToolset. В этом примере мы подключаемся к MCP-серверу, используя канал связи STDIO. В команде мы указываем, как запустить MCP-сервер, и устанавливаем параметр таймаута.

5. 🚀 Изменение параметров вызова инструмента

В описании инструмента сервера MCP мы разработали инструмент generate_video_with_image , который задает строку base64 в качестве параметров. Мы не можем попросить LLM сделать это за нас, поэтому нам необходимо разработать специальную стратегию для решения этой проблемы.

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

  1. Укажите LLM всегда отправлять значение artifact_id, если определенные параметры инструмента требуют отправки строковых данных в формате base64.
  2. Перехватите вызов инструмента в функции before_tool_callback и преобразуйте параметр из artifact_id в его байтовое содержимое, загрузив артефакт и перезаписав аргументы инструмента.

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

2d6142cf5d96830e.png

Для начала подготовим функцию before_tool_callback , создав новый файл product_photo_editor/tool_callbacks.py , выполнив следующую команду.

touch product_photo_editor/tool_callbacks.py

Затем скопируйте следующий код в файл.

# product_photo_editor/tool_callbacks.py

from google.genai.types import Part
from typing import Any
from google.adk.tools.tool_context import ToolContext
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.mcp_tool.mcp_tool import McpTool
import base64
import logging
import json
from mcp.types import CallToolResult


async def before_tool_modifier(
    tool: BaseTool, args: dict[str, Any], tool_context: ToolContext
):
    # Identify which tool input should be modified
    if isinstance(tool, McpTool) and tool.name == "generate_video_with_image":
        logging.info("Modify tool args for artifact: %s", args["image_data"])
        # Get the artifact filename from the tool input argument
        artifact_filename = args["image_data"]
        artifact = await tool_context.load_artifact(filename=artifact_filename)
        file_data = artifact.inline_data.data

        # Convert byte data to base64 string
        base64_data = base64.b64encode(file_data).decode("utf-8")

        # Then modify the tool input argument
        args["image_data"] = base64_data

Приведённый выше код демонстрирует следующие шаги:

  1. Проверьте, является ли вызываемый инструмент объектом McpTool и соответствует ли он целевому вызову инструмента, который мы хотим изменить.
  2. Получите значение аргумента image_data , который запрашивается в формате base64, но мы запрашиваем у LLM возврат artifact_id.
  3. Загрузите артефакт, используя службу артефактов в tool_context
  4. Перезапишите аргументы image_data данными в формате base64.

Теперь нам нужно добавить этот коллбэк в агент, а также немного изменить инструкции, чтобы агент всегда заполнял аргументы инструмента в формате base64 идентификатором артефакта.

Откройте файл product_photo_editor/agent.py и измените его содержимое, добавив следующий код.

# product_photo_editor/agent.py

from google.adk.agents.llm_agent import Agent
from product_photo_editor.custom_tools import edit_product_asset
from product_photo_editor.mcp_tools import mcp_toolset
from product_photo_editor.model_callbacks import before_model_modifier
from product_photo_editor.tool_callbacks import before_tool_modifier
from product_photo_editor.prompt import AGENT_INSTRUCTION

root_agent = Agent(
    model="gemini-2.5-flash",
    name="product_photo_editor",
    description="""A friendly product photo editor assistant that helps small business 
owners edit and enhance their product photos. Perfect for improving photos of handmade 
goods, food products, crafts, and small retail items""",
    instruction=AGENT_INSTRUCTION
    + """
**IMPORTANT: Base64 Argument Rule on Tool Call**

If you found any tool call arguments that requires base64 data,
ALWAYS provide the artifact_id of the referenced file to 
the tool call. NEVER ask user to provide base64 data. 
Base64 data encoding process is out of your 
responsibility and will be handled in another part of the system.
""",
    tools=[
        edit_product_asset,
        mcp_toolset,
    ],
    before_model_callback=before_model_modifier,
    before_tool_callback=before_tool_modifier,
)

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

uv run adk web --port 8080

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

INFO:     Started server process [xxxx]
INFO:     Waiting for application startup.

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

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

Чтобы проверить это, вы можете нажать Ctrl + Click на URL-адресе или нажать кнопку « Предварительный просмотр веб-страницы» в верхней части редактора Cloud Shell и выбрать «Предварительный просмотр на порту 8080».

edc73e971b9fc60c.png

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

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

Generate a slow zoom in and moving from left and right animation

fede23931847cb7e.png

Вы столкнетесь со следующей ошибкой.

6728902ed0b7cc55.png

Почему? Потому что инструмент возвращает результаты непосредственно в виде строки base64, что превышает максимально допустимое количество токенов. Давайте разберемся с этой ошибкой в ​​следующем разделе.

6. 🚀 Модификация ответа инструмента

В этом разделе мы обработаем ответ инструмента от MCP. Мы выполним следующие действия:

  1. Сохраните видеоответ от инструмента в службе артефактов.
  2. Вместо этого верните агенту идентификатор артефакта.

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

2d6142cf5d96830e.png

Для начала реализуем функцию обратного вызова. Откройте файл product_photo_editor/tool_callbacks.py и измените его, добавив реализацию after_tool_modifier

# product_photo_editor/tool_callbacks.py

from google.genai.types import Part
from typing import Any
from google.adk.tools.tool_context import ToolContext
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.mcp_tool.mcp_tool import McpTool
import base64
import logging
import json
from mcp.types import CallToolResult


async def before_tool_modifier(
    tool: BaseTool, args: dict[str, Any], tool_context: ToolContext
):
    # Identify which tool input should be modified
    if isinstance(tool, McpTool) and tool.name == "generate_video_with_image":
        logging.info("Modify tool args for artifact: %s", args["image_data"])
        # Get the artifact filename from the tool input argument
        artifact_filename = args["image_data"]
        artifact = await tool_context.load_artifact(filename=artifact_filename)
        file_data = artifact.inline_data.data

        # Convert byte data to base64 string
        base64_data = base64.b64encode(file_data).decode("utf-8")

        # Then modify the tool input argument
        args["image_data"] = base64_data


async def after_tool_modifier(
    tool: BaseTool,
    args: dict[str, Any],
    tool_context: ToolContext,
    tool_response: dict | CallToolResult,
):
    if isinstance(tool, McpTool) and tool.name == "generate_video_with_image":
        tool_result = json.loads(tool_response.content[0].text)

        # Get the expected response field which contains the video data
        video_data = tool_result["video_data"]
        artifact_filename = f"video_{tool_context.function_call_id}.mp4"

        # Convert base64 string to byte data
        video_bytes = base64.b64decode(video_data)

        # Save the video as artifact
        await tool_context.save_artifact(
            filename=artifact_filename,
            artifact=Part(inline_data={"mime_type": "video/mp4", "data": video_bytes}),
        )

        # Remove the video data from the tool response
        tool_result.pop("video_data")

        # Then modify the tool response to include the artifact filename and remove the base64 string
        tool_result["video_artifact_id"] = artifact_filename
        logging.info(
            "Modify tool response for artifact: %s", tool_result["video_artifact_id"]
        )

        return tool_result

После этого нам нужно наделить нашего агента этой функцией. Откройте файл product_photo_editor/agent.py и измените его следующим образом.

# product_photo_editor/agent.py

from google.adk.agents.llm_agent import Agent
from product_photo_editor.custom_tools import edit_product_asset
from product_photo_editor.mcp_tools import mcp_toolset
from product_photo_editor.model_callbacks import before_model_modifier
from product_photo_editor.tool_callbacks import (
    before_tool_modifier,
    after_tool_modifier,
)
from product_photo_editor.prompt import AGENT_INSTRUCTION

root_agent = Agent(
    model="gemini-2.5-flash",
    name="product_photo_editor",
    description="""A friendly product photo editor assistant that helps small business 
owners edit and enhance their product photos. Perfect for improving photos of handmade 
goods, food products, crafts, and small retail items""",
    instruction=AGENT_INSTRUCTION
    + """
**IMPORTANT: Base64 Argument Rule on Tool Call**

If you found any tool call arguments that requires base64 data,
ALWAYS provide the artifact_id of the referenced file to 
the tool call. NEVER ask user to provide base64 data. 
Base64 data encoding process is out of your 
responsibility and will be handled in another part of the system.
""",
    tools=[
        edit_product_asset,
        mcp_toolset,
    ],
    before_model_callback=before_model_modifier,
    before_tool_callback=before_tool_modifier,
    after_tool_callback=after_tool_modifier,
)

Готово! Теперь вы можете попросить агента не только помочь вам отредактировать фотографию, но и создать для вас видео! Повторите следующую команду ещё раз.

uv run adk web --port 8080

Затем попробуйте создать видео, используя это изображение.

Generate a slow zoom in and moving from left and right animation

fede23931847cb7e.png

Вы увидите сгенерированное видео, как показано в примере ниже, уже сохраненное как артефакт.

29150fa84f85d2fd.png

7. ⭐ Краткое содержание

Теперь давайте вспомним, что мы уже сделали в ходе этой практической работы. Вот основные выводы:

  1. Обработка многомодальных данных (ввод/вывод инструмента) : Усилена стратегия управления многомодальными данными (такими как изображения и видео) для ввода и вывода инструментом за счет использования службы артефактов ADK и специализированных обратных вызовов вместо прямой передачи необработанных байтовых данных.
  2. Интеграция с набором инструментов MCP : Разработан и интегрирован внешний сервер Veo MCP с использованием FastMCP через набор инструментов ADK MCP для добавления возможностей генерации видео в работу агента.
  3. Модификация входных данных инструмента (before_tool_callback) : Реализован обратный вызов для перехвата вызова инструмента generate_video_with_image, преобразующий artifact_id файла (выбранный LLM) в необходимые данные изображения в кодировке base64 для ввода на сервер MCP.
  4. Модификация выходных данных инструмента (after_tool_callback) : Реализован обратный вызов для перехвата большого видеоответа в кодировке base64 от сервера MCP, сохранения видео в качестве нового артефакта и возврата чистой ссылки video_artifact_id в LLM.

8. 🧹 Уборка

Чтобы избежать списания средств с вашего аккаунта Google Cloud за ресурсы, использованные в этом практическом задании, выполните следующие действия:

  1. В консоли Google Cloud перейдите на страницу «Управление ресурсами» .
  2. В списке проектов выберите проект, который хотите удалить, и нажмите кнопку «Удалить» .
  3. В диалоговом окне введите идентификатор проекта, а затем нажмите «Завершить» , чтобы удалить проект.