إنشاء وكيل ملخّص يومي عن التكنولوجيا باستخدام "الوكلاء المُدارون" في Gemini API

1. نظرة عامة

يتطوّر مجال الذكاء الاصطناعي والتكنولوجيا بشكل أسرع من أن يتمكّن أي شخص من متابعته. تتم إضافة نماذج وأوراق ومنتجات جديدة يوميًا. يمكن حلّ هذه المشكلة باستخدام وكيل ملخّص يجلب العناوين الرئيسية اليومية ويكتب ملخّصات موجزة وينشئ ملف PDF كل صباح، ولكن كان إنشاء هذا الوكيل يتطلّب اختيار إطار عمل وتحديد أدوات في Python وكتابة حلقة تنسيق وتعبئة حاوية ونشرها على Cloud Run. كل ذلك قبل أن يرسل الوكيل طلب ويب واحدًا.

تغيّر الوكلاء المُدارون في Gemini API المعادلة. يمكنك كتابة ملفَي إعداد بتنسيق Markdown ونص برمجي مُنشأ مسبقًا لعرض المحتوى، وإجراء طلب واحد من واجهة برمجة التطبيقات، وسيتم تشغيل بيئة اختبارية حقيقية من Ubuntu، وتصفّح الويب، وكتابة الملخّصات، وإنشاء ملف PDF. ما مِن حاويات لا تتوفر عملية نشر. لا يوجد رمز تنسيق.

في هذا الدرس التطبيقي حول الترميز، ستنشئ هذا الوكيل تحديدًا، بدءًا من دالة فارغة وصولاً إلى ملخّص يومي يعمل، وذلك من خلال استيعاب مفهوم واحد في كل مرة.

ما الذي ستنشئه؟

  • إنشاء وتشغيل أول وكيل مُدار في بيئة اختبار حقيقية لنظام التشغيل Linux
  • تخصيص الوكيل باستخدام أسلوب تحرير ومصادر ويب ومهارة PDF
  • إضافة خطاف أمان لحظر الأوامر المدمرة قبل تنفيذها
  • تنزيل ملف PDF الذي أنشأه الوكيل
  • تحسين الملخّص في محادثة مترابطة بدون إعادة جلب الويب
  • حفظ إعدادات الوكيل واستدعاؤها باستخدام رقم التعريف في عمليات التشغيل المستقبلية
  • إرسال الملخّص إلى بريدك الوارد من خلال Gmail API
  • جدولة الوكيل لتشغيله وإرساله تلقائيًا كل يوم

المتطلبات

  • ‫Python 3.10 أو إصدار أحدث
  • مفتاح Gemini API: aistudio.google.com/api-keys (تتضمّن هذه الحزمة درجة الاستخدام المجاني، ويُنصح بتفعيل الفوترة لضمان عدم انقطاع عمليات التشغيل)

2. ما هي "الوكلاء المُدارون" على Gemini API؟

ثلاثة مستويات لأنظمة الذكاء الاصطناعي

قبل التوغّل في تفاصيل الرمز، إليك موضع "الوكلاء المُدارون" مقارنةً بالبديلَين الآخرَين:

المستوى

ما المقصود بذلك

مَن يدير البنية التحتية؟

النموذج اللغوي الكبير العادي

تكتب طلبًا، فيردّ عليك بنص. لا أملك يدَين ولا ذاكرة ولا يمكنني الاتصال بالإنترنت.

لا ينطبق: لا يمكنه اتّخاذ أي إجراء من تلقاء نفسه

الوكيل المستضاف ذاتيًا

يمكنك ربط حزمة تطوير التطبيقات (ADK) أو LangChain أو AutoGen أو Docker أو الأدوات أو الذاكرة.

أنت: كل ذلك (أو منصة مُدارة مثل Agent Engine)

الوكيل المُدار

تحدّد له هدفًا. توفّر Google وضع حماية آمنًا. يكتب الوكيل الرمز البرمجي ويشغّله ويقرأ الأخطاء ويبحث على الويب ويصلح الأخطاء تلقائيًا.

‫Google: كلّ ذلك

يتناول هذا الدرس التطبيقي حول الترميز الصف الثالث. عليك تقديم مهمة وملفات إعداد. تتولّى Google تنفيذ جميع الإجراءات الأخرى.

ما يمكنك إنشاؤه باستخدام ADK وCloud Run

لإنشاء وكيل ملخّص أخبار يتصفّح الويب ويشغّل Python وينشئ ملف PDF، ستحتاج إلى كل ما يلي باستخدام ADK + Cloud Run:

# agent.py: define tools and wire up the agent
from google.adk.agents import LlmAgent
from google.adk.tools import google_search, built_in_code_execution

agent = LlmAgent(
    name="digest-agent",
    model=MODEL,
    instruction=AGENTS_MD,          # your editorial voice and rules
    tools=[google_search, built_in_code_execution],
)
# app.py: serve the agent over HTTP
from google.adk.runners import FastApiRunner
runner = FastApiRunner(agent=agent)
app = runner.app
# pdf_tool.py: custom tool, install reportlab, render PDF
# scraper.py: custom tool, fetch each news source
# streaming.py: wire agent events to your SSE endpoint
# Dockerfile: package everything
FROM python:3.12
COPY . /app
RUN pip install google-adk reportlab requests
CMD ["uvicorn", "app:app", "--host", "0.0.0.0"]
# Deploy to Cloud Run
gcloud run deploy digest-agent \
  --image gcr.io/your-project/digest-agent \
  --set-secrets GEMINI_API_KEY=gemini-key:latest \
  --memory 2Gi

ويكون ذلك قبل تشغيل الوكيل مرة واحدة. ستظلّ أنت المسؤول عن عزل البيئة التجريبية (وبالتالي لا يمكن للوكيل إتلاف الخادم)، وتثبيت الحِزم، وإدارة الحالة بين استدعاءات الأدوات، وبنية البث الأساسية لإرسال الأحداث إلى العميل.

