মাল্টিমোডাল টুল ইন্টারঅ্যাকশন সহ ADK : পর্ব ১ (মডেল কলব্যাক সহ কাস্টম টুল)

১. 📖 ভূমিকা

এই কোডল্যাবটি এজেন্ট ডেভেলপমেন্ট কিট (ADK)-এ কীভাবে একটি মাল্টিমোডাল টুল ইন্টারঅ্যাকশন ডিজাইন করতে হয় তা প্রদর্শন করে। এটি একটি নির্দিষ্ট ফ্লো, যেখানে আপনি চান যে এজেন্টটি একটি টুলের ইনপুট হিসাবে আপলোড করা ফাইলটিকে ব্যবহার করুক এবং টুলের প্রতিক্রিয়া থেকে প্রাপ্ত ফাইলের বিষয়বস্তুও বুঝতে পারুক। এর ফলে, নীচের স্ক্রিনশটে দেখানো ইন্টারঅ্যাকশনের মতো পরিস্থিতি তৈরি হয়। এই টিউটোরিয়ালে আমরা এমন একটি এজেন্ট তৈরি করতে যাচ্ছি যা ব্যবহারকারীকে তার প্রোডাক্ট শোকেসের জন্য একটি ভালো ছবি সম্পাদনা করতে সাহায্য করতে সক্ষম।

কোডল্যাবের মাধ্যমে, আপনি নিম্নলিখিত ধাপে ধাপে পদ্ধতিটি অনুসরণ করবেন:

  1. গুগল ক্লাউড প্রজেক্ট প্রস্তুত করুন
  2. কোডিং পরিবেশের জন্য কাজের ডিরেক্টরি তৈরি করুন।
  3. ADK ব্যবহার করে এজেন্ট শুরু করুন
  4. জেমিনি ২.৫ ফ্ল্যাশ ইমেজ দ্বারা চালিত ছবি সম্পাদনা করার জন্য একটি টুল ডিজাইন করুন।
  5. ব্যবহারকারীর ছবি আপলোড পরিচালনা করতে, সেটিকে আর্টিফ্যাক্ট হিসেবে সংরক্ষণ করতে এবং এজেন্টের কনটেক্সট হিসেবে যুক্ত করতে একটি কলব্যাক ফাংশন ডিজাইন করুন।
  6. টুলের প্রতিক্রিয়ায় তৈরি হওয়া ইমেজ হ্যান্ডেল করার জন্য একটি কলব্যাক ফাংশন ডিজাইন করুন, সেটিকে আর্টিফ্যাক্ট হিসেবে সেভ করুন এবং এজেন্টের কনটেক্সট হিসেবে যুক্ত করুন।

স্থাপত্যের সংক্ষিপ্ত বিবরণ

এই কোডল্যাবের সামগ্রিক মিথস্ক্রিয়া নিম্নলিখিত ডায়াগ্রামে দেখানো হয়েছে।

e07eaa83c1615ae7.jpeg

পূর্বশর্ত

  • পাইথন নিয়ে কাজ করতে স্বাচ্ছন্দ্যবোধ করি।
  • (ঐচ্ছিক) এজেন্ট ডেভেলপমেন্ট কিট (ADK) সম্পর্কিত মৌলিক কোডল্যাব
  1. goo.gle/adk-foundation
  2. goo.gle/adk-using-tools

আপনি যা শিখবেন

  • আর্টিফ্যাক্ট পরিষেবা অ্যাক্সেস করতে কলব্যাক কনটেক্সট কীভাবে ব্যবহার করবেন
  • সঠিক মাল্টিমোডাল ডেটা প্রোপাগেশন সহ টুল কীভাবে ডিজাইন করবেন
  • before_model_callback-এর মাধ্যমে আর্টিফ্যাক্ট কনটেক্সট যোগ করতে এজেন্ট llm রিকোয়েস্টটি কীভাবে পরিবর্তন করবেন
  • জেমিনি ২.৫ ফ্ল্যাশ ইমেজ ব্যবহার করে কীভাবে ছবি সম্পাদনা করবেন

আপনার যা যা লাগবে

  • ক্রোম ওয়েব ব্রাউজার
  • একটি জিমেইল অ্যাকাউন্ট
  • বিলিং অ্যাকাউন্ট সক্রিয় করা একটি ক্লাউড প্রজেক্ট

