Cloud RunとAgent Runtimeでブログの自動コーディングアプリ!【構築編】


Word原稿を社内フォーマットのHTMLへ自動変換 ~構築編~


▶ シリーズ後編はこちら Cloud RunとAgent Runtimeでブログの自動コーディングアプリ!【使用編】 ▶

1. はじめに

突然ですが社内外に記事を公開していく際に以下のような悩みはございませんか?

  • 作成した原稿をHTMLへコーディングするのに時間がかかる
  • Google Cloud、Azure、AWSなどソリューションごとにCSSが異なる

社内のテンプレートに沿ったHTMLを生成してくれるアプリをCloud Run × Agent Runtimeで簡単に作れるか、検証してみました。

本記事では、簡単に構成や実際に作成したアプリや生成されたHTMLについてお話したいと思います。

2. 全体構成図

概要

本検証では、以下の構成でHTMLコーディングアプリを開発してみます。

  • Webアプリ:Cloud Run
  • AIエージェント:Agent Runtime
  • 認証:Identity -Aware Proxy
構成図

図1構成図

Cloud Run使うメリット

  • サーバー管理がいらない(サーバーレス):
    自分でサーバーを立てて設定したり、メンテナンスしたりする必要がありません。Googleが裏側をすべて管理してくれます。
  • 自動でスケールする:
    ユーザーが10人から100万人に急増しても、Googleのインフラが自動で対応してくれます。
  • 無料枠が大きい:
    個人の開発や小さなプロジェクトであれば、無料の範囲内で十分に運用できます。

使用時の注意点

とても便利な構成ですが、いくつか気に留めておきたい点もあります。

  • コールドスタート:
    アクセスが無い状態からの初回リクエストは、コンテナやAgentの起動に少し時間がかかることがあります(今回のアプリでも生成に約1分を見込んでいます)。
  • Googleプラットフォームへの依存:
    Agent Runtime や IAP など Google Cloud 固有のサービスを活用しているため、将来的に別のクラウドへ移行する場合は作り直しの手間が発生します。

3. Cloud Runへのアプリデプロイ(Streamlit)とIAPでのアクセス制御

ここからは、実際にどのように構築したのかを順番にご紹介します。まずは利用者が唯一触れる窓口となる、Webアプリの部分です。

なぜStreamlitなのか

今回のアプリに必要なUIは、「ファイルのアップロード欄」「生成開始ボタン」「ダウンロードリンク」の3つだけです。このくらいシンプルな要件であれば、フロントエンドとバックエンドを別々に作り込むより、Pythonだけで画面まで完結するStreamlitが圧倒的に手早く、保守も簡単です。

実際のUIの中核はこれだけです。ファイルを受け取り、ボタンが押されたらAgentを呼び出すシンプルな作りにしています。

# app.py(UIの中核)
uploaded_file = st.file_uploader(
    "Wordファイル(.docx)を選択してください", type=["docx"])
if st.button("生成を開始する ✨", type="primary"):
    file_bytes = uploaded_file.read()
    result = call_agent_runtime(file_bytes, uploaded_file.name)

あわせて、処理が「今どこまで進んでいるか」を利用者に伝えるため、①原稿の一時保存 → ②Agentでの生成中 → ③リンク取得、という進捗を1ステップずつ画面に更新表示するようにしています。

Webアプリ(Streamlit)のトップ画面

図 3 Webアプリ(Streamlit)のトップ画面

Cloud Runへデプロイする

作成したStreamlitアプリはDockerイメージにまとめ、Cloud Runへデプロイします。おおまかな流れは次の通りです。

  • Dockerfileを用意し、アプリを起動するコンテナを定義する
  • イメージをビルドしてArtifact Registryへ登録する
  • そのイメージをCloud Runサービスとしてデプロイする

Cloud Runにすることで、アクセスが無い時間帯はコストを抑えつつ、生成リクエストが来たときにだけリソースを使う、という社内ツールに理想的な運用ができます。

IAPでゼロトラストを効かせる

ここが本構成のセキュリティの要です。Cloud Runの手前にIdentity-Aware Proxy(IAP)を有効化し、社内で許可した特定のユーザーやグループにだけアクセス権を付与します。許可されていないユーザーは、そもそもバックエンド(Cloud Run)に到達すらできません。

ポイントは、アプリのコードに認証の仕組みを一切書いていないことです。「誰がアクセスできるか」はプラットフォーム側(IAM/IAP)で管理するため、URLが漏れても勝手に使われず、アクセス制御の変更もアプリ改修なしで行えます。実際にアクセス権が付与されていないドメインのアカウントから、URLを叩くとアクセスがブロックされています。

IAPによるアクセス制御(アクセスブロック画面)

図 4 IAPによるアクセス制御(アクセスブロック画面)

4. AgentのデプロイとIdentity設定

続いて、この仕組みの主役であるHTMLコーディングAgentを、ADK(Agent Development Kit)で実装し、Agent Runtimeへデプロイします。

Agentの役割を1つに絞る

Agentの仕事は「①GCS上の原稿を読み取る」「②社内テンプレートに沿ってHTML化し、アップロードする」の2つだけです。あれもこれもできるAgentにせず、役割を絞ることが安全設計の第一歩です。ADKでは、この役割を「Agent(頭脳)」と「Tool(実際の道具)」という形で表現します。

# agent.py(Agentの定義)
blog_agent = Agent(
    name="blog_html_agent_runtime",
    instruction="ファイルの場所(gcs_uri)と名前(filename)を渡されたら、",
                "必ず generate_blog_html ツールを呼び出すこと。",
    model="gemini-2.5-flash",   # 司令塔は軽量・高速なモデル
    tools=[generate_blog_html], # 実際の変換処理はこのTool
)