الخدمة التي تحلّ محلّ الوكلاء المُدارون

from google import genai
client = genai.Client()

stream = client.interactions.create(
    agent="antigravity-preview-05-2026",
    input="Generate the digest.",
    stream=True,
    environment={
        "type": "remote",
        "sources": [          # your config files, mounted at startup
            {
                "type": "inline",
                "target": ".agents/AGENTS.md",
                "content": AGENTS_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/SKILL.md",
                "content": SKILL_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                "content": GENERATE_PDF_PY,
            },
        ],
    },
)

متطلبات استخدام ADK وCloud Run

المهام التي يتولّاها "الوكلاء المُدارون" نيابةً عنك

صورة الحاوية + الملف الشامل + التكامل المستمر/التسليم المستمر

بيئة اختبار معزولة Ubuntu مُدارة بالكامل (الإصدار 3.12 من Python والإصدار 22 من Node و4 وحدات معالجة مركزية وذاكرة وصول عشوائي بسعة 16 غيغابايت)

النشر والتوسيع في Cloud Run

يتم توفيرها لكل تفاعل، وتنتهي صلاحيتها تلقائيًا بعد 7 أيام من عدم النشاط

عزل وضع الحماية

معزولة لكل تفاعل

أداة PDF مخصّصة + pip install

يثبِّت الوكيل الحِزم داخل وضع الحماية

بنية بث SSE

تعرض الدالة stream=True تكرارًا للأحداث

تعريفات الأدوات في Python

الأدوات المضمّنة: تصفُّح الويب، وتنفيذ الرموز البرمجية، ونظام الملفات

إدارة الحالة بين عمليات استدعاء الأدوات

مضمّنة في حلقة الاستدلال الخاصة بالوكيل

يمكنك كتابة ملفات الإعداد (AGENTS.md وSKILL.md ونص برمجي مُعدّ مسبقًا) وإجراء طلب بيانات من واجهة برمجة التطبيقات. تتولّى Google تنفيذ جميع الإجراءات الأخرى.

طريقة عمل وضع الحماية

interactions.create() call
        │
        ▼
Google provisions Ubuntu sandbox (Python 3.12, Node 22, 4 CPU / 16 GB RAM)
        │
        ▼
Agent reasoning loop:
  plan → fetch URLs → run Python → write files → reason → repeat
        │
        ▼
Events stream back in real time: tool calls, text chunks, completion
        │
        ▼
interaction.completed → environment_id + interaction_id

تظل البيئة التجريبية متاحة لمدة 7 أيام من عدم النشاط. يمكنك استئنافها باستخدام environment_id لتحسين الناتج أو تنفيذ مهام متابعة أو إنشاء نسخة من المحادثة وحفظها باسم وكيل.

3- إعداد

انقر على الزر أدناه لفتح هذا الدرس العملي في Google Cloud Shell. جميع التبعيات مثبّتة مسبقًا.

الفتح في Cloud Shell

الخيار (ب): الإعداد المحلي

git clone https://github.com/Saoussen-CH/tech-digest-managed-agent.git
cd tech-digest-managed-agent

ثبِّت uv إذا لزم الأمر:

curl -LsSf https://astral.sh/uv/install.sh | sh

ضبط مفتاح واجهة برمجة التطبيقات

cp .env.example .env
cloudshell edit .env

اضبط المفتاح:

GEMINI_API_KEY=your-key-here

تثبيت الحِزم التابعة

uv sync

4. إجراء مكالمة الوكيل الأولى

فتح ملف البداية

cloudshell edit run_digest.py

لدى run_digest() مهمة واحدة يجب إكمالها الآن وثلاث مهام أخرى للخطوة التالية. تمت تعبئة مساعدَين مسبقًا أعلاه:

  • يقرأ load_source(path) ملفًا من .agents/ بالنسبة إلى النص البرمجي. ستستخدمها في التمرين التالي لتثبيت الصوت التحريري وكتاب قواعد اللعب بتنسيق PDF وأداة العرض في البيئة التجريبية.
  • ‫run_stream(stream): تعالج هذه الدالة سلسلة أحداث وتعرض (environment_id, interaction_id). لست بحاجة إلى كتابة حلقة معالجة الأحداث بنفسك.

ما يجب إضافته

قائمة المهام 1: استبدِل pass بما يلي (تجاهَل قائمة المهام 3 و4 في الوقت الحالي، فهي مخصّصة للخطوة التالية):

    from google import genai
    client = genai.Client()

    stream = client.interactions.create(
        agent=BASE_AGENT,
        agent_config={"type": "antigravity", "model": "gemini-3.7-flash"},
        input="Fetch the Hacker News front page and list the top 5 stories.",
        stream=True,
        environment="remote",
    )

    environment_id, interaction_id = run_stream(stream)
    print(f"\nDone. environment_id={environment_id}")

وظيفة كل جزء

يقرأ genai.Client() قيمة GEMINI_API_KEY من البيئة. وتتم جميع العمليات الأخرى من خلال هذا العميل.