সকল স্তরের (শিক্ষানবিশ সহ) ডেভেলপারদের জন্য ডিজাইন করা এই কোডল্যাবটির নমুনা অ্যাপ্লিকেশনে পাইথন ব্যবহার করা হয়েছে। তবে, এখানে উপস্থাপিত ধারণাগুলো বোঝার জন্য পাইথন জ্ঞানের প্রয়োজন নেই।

২. 🚀 ওয়ার্কশপ উন্নয়ন সেটআপ প্রস্তুত করা

ধাপ ১: ক্লাউড কনসোলে সক্রিয় প্রজেক্ট নির্বাচন করুন

গুগল ক্লাউড কনসোলের প্রজেক্ট সিলেক্টর পেজে, একটি গুগল ক্লাউড প্রজেক্ট নির্বাচন করুন বা তৈরি করুন (আপনার কনসোলের উপরের বাম অংশ দেখুন)।

6069be756af6452b.png

এটিতে ক্লিক করলে, আপনি এই উদাহরণের মতো আপনার সমস্ত প্রকল্পের একটি তালিকা দেখতে পাবেন।

dd8fcf0428ab868f.png

লাল বাক্স দ্বারা নির্দেশিত মানটি হলো প্রজেক্ট আইডি এবং এই মানটি পুরো টিউটোরিয়াল জুড়ে ব্যবহার করা হবে।

আপনার ক্লাউড প্রজেক্টের জন্য বিলিং চালু আছে কিনা তা নিশ্চিত করুন। এটি পরীক্ষা করতে, আপনার উপরের বাম দিকের বারে থাকা বার্গার আইকনে ☰ ক্লিক করুন, যেটি নেভিগেশন মেনু দেখাবে এবং বিলিং মেনুটি খুঁজুন।

db07810b26fc61d6.png

আপনার ক্লাউড কনসোলের উপরের বাম দিকের অংশে থাকা 'বিলিং / ওভারভিউ ' শিরোনামের অধীনে যদি "গুগল ক্লাউড প্ল্যাটফর্ম ট্রায়াল বিলিং অ্যাকাউন্ট" দেখতে পান, তাহলে আপনার প্রজেক্টটি এই টিউটোরিয়ালের জন্য ব্যবহারের জন্য প্রস্তুত। অন্যথায়, এই টিউটোরিয়ালের শুরুতে ফিরে যান এবং ট্রায়াল বিলিং অ্যাকাউন্টটি রিডিম করুন।

45539d4ac57dd995.png

ধাপ ২: ক্লাউড শেল-এর সাথে পরিচিত হন

টিউটোরিয়ালের বেশিরভাগ অংশেই আপনি ক্লাউড শেল ব্যবহার করবেন। গুগল ক্লাউড কনসোলের উপরে থাকা ‘Activate Cloud Shell’-এ ক্লিক করুন। যদি এটি আপনাকে অনুমোদন করতে বলে, তাহলে ‘Authorize’-এ ক্লিক করুন।

26f20e837ff06119.png

79b06cc89a99f840.png

ক্লাউড শেলে সংযুক্ত হওয়ার পর, আমাদের যাচাই করে দেখতে হবে যে শেলটি (বা টার্মিনালটি) আমাদের অ্যাকাউন্ট দিয়ে আগে থেকেই প্রমাণীকৃত আছে কি না।

gcloud auth list

আপনার ব্যক্তিগত জিমেইল যদি নিচের উদাহরণের মতো আউটপুট দেখায়, তাহলে সবকিছু ঠিক আছে।

Credentialed Accounts

ACTIVE: *
ACCOUNT: alvinprayuda@gmail.com

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

অন্যথায়, আপনার ব্রাউজার রিফ্রেশ করার চেষ্টা করুন এবং অনুরোধ করা হলে ' Authorize'- এ ক্লিক করুন (সংযোগ সমস্যার কারণে এটি বাধাগ্রস্ত হতে পারে)।

এরপর, আমাদের এটাও যাচাই করতে হবে যে শেলটি আপনার সঠিক প্রজেক্ট আইডিতে আগে থেকেই কনফিগার করা আছে কিনা। যদি আপনি টার্মিনালে $ আইকনের আগে ( ) বন্ধনীর ভেতরে কোনো মান দেখতে পান (নিচের স্ক্রিনশটে, মানটি হলো "adk-multimodal-tool" ), তাহলে এই মানটি আপনার সক্রিয় শেল সেশনের জন্য কনফিগার করা প্রজেক্টটি নির্দেশ করে।

