1- 📖 مقدمة
يوضّح هذا الدرس التطبيقي كيفية تصميم تفاعل أداة متعددة الوسائط في "حزمة تطوير الوكيل" (ADK). هذا مسار محدّد تريد فيه أن يشير الوكيل إلى الملف الذي تم تحميله كمدخل إلى إحدى الأدوات، وأن يفهم أيضًا محتوى الملف الذي تم إنشاؤه من خلال ردّ الأداة. وبالتالي، يمكن التفاعل كما هو موضّح في لقطة الشاشة أدناه. في هذا البرنامج التعليمي، سننشئ وكيلًا قادرًا على مساعدة المستخدم في تعديل صورة أفضل لعرض منتجه.
خلال هذا الدرس العملي، ستتّبع نهجًا خطوة بخطوة على النحو التالي:
- إعداد مشروع Google Cloud
- إعداد دليل العمل لبيئة الترميز
- إعداد الوكيل باستخدام ADK
- تصميم أداة يمكن استخدامها لتعديل الصور استنادًا إلى Gemini 2.5 Flash Image
- تصميم دالة ردّ اتصال للتعامل مع تحميل صورة المستخدم وحفظها كعنصر، وإضافتها كسياق إلى الوكيل
- تصميم دالة ردّ للتعامل مع الصورة التي تم إنتاجها من خلال ردّ إحدى الأدوات، وحفظها كعنصر وإضافتها كسياق إلى الوكيل
نظرة عامة على البنية
يظهر التفاعل العام في هذا الدرس العملي في الرسم البياني التالي

المتطلبات الأساسية
- إجادة العمل باستخدام لغة Python
- (اختياري) دروس برمجية أساسية حول "حزمة تطوير الوكيل" (ADK)
ما ستتعلمه
- كيفية استخدام سياق معاودة الاتصال للوصول إلى خدمة العناصر
- كيفية تصميم أداة مع نشر مناسب للبيانات المتعددة الوسائط
- كيفية تعديل طلب LLM الخاص بالوكيل لإضافة سياق العنصر عبر before_model_callback
- كيفية تعديل الصور باستخدام Gemini 2.5 Flash Image
المتطلبات
- متصفّح الويب Chrome
- حساب Gmail
- مشروع على السحابة الإلكترونية مع تفعيل حساب الفوترة
يستخدم هذا الدرس التطبيقي، المصمّم للمطوّرين من جميع المستويات (بما في ذلك المبتدئين)، لغة Python في التطبيق النموذجي. ومع ذلك، لا يُشترط معرفة لغة Python لفهم المفاهيم المقدَّمة.
2. 🚀 إعداد بيئة تطوير ورشة العمل
الخطوة 1: اختيار "المشروع النشط" في Cloud Console
في Google Cloud Console، في صفحة اختيار المشروع، اختَر أو أنشِئ مشروعًا على Google Cloud (راجِع القسم العلوي الأيمن من وحدة التحكّم).

انقر على هذا الرمز، وستظهر لك قائمة بجميع مشاريعك كما في المثال التالي:

القيمة الموضّحة في المربّع الأحمر هي معرّف المشروع وسيتم استخدام هذه القيمة في جميع أنحاء البرنامج التعليمي.
تأكَّد من تفعيل الفوترة لمشروعك على Cloud. للتأكّد من ذلك، انقر على رمز الهامبرغر ☰ في الشريط العلوي الأيمن الذي يعرض قائمة التنقّل وابحث عن قائمة "الفوترة".

إذا ظهر لك "حساب فوترة تجريبي في Google Cloud Platform" ضمن العنوان الفوترة / نظرة عامة ( الجزء العلوي الأيسر من Cloud Console )، يكون مشروعك جاهزًا للاستخدام في هذا البرنامج التعليمي. إذا لم يكن كذلك، ارجع إلى بداية هذا البرنامج التعليمي واستردّ قيمة حساب الفوترة التجريبي.

