1. 概要
AI とテクノロジーの状況は、誰もが追跡できるよりも速く変化しています。新しいモデル、論文、プロダクトが毎日リリースされます。今日の見出しを取得し、簡潔な要約を作成して、毎朝 PDF を生成するダイジェスト エージェントがあれば、この問題を解決できます。しかし、以前は、フレームワークを選択し、Python でツールを定義し、オーケストレーション ループを記述し、コンテナをパッケージ化して、Cloud Run にデプロイする必要がありました。エージェントがウェブ リクエストを 1 つも行っていない状態です。
Gemini API のマネージド エージェントは、この状況を一変させます。2 つのマークダウン構成ファイルと事前構築済みのレンダラ スクリプトを作成し、1 つの API 呼び出しを行うと、実際の Ubuntu サンドボックスが起動し、ウェブを閲覧して、要約を書き込み、PDF を生成します。コンテナはありません。デプロイなし。オーケストレーション コードはありません。
この Codelab では、まさにそのようなエージェントを構築します。空の関数から、1 つのコンセプトを 1 つずつ実装して、動作するデイリー ダイジェストを作成します。
作業内容
- 実際の Linux サンドボックスで初めてのマネージド エージェントを作成して実行する
- 詳細な手順でエージェントをカスタマイズする
- エージェントの PDF 出力をダウンロードする
- ウェブを再取得せずに会話を続けて要約を絞り込む
- エージェント構成を保存し、今後の実行で ID で呼び出す
必要なもの
- Python 3.10 以上
- 課金が有効になっている Gemini API キー: aistudio.google.com/api-keys
- API クレジット約$1(1 回の実行にかかる費用は $0.30 ~$1.30)
2. Gemini API のマネージド エージェントとは
AI システムの 3 つのレベル
コードに入る前に、2 つの代替案に対する Managed Agents の位置付けを以下に示します。
レベル | 内容 | インフラストラクチャの管理者は誰ですか? |
標準 LLM | プロンプトを入力すると、テキストで返信します。手も記憶もインターネットもありません。 | N/A: 単独では何もできない |
セルフホスト型エージェント | ADK/LangChain/AutoGen + Docker + ツール + メモリを接続します。 | すべて(または Agent Engine などのマネージド プラットフォーム) |
管理対象エージェント | 目標を設定します。Google は安全なサンドボックスをプロビジョニングします。エージェントは、コードの記述、実行、エラーの読み取り、ウェブ検索、バグの修正を自律的に行います。 | Google: すべて |
この Codelab では、3 行目について説明します。タスクと構成ファイルを指定します。その他の処理はすべて 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
これは、エージェントが 1 回実行される前です。サンドボックスの分離(エージェントがサーバーを破損できないようにする)、パッケージのインストール、ツール呼び出し間の状態管理、イベントをクライアントに取得するためのストリーミング インフラストラクチャは、引き続きユーザーが所有します。
Managed Agents による置き換え
from google import genai
client = genai.Client()
stream = client.interactions.create(
agent="antigravity-preview-05-2026",
input="",
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 の要件 | Managed Agents が処理する内容 |
コンテナ イメージ + Dockerfile + CI/CD | フルマネージド Ubuntu サンドボックス(Python 3.12、Node 22、4 CPU / 16 GB RAM) |
Cloud Run のデプロイとスケーリング | インタラクションごとにプロビジョニングされ、非アクティブ状態が 7 日間続くと自動的に期限切れになります |
サンドボックスの分離 | インタラクションごとに分離 |
カスタム PDF ツール + | エージェントがサンドボックス内にパッケージをインストールする |
SSE ストリーミング インフラストラクチャ |
|
Python でのツール定義 | 組み込みツール: ウェブ閲覧、コード実行、ファイル システム |
ツール呼び出し間の状態管理 | エージェントの推論ループに組み込まれている |
構成ファイル(AGENTS.md、SKILL.md、事前構築済みスクリプト)を作成し、1 回の API 呼び出しを行います。その他の処理はすべて 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. セットアップ
オプション A: Cloud Shell(推奨)
下のボタンをクリックして、この Codelab を Google Cloud Shell で開きます。すべての依存関係がプリインストールされています。
オプション B: ローカル設定
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
4. 最初のエージェント呼び出しを行う
スターター ファイルを開く
cloudshell edit run_digest.py
run_digest() には、今すぐ入力する TODO が 1 つと、次のステップで入力する TODO が 3 つあります。上記には、2 つのヘルパーがすでに事前入力されています。
load_source(path): スクリプトを基準として.agents/からファイルを読み取ります。次の演習では、このページを使用して、エディター ボイス、PDF プレイブック、レンダラをサンドボックスにマウントします。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,
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() はコア呼び出しです。次の 4 つのパラメータで動作します。
agent=BASE_AGENT: Gemini 3.5 Flash を搭載した汎用マネージド エージェントである Antigravity エージェント(antigravity-preview-05-2026)を選択します。デフォルトで有効になっている 3 つの組み込みツール(code_execution(Bash、Python、Node.js を実行)、google_search、url_context(ウェブページを取得して読み取る))が付属しています。environmentパラメータを渡すと、ファイルシステム ツール(read_file、write_file、list_files)が自動的に有効になります。1 回の呼び出しで、Python 3.12、Node.js 22、git、pip、curl がプリインストールされたフルマネージド Ubuntu 環境がプロビジョニングされます。ビルドするコンテナがなく、実行するデプロイもありません。input: この実行のタスク。エージェントが Hacker News を閲覧し、結果について推論します。environment="remote": このインタラクション用に新しいクラウド サンドボックスをプロビジョニングします。stream=True: ブロックする代わりに、イベントのイテラブルを返します。このフラグがない場合、呼び出しは 30 ~ 90 秒待機し、すべての出力をinteraction.output_textとして一度に返します。ストリーミングでは、エージェントの理由と行動をリアルタイムで確認できます。ストリーミングは高度な機能ではありません。90 秒のブラック ボックスでは、エージェントが動作しているか、停止しているかについてのシグナルが得られないため、ストリーミングが適切なデフォルトです。
プロビジョニングした内容: interactions.create() 呼び出しごとに専用のサンドボックスが起動します。
コンポーネント | 仕様 |
オペレーティング システム | 分離された Ubuntu Linux 環境 |
プリインストールされたランタイム | Python 3.12、Node.js 22、Bash |
コンピューティング | 4 個の CPU コア、16 GB の RAM |
コンテキスト管理 | 自動圧縮は ~135,000 トークンでトリガーされます |
ネットワーキング | 下り(外向き)プロキシ経由のウェブ アクセス |
エージェントは pip または npm を使用して任意のパッケージをインストールし、ファイルの読み取りと書き込みを行い、アウトバウンド ウェブ リクエストを行うことができます。マシンと認証情報が使用されることはありません。
environment_id は、実行されたサンドボックスのハンドルです。interaction.completed 後、サンドボックスはシャットダウンされず、最長 7 日間存続します。environment_id は、その状態に戻る方法です。これを 2 回目の 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
API は、environment="remote" があっても実際の environment_id を返します。サンドボックスが実行されました。不足しているのは設定です。音声、スキル、PDF ジェネレータがありません。エージェントはストーリーをテキストとして印刷しただけで、停止しました。次のステップでこれらを追加します。
出力の各行は、run_stream() のイベントにマッピングされます。
| 概要 |
|
| エージェントが URL を取得する |
|
| サンドボックスでコードを実行するエージェント |
|
| エージェントがウェブを検索している |
|
| ファイルツールなど |
|
| エージェントがテキストを入力している | stdout に直接ストリーミングされます。 |
5. エージェントをカスタマイズする
エージェントには指示がありませんでした。音声、スキル、PDF ジェネレータはありません。このステップでは、.agents/ から構成ファイルを読み込み、サンドボックスにマウントします。
変更内容
run_digest.py に次の 4 つの変更を加えます。
TODO 2: load_source() の下に、3 つのモジュール レベルの定数を追加します(これらは 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() 内でさらに 2 つの変更を行います。
TODO 3: environment を "remote" からソース辞書に変更し、input を "" に設定します。
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: print(f"\nDone. environment_id={environment_id}") の直後に次の行を追加します。
set_key(".env", "ENVIRONMENT_ID", environment_id)
set_key(".env", "INTERACTION_ID", interaction_id)
(set_key は run_digest.py の先頭でインポート済みです)。
これにより、この実行中に両方の ID が .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
| 変数 | ハーネスがどのように処理するか |
|
| 永続的な指示として自動的に読み込まれる: エディター ボイス、ワークフロー、実行ルール |
|
| 自動検出され、名前付きスキルとして登録されます。エージェントは名前で呼び出します。 |
|
| ビルド済みの PDF レンダラ。エージェントが |
確認
uv run python run_digest.py
実行時間は 1 ~ 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. PDF をダウンロードする
エージェントは、サンドボックス内の /workspace/digest.pdf に digest.pdf を書き込みました。環境スナップショットは、Gemini Files API を介して tar アーカイブとして使用できます。
必要に応じて requests をインストールします。
uv pip install requests
記入する内容
download_pdf.py を開きます。TODO が 2 つあります。
TODO 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 も認証します。
TODO 2: tar アーカイブから PDF を見つけて抽出します。
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 抽出に関する Python 3.13 の非推奨警告を抑制します。
確認
uv run python download_pdf.py
Saved digest.pdf (48,231 bytes)
同じディレクトリで digest.pdf を開きます。これには、エージェントがライブ ウェブページから生成したフォーマット済みのダイジェストが含まれます。
7. 会話を続ける
digest.pdf はすでに所有しています。ファイルだけが必要な場合は、これで完了です。このステップは、ウェブを再取得せずにダイジェストを変更するようエージェントに依頼するという、別の内容です。
サンドボックスはまだ有効です。エージェントは /workspace/digest.pdf を保持し、要約したすべてのストーリーを記憶しています。2 回目の interactions.create() 呼び出しでは、同じサンドボックスにフォローアップ メッセージが送信されます。ここでは、各記事の下に「重要性」のメモを追加するよう指示しています。PDF はその場で更新され、再取得や再要約は行われません。
記入する内容
refine_digest.py を開きます。TODO が 3 つあります。
TODO 1 と 2: interactions.create() 内の 2 つのマルチターン パラメータを入力します。
environment=environment_id,
previous_interaction_id=interaction_id,
environment=environment_id は、ファイルとパッケージを含む同じサンドボックスを再開します。previous_interaction_id=interaction_id は、エージェントに会話履歴を提供します。最初の通話から変更されることはありません。
TODO 3: イベント ループの後に、新しい interaction_id を .env に永続化します。
set_key(".env", "INTERACTION_ID", interaction_id)
interactions.create() を呼び出すたびに、新しい interaction_id が生成されます。書き戻すことで、次の実行でこの絞り込みが previous_interaction_id として渡され、ターンが正しくチェーンされます。サンドボックス ID は変更されないため、ENVIRONMENT_ID を更新する必要はありません。
マルチターンを機能させる 2 つのパラメータ
ID | 保持されるもの | 説明のための例え |
| ファイル、インストールされたパッケージ、システム状態: Linux ファイル システム上のすべて | 会議の合間に同じオフィス デスクを使用する |
| 会話の履歴: エージェントが以前のターンで言ったことと行ったこと | 前回の会議で話し合った内容を思い出す |
どちらの 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 と比較します。各ストーリーに「重要な理由」の行が追加されました。
8. 管理対象エージェント構成を永続化する
これまでのすべての呼び出しで、AGENTS.md、SKILL.md、generate_pdf.py がインラインで渡されています。これは機能しますが、呼び出しコードは実行ごとにファイルの内容全体を伝送します。agents.create() は、Google 側で保存された名前付きエージェントに構成を組み込みます。次の呼び出しでは、エージェント ID のみが渡されます。
Inline calls: send sources on every call
Named agent: bake once → invoke by ID, no sources
記入する内容
save_agent.py を開きます。TODO が 1 つ(TODO 1)あります。
定数は run_digest.py から直接インポートされます(重複はありません)。
from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY
TODO 1: agents.create() 呼び出しを記入します。
agent = client.agents.create(
id="my-digest",
base_agent=BASE_AGENT,
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,
},
],
},
)
base_environment(environment ではない)は、前の手順のインライン呼び出しとの大きな違いです。ソースは Google 側に保存され、以降の呼び出しごとに自動的にマウントされます。ダイジェストの実行ごとにではなく、1 回だけ実行します。
確認: エージェントを保存する
uv run python save_agent.py
Saved: my-digest
my-digest: Daily tech digest with editorial voice and PDF generation.
保存したエージェントを呼び出す
invoke_agent.py を開きます。ソースなしで ID によって保存されたエージェントを呼び出します。
stream = client.interactions.create(
agent="my-digest",
input="",
stream=True,
environment="remote",
)
インライン呼び出しと比較すると、agent=BASE_AGENT は "my-digest" に置き換えられ、3 つのインライン ソースを含む environment ブロック全体が environment="remote" に置き換えられています。構成は Google 側ですでに組み込まれています。
確認: 保存したエージェントを呼び出す
uv run python invoke_agent.py
インライン実行と同じライブ ストリームが表示されますが、呼び出しにはソースファイルは含まれません。実行後、.env の ENVIRONMENT_ID と INTERACTION_ID が更新され、これまでどおり 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-...
9. クリーンアップ
サンドボックスは、7 日間操作がないと自動的に期限切れになります。停止するサーバーはありません。削除するコンテナはありません。
エージェント構成を保存した場合は、削除します。
uv run python delete_agent.py
10. まとめ
マネージド エージェントをゼロから、一度に 1 つのコンセプトで構築しました。各演習で学んだ内容は次のとおりです。
演習 | コンセプト | Key API |
最初の呼び出しを実行する | 実際の Linux サンドボックスをプロビジョニングし、そのイベントをライブ ストリーミングする |
|
エージェントをカスタマイズする | 構成ファイルをマウントし、同じ実行で ID を |
|
PDF 資料をダウンロードする | エージェントを再実行せずに PDF をダウンロードする |
|
会話を継続 | ウェブを再取得せずに会話を続ける |
|
エージェント構成を永続化 | エージェント構成を永続化する。ID で呼び出す。ソースは不要 |
|
キーパターン
- 1 回の呼び出しで 1 つのサンドボックス:
interactions.create()がすべてのインフラストラクチャを処理します(デプロイするコンテナやローカルにインストールするパッケージはありません)。 - プログレッシブ ストリーミング:
stream=Trueは、90 秒のブラック ボックスをツール呼び出しとテキスト チャンクのライブフィードに変換します。 - インライン ソース: アップロードやデプロイの手順なしで、
AGENTS.md、SKILL.md、事前構築済みスクリプトをサンドボックスにマウントします。 - Harness の自動検出:
.agents/に配置されたファイルが自動的に取得されます(SDK 構成は不要)。 - 2 次元状態:
environment_idはファイルとパッケージを追跡し、previous_interaction_idは会話コンテキストを追跡します。どちらも個別に渡すことができます。 - スナップショットのダウンロード: 環境は Gemini Files API を介してアクセスできる完全なファイル システム tar です。
- 名前付きエージェント:
agents.create()は構成を永続的にベイクします。以降の呼び出しでは、ソースなしでエージェント ID とenvironment="remote"のみが渡されます。
ADK + Cloud Run とマネージド エージェント: 違いの概要
能力 | ADK + Cloud Run | Gemini API のマネージド エージェント |
サンドボックスをプロビジョニングする |
|
|
ツールを定義する | エージェントに登録された Python 関数 | 組み込み: ウェブ閲覧、コード実行、ファイル システム |
パッケージをインストールする | Dockerfile の | エージェントがサンドボックス内で |
イベントをストリーミングする | カスタム SSE インフラストラクチャ |
|
セッションを続行する | セッション データベース + コンテキスト インジェクション |
|
構成ファイル | エージェントにハードコードされているか、起動時に挿入されている |
|
管理するインフラストラクチャ | コンテナ、Cloud Run、IAM、シークレット | なし |