10a99ff80839b635.png

প্রদর্শিত মানটি যদি ইতিমধ্যেই সঠিক হয়, তাহলে আপনি পরবর্তী কমান্ডটি এড়িয়ে যেতে পারেন। তবে যদি এটি সঠিক না হয় বা অনুপস্থিত থাকে, তাহলে নিম্নলিখিত কমান্ডটি চালান।

gcloud config set project <YOUR_PROJECT_ID>

এরপর, গিটহাব থেকে এই কোডল্যাবের টেমপ্লেট ওয়ার্কিং ডিরেক্টরিটি ক্লোন করুন এবং নিচের কমান্ডটি চালান। এটি adk-multimodal-tool ডিরেক্টরিতে ওয়ার্কিং ডিরেক্টরিটি তৈরি করবে।

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

ধাপ ৩: ক্লাউড শেল এডিটর সম্পর্কে জানুন এবং অ্যাপ্লিকেশন ওয়ার্কিং ডিরেক্টরি সেটআপ করুন।

এখন, আমরা কোডিং করার জন্য আমাদের কোড এডিটর সেট আপ করতে পারি। এর জন্য আমরা ক্লাউড শেল এডিটর ব্যবহার করব।

ওপেন এডিটর বাটনে ক্লিক করুন, এটি একটি ক্লাউড শেল এডিটর খুলবে। 168eacea651b086c.png

এরপর, ক্লাউড শেল এডিটরের উপরের অংশে যান এবং File->Open Folder-এ ক্লিক করুন, আপনার ইউজারনেম ডিরেক্টরি এবং adk-multimodal-tool ডিরেক্টরিটি খুঁজুন, তারপর OK বোতামে ক্লিক করুন। এটি নির্বাচিত ডিরেক্টরিটিকে প্রধান ওয়ার্কিং ডিরেক্টরি হিসেবে সেট করবে। এই উদাহরণে, ইউজারনেম হলো alvinprayuda , তাই ডিরেক্টরি পাথটি নিচে দেখানো হলো।

8eb3f593141dbcbf.png

a4860f6be228d864.png

এখন, আপনার ক্লাউড শেল এডিটর ওয়ার্কিং ডিরেক্টরিটি ( adk-multimodal-tool এর ভিতরে) দেখতে এইরকম হবে।

aa2edaf29303167f.png

এখন, এডিটরের জন্য টার্মিনালটি খুলুন। আপনি মেনু বারে থাকা Terminal -> New Terminal- এ ক্লিক করে এটি করতে পারেন, অথবা Ctrl + Shift + C ব্যবহার করতে পারেন, এটি ব্রাউজারের নিচের অংশে একটি টার্মিনাল উইন্ডো খুলবে।

74d314f6ff34965b.png

আপনার বর্তমান সক্রিয় টার্মিনালটি adk-multimodal-tool ওয়ার্কিং ডিরেক্টরির ভিতরে থাকা উচিত। আমরা এই কোডল্যাবে পাইথন ৩.১২ ব্যবহার করব এবং পাইথন ভার্সন ও ভার্চুয়াল এনভায়রনমেন্ট তৈরি ও পরিচালনার কাজ সহজ করার জন্য uv পাইথন প্রজেক্ট ম্যানেজার ব্যবহার করব। এই uv প্যাকেজটি ক্লাউড শেলে আগে থেকেই ইনস্টল করা আছে।

.venv ডিরেক্টরিতে ভার্চুয়াল এনভায়রনমেন্টের জন্য প্রয়োজনীয় ডিপেন্ডেন্সিগুলো ইনস্টল করতে এই কমান্ডটি চালান।

uv sync --frozen

এই টিউটোরিয়ালের জন্য ঘোষিত ডিপেন্ডেন্সিগুলো, যেগুলো হলো google-adk, and python-dotenv , তা দেখতে pyproject.toml ফাইলটি দেখুন।

এখন, নিচে দেখানো কমান্ডের মাধ্যমে আমাদের প্রয়োজনীয় এপিআইগুলো সক্রিয় করতে হবে। এতে কিছুটা সময় লাগতে পারে।

