1. 📖 Wprowadzenie
Te ćwiczenia z programowania pokazują, jak zaprojektować interakcję z narzędziem multimodalnym w zestawie Agent Development Kit (ADK). Jest to konkretny proces, w którym chcesz, aby agent odwoływał się do przesłanego pliku jako danych wejściowych narzędzia, a także rozumiał zawartość pliku wygenerowanego w odpowiedzi narzędzia. Dlatego możliwa jest interakcja taka jak na zrzucie ekranu poniżej. W tym samouczku stworzymy agenta, który pomoże użytkownikowi w edycji zdjęć produktów.
W ramach ćwiczeń z programowania będziesz wykonywać kolejne czynności:
- Przygotowywanie projektu Google Cloud
- Konfigurowanie katalogu roboczego dla środowiska kodowania
- Inicjowanie agenta za pomocą ADK
- Zaprojektuj narzędzie do edycji zdjęć oparte na Gemini 2.5 Flash Image
- Zaprojektuj funkcję wywołania zwrotnego, która będzie obsługiwać przesyłanie obrazów przez użytkowników, zapisywać je jako artefakty i dodawać je jako kontekst do agenta.
- Zaprojektuj funkcję wywołania zwrotnego, która będzie obsługiwać obraz wygenerowany w odpowiedzi narzędzia, zapisywać go jako artefakt i dodawać go do kontekstu agenta.
Omówienie architektury
Ogólna interakcja w tym laboratorium kodu jest przedstawiona na tym diagramie.

Wymagania wstępne
- umiejętność pracy z Pythonem,
- (Opcjonalnie) Podstawowe ćwiczenia z kodowania dotyczące pakietu Agent Development Kit (ADK)
Czego się nauczysz
- Jak wykorzystać kontekst wywołania zwrotnego, aby uzyskać dostęp do usługi artefaktów
- Jak zaprojektować narzędzie z odpowiednim rozpowszechnianiem danych multimodalnych
- Jak zmodyfikować żądanie LLM agenta, aby dodać kontekst artefaktu za pomocą funkcji before_model_callback
- Jak edytować obraz za pomocą Gemini 2.5 Flash Image
Czego potrzebujesz
- przeglądarki Chrome,
- konto Gmail,
- Projekt w chmurze z włączonym kontem rozliczeniowym
Ten przewodnik, przeznaczony dla deweloperów na wszystkich poziomach zaawansowania (w tym dla początkujących), wykorzystuje w przykładowej aplikacji język Python. Znajomość Pythona nie jest jednak wymagana do zrozumienia przedstawionych koncepcji.
2. 🚀 Przygotowywanie konfiguracji środowiska programistycznego warsztatów
Krok 1. Wybierz aktywny projekt w Cloud Console
W konsoli Google Cloud na stronie selektora projektów wybierz lub utwórz projekt Google Cloud (patrz lewa górna sekcja konsoli).

Kliknij go, a zobaczysz listę wszystkich projektów, jak w tym przykładzie:

Wartość wskazana przez czerwone pole to IDENTYFIKATOR PROJEKTU. Będzie ona używana w całym samouczku.
Sprawdź, czy w projekcie Cloud włączone są płatności. Aby to sprawdzić, kliknij ikonę menu ☰ na pasku w lewym górnym rogu, aby wyświetlić menu nawigacyjne, a następnie znajdź menu Płatności.

Jeśli pod tytułem „Konto rozliczeniowe okresu próbnego Google Cloud Platform” w sekcji Płatności / Podsumowanie ( w lewym górnym rogu konsoli Google Cloud ) widzisz nazwę konta, Twój projekt jest gotowy do użycia w tym samouczku. Jeśli nie, wróć na początek tego samouczka i skorzystaj z konta rozliczeniowego w wersji próbnej.

Krok 2. Zapoznaj się z Cloud Shell
W większości samouczków będziesz używać Cloud Shell. Kliknij Aktywuj Cloud Shell u góry konsoli Google Cloud. Jeśli pojawi się prośba o autoryzację, kliknij Autoryzuj.