الخطوة 2: التعرّف على Cloud Shell
ستستخدم Cloud Shell في معظم أجزاء البرامج التعليمية، لذا انقر على "تفعيل Cloud Shell" في أعلى وحدة تحكّم Google Cloud. إذا طُلب منك التفويض، انقر على تفويض.


بعد الاتصال بـ Cloud Shell، علينا التحقّق مما إذا كان قد تمّت مصادقة الصدفة ( أو الوحدة الطرفية) باستخدام حسابنا.
gcloud auth list
إذا ظهر لك حساب Gmail الشخصي كما في المثال أدناه، يعني ذلك أنّ كل شيء على ما يرام.
Credentialed Accounts
ACTIVE: *
ACCOUNT: alvinprayuda@gmail.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
إذا لم يكن كذلك، جرِّب إعادة تحميل المتصفّح وتأكَّد من النقر على تفويض عند مطالبتك بذلك ( قد يتمّ مقاطعة العملية بسبب مشكلة في الاتصال).
بعد ذلك، نحتاج أيضًا إلى التحقّق مما إذا كان قد تمّ ضبط الصدفة على معرّف المشروع الصحيح الذي لديك، وإذا رأيت قيمة داخل ( ) قبل رمز $ في الوحدة الطرفية ( في لقطة الشاشة أدناه، القيمة هي "adk-multimodal-tool")، فإنّ هذه القيمة تعرض المشروع الذي تمّ إعداده لجلسة الصدفة النشطة.

إذا كانت القيمة المعروضة صحيحة، يمكنك تخطّي الأمر التالي. ومع ذلك، إذا كان غير صحيح أو غير متوفّر، شغِّل الأمر التالي
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".
بعد ذلك، انتقِل إلى القسم العلوي من "محرّر Cloud Shell" وانقر على ملف (File) -> فتح مجلد (Open Folder)، وابحث عن دليل اسم المستخدم، ثم ابحث عن دليل adk-multimodal-tool وانقر على الزر "حسنًا" (OK). سيؤدي ذلك إلى جعل الدليل الذي تم اختياره هو دليل العمل الرئيسي. في هذا المثال، اسم المستخدم هو alvinprayuda، وبالتالي يظهر مسار الدليل أدناه


يجب أن يبدو دليل العمل في "محرّر Cloud Shell" الآن على النحو التالي ( داخل adk-multimodal-tool)

الآن، افتح الوحدة الطرفية للمحرّر. يمكنك إجراء ذلك من خلال النقر على Terminal -> New Terminal في شريط القوائم، أو استخدام Ctrl + Shift + C، وسيؤدي ذلك إلى فتح نافذة طرفية في الجزء السفلي من المتصفح.

يجب أن تكون نافذة الجهاز النشطة الحالية داخل دليل العمل adk-multimodal-tool. سنستخدم الإصدار 3.12 من لغة Python في هذا الدرس العملي، كما سنستخدم أداة إدارة مشاريع Python (uv) لتسهيل عملية إنشاء إصدار Python وبيئة افتراضية وإدارتهما. تم تثبيت حزمة uv هذه مسبقًا على Cloud Shell.
نفِّذ هذا الأمر لتثبيت الاعتمادات المطلوبة في البيئة الافتراضية في الدليل .venv.
uv sync --frozen
راجِع ملف pyproject.toml للاطّلاع على التبعيات المحدّدة لهذا الدليل التعليمي، وهي google-adk, and python-dotenv.
الآن، علينا تفعيل واجهات برمجة التطبيقات المطلوبة من خلال الأمر الموضّح أدناه. قد يستغرق هذا الإجراء بعض الوقت.
gcloud services enable aiplatform.googleapis.com
عند تنفيذ الأمر بنجاح، من المفترض أن تظهر لك رسالة مشابهة للرسالة الموضّحة أدناه:
Operation "operations/..." finished successfully.
3- 🚀 إعداد "وكيل ADK"
في هذه الخطوة، سنبدأ تشغيل وكيلنا باستخدام واجهة سطر الأوامر (CLI) الخاصة بحزمة تطوير التطبيقات (ADK)، وننفّذ الأمر التالي
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)
الآن، للتحقّق من ذلك، يمكنك الضغط على Ctrl + النقر على عنوان URL أو النقر على الزر معاينة الويب في المنطقة العلوية من "محرّر Cloud Shell" واختيار المعاينة على المنفذ 8080.