gcloud services enable aiplatform.googleapis.com

কমান্ডটি সফলভাবে কার্যকর হলে, আপনি নিচে দেখানো বার্তার মতো একটি বার্তা দেখতে পাবেন:

Operation "operations/..." finished successfully.

৩. 🚀 ADK এজেন্ট চালু করুন

এই ধাপে, আমরা ADK CLI ব্যবহার করে আমাদের এজেন্টকে ইনিশিয়ালাইজ করব, নিম্নলিখিত কমান্ডটি চালান।

uv run adk create product_photo_editor \
   --model gemini-2.5-flash \
   --project your-project-id \
   --region us-central1

এই কমান্ডটি আপনাকে নিচে দেখানো আপনার এজেন্টের জন্য প্রয়োজনীয় কাঠামোটি দ্রুত প্রদান করতে সাহায্য করবে:

product_photo_editor/
├── __init__.py
├── .env
├── agent.py

এরপরে, চলুন আমাদের প্রোডাক্ট ফটো এডিটর এজেন্ট প্রস্তুত করি। প্রথমে, রিপোজিটরিতে আগে থেকেই অন্তর্ভুক্ত prompt.py ফাইলটি আপনার পূর্বে তৈরি করা এজেন্ট ডিরেক্টরিতে কপি করুন।

cp prompt.py product_photo_editor/prompt.py

এরপর, product_photo_editor/agent.py ফাইলটি খুলুন এবং নিচের কোড দিয়ে এর ভেতরের বিষয়বস্তু পরিবর্তন করুন।

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

এখন, আপনি আপনার মূল ফটো এডিটর এজেন্টটি পাবেন, যার সাথে আপনি আপনার ফটোগুলোর জন্য পরামর্শ চাইতে ইতোমধ্যেই আলাপ করতে পারবেন। আপনি এই কমান্ডটি ব্যবহার করে এটির সাথে যোগাযোগ করার চেষ্টা করতে পারেন।

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)

এখন, এটি পরীক্ষা করার জন্য আপনি URL-টির উপর Ctrl + Click করতে পারেন অথবা আপনার Cloud Shell Editor-এর উপরের অংশে থাকা Web Preview বাটনটিতে ক্লিক করে পোর্ট 8080-এ Preview নির্বাচন করতে পারেন।

edc73e971b9fc60c.png

আপনি নিম্নলিখিত ওয়েব পেজটি দেখতে পাবেন যেখানে আপনি উপরের বাম দিকের ড্রপ-ডাউন বোতাম থেকে উপলব্ধ এজেন্টদের (আমাদের ক্ষেত্রে এটি product_photo_editor হওয়া উচিত) নির্বাচন করতে এবং বটটির সাথে যোগাযোগ করতে পারবেন। চ্যাট ইন্টারফেসে নিম্নলিখিত ছবিটি আপলোড করার চেষ্টা করুন এবং নিম্নলিখিত প্রশ্নগুলি জিজ্ঞাসা করুন।

what is your suggestion for this photo?

a5ff3bc6c19a29ec.jpeg

আপনি নীচে দেখানো অনুরূপ মিথস্ক্রিয়া দেখতে পাবেন।

c1da4f7cf1466be6.png

আপনি ইতিমধ্যেই কিছু পরামর্শ চাইতে পারেন, তবে বর্তমানে এটি আপনার জন্য সম্পাদনার কাজটি করতে পারবে না। চলুন পরবর্তী ধাপে যাওয়া যাক, এজেন্টকে সম্পাদনার সরঞ্জামগুলো দিয়ে সজ্জিত করা যাক।

৪. 🚀 এলএলএম অনুরোধের প্রেক্ষাপট পরিবর্তন - ব্যবহারকারীর আপলোড করা ছবি