Po połączeniu z Cloud Shell musimy sprawdzić, czy powłoka ( lub terminal) jest już uwierzytelniona na naszym koncie.
gcloud auth list
Jeśli widzisz swój osobisty adres Gmail, jak w przykładzie poniżej, wszystko jest w porządku.
Credentialed Accounts
ACTIVE: *
ACCOUNT: alvinprayuda@gmail.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
Jeśli nie, spróbuj odświeżyć przeglądarkę i kliknąć Autoryzuj, gdy pojawi się odpowiedni komunikat ( może to zostać przerwane z powodu problemu z połączeniem).
Następnie musimy sprawdzić, czy powłoka jest już skonfigurowana z prawidłowym IDENTYFIKATOREM PROJEKTU. Jeśli w terminalu przed ikoną $ widzisz wartość w nawiasach ( ) ( na zrzucie ekranu poniżej wartość to „adk-multimodal-tool”), oznacza to, że w aktywnej sesji powłoki skonfigurowany jest projekt.

Jeśli wyświetlana wartość jest już prawidłowa, możesz pominąć następne polecenie. Jeśli jednak jest nieprawidłowy lub go brakuje, uruchom to polecenie:
gcloud config set project <YOUR_PROJECT_ID>
Następnie skopiuj katalog roboczy szablonu na potrzeby tego laboratorium z GitHuba, wykonując to polecenie: W katalogu adk-multimodal-tool zostanie utworzony katalog roboczy.
git clone https://github.com/alphinside/adk-mcp-multimodal.git adk-multimodal-tool
Krok 3. Zapoznaj się z edytorem Cloud Shell i skonfiguruj katalog roboczy aplikacji
Teraz możemy skonfigurować edytor kodu, aby wykonywać różne czynności związane z kodowaniem. W tym celu użyjemy edytora Cloud Shell.
Kliknij przycisk Otwórz edytor. Spowoduje to otwarcie edytora Cloud Shell
.
Następnie przejdź do górnej sekcji edytora Cloud Shell i kliknij File->Open Folder (Plik –>Otwórz folder), znajdź katalog username (nazwa użytkownika) i katalog adk-multimodal-tool,a potem kliknij OK. Spowoduje to ustawienie wybranego katalogu jako głównego katalogu roboczego. W tym przykładzie nazwa użytkownika to alvinprayuda, więc ścieżka do katalogu jest widoczna poniżej.


Teraz katalog roboczy edytora Cloud Shell powinien wyglądać tak ( w katalogu adk-multimodal-tool):

Teraz otwórz terminal edytora. Możesz to zrobić, klikając na pasku menu Terminal -> Nowy terminal lub używając skrótu Ctrl + Shift + C. W dolnej części przeglądarki otworzy się okno terminala.

Aktywny terminal powinien znajdować się w katalogu roboczym adk-multimodal-tool. W tym samouczku użyjemy Pythona 3.12 i menedżera projektów Pythona uv, aby uprościć tworzenie wersji Pythona i środowiska wirtualnego oraz zarządzanie nimi. Pakiet uv jest już wstępnie zainstalowany w Cloud Shell.
Uruchom to polecenie, aby zainstalować wymagane zależności w środowisku wirtualnym w katalogu .venv.
uv sync --frozen
Sprawdź plik pyproject.toml, aby zobaczyć zadeklarowane zależności na potrzeby tego samouczka, czyli google-adk, and python-dotenv.
Teraz musimy włączyć wymagane interfejsy API za pomocą polecenia pokazanego poniżej. To może chwilę potrwać.
gcloud services enable aiplatform.googleapis.com
Po pomyślnym wykonaniu polecenia powinien wyświetlić się komunikat podobny do tego poniżej:
Operation "operations/..." finished successfully.
3. 🚀 Zainicjuj agenta ADK
W tym kroku zainicjujemy agenta za pomocą interfejsu wiersza poleceń ADK. Uruchom to polecenie:
uv run adk create product_photo_editor \
--model gemini-2.5-flash \
--project your-project-id \
--region us-central1
To polecenie pomoże Ci szybko podać wymaganą strukturę agenta pokazaną poniżej:
product_photo_editor/ ├── __init__.py ├── .env ├── agent.py
Następnie przygotujmy agenta edytora zdjęć produktów. Najpierw skopiuj plik prompt.py, który jest już w repozytorium, do utworzonego wcześniej katalogu agenta.
cp prompt.py product_photo_editor/prompt.py
Następnie otwórz plik product_photo_editor/agent.py i zmodyfikuj jego zawartość, wklejając poniższy kod:
from google.adk.agents.llm_agent import Agent
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,
)
Teraz masz już podstawowego agenta do edycji zdjęć, z którym możesz rozmawiać, aby prosić o sugestie dotyczące zdjęć. Możesz spróbować wejść z nim w interakcję za pomocą tego polecenia:
uv run adk web --port 8080
Wygeneruje to dane wyjściowe podobne do tych w przykładzie poniżej, co oznacza, że możemy już uzyskać dostęp do interfejsu internetowego.
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)
Aby to sprawdzić, możesz kliknąć adres URL z naciśniętym klawiszem Ctrl lub kliknąć przycisk Podgląd w przeglądarce w górnej części edytora Cloud Shell i wybrać Podejrzyj na porcie 8080.