ستظهر لك صفحة الويب التالية حيث يمكنك اختيار الوكلاء المتاحين من زر القائمة المنسدلة في أعلى اليمين ( في حالتنا، يجب أن يكون product_photo_editor) والتفاعل مع البوت. جرِّب تحميل الصورة التالية في واجهة المحادثة وطرح الأسئلة التالية
what is your suggestion for this photo?

سيظهر لك تفاعل مشابه لما هو موضّح أدناه

يمكنك طلب بعض الاقتراحات، ولكن لا يمكنه حاليًا إجراء التعديلات نيابةً عنك. لننتقل إلى الخطوة التالية، وهي تزويد الوكيل بأدوات التعديل.
4. 🚀 تعديل سياق طلب النموذج اللغوي الكبير - صورة حمّلها المستخدم
نريد أن يكون الوكيل مرنًا في اختيار الصورة التي يريد تعديلها من بين الصور التي تم تحميلها. ومع ذلك، عادةً ما تكون أدوات النماذج اللغوية الكبيرة مصمَّمة لقبول مَعلمات بسيطة من نوع البيانات، مثل str أو int. وهذا نوع بيانات مختلف تمامًا عن البيانات المتعددة الوسائط التي يُنظر إليها عادةً على أنّها نوع بيانات bytes، وبالتالي سنحتاج إلى استراتيجية تتضمّن مفهوم القطع الأثرية للتعامل مع هذه البيانات. لذلك، بدلاً من تقديم بيانات البايت الكاملة في مَعلمة الأدوات، سنصمّم الأداة لقبول اسم معرّف العنصر بدلاً من ذلك.
ستتضمّن هذه الاستراتيجية خطوتَين:
- تعديل طلب النموذج اللغوي الكبير لربط كل ملف تم تحميله بمعرّف عنصر وإضافة ذلك كسياق إلى النموذج اللغوي الكبير
- تصميم الأداة لقبول معرّفات العناصر كمعلَمات إدخال
لننفّذ الخطوة الأولى، فمن أجل تعديل طلب نموذج اللغة الكبير، سنستخدم ميزة معاودة الاتصال في حزمة تطوير التطبيقات (ADK). على وجه التحديد، سنضيف before_model_callback للاستفادة من هذه الميزة قبل أن يرسل الوكيل السياق إلى النموذج اللغوي الكبير مباشرةً. يمكنك الاطّلاع على الرسم التوضيحي في الصورة أدناه 
لإجراء ذلك، أنشئ أولاً ملفًا جديدًا 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 الإجراءات التالية:
- الوصول إلى المتغيّر
llm_request.contentsوتكرار المحتوى - تحقَّق مما إذا كان الجزء يحتوي على inline_data ( ملف أو صورة تم تحميلها)، وإذا كان الأمر كذلك، عالِج البيانات المضمّنة
- إنشاء معرّف inline_data، وفي هذا المثال، نستخدم مزيجًا من اسم الملف والبيانات لإنشاء معرّف تجزئة المحتوى
- تحقَّق مما إذا كان معرّف العنصر متوفّرًا، وإذا لم يكن متوفّرًا، احفظ العنصر باستخدام معرّف العنصر
- عدِّل الجزء لتضمين طلب نصي يقدّم سياقًا حول معرّف العنصر الاصطناعي للبيانات المضمّنة التالية
بعد ذلك، عدِّل 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
ومحاولة تحميل الملف مرة أخرى والدردشة، يمكننا فحص ما إذا تم تعديل سياق طلب النموذج اللغوي الكبير بنجاح