আমরা চাই আমাদের এজেন্ট যেন আপলোড করা ছবিগুলোর মধ্যে কোনটি সম্পাদনা করবে তা বেছে নেওয়ার ক্ষেত্রে নমনীয় থাকে। তবে, এলএলএম (LLM) টুলগুলো সাধারণত str বা int-এর মতো সাধারণ ডেটা টাইপ প্যারামিটার গ্রহণ করার জন্য ডিজাইন করা হয়। মাল্টিমোডাল ডেটার জন্য এটি একটি সম্পূর্ণ ভিন্ন ডেটা টাইপ, যা সাধারণত বাইটস ডেটা টাইপ হিসাবে বিবেচিত হয়। তাই, এই ডেটাগুলো পরিচালনা করার জন্য আমাদের আর্টিফ্যাক্টস (Artifacts) ধারণা ব্যবহার করে একটি কৌশল অবলম্বন করতে হবে। সুতরাং, টুলস প্যারামিটারে সম্পূর্ণ বাইটস ডেটা দেওয়ার পরিবর্তে, আমরা টুলটিকে আর্টিফ্যাক্ট আইডেন্টিফায়ার নামটি গ্রহণ করার জন্য ডিজাইন করব।

এই কৌশলটিতে দুটি ধাপ থাকবে:

  1. LLM অনুরোধটি এমনভাবে পরিবর্তন করুন যাতে প্রতিটি আপলোড করা ফাইল একটি আর্টিফ্যাক্ট আইডেন্টিফায়ারের সাথে যুক্ত থাকে এবং এটিকে LLM-এর কনটেক্সট হিসেবে যোগ করুন।
  2. টুলটিকে এমনভাবে ডিজাইন করুন যাতে এটি ইনপুট প্যারামিটার হিসেবে আর্টিফ্যাক্ট আইডেন্টিফায়ার গ্রহণ করতে পারে।

চলুন প্রথম ধাপটি করা যাক। LLM রিকোয়েস্টটি পরিবর্তন করার জন্য আমরা ADK কলব্যাক ফিচারটি ব্যবহার করব। বিশেষ করে, এজেন্ট যখন LLM-এ কনটেক্সট পাঠায়, ঠিক তার আগে আমরা before_model_callback যোগ করব। নিচের ছবিতে এর উদাহরণ দেখতে পারেন। 722b5fac82954419.png

এটি করার জন্য, প্রথমে নিম্নলিখিত কমান্ডটি ব্যবহার করে product_photo_editor/model_callbacks.py নামে একটি নতুন ফাইল তৈরি করুন।

touch product_photo_editor/model_callbacks.py

তারপর, নিচের কোডটি ফাইলে কপি করুন।

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

before_model_modifier ফাংশনটি নিম্নলিখিত কাজগুলো করে:

  1. llm_request.contents ভেরিয়েবলটি অ্যাক্সেস করুন এবং এর বিষয়বস্তু পুনরাবৃত্তি করুন।
  2. অংশটিতে ইনলাইন ডেটা (আপলোড করা ফাইল/ছবি) আছে কিনা তা পরীক্ষা করুন, যদি থাকে তবে ইনলাইন ডেটাটি প্রসেস করুন।
  3. inline_data- এর জন্য একটি আইডেন্টিফায়ার তৈরি করুন, এই উদাহরণে আমরা একটি কন্টেন্ট হ্যাশ আইডেন্টিফায়ার তৈরি করতে ফাইলের নাম + ডেটার সংমিশ্রণ ব্যবহার করছি।
  4. আর্টিফ্যাক্ট আইডিটি আগে থেকেই আছে কিনা তা পরীক্ষা করুন, না থাকলে আর্টিফ্যাক্ট আইডি ব্যবহার করে আর্টিফ্যাক্টটি সংরক্ষণ করুন।
  5. নিম্নলিখিত ইনলাইন ডেটার আর্টিফ্যাক্ট আইডেন্টিফায়ার সম্পর্কে প্রাসঙ্গিক তথ্য প্রদানকারী টেক্সট প্রম্পট অন্তর্ভুক্ত করতে অংশটি পরিবর্তন করুন।

এরপরে, এজেন্টকে কলব্যাক দিয়ে সজ্জিত করতে product_photo_editor/agent.py ফাইলটি পরিবর্তন করুন।

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

এখন, আমরা এজেন্টের সাথে আবার যোগাযোগ করার চেষ্টা করতে পারি।

uv run adk web --port 8080

এবং ফাইলটি আবার আপলোড করার চেষ্টা করুন ও চ্যাট করুন, তাহলে আমরা যাচাই করতে পারব যে আমরা সফলভাবে এলএলএম অনুরোধের প্রেক্ষাপট পরিবর্তন করতে পেরেছি কিনা।

51404c0704f86ffa.png