Wyświetli się strona internetowa, na której w lewym górnym rogu możesz wybrać dostępnych agentów ( w naszym przypadku powinien to być product_photo_editor) i interagować z botem. Spróbuj przesłać w interfejsie czatu ten obraz i zadać te pytania:
what is your suggestion for this photo?

Zobaczysz interakcję podobną do tej poniżej.

Możesz już prosić o sugestie, ale obecnie nie może on wprowadzać zmian za Ciebie. Przejdźmy do następnego kroku, czyli wyposażenia agenta w narzędzia do edycji.
4. 🚀 LLM Request Context Modification - User Uploaded Image
Chcemy, aby nasz agent mógł elastycznie wybierać, który przesłany obraz chce edytować. Narzędzia LLM są jednak zwykle zaprojektowane tak, aby akceptować parametry prostych typów danych, takich jak str czy int. Jest to bardzo odmienny typ danych od danych multimodalnych, które są zwykle postrzegane jako typ danych bytes. Dlatego będziemy potrzebować strategii wykorzystującej koncepcję artefaktów do obsługi tych danych. Zamiast podawać pełne dane w bajtach w parametrze narzędzi, zaprojektujemy narzędzie tak, aby akceptowało nazwę identyfikatora artefaktu.
Strategia ta będzie obejmować 2 kroki:
- zmodyfikować żądanie LLM, aby każdy przesłany plik był powiązany z identyfikatorem artefaktu, i dodać go jako kontekst do LLM;
- Zaprojektuj narzędzie tak, aby akceptowało identyfikatory artefaktów jako parametry wejściowe.
Aby zmodyfikować żądanie LLM, użyjemy funkcji Callback ADK. Dodamy funkcję before_model_callback, która będzie wywoływana tuż przed wysłaniem kontekstu do LLM przez agenta. Ilustrację znajdziesz na obrazie poniżej. 
Aby to zrobić, najpierw utwórz nowy plik product_photo_editor/model_callbacks.py za pomocą tego polecenia:
touch product_photo_editor/model_callbacks.py
Następnie skopiuj do pliku ten kod:
# product_photo_editor/model_callbacks.py
from google.adk.agents.callback_context import CallbackContext
from google.adk.models import LlmResponse, LlmRequest
from google.genai.types import Part
import hashlib
from typing import List
async def before_model_modifier(
callback_context: CallbackContext, llm_request: LlmRequest
) -> LlmResponse | None:
"""Modify LLM request to include artifact references for images."""
for content in llm_request.contents:
if not content.parts:
continue
modified_parts = []
for idx, part in enumerate(content.parts):
# Handle user-uploaded inline images
if part.inline_data:
processed_parts = await _process_inline_data_part(
part, callback_context
)
# Default: keep part as-is
else:
processed_parts = [part]
modified_parts.extend(processed_parts)
content.parts = modified_parts
async def _process_inline_data_part(
part: Part, callback_context: CallbackContext
) -> List[Part]:
"""Process inline data parts (user-uploaded images).
Returns:
List of parts including artifact marker and the image.
"""
artifact_id = _generate_artifact_id(part)
# Save artifact if it doesn't exist
if artifact_id not in await callback_context.list_artifacts():
await callback_context.save_artifact(filename=artifact_id, artifact=part)
return [
Part(
text=f"[User Uploaded Artifact] Below is the content of artifact ID : {artifact_id}"
),
part,
]
def _generate_artifact_id(part: Part) -> str:
"""Generate a unique artifact ID for user uploaded image.
Returns:
Hash-based artifact ID with proper file extension.
"""
filename = part.inline_data.display_name or "uploaded_image"
image_data = part.inline_data.data
# Combine filename and image data for hash
hash_input = filename.encode("utf-8") + image_data
content_hash = hashlib.sha256(hash_input).hexdigest()[:16]
# Extract file extension from mime type
mime_type = part.inline_data.mime_type
extension = mime_type.split("/")[-1]
return f"usr_upl_img_{content_hash}.{extension}"
Funkcja before_model_modifier wykonuje te czynności:
- Uzyskiwanie dostępu do zmiennej
llm_request.contentsi iterowanie treści - Sprawdź, czy część zawiera inline_data ( przesłany plik lub obraz). Jeśli tak, przetwórz dane wbudowane.
- Utwórz identyfikator dla elementu inline_data. W tym przykładzie używamy kombinacji nazwy pliku i danych, aby utworzyć identyfikator skrótu treści.
- Sprawdź, czy identyfikator artefaktu już istnieje. Jeśli nie, zapisz artefakt, używając identyfikatora artefaktu.
- Zmodyfikuj część, aby uwzględnić prompt tekstowy zawierający kontekst identyfikatora artefaktu w przypadku tych danych wbudowanych.
Następnie zmodyfikuj plik product_photo_editor/agent.py, aby wyposażyć agenta w funkcję wywołania zwrotnego.
from google.adk.agents.llm_agent import Agent
from product_photo_editor.model_callbacks import before_model_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 for online stores, social media, and
marketing. Perfect for improving photos of handmade goods, food products, crafts, and small retail items""",
instruction=AGENT_INSTRUCTION,
before_model_callback=before_model_modifier,
)
Teraz możemy spróbować ponownie wejść w interakcję z agentem.
uv run adk web --port 8080
i spróbuj ponownie przesłać plik i rozpocząć czat. Możemy sprawdzić, czy udało nam się zmodyfikować kontekst żądania LLM.


W ten sposób możemy informować LLM o sekwencji i identyfikacji danych multimodalnych. Teraz utwórzmy narzędzie, które będzie wykorzystywać te informacje.
5. 🚀 Interakcja z narzędziami multimodalnymi
Teraz możemy przygotować narzędzie, które jako parametr wejściowy określa też identyfikator artefaktu. Uruchom to polecenie, aby utworzyć nowy plik product_photo_editor/custom_tools.py
touch product_photo_editor/custom_tools.py
Następnie skopiuj ten kod do pliku product_photo_editor/custom_tools.py.
# product_photo_editor/custom_tools.py
from google import genai
from dotenv import load_dotenv
import os
from google.adk.tools import ToolContext
import logging
load_dotenv()
client = genai.Client(
vertexai=True,
project=os.getenv("GOOGLE_CLOUD_PROJECT"),
location=os.getenv("GOOGLE_CLOUD_LOCATION"),
)
async def edit_product_asset(
tool_context: ToolContext,
change_description: str,
image_artifact_ids: list = [],
) -> dict[str, str]:
"""Modify an existing product photo or combine multiple product photos.
This tool lets you make changes to product photos. You can:
- Edit a single photo (change background, lighting, colors, etc.)
- Combine multiple products into one photo (arrange them side by side, create bundles, etc.)
**IMPORTANT**:
- Make ONE type of change per tool call (background OR lighting OR props OR arrangement)
- For complex edits, chain multiple tool calls together
- BE AS DETAILED AS POSSIBLE in the change_description for best results!
Args:
change_description: What do you want to do? BE VERY DETAILED AND SPECIFIC!
**The more details you provide, the better the result.**
Focus on ONE type of change, but describe it thoroughly.
For BACKGROUND changes:
- "change background to soft pure white with subtle gradient from top to bottom, clean and minimal aesthetic"
- "replace background with rustic dark wood table surface with natural grain texture visible, warm brown tones"
For ADDING PROPS:
- "add fresh pink roses and eucalyptus leaves arranged naturally around the product on the left and right sides,
with some petals scattered in front"
- "add fresh basil leaves and cherry tomatoes scattered around the product naturally"
For LIGHTING changes:
- "add soft natural window light coming from the left side at 45 degree angle, creating gentle shadows on the
right side, warm morning atmosphere"
- "increase brightness with soft diffused studio lighting from above, eliminating harsh shadows"
For ARRANGEMENT/POSITIONING:
- "reposition product to be perfectly centered in frame with equal space on all sides"
- "arrange these three products in a horizontal line, evenly spaced with 2 inches between each"
Note: When combining multiple products, you can include background/lighting in the initial arrangement since it's
one cohesive setup
image_artifact_ids: List of image IDs to edit or combine.
- For single image: provide a list with one item (e.g., ["product.png"])
- For multiple images: provide a list with multiple items (e.g., ["product1.png", "product2.png"])
Use multiple images to combine products into one photo.
Returns:
dict with keys:
- 'tool_response_artifact_id': Artifact ID for the edited image
- 'tool_input_artifact_ids': Comma-separated list of input artifact IDs
- 'edit_prompt': The full edit prompt used
- 'status': Success or error status
- 'message': Additional information or error details
"""
try:
# Validate input
if not image_artifact_ids:
return {
"status": "error",
"tool_response_artifact_id": "",
"tool_input_artifact_ids": "",
"edit_prompt": change_description,
"message": "No images provided. Please provide image_artifact_ids as a list.",
}
# Load all images
image_artifacts = []
for img_id in image_artifact_ids:
artifact = await tool_context.load_artifact(filename=img_id)
if artifact is None:
logging.error(f"Artifact {img_id} not found")
return {
"status": "error",
"tool_response_artifact_id": "",
"tool_input_artifact_ids": "",
"edit_prompt": change_description,
"message": f"Artifact {img_id} not found",
}
image_artifacts.append(artifact)
# Build edit prompt
if len(image_artifacts) > 1:
full_edit_prompt = (
f"{change_description}. "
f"Combine these {len(image_artifacts)} product images together. "
"IMPORTANT: Preserve each product's original appearance, shape, color, and design as faithfully as possible. "
"Only modify for aesthetic enhancements (lighting, background, composition) or viewing angle adjustments. "
"Do not alter the core product features, branding, or characteristics."
)
else:
full_edit_prompt = (
f"{change_description}. "
"IMPORTANT: Preserve the product's original appearance, shape, color, and design as faithfully as possible. "
"Only modify for aesthetic enhancements (lighting, background, composition) or viewing angle adjustments. "
"Do not alter the core product features, branding, or characteristics."
)
# Build contents list: all images followed by the prompt
contents = image_artifacts + [full_edit_prompt]
response = await client.aio.models.generate_content(
model="gemini-2.5-flash-image",
contents=contents,
config=genai.types.GenerateContentConfig(
response_modalities=["Image"]
),
)
artifact_id = ""
logging.info("Gemini Flash Image: response.candidates: ", response.candidates)
for part in response.candidates[0].content.parts:
if part.inline_data is not None:
artifact_id = f"edited_img_{tool_context.function_call_id}.png"
await tool_context.save_artifact(filename=artifact_id, artifact=part)
input_ids_str = ", ".join(image_artifact_ids)
return {
"status": "success",
"tool_response_artifact_id": artifact_id,
"tool_input_artifact_ids": input_ids_str,
"edit_prompt": full_edit_prompt,
"message": f"Image edited successfully using {len(image_artifacts)} input image(s)",
}
except Exception as e:
logging.error(e)
input_ids_str = ", ".join(image_artifact_ids) if image_artifact_ids else ""
return {
"status": "error",
"tool_response_artifact_id": "",
"tool_input_artifact_ids": input_ids_str,
"edit_prompt": change_description,
"message": f"Error editing image: {str(e)}",
}
Kod narzędzia wykonuje te czynności:
- Dokumentacja narzędzia szczegółowo opisuje sprawdzone metody wywoływania narzędzia.
- Sprawdź, czy lista image_artifact_ids nie jest pusta.
- Wczytaj wszystkie artefakty obrazu z tool_context, używając podanych identyfikatorów artefaktów.
- Tworzenie promptu do edycji: dodawanie instrukcji, aby profesjonalnie połączyć (wiele obrazów) lub edytować (jeden obraz)
- Wywołanie modelu Gemini 2.5 Flash Image z wynikiem w postaci obrazu i wyodrębnienie wygenerowanego obrazu
- Zapisywanie edytowanego obrazu jako nowego artefaktu
- Zwraca uporządkowaną odpowiedź zawierającą: stan, identyfikator artefaktu wyjściowego, identyfikatory wejściowe, pełny prompt i wiadomość.
Na koniec możemy wyposażyć agenta w to narzędzie. Zmień zawartość pliku product_photo_editor/agent.py na poniższy kod.
from google.adk.agents.llm_agent import Agent
from product_photo_editor.custom_tools import edit_product_asset
from product_photo_editor.model_callbacks import before_model_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 for online stores, social media, and
marketing. Perfect for improving photos of handmade goods, food products, crafts, and small retail items""",
instruction=AGENT_INSTRUCTION,
tools=[
edit_product_asset,
],
before_model_callback=before_model_modifier,
)
Nasz agent jest już w 80% gotowy do pomocy w edycji zdjęć. Spróbujmy z nim porozmawiać.
uv run adk web --port 8080
Spróbujmy ponownie użyć tego obrazu z innym promptem:
put these muffins in a white plate aesthetically