‫interactions.create() هي المكالمة الأساسية. تتوفّر أربع معلّمات تجعلها تعمل:

  • agent=BASE_AGENT: يختار وكيل Antigravity (antigravity-preview-05-2026)، وهو وكيل مُدار للأغراض العامة يستند إلى Gemini 3.7 Flash تلقائيًا. يمكنك ضبط النموذج الأساسي باستخدام agent_config (الخيارات: gemini-3.7-flash وgemini-3.6-flash وgemini-3.5-flash وgemini-3.5-flash-lite). ويتضمّن ثلاث أدوات مدمجة مفعّلة تلقائيًا: code_execution (تشغيل Bash وPython وNode.js) وgoogle_search وurl_context (استرداد صفحات الويب وقراءتها). يتم تفعيل أدوات نظام الملفات (read_file وwrite_file وlist_files) تلقائيًا عند تمرير المَعلمة environment. يوفّر طلب واحد بيئة Ubuntu مُدارة بالكامل مع تثبيت Python 3.12 وNode.js 22 وgit وpip وcurl مسبقًا. لا حاجة إلى إنشاء حاوية أو نشرها لتشغيلها.
  • input: المَهمة التي سيتم تنفيذها في هذا التشغيل يتصفّح الوكيل موقع Hacker News ويحلّل النتائج.
  • ‫environment="remote": يوفّر وضع حماية جديدًا على السحابة الإلكترونية لهذا التفاعل.
  • stream=True: تعرض عنصرًا قابلاً للتكرار من الأحداث بدلاً من الحظر. بدون هذه العلامة، تنتظر المكالمة من 30 إلى 90 ثانية وتعرض كل النتائج دفعة واحدة على شكل interaction.output_text. باستخدام ميزة البث، يمكنك الاطّلاع على سبب ردّ الوكيل والتصرّف فورًا. لا يُعدّ البث ميزة متقدّمة هنا، بل هو الإعداد التلقائي الصحيح، لأنّ المربع الأسود الذي يظهر لمدة 90 ثانية لا يقدّم لك أي إشارة حول ما إذا كان البرنامج يعمل أو متوقفًا.

‫environment_id هو معرّف لوضع الحماية الذي تم تنفيذه للتو. بعد interaction.completed، لا يتم إيقاف البيئة التجريبية، بل تبقى نشطة لمدة تصل إلى 7 أيام. يمكنك استخدام environment_id للرجوع إلى هذه الصفحة. مرِّرها إلى طلب interactions.create() ثانٍ وسيستأنف الوكيل العمل على نظام الملفات نفسه، مع الملفات والحِزم المثبَّتة نفسها، كما لو أنّه لم يتوقف أبدًا. تستخدم الخطوة التالية هذه السمة لتنزيل ملف PDF بدون إعادة تشغيل الوكيل، وتستخدمها الخطوة التي تليها لمواصلة المحادثة.

‫interaction_id هو معرّف لجولة المحادثة التي انتهت للتو. مرِّرها كـ previous_interaction_id في المكالمة التالية، وسيتذكّر الوكيل كل ما قاله وفعله في هذه الجولة.

تأكيد

uv run python run_digest.py

من المفترض أن تظهر لك النتائج المباشرة أثناء عمل الوكيل:

[agent started]
  [tool] run_code
Here are the top 5 stories currently on the Hacker News front page, retrieved via the official Hacker News API:

1. **Qwen 3.6 27B is the sweet spot for local development** (471 points)
2. **.self: A new top-level domain designed to support self-hosting** (116 points)
...
Done. environment_id=e3de58774073f75a6ef42924c6ce2e88

تعرض واجهة برمجة التطبيقات قيمة environment_id حقيقية حتى مع environment="remote". تم تشغيل وضع الحماية. ما ينقص هو الإعدادات: لا يوجد صوت ولا مهارة ولا أداة لإنشاء ملفات PDF. لقد طبع الوكيل القصص كنص وتوقّف. تضيف الخطوة التالية هذه الأذونات.

يتطابق كل سطر من الناتج مع حدث من run_stream():

step.type

المقصود بذلك

ما يمكن لـ "run_stream()" طباعته

"url_context_call"

وكيل يجلب عنوان URL

[tool] url_context (https://...)

"code_execution_call"

وكيل ينفّذ الرمز البرمجي في وضع الحماية

[tool] run_code

"google_search_call"

البحث على الويب

[tool] google_search

"function_call"

أدوات الملفات وغيرها

[tool] read_file (/workspace/...)

‫step.delta حيث delta.type == "text"

يكتب الوكيل نصًا

يتم بثّه مباشرةً إلى stdout

5- تخصيص الوكيل

لم يتضمّن الوكيل أي تعليمات: لا صوت ولا مهارة ولا أداة لإنشاء ملفات PDF. في هذه الخطوة، يتم تحميل ملفات الإعداد من .agents/ وتثبيتها في البيئة التجريبية.

ما يجب تغييره

أجرِ أربعة تغييرات على run_digest.py:

TODO 2: أسفل load_source()، أضِف الثوابت الثلاثة على مستوى الوحدة (تكون هذه الثوابت خارج run_digest()، في أعلى الملف):

AGENTS_MD       = load_source(".agents/AGENTS.md")
SKILL_MD        = load_source(".agents/skills/digest-pdf/SKILL.md")
GENERATE_PDF_PY = load_source(".agents/skills/digest-pdf/scripts/generate_pdf.py")

افتح كل ملف لمعرفة ما يتم تحميله: يضبط الملف AGENTS.md قواعد سير العمل والصوت التحريري، أما الملف SKILL.md فهو دليل PDF تفصيلي، والملف generate_pdf.py هو أداة العرض المسبقة الإنشاء التي سيشغّلها الوكيل.

الآن، أجرِ تغييرَين آخرَين داخل run_digest():

قائمة المهام 3: غيِّر environment من "remote" إلى قاموس المصادر، واضبط input على "Generate the digest.":

        environment={
            "type": "remote",
            "sources": [
                {
                    "type": "inline",
                    "target": ".agents/AGENTS.md",
                    "content": AGENTS_MD,
                },
                {
                    "type": "inline",
                    "target": ".agents/skills/digest-pdf/SKILL.md",
                    "content": SKILL_MD,
                },
                {
                    "type": "inline",
                    "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                    "content": GENERATE_PDF_PY,
                },
            ],
        },

مطلوب اتّخاذ إجراء 4: أضِف هذا السطر مباشرةً بعد print(f"\nDone. environment_id={environment_id}"):

    save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)