f82034bcdda068d9.png

এইভাবে আমরা এলএলএম-কে মাল্টিমোডাল ডেটার ক্রম এবং শনাক্তকরণ সম্পর্কে জানাতে পারি। এখন চলুন এমন একটি টুল তৈরি করি যা এই তথ্য ব্যবহার করবে।

৫. 🚀 মাল্টিমোডাল টুল ইন্টারঅ্যাকশন

এখন, আমরা এমন একটি টুল প্রস্তুত করতে পারি যা তার ইনপুট প্যারামিটার হিসেবে আর্টিফ্যাক্ট আইডি-ও নির্দিষ্ট করে। product_photo_editor/custom_tools.py নামে নতুন ফাইলটি তৈরি করতে নিম্নলিখিত কমান্ডটি চালান।

touch product_photo_editor/custom_tools.py

এরপর, নিচের কোডটি 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)}",
        }

টুল কোডটি নিম্নলিখিত কাজগুলো করে:

  1. টুলটি চালু করার সর্বোত্তম পদ্ধতি সম্পর্কে টুলের ডকুমেন্টেশনে বিস্তারিতভাবে বর্ণনা করা হয়েছে।
  2. image_artifact_ids তালিকাটি খালি নয় তা যাচাই করুন।
  3. প্রদত্ত আর্টিফ্যাক্ট আইডিগুলো ব্যবহার করে tool_context থেকে সমস্ত ইমেজ আর্টিফ্যাক্ট লোড করুন।
  4. সম্পাদনা নির্দেশিকা তৈরি করুন: পেশাদারভাবে একাধিক ছবি একত্রিত করতে বা একটি ছবি সম্পাদনা করতে নির্দেশাবলী যোগ করুন।
  5. শুধুমাত্র ইমেজ আউটপুট সহ জেমিনি ২.৫ ফ্ল্যাশ ইমেজ মডেলকে কল করুন এবং তৈরি হওয়া ইমেজটি এক্সট্র্যাক্ট করুন।
  6. সম্পাদিত ছবিটি নতুন আর্টিফ্যাক্ট হিসেবে সংরক্ষণ করুন
  7. স্ট্যাটাস, আউটপুট আর্টিফ্যাক্ট আইডি, ইনপুট আইডি, সম্পূর্ণ প্রম্পট এবং মেসেজ সহ একটি কাঠামোগত প্রতিক্রিয়া ফেরত দিন।

অবশেষে, আমরা আমাদের এজেন্টকে টুলটি দিয়ে সজ্জিত করতে পারি। 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.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,
)

এখন, আমাদের এজেন্ট আমাদের জন্য ছবি সম্পাদনা করতে ৮০% প্রস্তুত, চলুন এটি ব্যবহার করে দেখি।

uv run adk web --port 8080

এবং চলুন, ভিন্ন প্রম্পট ব্যবহার করে নিচের ছবিটি আবার চেষ্টা করি:

put these muffins in a white plate aesthetically

a5ff3bc6c19a29ec.jpeg

আপনি হয়তো এই ধরনের কথোপকথন দেখবেন এবং অবশেষে এজেন্টকে আপনার জন্য কিছু ছবি সম্পাদনা করতে দেখবেন।

92fb33f9c834330a.png

আপনি যখন ফাংশন কলের বিবরণ পরীক্ষা করবেন, তখন এটি ব্যবহারকারীর আপলোড করা ছবিটির আর্টিফ্যাক্ট আইডেন্টিফায়ার প্রদান করবে।

f5f440ccb36a4648.png

এখন, এজেন্টটি আপনাকে ছবিটিকে একটু একটু করে ক্রমাগত উন্নত করতে সাহায্য করতে পারে। এটি সম্পাদিত ছবিটিকে পরবর্তী সম্পাদনার নির্দেশনার জন্যও ব্যবহার করতে পারে, কারণ আমরা টুলের প্রতিক্রিয়ায় আর্টিফ্যাক্ট আইডেন্টিফায়ারটি সরবরাহ করে থাকি।

