Lab 7: Building a Visual Product Catalog Analyzer
Goalโ
In this lab, you will build a multimodal agent that can analyze a product image and generate a marketing description. This will teach you how to handle image inputs and perform programmatic execution with a vision-capable agent.
The Architectureโ
You will use the App and Runner pattern you learned in Module 6 to build a Python script that:
- Loads local image files as
types.Partobjects. - Constructs a multimodal prompt containing both text and image data.
- Executes the agent using
run_async.
Step 1: Create and Prepare the Projectโ
Note: unlike other modules,
visual_catalogmust live directly inside this module's own folder (module07-multimodal-and-images/) rather than in your sharedadk-trainingproject -- see point 3 below for why. If this is the first agent you're creating from inside this folder, set up a local environment here first:uv init --python 3.10 && uv add "google-adk>=2.1.0" python-dotenv.
- Create the agent project:
uv run adk create visual_catalog - Navigate into the new directory:
cd visual_catalog - Sample images: two sample product photos,
headphones.jpgandlaptop.jpg, are already provided in this module's folder (module07-multimodal-and-images/), one level up fromvisual_catalog/. That's why you must createvisual_catalogdirectly inside this module's folder (as done in step 1 above) โ the script below loads them with a relative../path. Feel free to swap in your own images later, as long as the filenames match. - Set up your
.envfile. Vision models require a Vertex AI configuration. Ensure your.envfile looks like this:GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT="your-project-id"
GOOGLE_CLOUD_LOCATION="us-central1"
Step 2: Implement the Multimodal Scriptโ
Exercise: Create a file named main.py in your visual_catalog directory. Your task is to complete the core logic inside the analyze_product method by filling in the # TODO sections.
Note on Sessions: When using
run_asyncprogrammatically, you must explicitly create a session in the runner's session service before sending a message, just as you did withcurlin Module 5.
import asyncio
import logging
import os
from google.adk import Agent
from google.adk.apps import App
from google.adk.runners import InMemoryRunner
from google.genai import types
from dotenv import load_dotenv
# Optional: Suppress noisy ADK/httpx logs
logging.getLogger("google.adk").setLevel(logging.WARNING)
# Helper function to load an image from a local file path
def load_image_from_file(path: str) -> types.Part:
"""Load image from file and return a types.Part object."""
with open(path, 'rb') as f:
image_bytes = f.read()
mime_type = 'image/png' if path.lower().endswith('.png') else 'image/jpeg'
return types.Part(
inline_data=types.Blob(data=image_bytes, mime_type=mime_type)
)
class VisualCatalogApp:
def __init__(self):
# 1. Define the Intelligence
self.agent = Agent(
model='gemini-3.5-flash',
name='catalog_writer',
instruction="You are a professional product catalog writer. Analyze the image and write a compelling description."
)
# 2. Build the Infrastructure
self.app = App(name="visual_catalog", root_agent=self.agent)
self.runner = InMemoryRunner(app=self.app)
async def analyze_product(self, product_id: str, image_path: str):
print(f"\n--- Analyzing Product: {product_id} ---")
user_id = "catalog_admin"
session_id = f"sess_{product_id}"
# TODO: 1. Create the session explicitly in the session service
# Hint: Use self.runner.session_service.create_session(...)
...
# TODO: 2. Load the image using the helper function
image_part = ...
# TODO: 3. Create the multimodal message (types.Content object)
msg = types.Content(
role="user",
parts=[
types.Part(text=f"Analyze this image for product {product_id}."),
image_part
]
)
# TODO: 4. Run the agent using run_async
print("๐ธ Sending image to Gemini...")
async for event in self.runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=msg
):
if event.is_final_response():
print(f"โ
Description:\n{event.content.parts[0].text}\n")
async def main():
load_dotenv()
catalog = VisualCatalogApp()
# Images are expected to be in the parent directory for this lab
products = [
('HEADPHONES-01', '../headphones.jpg'),
('LAPTOP-02', '../laptop.jpg'),
]
for product_id, path in products:
if os.path.exists(path):
await catalog.analyze_product(product_id, path)
await asyncio.sleep(1)
else:
print(f"โ ๏ธ Image not found: {path}")
if __name__ == '__main__':
asyncio.run(main())
Cleanup: Unlike other modules, this lab's project was created directly inside
module07-multimodal-and-images/(the actual course repo folder) rather than in your separateadk-trainingscratch directory. Once you're done, deletevisual_catalog/,.venv/,pyproject.toml, anduv.lockfrom inside this module's folder so they don't accidentally get committed to the repo.
Lab Summaryโ
You have successfully built a multimodal agent! You have learned:
- How to package image bytes into a
types.Partobject. - The importance of explicitly creating a Session when using
run_asyncprogrammatically. - How to construct a structured
types.Contentobject for complex inputs.
This completes Part 1: Foundations. You now know how to build, configure, run, and feed multimodal input to an agent. In Part 2, you'll start extending your agent's capabilities with tools.
Self-Reflection Questionsโ
- Why did we have to call
create_sessionmanually this time, whereas in Module 6'srun_debugwe didn't? - How does the
InMemoryRunnersimplify the setup of theSessionServicecompared to a baseRunner? - If you wanted to analyze a PDF document instead of an image, which
mime_typewould you use in thetypes.Blob?
๐ Reference Solution Vaultโ
Module 07 Reference Solution Vault
Active Comprehension Gate โข Zero Guessable URLs โข Guided Verification