Możesz zobaczyć taką interakcję i w końcu zobaczyć, jak agent edytuje dla Ciebie zdjęcie.

Gdy sprawdzisz szczegóły wywołania funkcji, zobaczysz identyfikator artefaktu przesłanego przez użytkownika obrazu.

Teraz agent może Ci pomóc w ciągłym ulepszaniu zdjęcia. Może też wykorzystać zmodyfikowane zdjęcie w kolejnej instrukcji edycji, ponieważ w odpowiedzi narzędzia podajemy identyfikator artefaktu.
Jednak w obecnej postaci agent nie może zobaczyć ani zrozumieć wyniku edycji obrazu, co widać na powyższym przykładzie. Dzieje się tak, ponieważ odpowiedź narzędzia, którą przekazujemy agentowi, zawiera tylko identyfikator artefaktu, a nie samą zawartość w bajtach. Niestety nie możemy umieścić zawartości w bajtach bezpośrednio w odpowiedzi narzędzia, ponieważ spowoduje to błąd. Dlatego w funkcji wywołania zwrotnego musimy mieć kolejną gałąź logiki, aby dodać zawartość bajtową jako dane wbudowane z wyniku odpowiedzi narzędzia.
6. 🚀 Modyfikacja kontekstu żądania LLM – obraz odpowiedzi funkcji
Zmodyfikujmy wywołanie zwrotne before_model_modifier, aby po odpowiedzi narzędzia dodać edytowane dane bajtowe obrazu. Dzięki temu agent w pełni zrozumie wynik.
Otwórz plik product_photo_editor/model_callbacks.py i zmień jego zawartość, aby wyglądała jak poniżej.
# product_photo_editor/model_callbacks.py
from google.adk.agents.callback_context import CallbackContext
from google.adk.models import LlmResponse, LlmRequest
from google.genai.types import Part
import hashlib
from typing import List
async def before_model_modifier(
callback_context: CallbackContext, llm_request: LlmRequest
) -> LlmResponse | None:
"""Modify LLM request to include artifact references for images."""
for content in llm_request.contents:
if not content.parts:
continue
modified_parts = []
for idx, part in enumerate(content.parts):
# Handle user-uploaded inline images
if part.inline_data:
processed_parts = await _process_inline_data_part(
part, callback_context
)
# Handle function response parts for image generation/editing
elif part.function_response:
if part.function_response.name in [
"edit_product_asset",
]:
processed_parts = await _process_function_response_part(
part, callback_context
)
else:
processed_parts = [part]
# Default: keep part as-is
else:
processed_parts = [part]
modified_parts.extend(processed_parts)
content.parts = modified_parts
async def _process_inline_data_part(
part: Part, callback_context: CallbackContext
) -> List[Part]:
"""Process inline data parts (user-uploaded images).
Returns:
List of parts including artifact marker and the image.
"""
artifact_id = _generate_artifact_id(part)
# Save artifact if it doesn't exist
if artifact_id not in await callback_context.list_artifacts():
await callback_context.save_artifact(filename=artifact_id, artifact=part)
return [
Part(
text=f"[User Uploaded Artifact] Below is the content of artifact ID : {artifact_id}"
),
part,
]
def _generate_artifact_id(part: Part) -> str:
"""Generate a unique artifact ID for user uploaded image.
Returns:
Hash-based artifact ID with proper file extension.
"""
filename = part.inline_data.display_name or "uploaded_image"
image_data = part.inline_data.data
# Combine filename and image data for hash
hash_input = filename.encode("utf-8") + image_data
content_hash = hashlib.sha256(hash_input).hexdigest()[:16]
# Extract file extension from mime type
mime_type = part.inline_data.mime_type
extension = mime_type.split("/")[-1]
return f"usr_upl_img_{content_hash}.{extension}"
async def _process_function_response_part(
part: Part, callback_context: CallbackContext
) -> List[Part]:
"""Process function response parts and append artifacts.
Returns:
List of parts including the original function response and artifact.
"""
artifact_id = part.function_response.response.get("tool_response_artifact_id")
if not artifact_id:
return [part]
artifact = await callback_context.load_artifact(filename=artifact_id)
return [
part, # Original function response
Part(
text=f"[Tool Response Artifact] Below is the content of artifact ID : {artifact_id}"
),
artifact,
]
W zmodyfikowanym kodzie powyżej dodaliśmy te funkcje:
- Sprawdź, czy Part jest odpowiedzią funkcji i czy znajduje się na liście nazw narzędzi, aby umożliwić modyfikację treści.
- Jeśli identyfikator artefaktu z odpowiedzi narzędzia istnieje, załaduj zawartość artefaktu.
- Zmodyfikuj treść, aby zawierała dane edytowanego obrazu z odpowiedzi narzędzia.
Teraz możemy sprawdzić, czy agent w pełni rozumie edytowany obraz na podstawie odpowiedzi narzędzia.