তবে এর বর্তমান অবস্থায়, এজেন্ট আসলে সম্পাদিত ছবির ফলাফল দেখতে ও বুঝতে পারে না, যেমনটা আপনি উপরের উদাহরণে দেখতে পাচ্ছেন। এর কারণ হলো, আমরা এজেন্টকে যে টুল রেসপন্স দিই তা শুধুমাত্র আর্টিফ্যাক্ট আইডি, বাইট কন্টেন্ট নিজে নয়, এবং দুর্ভাগ্যবশত আমরা বাইট কন্টেন্ট সরাসরি টুল রেসপন্সের ভেতরে রাখতে পারি না, রাখলে এটি একটি এরর দেখাবে। তাই, টুল রেসপন্সের ফলাফল থেকে বাইট কন্টেন্টকে ইনলাইন ডেটা হিসেবে যোগ করার জন্য কলব্যাকের ভেতরে আমাদের আরেকটি লজিক ব্রাঞ্চ রাখতে হবে।

৬. 🚀 এলএলএম অনুরোধের প্রেক্ষাপট পরিবর্তন - ফাংশনের প্রতিক্রিয়া চিত্র

চলুন আমাদের before_model_modifier কলব্যাকটি পরিবর্তন করি, যাতে টুলের প্রতিক্রিয়ার পরে সম্পাদিত ছবির বাইট ডেটা যোগ করা হয়, এবং এর ফলে আমাদের এজেন্ট ফলাফলটি সম্পূর্ণরূপে বুঝতে পারে।

product_photo_editor/model_callbacks.py ফাইলটি খুলুন এবং এর ভেতরের বিষয়বস্তু নিচের মতো করে পরিবর্তন করুন।

# 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,
    ]

উপরের পরিবর্তিত কোডে আমরা নিম্নলিখিত কার্যকারিতাগুলো যোগ করেছি:

  1. কোনো পার্ট একটি ফাংশন রেসপন্স কিনা এবং কন্টেন্ট পরিবর্তনের অনুমতি দেওয়ার জন্য সেটি আমাদের টুলের নামের তালিকায় আছে কিনা তা যাচাই করুন।
  2. টুলের প্রতিক্রিয়া থেকে আর্টিফ্যাক্ট শনাক্তকারী বিদ্যমান থাকলে, আর্টিফ্যাক্টের বিষয়বস্তু লোড করুন।
  3. বিষয়বস্তুটি এমনভাবে পরিবর্তন করুন যাতে এতে টুলের প্রতিক্রিয়া থেকে প্রাপ্ত সম্পাদিত ছবির ডেটা অন্তর্ভুক্ত থাকে।

এখন, টুলের প্রতিক্রিয়া থেকে আমরা যাচাই করতে পারি যে এজেন্টটি সম্পাদিত ছবিটি পুরোপুরি বুঝতে পেরেছে কি না।

5d4e880da6f2b9cb.png

চমৎকার, এখন আমাদের কাছে এমন একটি এজেন্ট আছে যা আমাদের নিজস্ব কাস্টম টুলের সাহায্যে মাল্টিমোডাল ইন্টারঅ্যাকশন ফ্লো সমর্থন করে।

এখন, আপনি এজেন্টের সাথে আরও জটিল কোনো প্রক্রিয়ার মাধ্যমে কাজ করার চেষ্টা করতে পারেন, যেমন—ছবিটি উন্নত করার জন্য একটি নতুন আইটেম (আইস ​​ল্যাটে) যোগ করা।

b561a4ae5cb40355.jpeg

e03674e0e1599c33.png

৭. ⭐ সারাংশ

