Go - TECH PLAY - TECH PLAY

TECH PLAY

Go

イベント

該当するコンテンツが見つかりませんでした

マガジン

技術ブログ

こんにちは、ミイダス Tech Officeです。 ミイダス株式会社のテックチームが直近で開発した機能を現場のエンジニアから共有する「MIIDAS Tech LIVE」。 第14回目の開催となる今回は2つのリリース情報をお届けしました。 採用マッチングサービス「ミイダス」は、独自の診断ツールで採用のミスマッチを減らす中途採用サービスです。メインの採用関連の機能に加え、診断や研修、組織サーベイの支援金の検索機能など、幅広い機能開発が行われています。 MIIDAS Tech LIVE #14 (2026/06/24 12:00〜) ## 📢 概要 MIIDAS Tech LIVEでは、ミイダス株式会社のテックチームが直近で開発した機能を現場のエンジニ miidas-tech.connpass.com
本記事は 2026 年 5 月 21 日に公開された Mike George 氏の “ Test Driven Development (TDD) with Kiro: this is how it should feel ” を翻訳したものです。 私のキャリアの初期に、所属していた組織は、コード品質の向上と自信を持ってリファクタリングできる体制づくりのために、本格的にユニットテストを導入するという正しい判断を下しました。私たちは テスト駆動開発(TDD) の導入を試みました。その利点は理解していたものの、TDD を実践する作業自体が負担に感じられ、エンジニアたちにこの手法を一貫して適用してもらえませんでした。私自身、TDD というアイデアは大好きでしたが、実際の作業は嫌いでした。本記事では、Kiro を使って TDD を実践する方法を紹介し、red-green-refactor サイクルに沿ったテストを手作業で書くという苦痛を伴うことなく、TDD の恩恵を受ける方法をお見せします。 テスト駆動開発(TDD)の概要 テスト駆動開発の基本的な考え方は次のとおりです。 構築する新機能ごとに、その新機能の振る舞いを特定します。 新しい振る舞いそれぞれに対して、もしその振る舞いが存在すればパスするはずのテストを書きます。 テストを実行します。振る舞いがまだ存在しないため、すべてのテストは失敗するはずです。 新しいテストをパスさせるもっともシンプルなコードを書きます。 すべてのテストがパスし続けることを確認しながら、必要に応じてリファクタリングします。 これが red-green-refactor サイクルと呼ばれるものです。書いたテストが失敗し(red フェーズ)、テストをパスさせるためにコードを書き(green フェーズ)、必要に応じてリファクタリングを続ける(refactor サイクル)という流れです。TDD は高品質なコードを生み出すのに役立ちますが、時間がかかると見られがちで、プロセスを一貫して守るためにかなりの規律が求められます。 私は TDD のアイデアは大好きでしたが、テストコードと実装コードの間を行き来するためのコンテキストスイッチが嫌いでした。どのテストを書いたかを管理することも好きではなく、率直に言って、TDD をきちんと実践するために必要なすべてのテストを書く単調さも嫌でした。TDD の経験について他の人と話すと、自分だけがそう感じているわけではないことがよくわかります。 Kiro のようなエージェント型開発ツールは、TDD のこうした欠点をうまく扱ってくれます。Kiro は仕様駆動開発をサポートしています。これは、Kiro が正しいものを正しい方法で正しいアーキテクチャに沿って構築できるよう導く、3 つのフェーズからなる開発プロセスです。各 spec は次の 3 つのフェーズで構成されます。要件フェーズでは、Kiro がユーザーストーリーと受け入れ基準の定義と承認を支援します。設計フェーズでは、技術アーキテクチャと実装アプローチを定義します。タスクフェーズでは、元の要件を満たすコードを生み出すための実行可能な実装タスクを定義します。 Kiro は仕様駆動開発に加えて、 hooks もサポートしています。hooks は、IDE で特定のイベントが発生したときに自動的に実行される自動化ツールです。たとえば、ファイルが保存されるたびにコードを lint する hook を作成したい場合などです。要するに、仕様駆動開発が全体的なエンジニアリングのアプローチを構造化するのに役立つ一方、hooks は TDD のような特定のプラクティスの徹底を可能にします。 私の場合は、TDD の取り組みを支援するために Kiro hook を作成しました。 本番環境での利用におけるセキュリティ上の考慮事項 このチュートリアルは、教育目的で例示プロンプトを使用して Kiro の TDD 機能を紹介しています。本番システム向けにこのアプローチを適用する場合は、次の点に留意してください。 デプロイ前に、 AI が生成したコードのセキュリティ脆弱性を必ずレビューしてください 。とくに、認証、認可、入力検証、データ処理ロジックには注意が必要です。コードが正しく動作することを確認するため、さまざまなシナリオでテストしてください。 この例では簡潔さのために認証なしのオープンな API を使っていますが、 本番環境のデプロイには包括的なセキュリティ対策が必要です 。具体的には、認証と認可、HTTPS/TLS 暗号化、保存時の暗号化、入力検証、レート制限、包括的なロギング、認証情報のシークレット管理などです。 AI 開発ツールを採用する組織は、アクセス制御、コード品質モニタリング、検証プロセスに関する ガバナンスポリシーを確立する 必要もあります。 包括的なセキュリティガイダンスについては、所属組織のセキュリティチームに相談し、 AWS のセキュリティのベストプラクティスに関するドキュメント を参照してください。 Kiro と TDD の統合 Kiro 内で hook を作成するには、Kiro タブに移動し、 agent hooks ウィンドウの「+」ボタンをクリックして manually create a hook を選択します。表示されたウィンドウで、以下を入力します。 Title : TDD: Test First Description : 本番コードを書く前に、失敗するテストを書くよう促すことでテスト駆動開発を徹底します。red/green/refactor サイクルに従います。すなわち、失敗するテストを書き(red)、それをパスさせる最小限のコードを書き(green)、その後リファクタリングします。 Event : Pre Tool Use Tool name : write Action : Ask Kiro Instructions for Kiro agent (以下は和訳です。原文は英語で記述されています): 待ってください! あなたは今コードを書こうとしています。TDD に従い、まずテストを書いてください。 必要なアクション: 1. これがテストファイル(*test*.py、*.spec.* など)の場合 — 続行する 2. これが例外(設定ファイル、ドキュメント、.kiro/)の場合 — 続行する 3. 対応するテストファイルが存在し、かつ実行した結果すべてのテストが失敗(RED フェーズ)していることが確認できている場合 — 続行する 4. それ以外の場合 — 中断し、まずテストファイルを書く: a. test_<filename>.py(または同等のもの)を作成する b. (単なる import ではなく)実際のアサーションで失敗するテストを書く c. テストを実行し、すべてがアサーションエラーで失敗することを確認する d. その後、戻ってこの実装を書く TDD サイクル: - RED: まず失敗するテストを書く(新しいテストはすべてアサーションで失敗しなければならない) - GREEN: テストをパスさせる最小限のコードを書く - REFACTOR: テストが green のままになるようにしつつコードを整理する RED フェーズの要件: - すべてのテストは AssertionError またはテスト失敗で失敗しなければならない - RED フェーズでいずれかのテストがパスする場合、そのテストは弱い/中身がない - RED フェーズでテストがパスするのは、空のループ、アサーションの欠落、テスト内のロジックエラーを意味する 正しい RED フェーズ: すべてのテストが AssertionError で失敗している 不正な RED フェーズ: 一部のテストがパスする、ImportError、ModuleNotFoundError、構文エラー 例外(テストなしで続行してよいもの): - テストファイル: *test*.py、*.spec.*、*.test.*、test_*.py、*_test.go - 設定: package.json、tsconfig.json、.eslintrc、webpack.config.js、vite.config.ts、jest.config.js、pytest.ini、setup.py、pyproject.toml、requirements.txt、Dockerfile、docker-compose.yml、.env、.gitignore - .kiro/ ディレクトリ内のファイル - 型定義: .d.ts ファイル、Protocol クラス - ドキュメント: .md、.txt、.rst、LICENSE、README、CHANGELOG - データファイル: .json、.yaml、.yml、.xml、.csv、.sql、.html、.css、.scss(ロジックを含まないもの) - ビルド成果物: dist/、build/、__pycache__/、*.pyc、node_modules/ 先にテストを必要とするもの: - 関数/クラスを含む Python ファイル(テストパターンに一致しない .py) - ロジックを含む JavaScript/TypeScript(テストパターンに一致しない .js、.ts、.jsx、.tsx) - 実行可能なロジックを含むすべてのソースコード(Go、Java、C#、Ruby など) 重要な確認: テストを実行し、すべてのテストが失敗したことを確認しましたか? スタブ実装でいずれかのテストがパスした場合は、まずテストを修正してください。すべてのテストが失敗する適切な RED フェーズが得られたときにのみ続行してください。 画面の一番下までスクロールし、 Create Hook をクリックします。 この hook は、Kiro がファイルを保存しようとするたびに実行されます。コードを書いている過程で red-green-refactor サイクルが守られているかをチェックします。 簡単なテストとして、 モンティ・ホール問題 を示すプログラムを書くよう Kiro に依頼してみました。Kiro はユニットテストを書き、それらは失敗します。次に Kiro は、対応するモジュールがまだ存在しないためにテストが失敗していることに気づきます。hook によれば、テストはアサーションの失敗によって失敗しなければなりません。そこで Kiro は基本的なモジュールを作成し、テストがアサーションエラーで失敗することを確認します。その後、テストをパスさせるための最小限のコードを書きます。 このシンプルな例で hook が機能していることがわかりますが、もう少し現実的なもの、つまり本番環境向けの REST ベースの API の構築でどう動くか見てみましょう。 REST ベースの API を構築する 実際のコードを書くため、私は Kiro の spec セッション を開始し、タスク管理システム用の REST ベースの API を構築するための要件を入力しました。最終的な要件ドキュメント(以下は和訳です。原文は英語で記述されています)は以下のとおりです。 # 要件ドキュメント ## はじめに タスク管理システム用の REST API です。この API は、永続化のための リレーショナルデータベースまたは組み込みデータベース(PostgreSQL や SQLite など)を備えた Python の Web フレームワーク(Flask や FastAPI など)を使って構築します。アプリケーションは Docker コンテナ としてパッケージ化されるため、コンテナをサポートする任意の プラットフォームにデプロイできます。この API はオープン(認証・認可 なし)です。タスクに対する完全な CRUD 操作をサポートし、クライアントが タスクを作成・読み取り・更新・削除できるようにします。フロントエンドは ありません。これは API のみのサービスです。 ## 用語集 - **API_Server**: HTTP リクエストを受け取り、適切なリクエスト ハンドラーへルーティングする Python の Web フレームワーク アプリケーション(Flask や FastAPI など) - **Task_Service**: タスク関連のビジネスロジックの処理を担う、 コンテナ内で動作するアプリケーションロジック - **Task**: 一意の識別子、タイトル、説明、ステータス、 タイムスタンプといったプロパティを持つ作業単位 - **Task_Store**: タスクが格納・取得されるリレーショナル データベースまたは組み込みデータベース(PostgreSQL や SQLite など) - **Client**: API に HTTP リクエストを送信する外部システム またはユーザー - **Dockerfile**: アプリケーションとその依存関係をポータブルな Docker イメージにパッケージ化する、コンテナのビルド定義 - **Container**: API_Server と Task_Service をホストする、 Docker イメージの実行中インスタンス - **Standard_Error_Format**: すべての API エラーレスポンスで 使われる一貫した JSON エラーレスポンス構造。 `{"error": "<説明的なメッセージ>"}` の形式 ## 要件 ### 要件 1: タスクの作成 **ユーザーストーリー:** クライアントとして、API を通じて 新しいタスクを作成したい。作業項目を追跡できるようにするためである。 #### 受け入れ基準 1. WHEN クライアントが title と description の両方を含む有効な リクエストボディとともに `/tasks` へ POST リクエストを送信する、 THE Task_Service SHALL Task_Store に新しい Task を作成し、 一意の識別子を持つ作成済みの Task を HTTP ステータス 201 とともに返す 2. WHEN クライアントが必須フィールド(title または description)が 欠落したリクエストボディとともに `/tasks` へ POST リクエストを 送信する、THE Task_Service SHALL Standard_Error_Format の エラーメッセージとともに HTTP 400 レスポンスを返す 3. THE Task_Service SHALL 新しく作成された各 Task に、一意の識別子、 `createdAt` タイムスタンプ、`updatedAt` タイムスタンプを割り当てる 4. THE Task_Service SHALL 新しい Task の初期ステータスを "pending" に設定する ### 要件 2: 単一タスクの取得 **ユーザーストーリー:** クライアントとして、識別子を指定して 特定のタスクを取得したい。その詳細を閲覧できるようにするためである。 #### 受け入れ基準 1. WHEN クライアントが有効なタスク識別子とともに `/tasks/{taskId}` へ GET リクエストを送信する、 THE Task_Service SHALL 一致する Task を HTTP ステータス 200 とともに返す 2. WHEN クライアントが Task_Store に存在しないタスク識別子とともに `/tasks/{taskId}` へ GET リクエストを送信する、 THE Task_Service SHALL Standard_Error_Format の エラーメッセージとともに HTTP 404 レスポンスを返す ### 要件 3: 全タスクの一覧取得 **ユーザーストーリー:** クライアントとして、すべてのタスクを 一覧表示したい。追跡中の全作業項目の概要を確認できるように するためである。 #### 受け入れ基準 1. WHEN クライアントが `/tasks` へ GET リクエストを送信する、 THE Task_Service SHALL Task_Store のすべての Task のリストを HTTP ステータス 200 とともに返す 2. WHEN Task_Store に Task が 1 つも存在しない、 THE Task_Service SHALL 空のリストを HTTP ステータス 200 とともに返す ### 要件 4: タスクの更新 **ユーザーストーリー:** クライアントとして、全体を置き換える形で 既存のタスクを更新したい。タイトル、説明、ステータスを変更できるように するためである。 #### 受け入れ基準 1. WHEN クライアントが有効なタスク識別子と、すべての必須フィールド (title、description、status)を含むリクエストボディとともに `/tasks/{taskId}` へ PUT リクエストを送信する、 THE Task_Service SHALL Task_Store 内の一致する Task を完全に 置き換え、更新後の Task を HTTP ステータス 200 とともに返す 2. WHEN クライアントが Task_Store に存在しないタスク識別子とともに `/tasks/{taskId}` へ PUT リクエストを送信する、 THE Task_Service SHALL Standard_Error_Format の エラーメッセージとともに HTTP 404 レスポンスを返す 3. WHEN クライアントが必須フィールド(title、description、status) のいずれかが欠落したリクエストボディとともに `/tasks/{taskId}` へ PUT リクエストを送信する、THE Task_Service SHALL Standard_Error_Format のエラーメッセージとともに HTTP 400 レスポンスを返す 4. THE Task_Service SHALL Task が変更されるたびに `updatedAt` タイムスタンプを更新する ### 要件 5: タスクの削除 **ユーザーストーリー:** クライアントとして、タスクを削除したい。 不要になった作業項目を完全に除去できるようにするためである。 #### 受け入れ基準 1. WHEN クライアントが有効なタスク識別子とともに `/tasks/{taskId}` へ DELETE リクエストを送信する、 THE Task_Service SHALL Task_Store から Task を完全に削除し、 HTTP ステータス 204 を返す 2. WHEN クライアントが Task_Store に存在しないタスク識別子とともに `/tasks/{taskId}` へ DELETE リクエストを送信する、 THE Task_Service SHALL Standard_Error_Format の エラーメッセージとともに HTTP 404 レスポンスを返す ### 要件 6: リクエストの検証 **ユーザーストーリー:** クライアントとして、不正なリクエストに 対して明確なエラーレスポンスがほしい。API 呼び出しの問題を 修正できるようにするためである。 #### 受け入れ基準 1. WHEN クライアントが無効な JSON ボディを含むリクエストを送信する、 THE API_Server SHALL Standard_Error_Format の エラーメッセージとともに HTTP 400 レスポンスを返す 2. WHEN クライアントが未定義のルートへリクエストを送信する、 THE API_Server SHALL HTTP 404 レスポンスを返す 3. WHEN クライアントが定義済みのルートに対してサポートされていない HTTP メソッドを使ってリクエストを送信する、THE API_Server SHALL HTTP 405 レスポンスを返す ### 要件 7: 入力長の検証 **ユーザーストーリー:** クライアントとして、API に妥当な入力長を 強制してほしい。過度に大きな入力が拒否されるようにするためである。 #### 受け入れ基準 1. WHEN クライアントが 100 文字を超える `title` フィールドを含む リクエストを送信する、THE Task_Service SHALL title が長すぎる ことを示す Standard_Error_Format のエラーメッセージとともに HTTP 400 レスポンスを返す 2. WHEN クライアントが 1000 文字を超える `description` フィールドを 含むリクエストを送信する、THE Task_Service SHALL description が 長すぎることを示す Standard_Error_Format のエラーメッセージと ともに HTTP 400 レスポンスを返す 3. THE Task_Service SHALL 作成(POST)操作と更新(PUT)操作の 両方で入力フィールドの長さを検証する ### 要件 8: タスクのデータモデル **ユーザーストーリー:** クライアントとして、一貫したタスクの データ構造がほしい。API レスポンスを確実にパースできるように するためである。 #### 受け入れ基準 1. THE Task_Service SHALL 各 Task を次のフィールドを持つ JSON オブジェクトとして表現する: `id`(string)、`title`(string)、 `description`(string)、`status`(string)、 `createdAt`(ISO 8601 タイムスタンプ)、 `updatedAt`(ISO 8601 タイムスタンプ) 2. THE Task_Service SHALL Task の status フィールドとして次の値を 受け入れる: "pending"、"in-progress"、"completed" 3. IF クライアントが受け入れ可能な集合に含まれない status 値を 指定する、THEN THE Task_Service SHALL Standard_Error_Format の エラーメッセージとともに HTTP 400 レスポンスを返す ### 要件 9: JSON シリアライズのラウンドトリップ **ユーザーストーリー:** クライアントとして、シリアライズと デシリアライズを通じて API がタスクデータを忠実に保持してほしい。 データが失われたり破損したりしないようにするためである。 #### 受け入れ基準 1. FOR ALL 有効な Task オブジェクトについて、Task を JSON へ シリアライズし、その JSON を再び Task へデシリアライズすると、 同等の Task オブジェクトが得られる SHALL(ラウンドトリップ特性) 2. THE Task_Service SHALL すべてのリクエストボディとレスポンス ボディの唯一のデータ交換形式として JSON を使用する 3. THE Task_Service SHALL すべてのレスポンスに `Content-Type: application/json` ヘッダーを含める ### 要件 10: 標準エラーレスポンス形式 **ユーザーストーリー:** クライアントとして、すべてのエラー レスポンスが一貫した JSON 構造に従ってほしい。エラーを確実に パースして処理できるようにするためである。 #### 受け入れ基準 1. THE Task_Service SHALL すべてのエラーレスポンスを Standard_Error_Format で返す: `{"error": "<説明的なメッセージ>"}` 2. THE Task_Service SHALL すべてのエラーレスポンスに `Content-Type: application/json` ヘッダーを含める 3. THE Task_Service SHALL 各エラーレスポンスの `error` フィールドに 人間が読めるエラーの説明を提供する ### 要件 11: コンテナの構成 **ユーザーストーリー:** 開発者として、妥当なデフォルト値を備えた Docker コンテナとしてアプリケーションをパッケージ化したい。 システムがポータブルで、任意のプラットフォームに簡単にデプロイ できるようにするためである。 #### 受け入れ基準 1. THE Dockerfile SHALL API_Server と Task_Service を、必要なすべての 依存関係とともに単一の Docker イメージにパッケージ化する 2. THE Dockerfile SHALL イメージサイズを最小化するため軽量な Python ベースイメージを使用する 3. THE Container SHALL API_Server が HTTP リクエストを受け付けるための 設定可能なポート(デフォルトは 8000)を公開する 4. THE Container SHALL データベース接続設定とサーバーポートについて、 環境変数による構成をサポートする 5. THE Dockerfile SHALL API_Server が稼働しているときに HTTP ステータス 200 を返すヘルスチェックエンドポイントを `/health` に含める ### 要件 12: データベースの移植性 **ユーザーストーリー:** 開発者として、アプリケーションに標準的な リレーショナルデータベースを使ってほしい。デプロイ環境に合った データベースバックエンドを選べるようにするためである。 #### 受け入れ基準 1. THE Task_Service SHALL SQL ベースのデータベースアクセスを使用し、 Task_Store を SQLite、PostgreSQL、その他の SQL 互換データベースで 支えられるようにする 2. THE Task_Service SHALL 外部データベース接続が構成されていない場合、 Task_Store としてデフォルトで SQLite を使用する 3. WHEN 環境変数を通じて外部データベース接続文字列が提供される、 THE Task_Service SHALL Task_Store として指定されたデータベースに 接続する 4. THE Task_Service SHALL 起動時にテーブルが存在しない場合、必要な データベーステーブルを自動的に作成する 要件が揃ったら、Kiro はそれらを設計ドキュメントに、最終的にはタスクリストへとまとめ上げてくれます。各タスクを実行するたびに、TDD hook が Kiro に red-green-refactor サイクルでの作業を強制します。 例として、API の GET /tasks/{task_id} のルートハンドラーを構築するタスクを Kiro に実行させてみました。Kiro は、この機能をサポートするコードを書く前に、まず失敗するテストを書かなければならないと判断します。 Kiro はテストを構築し、それらが失敗することを確認します。これは red フェーズが正常に完了したことを意味します。次にコードを修正してテストを再実行し、テストがパスすることを確認します。これにより green フェーズが正常に完了したことが確認できます。新しいテストがパスしたら、Kiro は既存のすべてのテストを再実行し、それらが引き続き正しく動作することを検証します。 まとめ 長年の間、私は TDD と格闘してきました。コード品質の向上、バグの減少、リファクタリングへの自信といった利点を信じてはいましたが、現実は疲れるものでした。絶え間ないコンテキストスイッチ、red-green-refactor サイクルを守り続けるために必要な規律、そして実装の前にテストを書くことの苦痛が、TDD を有益なプラクティスというより負担に感じさせていました。 Kiro はこの状況を完全に変えてくれます。シンプルな hook を使うことで、TDD に必要な規律を自動化できました。hook があれば、Kiro はステップを飛ばしたり近道をしたりすることを許しません。私が意識しなくてもサイクルを守らせてくれます。コンテキストスイッチの負担、プロセスを守り続けるための規律、良いテストを書くことの単調さといった重荷を背負うことなく、TDD の恩恵を受けられるのです。本記事で示したように、モンティ・ホール問題のような単純なデモであっても、数十もの要件を持つ本番環境向けの REST API であっても、hook が TDD のプロセスを徹底してくれます。 私にとって、これこそが TDD のあるべき姿です。Kiro は、負担なしですべての恩恵を与えてくれます。 このアプローチをご自身で試してみたい方は、本記事の hook 設定をコピーして、ご自身の Kiro プロジェクトで使ってみてください。小さなプロジェクトから始めて、感触を確かめ、自分のやり方に合うように hook の指示を調整してみてください。これまで TDD で苦労してきた方や、コード品質を向上させる方法を探している方は、Kiro の hook を使った TDD をぜひ試してみてください。
G-gen の佐々木です。当記事では、Agent Development Kit(以下 ADK と記載)で開発した AI エージェントを Cloud Run にデプロイし、Cloud Run のサンドボックス機能による Code Execution(LLM が生成したコードの安全な実行)を試します。 構成 当記事で使用するもの Cloud Run とは Cloud Run のサンドボックスとは サンドボックスの概要 sandbox コマンドラインツール 実行結果の取得とファイルの受け渡し Agent Development Kit(ADK)とは エージェントの開発 ディレクトリ構成 uv プロジェクトの作成 agent.py __init__.py main.py Dockerfile .dockerignore Google Cloud 側の準備 API の有効化 サービスアカウントの作成 デプロイ 動作確認 Web UI へのアクセス エージェントとの対話 ログの確認 構成 当記事では、ADK で開発した AI エージェントを、サンドボックス機能を有効化した Cloud Run サービスとしてデプロイします。 処理の流れは以下のとおりです。 ユーザーが ADK の Web UI からエージェントに質問する エージェントが質問への回答に必要なコードを生成し、サンドボックス内でコードを実行するためのカスタムツールを呼び出す ツールが Cloud Run の隔離されたサンドボックス内でコードを実行する エージェントが実行結果を元に回答を生成する LLM が生成したコードをアプリケーションのコンテナ内で直接実行すると、意図しないファイル操作や外部通信などのリスクがあります。Cloud Run のサンドボックス機能を使用すると、ホストコンテナから隔離された環境でコードを実行できます。 当記事で使用するもの Cloud Run とは Cloud Run は、Google Cloud のフルマネージドなサーバーレスコンテナ実行基盤です。コンテナイメージまたはソースコードをデプロイするだけで、リクエスト数に応じた自動スケーリングを備えた Web サービスを実行できます。 Cloud Run の詳細は、以下の記事で解説しています。 blog.g-gen.co.jp Cloud Run のサンドボックスとは サンドボックスの概要 Cloud Run の サンドボックス (Cloud Run sandboxes)は、信頼できないコードを高速・安全・隔離された環境で実行するための機能です。 Cloud Run サービスでサンドボックス機能を有効化すると、コンテナ内で sandbox コマンドラインツールが利用可能になり、任意のコマンドをサンドボックス内で実行できます。2026年7月現在、この機能は Preview 公開 です。 サンドボックスの主な特徴は以下のとおりです。 第2世代実行環境の Cloud Run でのみ使用可能 サンドボックスは必要に応じて瞬時に作成され、すぐにコマンドを実行できる サンドボックス内のプロセスは非 root ユーザーとして実行され、デフォルトでは親ワークロードや Cloud Run のメタデータサーバーにアクセスできない(プロセスレベルの分離) ホストコンテナの環境変数はサンドボックスに継承されず、API キーなどの機密情報が意図せず参照されることを防げる(渡す場合は --env フラグで明示的に指定する) 外部へのアウトバウンド通信はデフォルトでブロックされる( --allow-egress フラグで許可できる) サンドボックスから見えるルートファイルシステムは読み取り専用( --write フラグやバインドマウントで書き込みを許可できる) サンドボックスはホストコンテナと同一インスタンス内で動作し、CPU とメモリをホストコンテナと共有する サンドボックスの作成・削除などのライフサイクルイベントは Cloud Logging に自動的に記録される 参考 : Code execution in Cloud Run 参考 : Configure sandboxes for services sandbox コマンドラインツール sandbox コマンドラインツールには以下のサブコマンドがあります。 コマンド 説明 sandbox do 一時的なサンドボックスを作成してコマンドを実行し、終了後に破棄する sandbox run サンドボックスを起動する sandbox exec 実行中のサンドボックスでコマンドを実行する sandbox tar サンドボックスのファイルシステムのスナップショットを取得する sandbox delete サンドボックスを削除する 当記事では、単発のコード実行に適した sandbox do を使用します。 実行結果の取得とファイルの受け渡し サンドボックス内で実行したプロセスの標準出力・標準エラーは、呼び出し元のプロセスに直接返されます。後述のサンプルコードでは、この仕様を利用して subprocess モジュール経由でコードの実行結果を取得します。 サンドボックスのファイルシステムへの書き込みは、 --write フラグで許可した場合も一時的なもので、ホストコンテナからは参照できません。 実行結果としてファイルを取り出す場合は、サンドボックス内で変更されたファイルを tar アーカイブとして出力する --export-tar フラグ(取り込みは --import-tar 、双方向同期は --sync-tar )や、 --mount フラグによるバインドマウントを使用して、ホストコンテナとファイルを受け渡しします。 Agent Development Kit(ADK)とは Agent Development Kit (以下 ADK と記載)は、Google が開発するオープンソースのエージェント開発フレームワークです。 ADK は Python、TypeScript、Go、Java に対応しており、開発したエージェントはローカル環境のほか、Agent Runtime(旧称 Agent Engine)、Cloud Run、Google Kubernetes Engine(GKE)にデプロイできます。 当記事では Python 版の ADK( google-adk )を使用します。 参考 : Agent Development Kit 参考 : google/adk-python エージェントの開発 ディレクトリ構成 作成するプロジェクトのディレクトリ構成は以下のとおりです( uv init が生成する README.md や .python-version などは省略)。 sandbox-agent/ ├── .dockerignore ├── Dockerfile ├── main.py # FastAPI アプリのエントリーポイント ├── pyproject.toml ├── uv.lock └── sandbox_agent/ # ADK エージェントのパッケージ ├── __init__.py └── agent.py # エージェントとツールの定義 uv プロジェクトの作成 uv プロジェクトを初期化し、依存パッケージとして google-adk と uvicorn を追加します。 # uv のセットアップ $ uv init sandbox-agent --python 3 . 13 $ cd sandbox-agent # 依存パッケージのインストール $ uv add google-adk uvicorn # エージェントのパッケージディレクトリとファイルの作成 $ mkdir sandbox_agent $ touch sandbox_agent/__init__.py sandbox_agent/agent.py Dockerfile .dockerignore main.py は uv init によって生成されるため、ここでは作成せず、後の手順で内容を書き換えます。各ファイルの中身は以降の節で順に記述していきます。 pyproject.toml は以下のようになります。 [project] name = "sandbox-agent" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ "google-adk>=2.4.0" , "uvicorn>=0.51.0" , ] agent.py エージェント本体とカスタムツールを sandbox_agent/agent.py に定義します。 import subprocess from google.adk.agents import Agent from google.adk.tools import FunctionTool SANDBOX_BIN = "/usr/local/gcp/bin/sandbox" PYTHON_BIN = "/usr/local/bin/python3" def execute_python_code (code: str ) -> dict : """Python コードをサンドボックス内で実行し、結果を返す。 Args: code: 実行する Python ソースコード。 Returns: stdout、stderr、returncode を含む dict。 """ result = subprocess.run( [SANDBOX_BIN, "do" , "--" , PYTHON_BIN, "-c" , code], capture_output= True , text= True , timeout= 60 , ) return { "stdout" : result.stdout, "stderr" : result.stderr, "returncode" : result.returncode, } root_agent = Agent( name= "sandbox_agent" , model= "gemini-2.5-flash" , description= "Python コードをサンドボックスで実行して回答するエージェント" , instruction=( "あなたはユーザーの質問に答えるアシスタントです。" "計算やデータ処理が必要な場合は、必ず Python コードを書いて" " execute_python_code ツールで実行し、その実行結果に基づいて回答してください。" "実行結果の stdout をそのまま引用し、コードの内容も簡単に説明してください。" ), tools=[FunctionTool(func=execute_python_code)], ) ポイントは以下のとおりです。 execute_python_code 関数を FunctionTool でラップしてエージェントのツールとして登録している。関数のドキュメンテーション文字列と型ヒントがツールの仕様として LLM に渡される ツール内では、サンドボックス機能の有効化時にコンテナへ配置されるバイナリ /usr/local/gcp/bin/sandbox を subprocess で呼び出し、 sandbox do -- /usr/local/bin/python3 -c <コード> の形式で LLM が生成した Python コードをサンドボックス内で実行している サンドボックスからはホストコンテナのルートファイルシステムが読み取り専用で参照できるため、コンテナイメージに含まれる Python ランタイムをサンドボックス内でも実行できる サンドボックスにはホストの環境変数が継承されず、実行するコマンドの PATH 解決も行われないため、コマンドは /usr/local/bin/python3 のような絶対パスで指定する必要がある __init__.py ADK がエージェントを認識できるように、 sandbox_agent/__init__.py で agent モジュールをインポートしておきます。 from . import agent main.py Cloud Run 上でエージェントを Web アプリケーションとして公開するため、ADK が提供する get_fast_api_app() で FastAPI アプリを作成します。 import os from google.adk.cli.fast_api import get_fast_api_app AGENTS_DIR = os.path.dirname(os.path.abspath(__file__)) app = get_fast_api_app( agents_dir=AGENTS_DIR, allow_origins=[ "http://localhost:8080" , "http://127.0.0.1:8080" ], web= True , ) if __name__ == "__main__" : import uvicorn uvicorn.run(app, host= "0.0.0.0" , port= int (os.environ.get( "PORT" , 8080 ))) get_fast_api_app() は、ADK の開発用 Web UI とエージェント実行用の REST API を含む FastAPI アプリを返します。 agents_dir にはエージェントのパッケージ(当記事では sandbox_agent/ )が置かれたディレクトリを指定し、 web=True で Web UI を有効化します。ポート番号は Cloud Run が設定する環境変数 PORT から取得します。 allow_origins には、後述の動作確認で gcloud run services proxy コマンド経由で Web UI にアクセスするときのオリジンを指定します。ADK の API サーバーは、セキュリティ対策として POST などの状態変更リクエストの Origin ヘッダーを検証します。プロキシ経由のアクセスでは Origin( http://127.0.0.1:8080 など)とリクエスト先( run.app ドメイン)が一致しないため、 allow_origins を指定していないと Web UI からの操作が403エラーになります。 Dockerfile uv を使用してコンテナイメージをビルドする Dockerfile を作成します。 FROM python:3.13-slim COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv WORKDIR /app COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev COPY . . ENV PATH= "/app/.venv/bin:$PATH" CMD [ " python ", " main.py " ] .dockerignore .dockerignore に以下の内容を記述し、ローカルの .venv などをコンテナイメージのビルドコンテキストから除外します。 .venv __pycache__ *.pyc .git 後述のデプロイで使用する --source フラグは、カレントディレクトリ全体を Cloud Build にアップロードします。 .dockerignore で除外していない場合、 COPY . . の際にローカル環境用の .venv がコンテナ内に作成済みの .venv を上書きし、コンテナの起動に失敗するため注意してください。 Google Cloud 側の準備 API の有効化 使用する API を有効化します。ソースコードからのデプロイ( --source フラグ)では Cloud Build と Artifact Registry も使用されるため、あわせて有効化します。なお2026年7月現在、Gemini の呼び出しに使用する Agent Platform の API 名や IAM ロール ID には、旧称の Vertex AI に由来する aiplatform という名称が残っています。 $ gcloud services enable \ run.googleapis.com \ aiplatform.googleapis.com \ cloudbuild.googleapis.com \ artifactregistry.googleapis.com \ --project =< プロジェクトID > サービスアカウントの作成 Cloud Run サービスが使用するサービスアカウントを作成します。エージェントが Agent Platform の API 経由で Gemini を呼び出すため、Agent Platform ユーザー( roles/aiplatform.user )を付与します。 # サービスアカウントの作成 $ gcloud iam service-accounts create sandbox-agent \ --project =< プロジェクトID > # Agent Platform ユーザーの付与 $ gcloud projects add-iam-policy-binding < プロジェクトID > \ --member =" serviceAccount:sandbox-agent@<プロジェクトID>.iam.gserviceaccount.com " \ --role =" roles/aiplatform.user " デプロイ 作成したプロジェクトのディレクトリ( sandbox-agent/ )で、以下のコマンドを実行して Cloud Run にデプロイします。サンドボックス機能を有効化する --sandbox-launcher フラグは、2026年7月現在、 gcloud beta コマンドでのみ使用できます。 $ gcloud beta run deploy sandbox-agent \ --source . \ --project =< プロジェクトID > \ --region = asia-northeast1 \ --execution-environment = gen2 \ --sandbox-launcher \ --service-account = sandbox-agent@ < プロジェクトID > .iam.gserviceaccount.com \ --set-env-vars = GOOGLE_GENAI_USE_VERTEXAI =TRUE, GOOGLE_CLOUD_PROJECT = < プロジェクトID > , GOOGLE_CLOUD_LOCATION =asia-northeast1 \ --no-allow-unauthenticated 主なフラグの意味は以下のとおりです。 フラグ 説明 --source . カレントディレクトリのソースコードから Cloud Build でコンテナイメージをビルドしてデプロイする。 Dockerfile が存在する場合はそれが使用される --execution-environment=gen2 第2世代実行環境を指定する。サンドボックス機能の使用に必須 --sandbox-launcher サンドボックス機能を有効化する。コンテナ内に sandbox コマンドラインツールが配置される --service-account ランタイムサービスアカウントとして、前の手順で作成したサービスアカウントを指定する --set-env-vars ADK が Agent Platform 経由で Gemini を呼び出すための環境変数を設定する。 GOOGLE_GENAI_USE_VERTEXAI という変数名にも旧称に由来する名称が残っている --no-allow-unauthenticated 未認証のアクセスを拒否する 環境変数 GOOGLE_CLOUD_LOCATION は、Gemini を呼び出す Agent Platform 側のロケーションであり、Cloud Run サービスのリージョン( --region )とは独立しています。当記事では asia-northeast1 を指定し、東京リージョンのリージョンエンドポイント経由でモデルを呼び出します。 参考 : gcloud beta run deploy 動作確認 Web UI へのアクセス デプロイしたサービスは未認証アクセスを拒否しているため、 gcloud run services proxy コマンドで手元の端末からプロキシ経由でアクセスします。 $ gcloud run services proxy sandbox-agent \ --project =< プロジェクトID > \ --region = asia-northeast1 ブラウザで http://localhost:8080 を開くと、ADK の開発用 Web UI にアクセスできます。画面左上のプルダウンでエージェント sandbox_agent を選択します。 ADK の開発用 Web UI にアクセスし、sandbox_agent を選択する エージェントとの対話 エージェントに、コード実行が必要な質問を送信します。例として「1から100までの素数の合計を計算してください」と質問すると、エージェントは Python コードを生成して execute_python_code ツールを呼び出し、サンドボックス内での実行結果を元に回答します。 エージェントがコードを生成して実行している Web UI の Info ペインでは、ツール呼び出しの内容を確認できます。 Function Calls イベントにはエージェントが生成した Python コードが、 Function Responses イベントにはサンドボックスでの実行結果( stdout など)が記録されています。 Function Calls にエージェントが生成したコードが記録されている Function Responses にコードの実行結果が記録されている ログの確認 前述のとおり、サンドボックスのライフサイクルイベントは Cloud Logging に自動的に記録されます。サンドボックスの実行ログは、Cloud Run の標準ログ( stdout や requests )とは別の専用ログ run.googleapis.com//var/log/sandbox.log に出力されるため、ログエクスプローラで以下のクエリを実行して確認します。 resource.type="cloud_run_revision" resource.labels.service_name="sandbox-agent" logName="projects/<プロジェクトID>/logs/run.googleapis.com%2F%2Fvar%2Flog%2Fsandbox.log" sandbox do の実行1回につき [start] と [end] のペアが記録され、実行したコマンドの全文(LLM が生成した Python コードを含む)が残ります。以下は先ほどの対話で実際に記録されたログです( [end] のコマンド文字列は省略しています)。 [start] cwd=/app "/usr/local/gcp/bin/sandbox do -- /usr/local/bin/python3 -c def is_prime(n): if n < 2: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True total_sum = 0 for number in range(2, 101): if is_prime(number): total_sum += number print(total_sum) " [end] exit_code=0 elapsed=541ms "/usr/local/gcp/bin/sandbox do -- /usr/local/bin/python3 -c ..." Cloud Run でサンドボックスが使用されたときのログを検索する [end] エントリには終了コード( exit_code )と実行時間( elapsed )が付くため、サンドボックスが実際に使用されたこと、どのようなコードが実行されたか、正常に終了したかどうかまで確認できます。今回の実行時間は約540ミリ秒で、サンドボックスの作成からコード実行、破棄までが高速に完了していることもわかります。 参考 : Code execution in Cloud Run 佐々木 駿太 (記事一覧) クラウドソリューション部 クラウドエンジニアリング1課 北海道在住 大学院まで社会心理学を専攻し、AI に興味を持ち IT 業界へ。2022年6月に G-gen にジョイン。Google Cloud Partner Top Engineer に選出(2024 / 2025 Fellow / 2026)。好きな Google Cloud プロダクトは Cloud Run。 趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。最近は法律の勉強にも目覚め、2級知的財産管理技能士を取得。 Follow @sasashun0805

動画

該当するコンテンツが見つかりませんでした

書籍