地味に効いている工夫が、2つのモデルの使い分けです。司令塔となるAgentは軽量・高速なgemini-2.5-flashで交通整理を行い、実際にHTMLを書き起こすTool内部では品質重視のgemini-2.5-proを呼び出しています。「速い頭で交通整理し、賢い頭で本番作業をする」という役割分担です。

社内フォーマットをテンプレートとして読み込む

Agentが毎回ゼロからHTMLを想像で書くと、出力がブレます。そこで社内の公式フォーマット(template.html)をそのまま読み込み、これを“お手本”としてGeminiに渡します。

# agent.py(社内テンプレートを読み込む)
with open("template.html", "r", encoding="utf-8") as f:
    OFFICIAL_TEMPLATE = f.read()

さらに、テンプレートを勝手に省略・改変されないよう、プロンプトで厳しく釘を刺しています。具体的には「サンプルHTMLは冒頭から末尾まで1行も省略しない」「変更してよいのはタイトルと本文エリアのみで、head・CSS・ヘッダー・フッターは1文字も変えない」「本文は既存記事の構造(section・h2・p・画像divなど)のクセを完全に真似る」といったルールです。これにより、デザインには一切手を出さず、原稿の中身だけを“社内の書き癖”に合わせて流し込めます。

Agent Identityで最小権限に絞る

ここが設計上の最重要ポイントです。Agentには専用のAgent Identity(身分証)を割り当て、その権限は最小限にとどめます。Agent Identityの発行はPythonの中で明示的に指定します。

# agent.py(デプロイ設定・抜粋)
config={
    "staging_bucket": f"gs://{BUCKET}",
    # ★ Agent専用の身分証を割り当て(最小権限で動かす)
    "identity_type": types.IdentityType.AGENT_IDENTITY,
    # ★ Agent Gateway 経由の呼び出しに限定
    "agent_gateway_config": {...},
}

「AIが自律的に動く」ことと「AIに何でもさせる」ことは、まったくの別物です。行動範囲をアイデンティティと権限で囲い込むことで、誤作動や暴走のリスクを構造的に抑え込みます。あわせて、再デプロイ時は同じAgent IDを上書き(update)する作りにしているため、呼び出し側の設定を変えずに中身だけ差し替えられます。

Agent RuntimeとAgent Identityの設定画面

図 5 Agent RuntimeとAgent Identityの設定画面

5. Cloud RunからAgentの呼び出し

最後に、Webアプリ(Cloud Run)とAgentをつなぐ「生成開始」の流れを整理します。この処理はapp.pyのcall_agent_runtime関数が担当しています。

重い荷物は直接持たせない(GCS経由の受け渡し)

実装上いちばんの工夫がこれです。Wordのファイル本体をAgentに直接渡すのではなく、いったんGCSに保存し、その置き場所(URI)だけをAgentに伝えます。大きなファイルをリクエストに載せて詰まるのを避けるための定石です。

# app.py(call_agent_runtime)
# ① 原稿を先にGCSへ一時保存(UUIDで名前の衝突を回避)
blob_name = f"temp_uploads/{uuid.uuid4().hex[:8]}_{filename}"
bucket.blob(blob_name).upload_from_string(file_bytes)
gcs_uri = f"gs://{BUCKET}/{blob_name}"
# ② Agentには「住所(URI)」だけをスマートに伝える
prompt = f"filename: {filename}\ngcs_uri: {gcs_uri}"

呼び出しと進捗のストリーミング

呼び出しはVertex AI SDK経由で、Agent Gatewayに守られたAgent Runtimeに対して行います。stream_queryを使い、結果をストリーミングで受け取りながら画面の進捗表示に反映します。呼び出し元(Cloud Runのサービスアカウント)も認証するため、アプリ以外からAgentを勝手に叩かれることはありません。IAPで守った表側だけでなく、内部のサービス間通信までゼロトラストを貫くのがポイントです。

# app.py(Agent Runtimeを呼び出す)
remote_agent = agent_engines.get(AGENT_RUNTIME_ID)
session = remote_agent.create_session(user_id=user_id)
for event in remote_agent.stream_query(
        user_id=user_id, session_id=session["id"], message=prompt):
    ...  # イベントから最終的なDLリンクを組み立てる

こうしてAgentが生成したHTMLはGCSに書き戻され、その安全なダウンロードリンクがアプリに返ってきます。ここまでで、「Wordで書く → ボタン一つで社内フォーマットのHTMLになる」という体験を、ゼロトラストと最小権限を保ちながら実現できました。

6. 最後に(次回予告)

本記事では、Cloud Run × Agent Runtimeで「Word原稿を社内フォーマットのHTMLへ自動変換するアプリ」を、どのように構築したのかをご紹介しました。ポイントは、AIエージェントを「賢いから何でも任せる」のではなく、「役割・権限・境界をきちんと設計して任せる」という姿勢です。

  • 境界を引く(IAP):誰がWebアプリを開けるかアイデンティティで制御する
  • 権限を絞る(Agent Identity):やれることを最小限度に限定する
  • 疎結合にする(GCS):原稿ファイルは直接持たせず、保存先(URI)だけを渡す
  • 知識を仕組みに移す(テンプレート):属人化していたフォーマット知識をライブラリ化する

次回の「使ってみた編」では、実際にこのアプリへ原稿を投げ込み、社内フォーマットのHTMLが出てくるまでの様子を、認証まわりの挙動とあわせてお見せします。どうぞお楽しみに!

▶ シリーズ後編はこちら Cloud RunとAgent Runtimeでブログの自動コーディングアプリ!【使用編】 ▶