یک نماینده خلاصه فناوری روزانه با نمایندگان مدیریت‌شده در API Gemini بسازید

۱. مرور کلی

چشم‌انداز هوش مصنوعی و فناوری سریع‌تر از آن چیزی که هر کسی بتواند ردیابی کند، حرکت می‌کند. مدل‌ها، مقالات و محصولات جدید روزانه منتشر می‌شوند. یک عامل خلاصه‌کننده که عناوین امروز را دریافت می‌کند، خلاصه‌های دقیقی می‌نویسد و هر روز صبح یک فایل PDF تولید می‌کند، این مشکل را حل می‌کند، اما ساختن چنین عاملی قبلاً به معنای انتخاب یک چارچوب، تعریف ابزارها در پایتون، نوشتن یک حلقه تنظیم، بسته‌بندی یک کانتینر و استقرار در Cloud Run بود. همه این‌ها قبل از آن بود که عامل یک درخواست وب واحد ارسال کند.

عامل‌های مدیریت‌شده در رابط برنامه‌نویسی کاربردی Gemini معادله را تغییر می‌دهند. شما دو فایل پیکربندی markdown و یک اسکریپت رندر از پیش ساخته شده می‌نویسید، یک فراخوانی API انجام می‌دهید و یک سندباکس واقعی اوبونتو بوت می‌شود، وب را مرور می‌کند، خلاصه‌های شما را می‌نویسد و یک PDF تولید می‌کند. بدون کانتینر. بدون استقرار. بدون کد تنظیم.

در این آزمایشگاه کد، شما دقیقاً همان عامل را خواهید ساخت: از یک تابع خالی تا یک خلاصه روزانه‌ی کاری، هر بار یک مفهوم.

آنچه خواهید ساخت

  • اولین عامل مدیریت‌شده خود را در یک سندباکس واقعی لینوکس ایجاد و اجرا کنید
  • سفارشی‌سازی عامل با صدای سردبیر، منابع وب و مهارت PDF
  • یک قلاب ایمنی اضافه کنید تا دستورات مخرب را قبل از اجرا مسدود کنید
  • فایل PDF تولید شده توسط عامل را دانلود کنید
  • خلاصه را در یک مکالمه چند مرحله‌ای بدون نیاز به واکشی مجدد وب، اصلاح کنید.
  • پیکربندی عامل را ذخیره کنید و در اجراهای بعدی آن را با شناسه فراخوانی کنید
  • خلاصه را از طریق API Gmail به صندوق ورودی خود ارسال کنید
  • برنامه ریزی کنید که اپراتور هر روز به طور خودکار اجرا و ارسال شود

آنچه نیاز دارید

  • پایتون ۳.۱۰+
  • یک کلید API از Gemini: aistudio.google.com/api-keys (شامل نسخه رایگان؛ برای اجرای بدون وقفه، پرداخت با کارت اعتباری توصیه می‌شود)

۲. منظور از نمایندگان مدیریت‌شده در رابط برنامه‌نویسی نرم‌افزار Gemini چیست؟

سه سطح از سیستم‌های هوش مصنوعی

قبل از پرداختن به کد، در اینجا به بررسی جایگاه Managed Agents نسبت به دو گزینه دیگر می‌پردازیم:

سطح

آنچه هست

چه کسی زیرساخت‌ها را مدیریت می‌کند؟

استاندارد LLM

شما دستور می‌دهید، با متن پاسخ می‌دهد. نه دستی، نه حافظه‌ای، نه اینترنتی.

ناموجود: به تنهایی نمی‌تواند کاری انجام دهد

نماینده خود میزبان

شما ADK/LangChain/AutoGen + Docker + tools + memory را سیم‌کشی می‌کنید.

شما: همه آن (یا یک پلتفرم مدیریت‌شده مانند Agent Engine)

عامل مدیریت‌شده

شما به آن هدف می‌دهید. گوگل یک محیط امن (sandbox) فراهم می‌کند. عامل کد را می‌نویسد، آن را اجرا می‌کند، خطاها را می‌خواند، وب را جستجو می‌کند و اشکالات را به صورت خودکار برطرف می‌کند.