এখন এই কোডল্যাবে আমরা ইতিমধ্যে যা করেছি তা পুনরায় দেখে নেওয়া যাক, এখানে মূল শিক্ষাগুলো তুলে ধরা হলো:

  1. মাল্টিমোডাল ডেটা হ্যান্ডলিং: টুল আর্গুমেন্ট বা রেসপন্সের মাধ্যমে সরাসরি র ডেটা পাস করার পরিবর্তে, ADK-এর আর্টিফ্যাক্টস সার্ভিস ব্যবহার করে LLM কনটেক্সট ফ্লো-এর মধ্যে মাল্টিমোডাল ডেটা (যেমন ছবি) পরিচালনা করার কৌশল শিখেছি।
  2. before_model_callback ব্যবহার: LlmRequest কে LLM-এ পাঠানোর আগে সেটিকে আটকানো এবং পরিবর্তন করার জন্য before_model_callback ব্যবহার করা হয়েছে। আমরা নিম্নলিখিত ফ্লোটি অনুসরণ করেছি:
  • ব্যবহারকারীর আপলোড: ব্যবহারকারীর আপলোড করা ইনলাইন ডেটা শনাক্ত করার, সেটিকে একটি অনন্য শনাক্তকারী আর্টিফ্যাক্ট (যেমন, usr_upl_img_... ) হিসেবে সংরক্ষণ করার, এবং আর্টিফ্যাক্ট আইডি উল্লেখ করে প্রম্পট কনটেক্সটে টেক্সট যুক্ত করার লজিক প্রয়োগ করা হয়েছে, যা LLM-কে টুল ব্যবহারের জন্য সঠিক ফাইলটি নির্বাচন করতে সক্ষম করে।
  • টুলের প্রতিক্রিয়া: এমন লজিক প্রয়োগ করা হয়েছে যা আর্টিফ্যাক্ট (যেমন, সম্পাদিত ছবি) তৈরি করে এমন নির্দিষ্ট টুল ফাংশনের প্রতিক্রিয়া শনাক্ত করে, নতুন সংরক্ষিত আর্টিফ্যাক্টটি (যেমন, edited_img_... ) লোড করে, এবং আর্টিফ্যাক্ট আইডি রেফারেন্স ও ছবির বিষয়বস্তু উভয়ই সরাসরি কনটেক্সট স্ট্রিমে যুক্ত করে।
  1. কাস্টম টুল ডিজাইন: একটি কাস্টম পাইথন টুল ( edit_product_asset ) তৈরি করা হয়েছে, যা একটি image_artifact_ids তালিকা (স্ট্রিং আইডেন্টিফায়ার) গ্রহণ করে এবং ToolContext ব্যবহার করে Artifacts সার্ভিস থেকে প্রকৃত ইমেজ ডেটা পুনরুদ্ধার করে।
  2. ইমেজ জেনারেশন মডেল ইন্টিগ্রেশন: একটি বিশদ টেক্সট বিবরণের উপর ভিত্তি করে ইমেজ এডিটিং করার জন্য কাস্টম টুলের মধ্যে জেমিনি ২.৫ ফ্ল্যাশ ইমেজ মডেলটি ইন্টিগ্রেট করা হয়েছে।
  3. নিরবচ্ছিন্ন বহুবিধ মিথস্ক্রিয়া: এটি নিশ্চিত করা হয়েছিল যে এজেন্ট তার নিজের টুল কলের ফলাফল (সম্পাদিত চিত্র) বুঝতে পেরে এবং সেই আউটপুটকে পরবর্তী নির্দেশাবলীর জন্য ইনপুট হিসাবে ব্যবহার করে একটি নিরবচ্ছিন্ন সম্পাদনা সেশন বজায় রাখতে পারে।

৮. ➡️ পরবর্তী চ্যালেঞ্জ

ADK মাল্টিমোডাল টুল ইন্টারঅ্যাকশনের পার্ট ১ শেষ করার জন্য অভিনন্দন। এই টিউটোরিয়ালে আমরা কাস্টম টুলের ইন্টারঅ্যাকশনের উপর মনোযোগ দেব। এখন আপনি মাল্টিমোডাল MCP টুলসেটের সাথে কীভাবে ইন্টারঅ্যাক্ট করা যায়, সেই পরবর্তী ধাপে যাওয়ার জন্য প্রস্তুত। পরবর্তী ল্যাবে যান।

৯. 🧹 পরিষ্কার করা

এই কোডল্যাবে ব্যবহৃত রিসোর্সগুলির জন্য আপনার গুগল ক্লাউড অ্যাকাউন্টে চার্জ হওয়া এড়াতে, এই ধাপগুলি অনুসরণ করুন:

  1. গুগল ক্লাউড কনসোলে, 'ম্যানেজ রিসোর্সেস' পৃষ্ঠায় যান।
  2. প্রজেক্ট তালিকা থেকে, আপনি যে প্রজেক্টটি মুছতে চান সেটি নির্বাচন করুন এবং তারপর ডিলিট বোতামে ক্লিক করুন।
  3. ডায়ালগ বক্সে প্রজেক্ট আইডি টাইপ করুন এবং তারপর প্রজেক্টটি মুছে ফেলার জন্য 'শাট ডাউন'-এ ক্লিক করুন।