تم تحديد save_env في run_digest.py من قبل. يكتب كلا المعرّفَين في .env حتى تتمكّن الخطوة التالية من تنزيل ملف PDF بدون إعادة تشغيل الوكيل.

وظيفة كل مصدر

كل مصدر هو ملف يتم تحميله في نظام ملفات وضع الحماية عند بدء التشغيل قبل تشغيل الوكيل. تتطابق مسارات target مع الأماكن التي تتوقّع أداة Antigravity العثور عليها فيها:

.agents/
├── AGENTS.md                              ← auto-loaded as global instructions
└── skills/
    └── digest-pdf/
        ├── SKILL.md                       ← auto-discovered and registered as a skill
        └── scripts/
            └── generate_pdf.py            ← pre-built renderer the agent can run

target مسار

متغيّر

ماذا تفعل الأداة بالبيانات؟

.agents/AGENTS.md

AGENTS_MD

يتم تحميلها تلقائيًا كتعليمات ثابتة: أسلوب التحرير وسير العمل وقواعد التنفيذ

.agents/skills/digest-pdf/SKILL.md

SKILL_MD

يتم اكتشافها وتسجيلها تلقائيًا كمهارة محدّدة الاسم، ويستدعيها الوكيل بالاسم

.agents/skills/digest-pdf/scripts/generate_pdf.py

GENERATE_PDF_PY

عارض ملفات PDF مُنشأ مسبقًا، يكتب الوكيل summaries.json ثم ينفّذ هذا النص البرمجي

تأكيد

uv run python run_digest.py

تستغرق عملية التشغيل الآن من دقيقة إلى 3 دقائق. من المفترض أن ترى الوكيل يقرأ ملفات الإعدادات ويكتب الملخّصات ويحفظ ملف PDF:

[agent started]
  [tool] read_file (/.agents/skills/digest-pdf/SKILL.md)
  [tool] list_files (/.agents/skills/digest-pdf/scripts)
  [tool] read_file (/.agents/skills/digest-pdf/scripts/generate_pdf.py)
  [tool] run_code
  [tool] write_file (/workspace/summaries.json)
  [tool] run_code
  [tool] delete_file (/tmp/test_scrape.py)
I have successfully generated today's tech news digest and saved the formatted document to /workspace/digest.pdf.
Done. environment_id=4129ffd75574e308748e9425d7ec828f

أصبحت environment_id الآن قيمة حقيقية: تم تشغيل وضع الاختبار المعزول باستخدام ملفات الإعدادات وأنشأ الوكيل digest.pdf. تضيف الخطوة التالية إجراءً وقائيًا قبل التنزيل.

6. إضافة Safety Hook

تتيح لك الخطّافات تشغيل نص برمجي داخل البيئة التجريبية قبل أو بعد كل استدعاء أداة. يستخدم برنامج التلخيص code_execution لتشغيل نصوص Python البرمجية، لذا يمكن لخطاف pre_tool_execution اعتراض هذه الطلبات وحظر أوامر shell المدمرة قبل تنفيذها.

يقرأ وقت التشغيل .agents/hooks.json من البيئة التجريبية. قبل كل استدعاء لأداة مطابقة، يتم توجيه تفاصيل الاستدعاء إلى نص برمجي للبوابة على stdin. يطبع النص البرمجي {"decision": "allow"} أو {"decision": "deny", "reason": "..."} إلى stdout. يؤدي الرفض إلى إلغاء استدعاء الأداة، ويرى الوكيل سبب الرفض ويصحّح نفسه.

ما يجب إضافته

TODO 5: في run_digest.py، أضِف هذين الثابتَين بالقرب من أعلى الصفحة، بعد طلبات load_source الحالية:

import json

HOOKS_JSON = json.dumps({
    "safety-gate": {
        "pre_tool_execution": [
            {
                "matcher": "code_execution",
                "hooks": [
                    {
                        "type": "command",
                        "command": "python3 /.agents/hooks-scripts/gate.py",
                        "timeout": 10,
                    }
                ],
            }
        ]
    }
}, indent=2)

GATE_PY = """\
#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
cmd = str(data.get("tool_call", {}).get("args", {}))
if "rm -rf" in cmd:
    print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by safety gate."}))
else:
    print(json.dumps({"decision": "allow"}))
"""

مطلوب اتّخاذ إجراء 6: أضِف إدخالَين آخرَين إلى قائمة sources داخل interactions.create():

{"type": "inline", "target": ".agents/hooks.json",            "content": HOOKS_JSON},
{"type": "inline", "target": ".agents/hooks-scripts/gate.py", "content": GATE_PY},

كيف يتم تشغيل الخطافات أثناء تنفيذ الملخّص

في كل مرة يستدعي فيها الوكيل code_execution لتشغيل نص برمجي بلغة Python أو أمر shell، يوجّه وقت التشغيل تفاصيل الاستدعاء إلى gate.py أولاً. إذا كان الأمر يحتوي على rm -rf، يعرض الخطاف deny ويتلقّى الوكيل سبب الرفض ويعيد المحاولة باستخدام بديل آمن. تمرّ جميع طلبات تطبيق الرموز البرمجية الأخرى بدون تغيير.

تأكيد

uv run python run_digest.py

ستكون النتيجة مماثلة لما كانت عليه من قبل: يسمح حاجز الأمان بجميع أوامر إنشاء ملفات PDF العادية. للتأكّد من أنّ الخطاف يتم تنشيطه، غيِّر مؤقتًا إدخال الوكيل لطلب تشغيل rm -rf /tmp/test، وسيُبلغك الوكيل بأنّه تم حظر الأمر ويمكنك اختيار بديل.

7. تنزيل ملف PDF