گوگل: همه چیز

این آزمایشگاه کد مربوط به ردیف سوم است. شما یک وظیفه و فایل‌های پیکربندی را ارائه می‌دهید. گوگل بقیه موارد را مدیریت می‌کند.

آنچه با ADK + Cloud Run خواهید ساخت

برای ساخت یک عامل خلاصه اخبار که وب را مرور می‌کند، پایتون را اجرا می‌کند و 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

این قبل از آن است که عامل یک بار اجرا شود. شما هنوز هم ایزوله‌سازی سندباکس (بنابراین عامل نمی‌تواند به سرور شما آسیب برساند)، نصب بسته، مدیریت وضعیت بین فراخوانی‌های ابزار و زیرساخت استریمینگ برای ارسال رویدادها به کلاینت را در اختیار دارید.

چه چیزی جایگزین Managed Agents می‌شود؟

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 نیاز دارد

آنچه نمایندگان مدیریت‌شده برای شما انجام می‌دهند

تصویر کانتینر + داکرفایل + CI/CD

سندباکس اوبونتو کاملاً مدیریت‌شده (پایتون ۳.۱۲، نود ۲۲، ۴ پردازنده / ۱۶ گیگابایت رم)

استقرار + مقیاس‌بندی Cloud Run

به ازای هر تعامل ارائه می‌شود، پس از ۷ روز عدم فعالیت به طور خودکار منقضی می‌شود

جداسازی در جعبه شنی

ایزوله به ازای هر تعامل

ابزار PDF سفارشی + pip install

عامل بسته‌ها را درون جعبه شنی نصب می‌کند

زیرساخت استریمینگ SSE

stream=True یک رویداد قابل تکرار را برمی‌گرداند

تعریف ابزار در پایتون

ابزارهای داخلی: مرور وب، اجرای کد، سیستم فایل

مدیریت وضعیت بین فراخوانی‌های ابزار

در حلقه استدلال عامل تعبیه شده است

شما فایل‌های پیکربندی ( AGENTS.md ، SKILL.md ، یک اسکریپت از پیش ساخته شده) را می‌نویسید و یک فراخوانی API انجام می‌دهید. گوگل بقیه کارها را انجام می‌دهد.

نحوه کار جعبه شنی

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

سندباکس به مدت ۷ روز عدم فعالیت ادامه می‌یابد. می‌توانید آن را با environment_id از سر بگیرید تا خروجی را اصلاح کنید، وظایف بعدی را اجرا کنید یا آن را در یک عامل نامگذاری شده ذخیره شده فورک کنید.

۳. راه‌اندازی

برای باز کردن این آزمایشگاه کد در پوسته ابری گوگل، روی دکمه زیر کلیک کنید. همه وابستگی‌ها از قبل نصب شده‌اند.

باز کردن در 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

کلید API خود را پیکربندی کنید

cp .env.example .env
cloudshell edit .env

کلید خود را تنظیم کنید:

GEMINI_API_KEY=your-key-here

نصب وابستگی‌ها

uv sync

۴. اولین تماس خود را با کارشناس مربوطه برقرار کنید

فایل آغازگر را باز کنید

cloudshell edit run_digest.py

run_digest() یک TODO برای پر کردن در حال حاضر و سه TODO دیگر برای مرحله بعدی دارد. دو کمکی از قبل در بالای آن پر شده‌اند:

  • load_source(path) : فایلی را از .agents/ نسبت به اسکریپت می‌خواند. در تمرین بعدی از آن برای سوار کردن صدای ویراستار، فایل PDF playbook و رندرکننده در sandbox استفاده خواهید کرد.
  • run_stream(stream) : جریان رویداد را پردازش می‌کند و (environment_id, interaction_id) را برمی‌گرداند. نیازی نیست خودتان حلقه رویداد را بنویسید.

چه چیزی اضافه کنیم