هذه إحدى الطرق التي يمكننا من خلالها إخبار النموذج اللغوي الكبير بالتسلسل والتعرّف على البيانات المتعددة الوسائط. لننشئ الآن الأداة التي ستستخدم هذه المعلومات.
5- 🚀 التفاعل مع الأدوات المتعدّدة الوسائط
الآن، يمكننا إعداد أداة تحدّد أيضًا معرّف العنصر كمعلَمة إدخال. نفِّذ الأمر التالي لإنشاء ملف جديد 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)}",
}
تنفّذ أداة الترميز الإجراءات التالية:
- توضّح المستندات حول الأداة بالتفصيل أفضل الممارسات لاستخدام الأداة
- التأكّد من أنّ قائمة image_artifact_ids ليست فارغة
- تحميل جميع عناصر الصورة من tool_context باستخدام أرقام تعريف العناصر المقدَّمة
- إنشاء طلب تعديل: إضافة تعليمات للدمج (صور متعددة) أو التعديل (صورة واحدة) بشكل احترافي
- استدعاء نموذج Gemini 2.5 Flash Image مع إخراج الصورة فقط واستخراج الصورة التي تم إنشاؤها
- حفظ الصورة المعدَّلة كعنصر جديد
- إرجاع ردّ منظَّم يتضمّن: الحالة ومعرّف العنصر الناتج ومعرّفات الإدخال والطلب الكامل والرسالة
أخيرًا، يمكننا تزويد الوكيل بالأداة. عدِّل محتوى 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,
)
الآن، أصبح مساعدنا جاهزًا بنسبة% 80 لمساعدتنا في تعديل الصور، فلنجرّب التفاعل معه
uv run adk web --port 8080
لنجرِّب الصورة التالية مرة أخرى باستخدام طلب مختلف:
put these muffins in a white plate aesthetically

قد ترى تفاعلاً من هذا النوع، وفي النهاية ترى الوكيل يجري بعض التعديلات على الصور نيابةً عنك.

عند التحقّق من تفاصيل استدعاء الدالة، سيتم توفير معرّف العنصر الخاص بالصورة التي حمّلها المستخدم.

يمكن للوكيل الآن مساعدتك في تحسين الصورة باستمرار جزءًا تلو الآخر. يمكنه أيضًا استخدام الصورة المعدَّلة في تعليمات التعديل التالية لأنّنا نقدّم معرّف العنصر في ردّ الأداة.
ومع ذلك، في حالتها الحالية، لا يمكن للوكيل رؤية نتيجة الصورة المعدَّلة وفهمها، كما يتضح من المثال أعلاه. يرجع ذلك إلى أنّ استجابة الأداة التي نقدّمها للوكيل هي معرّف العنصر فقط وليس محتوى البايت نفسه، ولا يمكننا للأسف وضع محتوى البايت مباشرةً داخل استجابة الأداة، لأنّ ذلك سيؤدي إلى حدوث خطأ. لذلك، نحتاج إلى فرع منطقي آخر داخل دالة معاودة الاتصال لإضافة محتوى البايت كبيانات مضمّنة من نتيجة استجابة الأداة.
6. 🚀 تعديل سياق الطلب في النموذج اللغوي الكبير (LLM) - صورة ردّ الدالة
لنعدّل دالة 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,
]
في الرمز المعدَّل أعلاه، نضيف الوظائف التالية:
- التحقّق مما إذا كان Part هو ردّ على دالة وما إذا كان مدرجًا في قائمة أسماء الأدوات للسماح بتعديل المحتوى
- إذا كان معرّف العنصر الأثري من استجابة الأداة متوفّرًا، حمِّل محتوى العنصر الأثري
- تعديل المحتوى ليتضمّن بيانات الصورة المعدَّلة من ردّ الأداة
يمكننا الآن التحقّق مما إذا كان الوكيل يفهم الصورة المعدَّلة بالكامل من ردّ الأداة