كتب الوكيل digest.pdf إلى /workspace/digest.pdf داخل البيئة التجريبية. تتوفّر لقطة البيئة كأرشيف tar من خلال Gemini Files API.

ثبِّت requests إذا لزم الأمر:

uv pip install requests

المعلومات المطلوب إدخالها

فتح "download_pdf.py" تتضمّن مهمّتَين.

مطلوب اتّخاذ إجراء 1: ملء استدعاء requests.get():

    r = requests.get(
        f"https://generativelanguage.googleapis.com/v1beta/files/environment-{environment_id}:download",
        params={"alt": "media"},
        headers={"x-goog-api-key": api_key},
        allow_redirects=True,
    )
    r.raise_for_status()

يمثّل عنوان URL لقطة البيئة التجريبية. تعرض الدالة params={"alt": "media"} وحدات بايت أولية بدلاً من البيانات الوصفية. يتم أيضًا إثبات ملكية GEMINI_API_KEY الحالي باستخدام Files API.

مهمة 2: العثور على ملف PDF واستخراجه من أرشيف tar:

            member = next(m for m in tar.getmembers() if m.name.endswith("workspace/digest.pdf"))
            tar.extract(member, path=tmp, filter="data")

يختلف بادئة مسار tar باختلاف عمليات التشغيل، لذا ابحث حسب اللاحقة بدلاً من الترميز الثابت للمسار الدقيق. يؤدي filter="data" إلى إيقاف التحذير بشأن إيقاف استخراج ملفات tar غير الآمنة نهائيًا في الإصدار 3.13 من Python.

تأكيد

uv run python download_pdf.py
Saved digest.pdf (48,231 bytes)

افتح digest.pdf في الدليل نفسه. تحتوي هذه السمة على الملخّص المنسّق الذي أنشأه الوكيل من صفحات الويب المباشرة.

8. مواصلة المحادثة

لديك حاليًا اشتراك في digest.pdf. إذا كنت تريد الملف فقط، يمكنك التوقف هنا. تتعلّق هذه الخطوة بشيء مختلف: الطلب من الوكيل تغيير الملخّص بدون إعادة جلب المعلومات من الويب.

لا يزال وضع الحماية نشطًا. لا يزال لدى الوكيل /workspace/digest.pdf ويتذكّر كل قصة لخصها. يؤدي إجراء مكالمة ثانية إلى interactions.create() إلى إرسال رسالة متابعة إلى بيئة الاختبار المعزولة نفسها. في هذا المثال، نطلب من التطبيق إضافة ملاحظة "أهمية الخبر" تحت كل قصة، ويعدّل التطبيق ملف PDF في مكانه، بدون إعادة جلب البيانات وبدون إعادة تلخيصها.

المعلومات المطلوب إدخالها

فتح "refine_digest.py" تتضمّن ثلاثة مهام.

قائمة المهام 1 و2: املأ المَعلمتَين المتعدّدَتَي الأدوار داخل interactions.create():

    environment=environment_id,
    previous_interaction_id=interaction_id,

يستأنف environment=environment_id استخدام وضع الحماية نفسه مع ملفاته وحِزمه. تمنح previous_interaction_id=interaction_id الوكيل سجلّ المحادثات. لن يتم إجراء أي تغييرات أخرى مقارنةً بالطلب الأول.

مهمة 3: الاحتفاظ بقيمة interaction_id الجديدة في .env بعد انتهاء حلقة معالجة الأحداث:

save_env(INTERACTION_ID=interaction_id)

يؤدي كل طلب interactions.create() إلى إنشاء interaction_id جديد. تعني إعادة كتابتها أنّ عملية التنفيذ التالية ستمرر هذا التحسين كـ previous_interaction_id، وسيتم ربط عمليات التنفيذ بشكل صحيح. لا يتغيّر معرّف وضع الحماية أبدًا، لذا لا يلزم تعديل ENVIRONMENT_ID.

المَعلمتان اللتان تجعلان المحادثة المترابطة تعملان

رقم التعريف

ما يحافظ عليه

التشبيه

environment=environment_id

الملفات والحِزم المثبَّتة وحالة النظام: كل شيء في نظام ملفات Linux

الحفاظ على مكتب العمل نفسه بين الاجتماعات

previous_interaction_id=interaction_id

سجلّ المحادثات: ما قاله الوكيل وفعله في الجولات السابقة

تذكُّر ما تمت مناقشته في الاجتماع الأخير

يمكنك تمرير أيّ من المعرّفَين بشكلٍ مستقل:

  • environment_id فقط: إعادة استخدام الملفات والحِزم، ولكن بدء محادثة جديدة مفيدة لمهمة جديدة في مساحة العمل نفسها
  • previous_interaction_id فقط: مواصلة سياق المحادثة، ولكن في بيئة اختبار جديدة (ستتم إزالة الملفات).
  • كلاهما: استمرارية كاملة، وهو ما تستخدمه هذه الخطوة.

بدون environment_id: وضع حماية فارغ، بدون ملف PDF بدون previous_interaction_id: ما مِن سياق، ولا يمكن للوكيل تحسين قسم معيّن.

تأكيد

uv run python refine_digest.py

يجب أن يكون البث سريعًا، فالوكيل لا يعيد جلب أي بيانات. بعد الانتهاء من ذلك:

Refinement done.
Saved digest_v2.pdf (52,418 bytes)

افتح digest_v2.pdf وقارِنه بـ digest.pdf. يجب أن تتضمّن كل قصة الآن سطرًا بعنوان "أهمية الخبر".

9. الاحتفاظ بإعدادات الوكيل المُدار

لقد اجتازت كل مكالمة حتى الآن AGENTS.md وSKILL.md وgenerate_pdf.py المضمّنة. هذا الإجراء فعّال، ولكن رمز الاتصال يحمل محتوى الملف الكامل في كل عملية تشغيل. agents.create() يدمج عملية الإعداد في وكيل محفوظ باسم على جهة Google. يتم في الاستدعاء التالي تمرير معرّف الوكيل فقط:

Inline calls:   send sources on every call
Named agent:    bake once → invoke by ID, no sources

المعلومات المطلوب إدخالها

فتح "save_agent.py" يتضمّن مهمة واحدة (المهمة 1).

لاحظ أنّه يتم استيراد الثوابت مباشرةً من run_digest.py (بدون تكرار):

from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY

المهمة 1: ملء agents.create() المكالمة:

agent = client.agents.create(
    id="my-digest",
    base_agent=BASE_AGENT,
    agent_config={
        "type": "antigravity",
        "model": "gemini-3.7-flash",
    },
    description="Daily tech digest with editorial voice and PDF generation.",
    base_environment={
        "type": "remote",
        "sources": [
            {
                "type": "inline",
                "target": ".agents/AGENTS.md",
                "content": AGENTS_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/SKILL.md",
                "content": SKILL_MD,
            },
            {
                "type": "inline",
                "target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
                "content": GENERATE_PDF_PY,
            },
        ],
    },
)

agent_config يضبط النموذج الأساسي. gemini-3.7-flash هو الخيار التلقائي والأفضل لسير العمل هذا، ويتوفّر gemini-3.6-flash وgemini-3.5-flash وgemini-3.5-flash-lite إذا كنت تريد تشغيلًا أسرع أو أقل تكلفة.

base_environment (وليس environment) هو الفرق الأساسي عن الاتصال المضمّن في الخطوة السابقة: يتم تخزين المصادر من جهة Google ويتم تحميلها تلقائيًا في كل عملية استدعاء مستقبلية. يجب تشغيله مرة واحدة فقط، وليس في كل مرة يتم فيها تشغيل الملخّص.

التأكّد من صحة معلومات الوكيل: حفظ الوكيل

uv run python save_agent.py
Saved: my-digest
my-digest: Daily tech digest with editorial voice and PDF generation.

استدعاء الوكيل المحفوظ

فتح "invoke_agent.py" يتم استدعاء الوكيل المحفوظ حسب المعرّف بدون مصادر:

stream = client.interactions.create(
    agent="my-digest",
    input="Generate the digest.",
    stream=True,
    environment="remote",
)

قارِن ذلك بالاستدعاء المضمّن: يتم استبدال agent=BASE_AGENT بـ "my-digest"، ويتم استبدال كتلة environment الكاملة التي تتضمّن ثلاثة مصادر مضمّنة بـ environment="remote". تمّت إضافة الإعدادات مسبقًا من جهة Google.

التحقّق: استدعاء الوكيل المحفوظ

uv run python invoke_agent.py

سيظهر لك البث المباشر نفسه الذي يظهر في عملية التنفيذ المضمّنة، ولكن لا يتضمّن طلب البحث أي ملفات مصدر. بعد اكتمال عملية التشغيل، يتم تعديل ENVIRONMENT_ID وINTERACTION_ID في .env لتتمكّن من مواصلة استخدام refine_digest.py كما كان من قبل.

[agent started]
  [tool] read_file
  [tool] write_file
  [tool] run_code
I have successfully created today's tech news digest.
Done. environment_id=9a1c3e02-...

10. الإرسال عبر Gmail

أنشأ الوكيل الملخّص وحفظه في /workspace/digest.pdf. لقد نزّلته على جهازك حتى الآن. تُسلِّم هذه الخطوة الرسالة مباشرةً إلى بريدك الوارد من خلال جعل الوكيل يستدعي واجهة برمجة تطبيقات REST في Gmail من داخل وضع الحماية.

الأسلوب: يمكنك الحصول على رمز دخول OAuth 2.0 محليًا وتمريره إلى الوكيل في طلب input. يستخدم البرنامج code_execution لإنشاء رسالة إلكترونية بتنسيق MIME مع إرفاق ملف PDF وإرسالها إلى Gmail API. لا توجد أدوات مخصّصة، ولا يمكن تسجيل خادم MCP.

المتطلبات الأساسية

فعِّل واجهة برمجة التطبيقات Gmail API في مشروعك على Google Cloud Platform وأنشئ معرّف عميل OAuth 2.0:

  1. انتقِل إلى console.cloud.google.com/apis/library/gmail.googleapis.com وفعِّل Gmail API.
  2. انتقِل إلى واجهات برمجة التطبيقات والخدمات > بيانات الاعتماد > إنشاء بيانات اعتماد > معرّف عميل OAuth 2.0.
  3. نوع التطبيق: تطبيق على الكمبيوتر. نزِّل ملف JSON واحفظه باسم credentials.json في جذر المشروع.

أضِف عنوان البريد الإلكتروني للمستلِم إلى .env:

RECIPIENT_EMAIL=you@gmail.com

ثبِّت مكتبات المصادقة إذا لزم الأمر:

uv sync

المعلومات المطلوب إدخالها

فتح "send_digest.py" تتضمّن مهمّتَين.

مهمة 1: تحميل رمز دخول OAuth 2.0 أو إعادة تحميله:

creds = None
if TOKEN_FILE.exists():
    creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
        TOKEN_FILE.write_text(creds.to_json())
    else:
        flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
        creds = flow.run_local_server(port=8080, open_browser=False)
        TOKEN_FILE.write_text(creds.to_json())

أزِل السطر raise NotImplementedError بعد إضافته. عند التشغيل لأول مرة، سيتم فتح متصفّح لشاشة طلب الموافقة المتعلّقة ببروتوكول OAuth. يتم تخزين الرمز المميز مؤقتًا في .gmail_token.json لعمليات التشغيل المستقبلية.