TODO 1: به جای pass بنویسید (فعلا TODO های 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 به‌طور خودکار فعال می‌شوند. با یک فراخوانی، یک محیط اوبونتو کاملاً مدیریت‌شده با Python 3.12، Node.js 22، git، pip و curl از پیش نصب‌شده فراهم می‌شود. هیچ کانتینری برای ساخت، هیچ استقراری برای اجرا وجود ندارد.
  • input : وظیفه مربوط به این اجرا. عامل، اخبار هکرها را مرور می‌کند و در مورد نتایج استدلال می‌کند.
  • environment="remote" : یک فضای ابری جدید برای این تعامل فراهم می‌کند.
  • stream=True : به جای مسدود کردن، مجموعه‌ای از رویدادها را برمی‌گرداند. بدون آن، فراخوانی 30 تا 90 ثانیه منتظر می‌ماند و تمام خروجی‌ها را به صورت interaction.output_text برمی‌گرداند. با پخش جریانی، دلیل عامل را می‌بینید و همانطور که اتفاق می‌افتد عمل می‌کنید. پخش جریانی در اینجا یک ویژگی پیشرفته نیست: پیش‌فرض درست است، زیرا یک جعبه سیاه 90 ثانیه‌ای هیچ سیگنالی در مورد اینکه آیا عامل کار می‌کند یا گیر کرده است، به شما نمی‌دهد.

environment_id یک هندل برای sandbox است که تازه اجرا شده است. پس از interaction.completed ، sandbox خاموش نمی‌شود: تا ۷ روز زنده می‌ماند. environment_id روشی است که شما به آن برمی‌گردید. آن را به یک interactions.create() دوم منتقل کنید و agent در همان سیستم فایل، با همان فایل‌ها و بسته‌های نصب شده، کار خود را از سر می‌گیرد، گویی هرگز آنجا را ترک نکرده است. مرحله بعدی از آن برای دانلود PDF بدون اجرای مجدد agent استفاده می‌کند و مرحله بعد از آن از آن برای ادامه مکالمه استفاده می‌کند.

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

API حتی با environment="remote" یک environment_id واقعی برمی‌گرداند. سندباکس اجرا شد. چیزی که کم است پیکربندی است: بدون صدا، بدون مهارت، بدون مولد 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) منتقل می‌شود

۵. شخصی‌سازی عامل

عامل هیچ دستورالعملی نداشت: نه صدایی، نه مهارتی، نه تولیدکننده‌ی PDF. در این مرحله، فایل‌های پیکربندی را از .agents/ بارگذاری می‌کنید و آنها را در sandbox قرار می‌دهید.

چه چیزی را تغییر دهیم

چهار تغییر در run_digest.py ایجاد کنید:

مرحله ۲: در زیر 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() ایجاد کنید:

کار ۳: environment از "remote" به sources dict تغییر دهید و 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,
                },
            ],
        },

مرحله ۴: این خط را درست بعد از print(f"\nDone. environment_id={environment_id}") اضافه کنید:

    save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)

save_env از قبل در run_digest.py تعریف شده است. این تابع هر دو شناسه را در .env می‌نویسد تا در مرحله بعدی بتواند فایل PDF را بدون اجرای مجدد عامل دانلود کند.

کاری که هر منبع انجام می‌دهد

هر منبع، فایلی است که در هنگام راه‌اندازی و قبل از اجرای عامل، در سیستم فایل sandbox نصب می‌شود. مسیرهای target با جایی که Antigravity harness انتظار دارد آنها را پیدا کند، مطابقت دارند:

.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

حالا اجرا ۱ تا ۳ دقیقه طول می‌کشد. باید ببینید که عامل فایل‌های پیکربندی را می‌خواند، خلاصه‌ها را می‌نویسد و 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 اکنون یک مقدار واقعی است: sandbox با فایل‌های پیکربندی شما اجرا شد و عامل digest.pdf را ایجاد کرد. مرحله بعدی قبل از دانلود، یک قلاب ایمنی اضافه می‌کند.

۶. یک قلاب ایمنی اضافه کنید

