1. Présentation
Le paysage de l'IA et de la technologie évolue plus vite que quiconque ne peut le suivre. De nouveaux modèles, articles et produits sont disponibles tous les jours. Un agent de synthèse qui récupère les titres du jour, rédige des résumés précis et génère un PDF tous les matins résoudrait ce problème. Toutefois, pour en créer un, il fallait auparavant choisir un framework, définir des outils en Python, écrire une boucle d'orchestration, empaqueter un conteneur et le déployer sur Cloud Run. Tout cela avant que l'agent n'ait effectué une seule requête Web.
Les Managed Agents de l'API Gemini changent la donne. Vous écrivez deux fichiers de configuration Markdown et un script de rendu prédéfini, effectuez un appel d'API, et un véritable bac à sable Ubuntu démarre, navigue sur le Web, rédige vos résumés et génère un PDF. Aucun conteneur. Aucun déploiement. Aucun code d'orchestration.
Dans cet atelier de programmation, vous allez créer cet agent, en partant d'une fonction vide pour arriver à un récapitulatif quotidien fonctionnel, un concept à la fois.
Objectifs de l'atelier
- Créer et exécuter votre premier agent géré dans un véritable bac à sable Linux
- Personnaliser l'agent avec une ligne éditoriale, des sources Web et une compétence PDF
- Ajouter un crochet de sécurité pour bloquer les commandes destructrices avant leur exécution
- Télécharger le PDF généré par l'agent
- Affiner le résumé dans une conversation multitour sans récupérer à nouveau le contenu Web
- Enregistrer la configuration de l'agent et l'appeler par ID lors des prochaines exécutions
- Envoyer le récapitulatif dans votre boîte de réception via l'API Gmail
- Programmer l'exécution et l'envoi automatiques de l'agent chaque jour
Prérequis
- Python 3.10+
- Clé API Gemini : aistudio.google.com/api-keys (niveau sans frais inclus ; facturation recommandée pour une exécution ininterrompue)
2. Que sont les Managed Agents dans l'API Gemini ?
Trois niveaux de systèmes d'IA
Avant de nous intéresser au code, voici où se situe Managed Agents par rapport aux deux autres alternatives :
Niveau | Description | Qui gère l'infrastructure ? |
LLM standard | Vous lui posez une question, elle vous répond par écrit. Sans les mains, sans mémoire, sans Internet. | N/A : il ne peut rien faire seul |
Agent auto-hébergé | Vous connectez ADK/LangChain/AutoGen + Docker + outils + mémoire. | Vous : tout (ou une plate-forme gérée comme Agent Engine) |
Agent géré | Vous lui donnez un objectif. Google provisionne un bac à sable sécurisé. L'agent écrit du code, l'exécute, lit les erreurs, effectue des recherches sur le Web et corrige les bugs de manière autonome. | Google : tout |
Cet atelier de programmation porte sur la troisième ligne. Vous fournissez des fichiers de tâches et de configuration. Google s'occupe du reste.
Ce que vous pouvez créer avec ADK et Cloud Run
Pour créer un agent de synthèse d'actualités qui navigue sur le Web, exécute Python et génère un PDF, vous aurez besoin de tout cela avec 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
C'est avant que l'agent ne s'exécute une fois. Vous êtes toujours propriétaire de l'isolation du bac à sable (l'agent ne peut donc pas endommager votre serveur), de l'installation des packages, de la gestion de l'état entre les appels d'outils et de l'infrastructure de streaming pour transmettre les événements à un client.
Ce que les agents gérés remplacent
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,
},
],
},
)
Ce que nécessitent ADK et Cloud Run | Ce que les Managed Agents gèrent pour vous |
Image de conteneur + Dockerfile + CI/CD | Bac à sable Ubuntu entièrement géré (Python 3.12, Node 22, 4 CPU / 16 Go de RAM) |
Déploiement et scaling Cloud Run | Provisionné par interaction, expire automatiquement après sept jours d'inactivité |
Isolation du bac à sable | Isolée par interaction |
Outil PDF personnalisé + | L'agent installe les packages dans le bac à sable. |
Infrastructure de streaming SSE |
|
Définitions d'outils en Python | Outils intégrés : navigation Web, exécution de code, système de fichiers |
Gestion de l'état entre les appels d'outils | Intégré à la boucle de raisonnement de l'agent |
Vous écrivez des fichiers de configuration (AGENTS.md, SKILL.md, un script prédéfini) et effectuez un appel d'API. Google s'occupe du reste.
Fonctionnement du bac à sable
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
Le bac à sable est conservé pendant sept jours d'inactivité. Vous pouvez la reprendre avec environment_id pour affiner le résultat, exécuter des tâches de suivi ou la dupliquer dans un agent nommé enregistré.
3. Configurer
Option A : Cloud Shell (recommandée)
Cliquez sur le bouton ci-dessous pour ouvrir cet atelier de programmation dans Google Cloud Shell. Toutes les dépendances sont préinstallées.
Option B : Configuration locale
git clone https://github.com/Saoussen-CH/tech-digest-managed-agent.git
cd tech-digest-managed-agent
Installez uv si nécessaire :
curl -LsSf https://astral.sh/uv/install.sh | sh
Configurer votre clé API
cp .env.example .env
cloudshell edit .env
Définissez votre clé :
GEMINI_API_KEY=your-key-here
Installer des dépendances
uv sync
4. Effectuer votre premier appel d'agent
Ouvrir le fichier de démarrage
cloudshell edit run_digest.py
run_digest() comporte un TODO à remplir maintenant et trois autres pour l'étape suivante. Deux assistants sont déjà préremplis au-dessus :
load_source(path): lit un fichier à partir de.agents/par rapport au script. Vous l'utiliserez dans le prochain exercice pour monter la ligne éditoriale, le playbook PDF et le moteur de rendu dans le bac à sable.run_stream(stream): traite le flux d'événements et renvoie(environment_id, interaction_id). Vous n'avez pas besoin d'écrire la boucle d'événements vous-même.
Élément à ajouter
TODO 1 : remplacez pass par (ignorez les TODO 3 et 4 pour l'instant, ils concernent la prochaine étape) :
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}")
Fonctionnement de chaque partie
genai.Client() lit GEMINI_API_KEY à partir de l'environnement. Tout le reste passe par ce client.
interactions.create() est l'appel principal. Quatre paramètres sont nécessaires :
agent=BASE_AGENT: sélectionne l'agent Antigravity (antigravity-preview-05-2026), un agent géré à usage général optimisé par Gemini 3.7 Flash par défaut. Vous pouvez configurer le modèle sous-jacent à l'aide deagent_config(options :gemini-3.7-flash,gemini-3.6-flash,gemini-3.5-flash,gemini-3.5-flash-lite). Il est fourni avec trois outils intégrés activés par défaut :code_execution(exécuter Bash, Python, Node.js),google_searcheturl_context(récupérer et lire des pages Web). Les outils du système de fichiers (read_file,write_file,list_files) sont activés automatiquement lorsque vous transmettez le paramètreenvironment. Un appel provisionne un environnement Ubuntu entièrement géré avec Python 3.12, Node.js 22, git, pip et curl préinstallés. Aucun conteneur à compiler, aucun déploiement à exécuter.input: tâche de cette exécution. L'agent parcourt Hacker News et réfléchit aux résultats.environment="remote": provisionne un nouveau bac à sable cloud pour cette interaction.stream=True: renvoie un itérable d'événements au lieu de bloquer. Sans cela, l'appel attend 30 à 90 secondes et renvoie toutes les sorties à la fois sous la formeinteraction.output_text. Avec le streaming, vous voyez la raison de l'agent et agissez en temps réel. Le streaming n'est pas une fonctionnalité avancée ici : il s'agit du bon paramètre par défaut, car une boîte noire de 90 secondes ne vous donne aucune indication sur le fonctionnement ou le blocage de l'agent.
environment_id est un handle vers le bac à sable qui vient d'être exécuté. Après interaction.completed, le bac à sable ne s'éteint pas : il reste actif pendant sept jours maximum. Le environment_id vous permet d'y revenir. Transmettez-le à un deuxième appel interactions.create(). L'agent reprend sur le même système de fichiers, avec les mêmes fichiers et les mêmes packages installés, comme s'il n'était jamais parti. L'étape suivante l'utilise pour télécharger le PDF sans réexécuter l'agent, et l'étape suivante l'utilise pour poursuivre la conversation.
interaction_id est un handle pour le tour de conversation qui vient de se terminer. Transmettez-le en tant que previous_interaction_id lors du prochain appel. L'agent se souviendra alors parfaitement de ce qu'il a dit et fait lors de ce tour.
Valider
uv run python run_digest.py
Vous devriez voir le résultat en direct pendant que l'agent travaille :
[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
L'API renvoie un environment_id réel même avec environment="remote". Le bac à sable s'est exécuté. Il manque la configuration : pas de voix, pas de skill, pas de générateur de PDF. L'agent a simplement imprimé des histoires sous forme de texte, puis s'est arrêté. L'étape suivante permet de les ajouter.
Chaque ligne de résultat correspond à un événement de run_stream() :
| Description | Ce que |
| un agent récupérant une URL ; |
|
| agent exécutant du code dans le bac à sable. |
|
| Agent effectuant une recherche sur le Web |
|
| outils de gestion de fichiers et autres |
|
| l'agent est en train de saisir du texte. | diffusé directement sur stdout |
5. Personnaliser l'agent
L'agent n'avait aucune instruction : pas de voix, pas de compétence, pas de générateur de PDF. Lors de cette étape, vous chargez les fichiers de configuration à partir de .agents/ et les installez dans le bac à sable.
Ce que vous devez modifier
Apportez quatre modifications à run_digest.py :
TODO 2 : Sous load_source(), ajoutez les trois constantes au niveau du module (elles se trouvent en dehors de run_digest(), en haut du fichier) :
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")
Ouvrez chaque fichier pour voir ce que vous chargez : AGENTS.md définit la ligne éditoriale et les règles du workflow ; SKILL.md est le playbook PDF pas à pas ; generate_pdf.py est le moteur de rendu prédéfini que l'agent exécutera.
Apportez maintenant deux autres modifications dans run_digest() :
TODO 3 : remplacez environment de "remote" par le dictionnaire des sources et définissez input sur "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,
},
],
},
TODO 4 : ajoutez cette ligne juste après print(f"\nDone. environment_id={environment_id}") :
save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)
save_env est déjà défini dans run_digest.py. Il écrit les deux ID dans .env afin que l'étape suivante puisse télécharger le PDF sans réexécuter l'agent.
Fonctionnement de chaque source
Chaque source est un fichier installé dans le système de fichiers du bac à sable au démarrage, avant l'exécution de l'agent. Les chemins d'accès target correspondent à l'emplacement où le harnais Antigravity s'attend à les trouver :
.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
chemin de l' | Variable | Ce que fait le harnais avec |
|
| Instructions persistantes chargées automatiquement : ligne éditoriale, workflow, règles d'exécution |
|
| Découverte et enregistrée automatiquement en tant que compétence nommée ; l'agent l'appelle par son nom |
|
| Rendu PDF prédéfini ; l'agent écrit |
Valider
uv run python run_digest.py
L'exécution prend désormais entre une et trois minutes. Vous devriez voir l'agent lire les fichiers de configuration, écrire des résumés et enregistrer le 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 est désormais une valeur réelle : le bac à sable s'est exécuté avec vos fichiers de configuration et l'agent a créé digest.pdf. L'étape suivante ajoute un crochet de sécurité avant le téléchargement.
6. Ajouter un crochet de sécurité
Les hooks vous permettent d'exécuter un script dans le bac à sable avant ou après chaque appel d'outil. L'agent de synthèse utilise code_execution pour exécuter des scripts Python. Un hook pre_tool_execution peut donc intercepter ces appels et bloquer les commandes shell destructrices avant leur exécution.
L'environnement d'exécution lit .agents/hooks.json à partir du bac à sable. Avant chaque appel d'outil de mise en correspondance, il transmet les détails de l'appel à votre script de porte sur stdin. Le script affiche {"decision": "allow"} ou {"decision": "deny", "reason": "..."} dans stdout. Un refus annule l'appel d'outil. L'agent voit votre raison et se corrige.
Élément à ajouter
TODO 5 : dans run_digest.py, ajoutez ces deux constantes en haut du fichier, après les appels load_source existants :
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"}))
"""
TODO 6 : ajoutez deux entrées supplémentaires à la liste sources à l'intérieur de interactions.create() :
{"type": "inline", "target": ".agents/hooks.json", "content": HOOKS_JSON},
{"type": "inline", "target": ".agents/hooks-scripts/gate.py", "content": GATE_PY},
Comment les hooks se déclenchent lors de l'exécution du résumé
Chaque fois que l'agent appelle code_execution pour exécuter un script Python ou une commande shell, le runtime transmet d'abord les détails de l'appel à gate.py. Si la commande contient rm -rf, le crochet renvoie deny et l'agent reçoit le motif du refus et effectue une nouvelle tentative avec une alternative sûre. Tous les autres appels d'exécution de code sont transmis sans modification.
Valider
uv run python run_digest.py
Le résultat est identique à celui obtenu précédemment : le pare-feu autorise toutes les commandes normales de génération de PDF. Pour confirmer que le hook se déclenche, modifiez temporairement l'entrée de l'agent pour lui demander d'exécuter rm -rf /tmp/test. L'agent indiquera que la commande a été bloquée et proposera une alternative.
7. Télécharger le PDF
L'agent a écrit digest.pdf à /workspace/digest.pdf dans le bac à sable. L'instantané de l'environnement est disponible sous forme d'archive tar via l'API Gemini Files.
Installez requests si nécessaire :
uv pip install requests
Éléments à remplir
Ouvrez download_pdf.py. Il comporte deux tâches à faire.
TODO 1 : complétez l'appel 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()
L'URL pointe vers l'instantané du bac à sable. params={"alt": "media"} renvoie des octets bruts au lieu de métadonnées. Votre GEMINI_API_KEY existant authentifie également l'API Files.
TODO 2 : recherchez et extrayez le PDF de l'archive tar :
member = next(m for m in tar.getmembers() if m.name.endswith("workspace/digest.pdf"))
tar.extract(member, path=tmp, filter="data")
Le préfixe du chemin d'accès tar varie selon les exécutions. Il est donc préférable de rechercher par suffixe plutôt que d'encoder en dur le chemin d'accès exact. filter="data" supprime l'avertissement de dépréciation de Python 3.13 concernant l'extraction de fichiers tar non sécurisée.
Valider
uv run python download_pdf.py
Saved digest.pdf (48,231 bytes)
Ouvrez digest.pdf dans le même répertoire. Il contient le résumé formaté que l'agent a généré à partir de pages Web en direct.
8. Poursuivre la conversation
Vous disposez déjà de digest.pdf. Si vous vouliez simplement obtenir le fichier, vous avez terminé. Cette étape porte sur autre chose : demander à l'agent de modifier le résumé sans récupérer à nouveau le contenu Web.
Le bac à sable est toujours actif. L'agent dispose toujours de /workspace/digest.pdf et se souvient de chaque article qu'il a résumé. Un deuxième appel interactions.create() envoie un message de suivi dans le même bac à sable. Ici, vous lui demandez d'ajouter une note "Pourquoi c'est important" sous chaque article. Il met à jour le PDF sur place, sans avoir à récupérer ni à résumer à nouveau les informations.
Éléments à remplir
Ouvrez refine_digest.py. Il comporte trois tâches à faire.
TODO 1 et 2 : renseignez les deux paramètres multitours dans interactions.create() :
environment=environment_id,
previous_interaction_id=interaction_id,
environment=environment_id reprend le même bac à sable avec ses fichiers et ses packages. previous_interaction_id=interaction_id fournit à l'agent son historique de conversations. Aucun autre changement n'est à prévoir par rapport au premier appel.
TODO 3 : persister le nouveau interaction_id dans .env après la boucle d'événement :
save_env(INTERACTION_ID=interaction_id)
Chaque appel interactions.create() produit un nouveau interaction_id. Le fait de le réécrire signifie que la prochaine exécution transmettra cet affinement en tant que previous_interaction_id, enchaînant correctement les tours. L'ID du bac à sable ne change jamais. Vous n'avez donc pas besoin de mettre à jour ENVIRONMENT_ID.
Les deux paramètres qui permettent au multi-tour de fonctionner
ID | Éléments conservés | Analogie |
| Fichiers, packages installés, état du système : tout ce qui se trouve dans le système de fichiers Linux | Conserver le même bureau entre deux réunions |
| Historique de la conversation : ce que l'agent a dit et fait lors des tours précédents | Se souvenir de ce qui a été abordé lors de la dernière réunion |
Vous pouvez transmettre l'un ou l'autre de ces ID de manière indépendante :
environment_iduniquement : réutiliser des fichiers et des packages, mais démarrer une nouvelle conversation. Utile pour une nouvelle tâche dans le même espace de travail.previous_interaction_iduniquement : poursuivez le contexte de la conversation, mais dans un bac à sable vierge (les fichiers ont disparu).- Les deux : continuité complète, qui est utilisée dans cette étape.
Sans environment_id : bac à sable vide, sans PDF. Sans previous_interaction_id : aucun contexte, l'agent ne peut pas affiner une section spécifique.
Valider
uv run python refine_digest.py
Le flux doit être rapide, car l'agent ne récupère rien. Une fois l'opération terminée :
Refinement done.
Saved digest_v2.pdf (52,418 bytes)
Ouvrez digest_v2.pdf et comparez-le à digest.pdf. Une ligne "Pourquoi c'est important" doit maintenant être ajoutée à chaque article.
9. Persister une configuration d'agent gérée
Jusqu'à présent, chaque appel a transmis AGENTS.md, SKILL.md et generate_pdf.py en ligne. Cela fonctionne, mais votre code d'appel transporte le contenu complet du fichier à chaque exécution. agents.create() intègre la configuration à un agent nommé enregistré côté Google. L'invocation suivante transmet simplement l'ID de l'agent :
Inline calls: send sources on every call
Named agent: bake once → invoke by ID, no sources
Éléments à remplir
Ouvrez save_agent.py. Il comporte une tâche à accomplir (TODO 1).
Notez que les constantes sont importées directement depuis run_digest.py (aucune duplication) :
from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY
TODO 1 : complétez l'appel 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 définit le modèle sous-jacent. gemini-3.7-flash est la valeur par défaut et le meilleur choix pour ce workflow. gemini-3.6-flash, gemini-3.5-flash et gemini-3.5-flash-lite sont disponibles si vous souhaitez une exécution plus légère ou moins coûteuse.
base_environment (et non environment) est la principale différence par rapport à l'appel intégré de l'étape précédente : les sources sont stockées côté Google et montées automatiquement à chaque invocation future. Exécutez-le une seule fois, et non à chaque exécution du récapitulatif.
Valider : enregistrer l'agent
uv run python save_agent.py
Saved: my-digest
my-digest: Daily tech digest with editorial voice and PDF generation.
Appeler l'agent enregistré
Ouvrez invoke_agent.py. Il appelle l'agent enregistré par son ID sans sources :
stream = client.interactions.create(
agent="my-digest",
input="Generate the digest.",
stream=True,
environment="remote",
)
Comparez cela à l'appel intégré : agent=BASE_AGENT est remplacé par "my-digest", et le bloc environment complet avec trois sources intégrées est remplacé par environment="remote". La configuration est déjà intégrée côté Google.
Vérifier : appeler l'agent enregistré
uv run python invoke_agent.py
Vous devriez voir la même diffusion en direct que lors de l'exécution intégrée, mais l'appel ne comporte aucun fichier source. Après l'exécution, ENVIRONMENT_ID et INTERACTION_ID dans .env sont mis à jour pour que vous puissiez continuer avec refine_digest.py comme avant.
[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. Envoyer par Gmail
L'agent a généré le récapitulatif et l'a enregistré dans /workspace/digest.pdf. Jusqu'à présent, vous l'avez téléchargé localement. Cette étape permet de l'envoyer directement dans votre boîte de réception en demandant à l'agent d'appeler l'API REST Gmail depuis le bac à sable.
L'approche : vous obtenez un jeton d'accès OAuth 2.0 en local et le transmettez à l'agent dans l'invite input. L'agent utilise code_execution pour créer un e-mail MIME avec le PDF en pièce jointe et l'envoyer à l'API Gmail. Aucun outil personnalisé ni enregistrement de serveur MCP.
Prérequis
Activez l'API Gmail dans votre projet GCP et créez un ID client OAuth 2.0 :
- Accédez à console.cloud.google.com/apis/library/gmail.googleapis.com et activez l'API Gmail.
- Accédez à API et services > Identifiants > Créer des identifiants > ID client OAuth 2.0.
- Type d'application : Application de bureau. Téléchargez le fichier JSON et enregistrez-le sous le nom
credentials.jsondans la racine du projet.
Ajoutez l'adresse e-mail du destinataire à .env :
RECIPIENT_EMAIL=you@gmail.com
Installez les bibliothèques d'authentification si nécessaire :
uv sync
Éléments à remplir
Ouvrez send_digest.py. Il comporte deux tâches à faire.
TODO 1 : charger ou actualiser un jeton d'accès 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())
Supprimez la ligne raise NotImplementedError après l'avoir ajoutée. Lors de la première exécution, un navigateur s'ouvre sur l'écran de consentement OAuth. Le jeton est mis en cache dans .gmail_token.json pour les futures exécutions.
TODO 2 : remplacez input="" par les instructions de l'e-mail. Le jeton est déjà dans le champ d'application en tant que 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}"
),
Fonctionnement de chaque partie
L'interaction reprend dans le même bac à sable où l'agent a déjà généré digest.pdf et summaries.json. previous_interaction_id fournit à l'agent son historique de conversations.
Le jeton d'accès est transmis dans la chaîne input. L'agent le lit à partir du prompt et l'utilise dans l'en-tête Authorization: Bearer lorsqu'il appelle l'API Gmail. Il n'accède jamais à votre ordinateur local ni à votre système de fichiers.
L'agent utilise code_execution pour écrire et exécuter un script Python dans le bac à sable : il lit summaries.json, le met en forme en tant que newsletter, lit digest.pdf, crée un message MIME multipart, l'encode en base64url et l'envoie à https://gmail.googleapis.com/gmail/v1/users/me/messages/send.
Valider
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.
Accédez à votre boîte de réception. L'e-mail arrive avec le corps au format newsletter et digest.pdf en pièce jointe.
11. Programmer des courses quotidiennes
Jusqu'à présent, chaque étape a été déclenchée manuellement. Les déclencheurs vous permettent de programmer l'exécution de l'agent nommé automatiquement selon une expression Cron. L'agent se déclenche à l'heure prévue, exécute l'intégralité du workflow de synthèse et l'environnement persiste entre les exécutions. Les packages installés lors de la première exécution sont donc disponibles pour toutes les exécutions suivantes.
Manual: python run_digest.py → runs once, now
Trigger: client.triggers.create() → runs every morning, automatically
Éléments à remplir
Ouvrez create_trigger.py. Il comporte une tâche à faire.
TÂCHE TODO 1 : complétez l'appel triggers.create(). Le déclencheur exécute l'intégralité du workflow chaque jour : il génère le récapitulatif ET l'envoie dans votre boîte de réception. Comme les jetons d'accès expirent au bout d'une heure, il injecte le jeton d'actualisation de .gmail_token.json en tant que source intégrée afin que l'agent puisse l'échanger contre un nouveau jeton à chaque exécution.
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 est le délai avant expiration par défaut. max_consecutive_failures=3 met automatiquement le déclencheur en pause après trois échecs d'exécution consécutifs (la valeur par défaut de l'API est de cinq, mais trois est plus prudent pour un atelier).
La liste sources injecte .gmail_creds.json dans le bac à sable à /workspace/.gmail_creds.json. L'agent le lit, échange le jeton d'actualisation contre un nouveau jeton d'accès et appelle l'API Gmail. Les jetons d'actualisation n'expirent pas. Cette méthode fonctionne donc à chaque exécution planifiée, sans actualisation manuelle du jeton.
Supprimez la ligne raise NotImplementedError après avoir ajouté l'appel.
Valider
uv run python create_trigger.py
Trigger created: trig_abc123
Next run: 2026-07-23T09:00:00Z
create_trigger.py enregistre automatiquement l'ID du déclencheur dans .env.
Pour vérifier l'historique d'exécution après une exécution :
uv run python check_trigger.py
Pour déclencher le déclencheur immédiatement sans attendre la prochaine heure planifiée :
uv run python fire_trigger.py
Pour suspendre ou supprimer le déclencheur :
uv run python pause_trigger.py
12. Effectuer un nettoyage
Le bac à sable expire automatiquement après sept jours d'inactivité. Aucun serveur à arrêter. Aucun conteneur à supprimer.
Si vous avez enregistré une configuration d'agent, supprimez-la :
uv run python delete_agent.py
13. Résumé
Vous avez créé un agent géré de A à Z, un concept à la fois. Voici ce que chaque exercice vous a appris :
Exercice | Concept | API Key |
Effectuer votre premier appel | Provisionner un véritable bac à sable Linux et diffuser ses événements en direct |
|
Personnaliser l'agent | Monter les fichiers de configuration ; conserver les ID dans |
|
Ajouter un crochet de sécurité | Intercepter les appels d'outils avant leur exécution ; refuser les commandes destructrices |
|
Télécharger le PDF | Télécharger le PDF sans réexécuter l'agent | API Gemini Files |
Continuez la conversation | Poursuivre la conversation sans récupérer à nouveau le contenu Web |
|
Conserver la configuration de l'agent | Persister la configuration de l'agent ; invoquer par ID, aucune source requise |
|
Envoyer par Gmail | Obtenez un jeton OAuth en local, puis transmettez-le à l'agent, qui appelle l'API REST Gmail via | OAuth 2.0, |
Planifier des exécutions quotidiennes | Exécuter l'agent automatiquement selon une programmation Cron |
|
Formats de clés
- Un appel, un bac à sable :
interactions.create()gère toute l'infrastructure (aucun conteneur à déployer, aucun package à installer localement). - Streaming progressif :
stream=Truetransforme une boîte noire de 90 secondes en flux en direct d'appels d'outils et de blocs de texte - Sources intégrées : montez
AGENTS.md,SKILL.mdet les scripts prédéfinis dans le bac à sable sans aucune étape d'importation ni de déploiement. - Découverte automatique Harness : les fichiers placés dans
.agents/sont récupérés automatiquement (aucune configuration du SDK n'est requise). - État bidimensionnel :
environment_idsuit les fichiers et les packages ;previous_interaction_idsuit le contexte de la conversation ; l'un ou l'autre peut être transmis indépendamment - Téléchargement de l'instantané : l'environnement est un fichier tar complet du système de fichiers, accessible via l'API Gemini Files.
- Agents nommés :
agents.create()intègre la configuration de manière permanente. Les futurs appels ne transmettent que l'ID de l'agent etenvironment="remote", sans sources. - Hooks :
hooks.json+ un script de porte intercepte les appels d'outils avant leur exécution. Une réponsedenyannule l'appel et l'agent s'autocorrige. - Appels d'API externes : transmettez un identifiant dans le prompt
input. L'agent écrit et exécute le code d'intégration dans le bac à sable viacode_execution. - Déclencheurs : planifiez un agent sur une expression Cron avec
client.triggers.create(). L'environnement persiste lors des exécutions.
ADK + Cloud Run vs Managed Agents : aperçu des différences
Capacité | ADK + Cloud Run | Agents gérés sur l'API Gemini |
Provisionner un bac à sable |
|
|
Définir les outils | Fonctions Python enregistrées auprès de l'agent | Intégrés : navigation Web, exécution de code, système de fichiers |
Installer des packages |
| L'agent s'exécute |
Événements de flux | Infrastructure SSE personnalisée |
|
Continuer une session | Base de données de session + injection de contexte |
|
Fichiers de configuration | Codé en dur dans l'agent ou injecté au démarrage | Monté via |
Infrastructure à gérer | Conteneur, Cloud Run, IAM, secrets | Aucun |
Étapes suivantes
- Consultez la documentation sur les agents gérés dans l'API Gemini.