الإجراء المطلوب 2: استبدِل input="" بالتعليمات المتعلقة بالبريد الإلكتروني. الرمز المميّز ضِمن النطاق الحالي باسم creds.token:

    input=(
        "Use the Gmail REST API to send an email:\n"
        f"  To: {recipient}\n"
        "  Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
        "  Attachment: /workspace/digest.pdf attached as digest.pdf\n\n"
        "For the body, read /workspace/summaries.json and format it as a "
        "human-readable newsletter, NOT raw JSON. Use this structure:\n"
        "  Tech Digest - <date>\n\n"
        "  === <source name> ===\n"
        "  1. <title>\n"
        "     <summary>\n\n"
        "Steps:\n"
        "1. Parse /workspace/summaries.json and build the formatted body text above.\n"
        "2. Read /workspace/digest.pdf as bytes.\n"
        "3. Build a MIME multipart message using Python's email library.\n"
        "4. Base64url-encode the raw message.\n"
        "5. POST to https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
        "with Authorization header using this token: "
        f"{creds.token}"
    ),

وظيفة كل جزء

يستأنف التفاعل في وضع الحماية نفسه الذي أنشأ فيه الوكيل digest.pdf وsummaries.json. تمنح previous_interaction_id الوكيل سجلّ المحادثات.

يتم تمرير رمز الدخول في السلسلة input. يقرأ الوكيل هذا المعرّف من الطلب ويستخدمه في العنوان Authorization: Bearer عند استدعاء Gmail API. ولا يتم نقلها إلى جهازك أو نظام الملفات المحلي مطلقًا.

يستخدم الوكيل code_execution لكتابة نص Python البرمجي وتشغيله داخل البيئة التجريبية: يقرأ summaries.json، وينسّقه كرسالة إخبارية، ويقرأ digest.pdf، وينشئ رسالة MIME متعددة الأجزاء، ويشفّرها باستخدام base64url، ثم يرسلها إلى https://gmail.googleapis.com/gmail/v1/users/me/messages/send.

تأكيد

uv run python send_digest.py
Sending digest...
[agent started]
  [tool] read_file (/workspace/summaries.json)
  [tool] run_code
  [tool] run_code
Email sent successfully.
Email sent. Check your inbox.

يُرجى التحقّق من صندوق البريد الوارد. تصل الرسالة الإلكترونية مع النص الأساسي المنسَّق على شكل نشرة إخبارية والمرفق digest.pdf.

11. جدولة عمليات التشغيل اليومية

تم تشغيل كل خطوة حتى الآن يدويًا. تتيح لك المشغّلات جدولة الوكيل المسمّى ليتم تشغيله تلقائيًا على تعبير cron. يتم تشغيل الوكيل في الوقت المحدّد، ويتم تنفيذ سير عمل الملخّص الكامل، ويبقى بيئة التنفيذ كما هي بين عمليات التنفيذ، لذا تكون الحِزم المثبَّتة في عملية التنفيذ الأولى متاحة في كل عملية تنفيذ لاحقة.

Manual:     python run_digest.py     → runs once, now
Trigger:    client.triggers.create() → runs every morning, automatically

المعلومات المطلوب إدخالها

فتح "create_trigger.py" يحتوي على مهمة واحدة.

المهمة 1: ملء المكالمة triggers.create() يتم تشغيل المشغّل لسير العمل الكامل كل يوم: إنشاء الملخّص وإرساله إلى بريدك الوارد. بما أنّ رموز الدخول تنتهي صلاحيتها بعد ساعة واحدة، يتم إدخال الرمز المميز لإعادة التحميل من .gmail_token.json كمصدر مضمّن حتى يتمكّن الوكيل من استبداله برمز مميز جديد في كل عملية تشغيل.

trigger = client.triggers.create(
    schedule="0 9 * * *",
    time_zone="UTC",
    display_name="daily-tech-digest",
    max_consecutive_failures=3,
    execution_timeout_seconds=600,
    interaction={
        "agent": "my-digest",
        "input": (
            f"Generate the daily tech digest following AGENTS.md instructions. "
            f"Then send an email to {recipient}:\n"
            "- Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
            "- Body: the content of /workspace/summaries.json formatted as a readable "
            "newsletter (NOT raw JSON).\n"
            "- Attachment: /workspace/digest.pdf\n\n"
            "For Gmail auth: read /workspace/.gmail_creds.json, POST to "
            "https://oauth2.googleapis.com/token with grant_type=refresh_token "
            "and the client_id, client_secret, refresh_token from the file to get an "
            "access_token. Then POST to "
            "https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
            "with Authorization: Bearer <access_token>."
        ),
        "environment": {
            "type": "remote",
            "sources": [
                {
                    "type": "inline",
                    "target": "/workspace/.gmail_creds.json",
                    "content": gmail_creds,
                }
            ],
        },
    },
)

execution_timeout_seconds=600 هي المهلة التلقائية. max_consecutive_failures=3 توقِف المشغّل مؤقتًا تلقائيًا بعد 3 عمليات تشغيل فاشلة على التوالي (القيمة التلقائية لواجهة برمجة التطبيقات هي 5، و3 هي قيمة أكثر تحفظًا لورشة العمل).

تُدرِج قائمة sources .gmail_creds.json في وضع الحماية عند /workspace/.gmail_creds.json. يقرأ الوكيل الرمز المميز لإعادة التحميل، ويستبدله برمز دخول مميز جديد، ثم يستدعي واجهة برمجة التطبيقات Gmail API. لا تنتهي صلاحية رموز التحديث، لذا تعمل هذه الطريقة في كل عملية تشغيل مجدوَلة بدون الحاجة إلى إعادة تحميل الرمز يدويًا.

أزِل السطر raise NotImplementedError بعد إضافة المكالمة.

تأكيد

uv run python create_trigger.py
Trigger created: trig_abc123
Next run:        2026-07-23T09:00:00Z