رائع، لدينا الآن وكيل يتيح سير التفاعل المتعدّد الوسائط مع أداتنا المخصّصة.
يمكنك الآن تجربة التفاعل مع الوكيل باستخدام مسار أكثر تعقيدًا، مثل إضافة عنصر جديد ( لاتيه مثلج) لتحسين الصورة مثلاً.


7. ⭐ الملخّص
لنراجع الآن ما سبق أن أجريناه خلال هذا الدرس العملي، إليك النقطة الأساسية التي تعلّمناها:
- التعامل مع البيانات المتعدّدة الوسائط: تعلّمنا استراتيجية إدارة البيانات المتعدّدة الوسائط (مثل الصور) ضمن سياق النموذج اللغوي الكبير من خلال استخدام خدمة العناصر في "حزمة تطوير التطبيقات" بدلاً من تمرير بيانات البايت الخام مباشرةً من خلال وسيطات الأدوات أو الردود.
before_model_callbackالاستخدام: تم استخدامbefore_model_callbackلاعتراضLlmRequestوتعديله قبل إرساله إلى نموذج اللغة الكبير. اتّبِعنا الخطوات التالية:
- عمليات تحميل المستخدمين: تم تنفيذ منطق لرصد البيانات المضمّنة التي حمّلها المستخدمون، وحفظها كعنصر محدّد بشكل فريد (مثل
usr_upl_img_...)، وإدخال نص في سياق الطلب يشير إلى رقم تعريف العنصر، ما يتيح للنموذج اللغوي الكبير اختيار الملف الصحيح لاستخدام الأداة. - ردود الأدوات: تم تنفيذ منطق لرصد ردود وظائف أدوات معيّنة تنتج عناصر (مثل الصور المعدَّلة)، وتحميل العنصر المحفوظ حديثًا (مثل
edited_img_...)، وأدرِج كلّاً من مرجع معرّف العنصر ومحتوى الصورة مباشرةً في دفق السياق.
- تصميم أداة مخصّصة: تم إنشاء أداة Python مخصّصة (
edit_product_asset) تقبل قائمةimage_artifact_ids(معرّفات السلسلة) وتستخدمToolContextلاسترداد بيانات الصور الفعلية من خدمة Artifacts. - دمج نموذج إنشاء الصور: تم دمج نموذج Gemini 2.5 Flash Image في الأداة المخصّصة لتعديل الصور استنادًا إلى وصف نصّي مفصّل.
- التفاعل المستمر المتعدد الوسائط: تم التأكّد من أنّ الوكيل يمكنه الحفاظ على جلسة تعديل مستمرة من خلال فهم نتائج طلبات الأدوات الخاصة به (الصورة المعدَّلة) واستخدام هذا الناتج كمدخل للتعليمات اللاحقة.
8. ➡️ التحدي التالي
تهانينا على إكمال الجزء 1 من التفاعل مع "أداة ADK المتعددة الوسائط". في هذا البرنامج التعليمي، نركّز على التفاعل مع الأدوات المخصّصة. أنت الآن جاهز للانتقال إلى الخطوة التالية حول كيفية التفاعل مع مجموعة أدوات MCP المتعددة الوسائط. الانتقال إلى المختبر التالي
9- 🧹 تنظيف
لتجنُّب تحمّل رسوم في حسابك على Google Cloud مقابل الموارد المستخدَمة في هذا الدرس العملي، اتّبِع الخطوات التالية:
- في Google Cloud Console، انتقِل إلى صفحة إدارة الموارد.
- في قائمة المشاريع، اختَر المشروع الذي تريد حذفه، ثم انقر على حذف.
- في مربّع الحوار، اكتب رقم تعريف المشروع، ثم انقر على إيقاف لحذف المشروع.