هوک‌ها به شما اجازه می‌دهند قبل یا بعد از هر فراخوانی ابزار، یک اسکریپت را درون جعبه شنی اجرا کنید. عامل digest از code_execution برای اجرای اسکریپت‌های پایتون استفاده می‌کند، بنابراین یک هوک pre_tool_execution می‌تواند آن فراخوانی‌ها را رهگیری کرده و دستورات مخرب shell را قبل از اجرا مسدود کند.

زمان اجرا، فایل .agents/hooks.json را از sandbox می‌خواند. قبل از هر فراخوانی ابزار منطبق، جزئیات فراخوانی را به اسکریپت گیت شما در stdin ارسال می‌کند. اسکریپت عبارت‌های {"decision": "allow"} یا {"decision": "deny", "reason": "..."} را در stdout چاپ می‌کند. یک deny فراخوانی ابزار را لغو می‌کند و عامل دلیل شما را می‌بیند و خود را اصلاح می‌کند.

چه چیزی اضافه کنیم

مرحله ۵: در 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"}))
"""

مرحله ۶: دو ورودی دیگر به لیست 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 برای اجرای یک اسکریپت پایتون یا دستور shell فراخوانی می‌کند، زمان اجرا ابتدا جزئیات فراخوانی را به gate.py ارسال می‌کند. اگر دستور شامل rm -rf باشد، قلاب deny را برمی‌گرداند و عامل دلیل رد شدن را دریافت می‌کند و با یک جایگزین امن دوباره تلاش می‌کند. سایر فراخوانی‌های اجرای کد بدون تغییر عبور می‌کنند.

تأیید

uv run python run_digest.py

خروجی مشابه قبل است: دروازه ایمنی به تمام دستورات تولید PDF معمولی اجازه می‌دهد. برای تأیید اجرای قلاب، ورودی عامل را موقتاً تغییر دهید تا از آن بخواهید rm -rf /tmp/test اجرا کند - خواهید دید که عامل گزارش می‌دهد که دستور مسدود شده است و می‌توانید جایگزین دیگری را انتخاب کنید.

۷. فایل PDF را دانلود کنید

عامل digest.pdf در /workspace/digest.pdf درون سندباکس نوشت. اسنپ‌شات محیط به صورت یک آرشیو tar از طریق رابط برنامه‌نویسی Gemini Files در دسترس است.

requests نصب در صورت نیاز:

uv pip install requests

چه چیزی را پر کنیم

download_pdf.py باز کنید. این فایل دو TODO دارد.

کار ۱: فراخوانی 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 به snapshot مربوط به sandbox اشاره می‌کند. params={"alt": "media"} به جای فراداده، بایت‌های خام را برمی‌گرداند. GEMINI_API_KEY موجود شما، API فایل‌ها را نیز احراز هویت می‌کند.

مرحله ۲: پیدا کردن و استخراج فایل 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 را سرکوب می‌کند.

تأیید

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

digest.pdf در همان دایرکتوری باز کنید. این فایل شامل فایل خلاصه فرمت‌شده‌ای است که عامل از صفحات وب زنده تولید کرده است.

۸. مکالمه را ادامه دهید

شما از قبل digest.pdf دارید. اگر فقط فایل را می‌خواستید، کار تمام است. این مرحله در مورد چیز متفاوتی است: درخواست از عامل برای تغییر digest بدون واکشی مجدد وب.

سندباکس هنوز فعال است. عامل هنوز /workspace/digest.pdf را دارد و هر داستانی را که خلاصه کرده به خاطر می‌آورد. فراخوانی دوم interactions.create() یک پیام پیگیری به همان سندباکس ارسال می‌کند. در اینجا از آن می‌خواهید که یک یادداشت «چرا مهم است» زیر هر داستان اضافه کند و PDF را بدون واکشی مجدد و خلاصه‌سازی مجدد، به‌روزرسانی می‌کند.

چه چیزی را پر کنیم

refine_digest.py را باز کنید. این فایل سه TODO دارد.

TODO های ۱ و ۲: دو پارامتر multi-turn را درون interactions.create() وارد کنید:

    environment=environment_id,
    previous_interaction_id=interaction_id,

environment=environment_id همان sandbox را با فایل‌ها و بسته‌هایش از سر می‌گیرد. previous_interaction_id=interaction_id تاریخچه مکالمات عامل را به او می‌دهد. از اولین فراخوانی هیچ چیز دیگری تغییر نمی‌کند.

مرحله ۳: مقدار interaction_id جدید را پس از حلقه رویداد، در فایل .env ذخیره کنید:

save_env(INTERACTION_ID=interaction_id)

هر فراخوانی interactions.create() یک interaction_id جدید تولید می‌کند. نوشتن مجدد آن به این معنی است که اجرای بعدی این اصلاح را به عنوان previous_interaction_id پشت سر می‌گذارد و زنجیره‌سازی به درستی انجام می‌شود. شناسه sandbox هرگز تغییر نمی‌کند، بنابراین ENVIRONMENT_ID نیازی به به‌روزرسانی ندارد.

دو پارامتری که باعث می‌شوند چند دور زدن کار کند

شناسه

آنچه را که حفظ می‌کند

مقایسه

environment=environment_id

فایل‌ها، بسته‌های نصب‌شده، وضعیت سیستم: همه چیز در سیستم فایل لینوکس

نگه داشتن میز اداری یکسان بین جلسات

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 مقایسه کنید. اکنون باید به هر داستان یک خط «چرا مهم است» اضافه شده باشد.

۹. پیکربندی عامل مدیریت‌شده را حفظ کنید

هر فراخوانی تاکنون به صورت درون‌خطی به AGENTS.md ، SKILL.md و generate_pdf.py ارسال شده است. این روش جواب می‌دهد، اما کد فراخوانی شما در هر اجرا، محتوای کامل فایل را حمل می‌کند. agents.create() پیکربندی را در یک عامل ذخیره‌شده با نام در سمت گوگل ذخیره می‌کند. فراخوانی بعدی فقط شناسه عامل را ارسال می‌کند:

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

چه چیزی را پر کنیم

save_agent.py را باز کنید. این یک TODO (TODO 1) دارد.

توجه داشته باشید که ثابت‌ها مستقیماً از run_digest.py وارد شده‌اند (بدون تکرار):

from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY

مرحله ۱: فراخوانی 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 ) تفاوت کلیدی با فراخوانی درون‌خطی در مرحله قبل است: منابع در سمت گوگل ذخیره می‌شوند و در هر فراخوانی بعدی به طور خودکار نصب می‌شوند. آن را یک بار اجرا کنید، نه در هر اجرای خلاصه.

تأیید کنید: عامل را ذخیره کنید

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" جایگزین شده است. پیکربندی از قبل در سمت گوگل اعمال شده است.

تأیید: عامل ذخیره شده را فراخوانی کنید

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-...

۱۰. ارسال از طریق جیمیل

عامل، خلاصه را تولید و آن را در /workspace/digest.pdf ذخیره کرده است. تاکنون آن را به صورت محلی دانلود کرده‌اید. در این مرحله، با فراخوانی رابط برنامه‌نویسی کاربردی Gmail REST از داخل سندباکس، آن را مستقیماً به صندوق ورودی شما ارسال می‌کند.

رویکرد: شما یک توکن دسترسی OAuth 2.0 را به صورت محلی دریافت می‌کنید و آن را در اعلان input به عامل (agent) ارسال می‌کنید. عامل code_execution برای ساخت یک ایمیل MIME با پیوست PDF استفاده می‌کند و آن را به API Gmail ارسال می‌کند. بدون ابزار سفارشی، بدون ثبت نام در سرور MCP.

پیش‌نیازها

API جیمیل را در پروژه GCP خود فعال کنید و یک شناسه کلاینت OAuth 2.0 ایجاد کنید:

  1. به console.cloud.google.com/apis/library/gmail.googleapis.com بروید و Gmail API را فعال کنید.
  2. به APIها و خدمات > اعتبارنامه‌ها > ایجاد اعتبارنامه‌ها > شناسه کلاینت OAuth 2.0 بروید.
  3. نوع برنامه: برنامه دسکتاپ . فایل JSON را دانلود کرده و آن را با نام credentials.json در ریشه پروژه ذخیره کنید.

ایمیل گیرنده خود را به .env اضافه کنید:

RECIPIENT_EMAIL=you@gmail.com

در صورت نیاز، کتابخانه‌های احراز هویت را نصب کنید:

uv sync

چه چیزی را پر کنیم

send_digest.py باز کنید. دو TODO دارد.

انجام ۱: بارگذاری یا به‌روزرسانی توکن دسترسی 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 ذخیره می‌شود.

کار ۲: 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 ارسال می‌شود. عامل آن را از اعلان می‌خواند و هنگام فراخوانی API جیمیل، در هدر Authorization: Bearer از آن استفاده می‌کند. این توکن هرگز به دستگاه محلی یا سیستم فایل شما دست نمی‌زند.

عامل از code_execution برای نوشتن و اجرای یک اسکریپت پایتون در داخل جعبه شنی استفاده می‌کند: فایل summaries.json را می‌خواند، آن را به صورت خبرنامه قالب‌بندی می‌کند، digest.pdf می‌خواند، یک پیام چندبخشی MIME می‌سازد، آن را base64url-encode می‌کند و به 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 پیوست شده، به دستتان می‌رسد.

۱۱. برای دویدن‌های روزانه برنامه‌ریزی کنید

تاکنون هر مرحله به صورت دستی فعال شده است. تریگرها به شما امکان می‌دهند عامل نامگذاری شده را طوری برنامه‌ریزی کنید که به طور خودکار روی یک عبارت cron اجرا شود. عامل در زمان برنامه‌ریزی شده فعال می‌شود، گردش کار کامل خلاصه را اجرا می‌کند و محیط بین اجراها حفظ می‌شود، بنابراین بسته‌های نصب شده در اولین اجرا در هر اجرای بعدی در دسترس هستند.

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

چه چیزی را پر کنیم

create_trigger.py باز کنید. یک TODO دارد.

انجام ۱: فراخوانی triggers.create() را پر کنید. trigger هر روز گردش کار کامل را اجرا می‌کند: خلاصه را تولید کرده و آن را به صندوق ورودی شما ارسال می‌کند. از آنجا که توکن‌های دسترسی ظرف یک ساعت منقضی می‌شوند، توکن refresh را از .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 پس از ۳ اجرای ناموفق پشت سر هم، تریگر را به طور خودکار متوقف می‌کند (پیش‌فرض API، ۵ است؛ ۳ برای یک کارگاه محتاطانه‌تر است).

فهرست sources ، فایل .gmail_creds.json را به داخل sandbox در /workspace/.gmail_creds.json تزریق می‌کند. عامل آن را می‌خواند، توکن به‌روزرسانی را با یک توکن دسترسی جدید تعویض می‌کند و 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

۱۲. تمیز کردن

سندباکس پس از ۷ روز عدم فعالیت به طور خودکار منقضی می‌شود. هیچ سروری برای متوقف کردن وجود ندارد. هیچ کانتینری برای حذف وجود ندارد.

اگر پیکربندی عامل را ذخیره کرده‌اید، آن را حذف کنید:

uv run python delete_agent.py

۱۳. خلاصه

شما یک عامل مدیریت‌شده را از ابتدا، یک مفهوم در هر زمان، ساختید. در اینجا چیزی است که هر تمرین آموزش می‌دهد:

ورزش

مفهوم

API کلید

اولین تماس خود را برقرار کنید

یک سندباکس واقعی لینوکس تهیه کنید و رویدادهای آن را به صورت زنده پخش کنید

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

سفارشی‌سازی عامل

فایل‌های پیکربندی را mount کنید؛ شناسه‌ها را در همان اجرا در .env ‎ حفظ کنید

environment.sources ، save_env

قلاب ایمنی اضافه کنید

قبل از اجرا، فراخوانی‌های ابزار را رهگیری کنید؛ دستورات مخرب را رد کنید

hooks.json ، pre_tool_execution ، gate.py

دانلود پی دی اف

دانلود فایل PDF بدون اجرای مجدد عامل

API فایل‌های Gemini :download در download_pdf.py

ادامه گفتگو

ادامه مکالمه بدون نیاز به باز کردن مجدد وب

environment=environment_id ، previous_interaction_id=interaction_id

پیکربندی عامل Persist

پیکربندی عامل را حفظ کنید؛ با شناسه فراخوانی کنید، هیچ منبعی لازم نیست

agents.create() ، agents.list()

ارسال از طریق جیمیل

یک توکن OAuth را به صورت محلی دریافت کنید؛ آن را به عامل ارسال کنید، که Gmail REST API را از طریق code_execution فراخوانی می‌کند.

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

برنامه‌ریزی برای دویدن‌های روزانه

اجرای خودکار عامل در یک برنامه cron

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

الگوهای کلیدی

  1. یک فراخوانی، یک جعبه شنی : interactions.create() تمام زیرساخت‌ها را مدیریت می‌کند (بدون کانتینر برای استقرار، بدون بسته برای نصب محلی)
  2. پخش پیش‌رونده : stream=True یک جعبه سیاه ۹۰ ثانیه‌ای را به یک فید زنده از فراخوانی‌های ابزار و تکه‌های متن تبدیل می‌کند.
  3. منابع درون‌خطی : نصب AGENTS.md ، SKILL.md و اسکریپت‌های از پیش ساخته شده در sandbox بدون هیچ مرحله آپلود یا استقرار
  4. مهار کشف خودکار : فایل‌های قرار داده شده در .agents/ به طور خودکار انتخاب می‌شوند (نیازی به پیکربندی SDK نیست)
  5. حالت دوبعدی : environment_id فایل‌ها و بسته‌ها را ردیابی می‌کند؛ previous_interaction_id زمینه مکالمه را ردیابی می‌کند؛ هر کدام را می‌توان به طور مستقل ارسال کرد
  6. دانلود اسنپ‌شات : محیط یک فایل سیستم کامل tar است که از طریق Gemini Files API قابل دسترسی است.
  7. عامل‌های نامگذاری‌شده : agents.create() پیکربندی را به‌طور دائم ذخیره می‌کند؛ فراخوانی‌های بعدی فقط شناسه عامل و environment="remote" را بدون هیچ منبعی ارسال می‌کنند.
  8. Hooks : hooks.json + یک اسکریپت گیت، فراخوانی‌های ابزار را قبل از اجرا رهگیری می‌کند؛ یک پاسخ deny فراخوانی را لغو می‌کند و عامل خود را اصلاح می‌کند.
  9. فراخوانی‌های خارجی API : یک اعتبارنامه را در اعلان input ارسال کنید؛ عامل کد ادغام را از طریق code_execution در داخل sandbox می‌نویسد و اجرا می‌کند.
  10. تریگرها : یک عامل را روی یک عبارت cron با client.triggers.create() زمان‌بندی می‌کند؛ محیط در طول اجراها حفظ می‌شود.

ADK + Cloud Run در مقابل Managed Agents: تفاوت در یک نگاه

قابلیت

ADK + اجرای ابری

عامل‌های مدیریت‌شده در رابط برنامه‌نویسی نرم‌افزار Gemini

فراهم کردن یک سندباکس

docker build + gcloud run deploy

interactions.create()

تعریف ابزارها

توابع پایتون ثبت شده در عامل

داخلی: مرور وب، اجرای کد، سیستم فایل

نصب بسته‌ها

pip install در داکرفایل

عامل، pip install درون جعبه شنی اجرا می‌کند.

رویدادهای جریان

زیرساخت SSE سفارشی

stream=True

ادامه یک جلسه

پایگاه داده جلسه + تزریق زمینه

environment_id + previous_interaction_id

فایل‌های پیکربندی

کدنویسی ثابت در عامل یا تزریق در هنگام راه‌اندازی

از طریق environment.sources نصب شده است

زیرساخت برای مدیریت

کانتینر، Cloud Run، IAM، اسرار

هیچکدام

مراحل بعدی