يحفظ create_trigger.py معرّف المشغّل في .env تلقائيًا.

للاطّلاع على سجلّ التنفيذ بعد عملية تشغيل، اتّبِع الخطوات التالية:

uv run python check_trigger.py

لتشغيل المشغّل على الفور بدون انتظار الوقت المُجدوَل التالي، اتّبِع الخطوات التالية:

uv run python fire_trigger.py

لإيقاف المشغّل مؤقتًا أو حذفه، اتّبِع الخطوات التالية:

uv run python pause_trigger.py

12. تنظيف

تنتهي صلاحية وضع الحماية تلقائيًا بعد 7 أيام من عدم النشاط. لا توجد خوادم لإيقافها. ما مِن حاويات لحذفها.

إذا حفظت إعدادات وكيل، احذفها باتّباع الخطوات التالية:

uv run python delete_agent.py

13. ملخّص

لقد أنشأت وكيلًا مُدارًا من البداية، مفهومًا واحدًا في كل مرة. في ما يلي ما تعلّمه كل تمرين:

تمرين

المفهوم

Key API

إجراء مكالمتك الأولى

توفير بيئة اختبارية حقيقية لنظام التشغيل Linux وبث أحداثها مباشرةً

‫interactions.create(agent, input, environment, stream=True)، event.event_type

تخصيص الوكيل

تثبيت ملفات الإعداد والاحتفاظ بأرقام التعريف في .env في عملية التشغيل نفسها

‫environment.sources، save_env

إضافة خطاف أمان

اعتراض طلبات الأدوات قبل تنفيذها ورفض الأوامر المدمرة

‫hooks.json، pre_tool_execution، gate.py

تنزيل ملف PDF

تنزيل ملف PDF بدون إعادة تشغيل الوكيل

‫Gemini Files API :download في download_pdf.py

متابعة المحادثة

مواصلة المحادثة بدون إعادة جلب الويب

‫environment=environment_id، previous_interaction_id=interaction_id

الاحتفاظ بإعدادات الوكيل

الاحتفاظ بإعدادات الوكيل واستدعاؤها حسب رقم التعريف بدون الحاجة إلى مصادر

‫agents.create()، agents.list()

الإرسال عبر Gmail

الحصول على رمز OAuth المميز محليًا، ثم تمريره إلى البرنامج الذي يستدعي واجهة برمجة تطبيقات REST في Gmail من خلال code_execution

OAuth 2.0، client.interactions.create(input=...)

تحديد مواعيد تشغيل يومية

تشغيل الوكيل تلقائيًا وفقًا لجدول زمني

client.triggers.create(schedule, time_zone, interaction)

الأنماط الرئيسية

  1. مكالمة واحدة، وضع حماية واحد: تعالج interactions.create() جميع البنية الأساسية (لا حاجة إلى نشر الحاويات أو تثبيت الحِزم محليًا)
  2. البث التقدّمي: stream=True يحوّل مربّعًا أسود مدته 90 ثانية إلى خلاصة مباشرة لاستدعاءات الأدوات وأجزاء النص
  3. المصادر المضمّنة: يمكنك تحميل AGENTS.md وSKILL.md والنصوص البرمجية المُنشأة مسبقًا إلى وضع الحماية بدون أي خطوة تحميل أو نشر.
  4. الاستفادة من ميزة "الاكتشاف التلقائي": يتم تلقائيًا اختيار الملفات الموضوعة في .agents/ (لا يلزم إعداد حزمة تطوير البرامج (SDK))
  5. الحالة الثنائية الأبعاد: تتتبّع environment_id الملفات والحِزم، وتتتبّع previous_interaction_id سياق المحادثة، ويمكن تمرير أي منهما بشكل مستقل
  6. تنزيل اللقطة: البيئة هي ملف tar كامل لنظام الملفات، ويمكن الوصول إليها من خلال Gemini Files API
  7. المستخدمون المحدّدون: يخبز agents.create() الإعداد بشكل دائم، ولا تمرر المكالمات المستقبلية سوى معرّف المستخدم وenvironment="remote"، بدون مصادر
  8. خطافات: hooks.json + أداة اعتراض النصوص البرمجية التي تعمل كبوابة قبل تنفيذ المكالمات، ويؤدي الرد deny إلى إلغاء المكالمة وتصحيح الوكيل لنفسه
  9. طلبات البيانات من واجهات برمجة التطبيقات الخارجية: إدخال بيانات الاعتماد في نافذة input المنبثقة، ثم يكتب الوكيل رمز الدمج ويشغّله داخل البيئة التجريبية من خلال code_execution
  10. عوامل التشغيل: جدولة وكيل باستخدام تعبير cron مع client.triggers.create()، وستبقى البيئة كما هي في جميع عمليات التنفيذ

‫ADK + Cloud Run في مقابل "الوكلاء المُدارون": نظرة سريعة على الفرق

إمكانية

‫ADK + Cloud Run

الوكلاء المُدارون في Gemini API

توفير وضع حماية

docker build + gcloud run deploy

interactions.create()

تحديد الأدوات

دوال Python المسجّلة لدى الوكيل

مضمّنة في: تصفّح الويب، وتنفيذ الرموز البرمجية، ونظام الملفات

تثبيت الحِزم

‫pip install في Dockerfile

تشغيل الوكيل pip install داخل وضع الحماية

أحداث البث

بنية تحتية مخصّصة لخدمة SSE

stream=True

مواصلة جلسة

قاعدة بيانات الجلسات + إدخال السياق

environment_id + previous_interaction_id

ملفات الإعداد

مضمّن في الوكيل أو يتم إدخاله عند بدء التشغيل

تم التحميل عبر environment.sources

البنية الأساسية المطلوب إدارتها

الحاوية وCloud Run وخدمة إدارة الهوية وإمكانية الوصول والأسرار

بلا

الخطوات التالية