Świetnie, mamy już agenta, który obsługuje multimodalny przepływ interakcji z naszym własnym narzędziem niestandardowym.
Teraz możesz spróbować interakcji z agentem w bardziej złożonym procesie, np. dodając nowy element ( mrożoną kawę latte), aby ulepszyć zdjęcie.


7. ⭐ Podsumowanie
Podsumujmy teraz, co już zrobiliśmy w tym laboratorium. Oto najważniejsze informacje:
- Obsługa danych multimodalnych: opracowano strategię zarządzania danymi multimodalnymi (np. obrazami) w kontekście LLM przez używanie usługi Artifacts w ADK zamiast przekazywania surowych danych w postaci bajtów bezpośrednio przez argumenty lub odpowiedzi narzędzia.
before_model_callbackWykorzystanie: wykorzystanobefore_model_callbackdo przechwycenia i zmodyfikowaniaLlmRequestprzed wysłaniem go do LLM. Klikamy kolejno:
- Przesłane przez użytkownika: wdrożono logikę wykrywania danych wstawionych przez użytkownika, zapisywania ich jako unikalnie identyfikowanego artefaktu (np.
usr_upl_img_...) i wstrzykiwać do kontekstu prompta tekst odwołujący się do identyfikatora artefaktu, co umożliwi LLM wybranie odpowiedniego pliku do użycia narzędzia. - Odpowiedzi narzędzi: wdrożono logikę wykrywania odpowiedzi na konkretne funkcje narzędzi, które tworzą artefakty (np. edytowane obrazy), oraz wczytywania nowo zapisanych artefaktów (np.
edited_img_...) i wstrzyknąć do strumienia kontekstu zarówno odwołanie do identyfikatora artefaktu, jak i treść obrazu.
- Narzędzie niestandardowe: utworzono niestandardowe narzędzie w Pythonie (
edit_product_asset), które akceptujeimage_artifact_idslistę (identyfikatory ciągów znaków) i używaToolContextdo pobierania rzeczywistych danych obrazu z usługi Artifacts. - Integracja modelu generowania obrazów: zintegrowaliśmy w narzędziu niestandardowym model obrazów Gemini 2.5 Flash, aby wykonywać edycję obrazów na podstawie szczegółowego opisu tekstowego.
- Ciągła interakcja multimodalna: zapewniliśmy, że agent może prowadzić ciągłą sesję edycji, rozumiejąc wyniki własnych wywołań narzędzi (edytowany obraz) i używając tych danych wyjściowych jako danych wejściowych dla kolejnych instrukcji.
8. ➡️ Następne wyzwanie
Gratulacje! Część 1 interakcji z narzędziem multimodalnym ADK została ukończona. W tym samouczku skupimy się na interakcji z narzędziami niestandardowymi. Możesz teraz przejść do następnego kroku, aby dowiedzieć się, jak korzystać z wielomodowego zestawu narzędzi MCP. Przejdź do następnego modułu
9. 🧹 Czyszczenie
Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby użyte w tym laboratorium, wykonaj te czynności:
- W konsoli Google Cloud otwórz stronę Zarządzanie zasobami.
- Z listy projektów wybierz projekt do usunięcia, a potem kliknij Usuń.
- W oknie wpisz identyfikator projektu i kliknij Wyłącz, aby usunąć projekt.