はじめに
2025年5月16日、AWSからAIエージェント構築用SDKであるStrands Agentsのリリースが発表されました。
本記事では、AWSが開発したAIエージェントフレームワーク「Strands Agents」を使用して、自然言語の指示だけでAWSインフラを自律的に構築させるプロセスを実施させてみます。
Strands Agentsとは
Strands Agentsとは、一言で言えば「AIエージェントを迅速かつシンプルに構築するためのオープンソースSDK(ソフトウェア開発キット)」です。
Pythonライブラリとして提供されており、誰でも自由に利用できます。
このフレームワークの最大の特徴は、「モデル駆動型 (Model-Driven)」という開発思想にあります。
これは従来のように開発者がエージェントの複雑な行動ロジックを一つひとつプログラムするのではなく、タスク遂行の大部分を大規模言語モデル(LLM)に委ねるというアプローチです。
これにより、開発者は「何をすべきか」という目標と、「どんな道具(ツール)を使えるか」を定義するだけで、LLMが自律的に判断してタスクを完了してくれるようになります。
さらにAIエージェントのLLM(大規模言語モデル)を自由に選べる点も大きな特長で、Amazon Bedrockで提供されるさまざまなモデルやAnthropicのClaude、OpenAIのGPTなどのモデルも使用できます。
また企業が独自に開発した「自社専用のAI」を組み込んで使うことも可能です。
Strands Agentsを支える技術
Strands Agentsは、主に以下の3つの要素で構成されます。
モデル(Model) :
エージェントの「脳」にあたる部分で、思考と推論を担当します。
AnthropicのClaude、OpenAIのGPT、GoogleのGemini、Amazon Bedrock上の各種モデルなど、主要なLLMに接続できます。
ツール(Tools) :
エージェントの「手足」となる機能群です。
ファイル操作やAWSサービスとの連携など、すぐに使える20種類以上のサンプルツールが標準で提供されています。
また既存のPython関数に「@tool」という一行を追加するだけで、エージェントがそのPython関数を使用できるようになります。
さらに「Model Context Protocol (MCP)」を通じて公開されているサーバを外部ツールとして利用することも可能で、世界中の開発者が作った便利な機能を自分のエージェントに組み込むことができます。
プロンプト(Prompt) :
モデルに対して、タスクの目的、利用可能なツール、そして現在の状況を伝えるための指示書です。
Strands Agentsではこのプロンプトが半自動的に生成されるため、開発者はプロンプトエンジニアリングの負担から解放されます。
Agentic Loop
Agentic Loopとは、AIエージェントがタスクを自律的に完了するために、大規模言語モデル(LLM)とツールを継続的に相互作用させる循環的なプロセスです。
エージェントは、以下の「観察 → 思考 → 行動」というサイクルを自律的に繰り返すことでタスクを遂行します。
観察(Observe) :
エージェントは現在の状況や、前のステップでツールを実行した結果を観察します。
思考(Think) :
観察した内容と最終目標にもとづき、モデル(LLM)が次に行うべき最適な行動を推論します。
「どのツールを、どのパラメーターで使うか」あるいは「タスクは完了したか」を判断します。
行動(Act) :
思考ステップで決定した通りに、特定のツールを実行します。
このループを、タスクが完了したとモデルが判断するまで繰り返します。
考えられるユースケース
Strands Agentsのような「Agentic Loop」に特化したフレームワークを使用する真の意義は、複雑で予測不可能なタスクにおいてエージェントが自律的に判断し、最終的な目標に到達する能力を発揮する点にあります。
Strands Agentsが活躍するケースは、以下のような特徴を持つものです。
タスクの達成パスが事前に完全に定義できない場合 :
ユーザーの指示が曖昧であったり、解決策にたどり着くまでに複数の異なるツールやステップが必要となるケース。
試行錯誤や反復的な改善が必要な場合 :
一度の実行では目的を達成できず、ツールの実行結果を評価し、そのフィードバックをもとに再計画・再実行が必要となるケース。
多様なツールや情報源を横断的に利用する場合 :
複数の異なるAPI、データベース、Webサービスなどを組み合わせて情報収集・処理を行う必要があるケース。
ユーザーとの対話を通じてタスクを段階的に洗練させていく場合 :
エージェントが途中でユーザーに追加の質問をしたり、中間結果を提示して確認を求めたりしながら、最終的な成果物を作り上げていくケース。
考えられるユースケースとしては、以下のようなものが挙げられます。
高度なデータ調査・分析エージェント :
(例)
最新の財務データやニュース記事を参照し、〇〇業界における最新の市場トレンドを詳細に分析し、競合他社の戦略を調査する。
その上で取るべき具体的な戦略オプションを3つ提案し、それぞれのリスクとリターンを評価する。
このケースでは市場調査API、ニュースAPI、企業財務データベースなど、複数の外部ツールを横断的に使用し、その結果を統合して深い洞察を導き出す必要があります。
さらに分析の過程で不足する情報があれば、自律的に追加の検索やデータ取得を行い、試行錯誤しながら最適な回答パスを模索します。
提示された情報だけでなく、背景知識や推論にもとづいて「なぜそうなるのか」という深い洞察を生成する能力が求められます。
パーソナルタスク自動化・アシスタント :
(例)
東京出張の交通手段と宿泊を手配し、最適なルートとホテルを複数提案する。
会議場所は新宿で、予算は〇〇円、朝食付きで評判の良いホテルを選ぶ。
もし天候の影響を受ける可能性があるなら、その情報も考慮に入れる。
このケースでは交通手段検索(電車、飛行機など)、ホテル予約サイト、天気予報APIなど、複数の異なるサービスと連携する必要があります。
ユーザーの複数の制約条件(予算、場所、設備、評判)を総合的に考慮し、最適な組み合わせを自律的に見つけ出す複雑な計画が必要です。
予約が埋まっていたり、条件に合う選択肢が少ない場合には代替案を検討したり、ユーザーに確認したりする動的な対応が求められます。
インタラクティブなコンテンツ生成・クリエイティブアシスタント :
(例)
市場調査やトレンド分析を行い、新規モバイルゲームの企画をゼロから提案する。
ターゲット層はZ世代で、流行りの要素(例: ショート動画、ソーシャル機能)を取り入れつつ、斬新なゲームプレイを考案する。
このケースは単なる情報検索ではなく、創造的な思考とアイデア生成が中心となり、市場調査ツール、トレンド分析ツール、競合ゲームのデータベースなどを利用し、その結果をもとに新たなコンセプトを「思考」するプロセスが必要になります。
生成されたアイデアを自己評価し、さらに改善するための反復的なサイクル(Agentic Loop)が非常に有効です。
これらのユースケースでは、単にツールを実行するだけでなく、エージェントが状況を理解し思考し計画し行動し、そしてその結果を評価して次につなげるという「Agentic Loop」の全容が活きてきます。
Strands Agentsは、このような自律的な思考と行動のサイクルを、開発者がシンプルに構築できる環境を提供します。
実際にやってみる
それでは実際にAWSインフラを構築してもらおうと思います。
動作環境はMacで、今回はOpenAI APIを使用します。
まずはエージェントを動かすために、以下がインストール済みであることを確認してください(手順は割愛します)。
- Python(3.10以上)
- AWS CLI(IAMユーザーの認証情報が
aws configure設定済みであること) - Terraform
上記の準備が完了したらターミナルを開き、仮想環境を作成します。
# 作業用ディレクトリを作成・移動 mkdir <任意のディレクトリ名> cd <任意のディレクトリ名> # 仮想環境を作成 python3 -m venv <任意の名称> # 仮想環境のアクティベート source <任意の名称>/bin/activate
続いて必要なパッケージをインストールします。
今回はOpenAI APIキーを使用するのでopenaiパッケージも一緒にインストールします。
pip install "strands-agents[openai]"
次に、OpenAIのAPIキーを環境変数に設定します。
ご自身でお持ちのAPIキーを設定してください。
export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxx"
Strands AgentsがOpenAI APIキーを使用するようになっているか確認します。
以下のコマンドでPythonインタプリタを起動します。
python3
以下のコードを一行ずつ入力してEnterキーを押します。
>>> from strands import Agent >>> agent = Agent() >>> print(type(agent.model))
すると以下のように出力されるはずです。
<class 'strands.models.bedrock.BedrockModel'>
これはStrands AgentsがデフォルトでBedrockModelを使用するようになっているためです。
この設定を編集し、OpenAI APIキーを使用するようにします。
>>> import os >>> from strands import Agent >>> from strands.models.openai import OpenAIModel >>> openai_model = OpenAIModel(model_id="gpt-4o") >>> agent = Agent(model=openai_model) >>> print(type(agent.model))
上記を実行し、以下のような出力があれば設定完了です。
<class 'strands.models.openai.OpenAIModel'>
簡単な動作確認を行います。
以下のようにエラーが出なければ問題ありません。
>>> response = agent("日本の首都はどこですか?")
日本の首都は東京です
作成するエージェントについて
今回は、ユーザーの抽象的な要件から自律的にAWSインフラを設計・構築するAIエージェントを作成します。
Strands Agentsは、これらの工程を「Agent」と呼ばれる自律的な単位に分割し、それぞれを連携させることで実現できます。
各工程を担当するAgentの役割は以下の通りです。
User Facing Agent(対話エージェント)
ユーザーとの対話インターフェイスを担当します。
ユーザーからの初期要件を受け取り、必要に応じて追加の質問(例:「想定するアクセス数はどのくらいですか?」「データベースは必要ですか?」など)を投げかけ、要件を具体化します。Architect Agent(設計エージェント)
具体化された要件を受け取り、AWSのベストプラクティス(セキュリティ、コスト、パフォーマンス)にもとづいて最適なインフラ構成を設計します。
どのAWSサービスをどのようなパラメーターで利用するかを決定し、構成案を作成します。Terraform Coder Agent(コード生成エージェント)
Architect Agentが作成したインフラ構成案を、実行可能なTerraformのHCLコードに変換します。
VPC、サブネット、セキュリティグループなど、各リソースの定義を記述します。Test Agent(テストエージェント)
生成されたTerraformコードが意図通りかつ安全に動作するかを事前に検証します。Deployment Agent(デプロイエージェント)
テスト済みのTerraformコードを実行し、AWS上にインフラを構築します。
terraform planで最終確認を行った後、terraform applyでプロビジョニングを実行します。Cleanup Agent(削除エージェント)
検証が完了したインフラリソースをすべて削除し、不要なコストが発生しないようにします。
terraform destroyコマンドを実行して、プロビジョニングされたリソースをクリーンアップします。
上記のエージェントを使用し、全体のワークフローは以下のようになります。
要件定義
ユーザーが①User Facing Agentに要件を入力し、対話を通じて要件が具体化されます。設計
②Architect Agentが要件をもとにインフラを設計します。コード生成
③Terraform Coder Agentが設計案からTerraformコードを生成します。テスト
④Test Agentがterraform validateを実行し、生成されたコードの構文にエラーがないかを確認します。
構文エラーがあれば、修正のために③のステップに差し戻します。デプロイ
テストを通過したコードを⑤Deployment Agentが受け取ります。
terraform planで実行計画をユーザーに提示し、承認を得てからterraform applyでインフラを構築します。クリーンアップ
今回は検証用のため、デプロイ後に⑥Cleanup Agentがterraform destroyを実行し、作成されたリソースをすべて削除します。
実装
作業に入る前に作業ディレクトリを作成して移動します。
作業ディレクトリ名は任意のもので問題ありません。
mkdir <任意のディレクトリ名> cd <任意のディレクトリ名>
作成したディレクトリ内にrun_agents.pyというファイルを作成します。
任意のテキストエディターを開き、以下を貼り付けてください。
run_agents.py
import os import subprocess import json import uuid from strands import Agent from strands.models.openai import OpenAIModel # --- 準備 --- TERRAFORM_PROJECT_PATH = "./terraform_project" MAX_RETRIES = 3 # --- Strands Agentの初期化 --- try: openai_model = OpenAIModel(model_id="gpt-4o") strands_agent = Agent(model=openai_model) print("Strands Agent with OpenAI model initialized successfully.") except Exception as e: print(f"Strands Agentの初期化に失敗しました: {e}") exit() # --- Agentの基盤となるクラス --- class BaseAgent: def __init__(self, name: str, role: str): self.name = name self.role = role def _header(self): print("\n" + "="*50) print(f"Agent: {self.name}") print(f"Role: {self.role}") print("="*50) def run(self, *args, **kwargs): raise NotImplementedError # --- 各エージェントの実装 --- class UserFacingAgent(BaseAgent): def __init__(self, main_agent: Agent): super().__init__("User Facing Agent", "対話を通じて、ユーザーの曖昧な要求を明確なインフラ要件に定義します。") self.agent = main_agent def run(self) -> str: self._header() conversation_str = "" initial_request = input("どのようなインフラを構築したいですか?\n> ") conversation_str += f"ユーザー: {initial_request}\n" system_prompt = """ あなたは、非常に優秀なITコンサルタントです。ユーザーとの対話を通じて、曖昧な要求を明確なインフラ要件に落とし込みます。 # 「情報十分」と判断するための最低条件 - 目的 - 特別な要件 # 対話の進め方 1. 対話履歴を分析し、上記の最低条件を満たしているか確認します。 2. 不足している項目があれば、それを明らかにするための簡潔な質問文のみを生成します。 3. すべての条件が満たされたと確信できた場合にのみ、「COMPLETE」という単語を応答に含め、その後に箇条書きで最終的な要件の概要を記述してください。 """ while True: print("\nAIが次の質問を考えています...") try: prompt_for_llm = f"{system_prompt}\n\n# 対話履歴\n{conversation_str}" response_obj = self.agent(prompt_for_llm) response_str = str(response_obj) if "COMPLETE" in response_str: summary_part = response_str.split("COMPLETE", 1)[1] final_summary = summary_part.strip() print(f"\n要件が確定しました。") return final_summary else: question = response_str user_answer = input("> ") conversation_str += f"AI: {question}\nユーザー: {user_answer}\n" except Exception as e: print(f"対話処理中にエラーが発生しました: {e}") return initial_request class ArchitectAgent(BaseAgent): def __init__(self, main_agent: Agent): super().__init__("Architect Agent", "要件に最適なAWSインフラ構成を設計、またはエラーに基づき再設計します。") self.agent = main_agent def run(self, requirement: str, feedback_error: str = None) -> dict: self._header() if feedback_error: print("前回の設計で実行時エラーが確認されました。エラーを基に設計を根本から見直します...") else: print(f"受け取った要件を分析し、インフラを設計します...") prompt_for_llm = f""" あなたは非常に優秀なAWSのソリューションアーキテクトです。 ユーザーの要求を分析し、それを実現するための最適なAWSインフラ構成をJSON形式で提案してください。 出力は、説明文などを一切含めず、必ず```json ... ```で囲んだコードブロックとしてJSONオブジェクトのみを返してください。 # 設計の基本原則 1. ユーザーが事前に何か特別なリソースを持っていることを前提としないでください。 2. ユーザーがドメイン名を明示しない限り、Route53リソースや、それに依存する設定を設計に含めないでください。 3. グローバルでユニークな名前を要求するリソースには、競合を避けるため `{{random_suffix}}` プレースホルダーを設計に含めてください。 4. AWSのデフォルト設定はセキュリティを最優先にしています。ユーザーの要求を実現するために、これらのデフォルト設定を変更する必要がある場合は、その変更も設計に含めてください。 # ユーザー要求 {requirement} """ if feedback_error: print(f"再設計の根拠となるエラー:\n---\n{feedback_error}\n---") prompt_for_llm += f"\n\n# 重要\n以前提案した設計は`terraform apply`で上記のエラーを出しました。設計そのものに問題がある可能性が高いです。エラーと設計原則を基に、問題が発生しない代替の設計案を再提案してください。" try: response_obj = self.agent(prompt_for_llm) response_str = str(response_obj) json_part = response_str.split("```json")[1].split("```")[0] design = json.loads(json_part) main_service = "N/A" if isinstance(design, dict): if "main_service" in design: main_service = design["main_service"] elif "Resources" in design and isinstance(design["Resources"], dict): main_service = next(iter(design["Resources"].values()), {}).get("Type", "N/A") print(f"LLMによるインフラ設計が完了しました。(主要サービス: {main_service})") return design except Exception as e: print(f"インフラ設計に失敗しました: {e}") raise class TerraformCoderAgent(BaseAgent): def __init__(self, main_agent: Agent): super().__init__("Terraform Coder Agent", "インフラ設計からTerraform HCLコードを生成、または構文エラーを修正します。") self.agent = main_agent def run(self, design: dict, project_path: str, feedback_error: str = None) -> str: self._header() if feedback_error: print("前回のコードで構文エラーが確認されました。エラーを基にコードを修正します...") else: print("受け取った設計を基に、Terraformコードを生成します...") design_str = json.dumps(design) if "{random_suffix}" in design_str: random_suffix = uuid.uuid4().hex[:8] print(f"ユニークな接尾辞 `{random_suffix}` を生成し、設計に適用しました。") design_str = design_str.replace("{random_suffix}", random_suffix) design_json_string = json.dumps(json.loads(design_str), indent=2, ensure_ascii=False) prompt_for_llm = f""" あなたはTerraformの専門家です。与えられたJSON形式のAWSインフラ設計書に基づき、ベストプラクティスに従った正確なTerraformのHCLコードを生成してください。 # 実装の基本原則 1. `provider "aws"` ブロックを必ずコードの先頭に含め、リージョンが設計書の`region`キーと一致するように `region` を明示的に設定してください。 2. 非推奨(deprecated)の引数やブロックではなく、常に最新の推奨リソースや記述方法を使用してください。 3. 本来は独立したリソースとして定義されるべき設定項目を、別のリソースの内部にブロックとして記述しないでください。 4. 全てのリソースにおいて、必須(Required)とされている引数やブロックは必ず含めてください。 5. 設計書に`outputs`セクションが存在する場合、**必ず**対応する`output`ブロックをコードの末尾に生成してください。ユーザーが最終的にアクセスするURLなどを出力することが重要です。 # 設計書 {design_json_string} # ルール - 出力はTerraformコードのみとし、説明文や```hcl ```のような余計な文字列は一切含めないでください。 """ if feedback_error: print(f"修正対象の構文エラー:\n---\n{feedback_error}\n---") prompt_for_llm += f"\n\n# 重要: 前回の試行で構文エラーが発生しました\n以前生成したコードは`terraform validate`で上記のエラーを出しました。エラーを解決する修正版コードを生成してください。" try: hcl_code_obj = self.agent(prompt_for_llm) hcl_code = str(hcl_code_obj).strip("```hcl").strip("```").strip() os.makedirs(project_path, exist_ok=True) with open(os.path.join(project_path, "main.tf"), "w") as f: f.write(hcl_code) print(f"Terraformコードを '{project_path}/main.tf' に生成/修正しました。") return project_path except Exception as e: print(f"コード生成に失敗しました: {e}") raise class TestAgent(BaseAgent): def __init__(self): super().__init__("Test Agent", "Terraformコードの構文を検証します。") def run(self, project_path: str) -> str | None: self._header() try: subprocess.run(["terraform", "init", "-upgrade"], cwd=project_path, check=True, capture_output=True, text=True) subprocess.run(["terraform", "validate"], cwd=project_path, check=True, capture_output=True, text=True) print("テスト成功: 構文は正常です。") return None except subprocess.CalledProcessError as e: print(f"テスト失敗: Terraformコードに構文エラーが見つかりました。") return e.stderr class DeploymentAgent(BaseAgent): def __init__(self): super().__init__("Deployment Agent", "インフラをAWS上に構築し、問題があればエラーを報告します。") def run(self, project_path: str) -> str | None: self._header() print("実行計画を作成しています...") try: plan_result = subprocess.run(["terraform", "plan", "-out=tfplan"], cwd=project_path, check=True, capture_output=True, text=True) print(plan_result.stdout) except subprocess.CalledProcessError as e: print("Terraform Planでエラーが発生しました。") print(e.stderr) return e.stderr if input("上記の計画でインフラを構築しますか? (yes/no): ").lower() != 'yes': raise SystemExit("User cancelled deployment.") print("デプロイを開始します...完了まで数分かかることがあります。") try: apply_result = subprocess.run(["terraform", "apply", "-auto-approve", "tfplan"], cwd=project_path, check=True, capture_output=True, text=True) print("\nデプロイが完了しました。") outputs_section = "" if "Outputs:" in apply_result.stdout: outputs_section = apply_result.stdout[apply_result.stdout.find("Outputs:"):] print("---------- Outputs ----------") print(outputs_section.strip()) print("-----------------------------") return None except subprocess.CalledProcessError as e: print(f"デプロイ失敗: Terraform実行時にエラーが発生しました。") return e.stderr class CleanupAgent(BaseAgent): def __init__(self): super().__init__("Cleanup Agent", "インフラをすべて削除します。") def run(self, project_path: str): self._header() if input("構築したすべてのリソースを削除しますか? (yes/no): ").lower() == 'yes': print("クリーンアップを開始します...完了まで数分かかることがあります。") try: subprocess.run(["terraform", "destroy", "-auto-approve"], cwd=project_path, check=True, capture_output=True, text=True) print("クリーンアップが完了しました。") except subprocess.CalledProcessError as e: print(f"クリーンアップ失敗: {e.stderr}") raise def main_workflow(): print("AIエージェントによるAWSインフラ自動構築プロセスを開始します。") try: agents = {"user": UserFacingAgent(main_agent=strands_agent), "architect": ArchitectAgent(main_agent=strands_agent), "coder": TerraformCoderAgent(main_agent=strands_agent), "test": TestAgent(), "deploy": DeploymentAgent(), "cleanup": CleanupAgent()} requirement = agents["user"].run() design = agents["architect"].run(requirement) last_error, error_origin, project_path = None, None, None for attempt in range(MAX_RETRIES): print(f"\n--- [試行 {attempt + 1}/{MAX_RETRIES}] ---") if error_origin == 'design': design = agents["architect"].run(requirement, feedback_error=last_error) project_path = agents["coder"].run(design, TERRAFORM_PROJECT_PATH, feedback_error=None) elif error_origin == 'code': project_path = agents["coder"].run(design, TERRAFORM_PROJECT_PATH, feedback_error=last_error) else: project_path = agents["coder"].run(design, TERRAFORM_PROJECT_PATH) last_error = agents["test"].run(project_path) if last_error: error_origin = 'code' continue last_error = agents["deploy"].run(project_path) if last_error: error_origin = 'design' continue last_error = None break if last_error is not None: raise Exception("Terraformコードの自動修正・デプロイに失敗しました。") input("\nインフラの構築が完了しました。何かキーを押すとリソースの削除に進みます...") agents["cleanup"].run(project_path) print("\nすべてのプロセスが正常に完了しました。") except Exception as e: print(f"\nプロセスでエラーが発生しました: {e}") except SystemExit as e: print(f"\nプロセスがユーザーによって中断されました: {e}") if __name__ == "__main__": main_workflow()
ファイルが作成できたら仮想環境内でrun_agents.pyを実行します。
python run_agents.py
実行が開始されると、ターミナルにはAIの思考プロセス、ツールの呼び出し、Terraformの実行結果、AWS APIからの応答などが表示されます。
AIはこれらの情報をもとに次の行動を決定し、最終的にインフラの構築、テスト、削除を試みます。
結果確認
作成したrun_agents.pyを実行すると、ターミナル上でエージェントが思考し、行動する様子がリアルタイムで表示されます。
※表示崩れ防止のため、ログの一部を変更しています。
実際のログ(実行結果の例)
Strands Agent with OpenAI model initialized successfully.
AIエージェントによるAWSインフラ自動構築プロセスを開始します。
==================================================
Agent: User Facing Agent
Role: 対話を通じて、ユーザーの曖昧な要求を明確なインフラ要件に定義します。
==================================================
どのようなインフラを構築したいですか?
> 静的なwebサイトを構築したい
AIが次の質問を考えています...
- サイト構築の目的を教えてください。
- 特別な要件や具体的な機能はありますか?> 個人的なホームページを公開したいです。シンプルで安価なものが良いです。
AIが次の質問を考えています...
COMPLETE
- 目的: 個人的なホームページの公開
- 特別な要件: シンプルで安価な構成
要件が確定しました。
==================================================
Agent: Architect Agent
Role: 要件に最適なAWSインフラ構成を設計、またはエラーに基づき再設計します。
==================================================
受け取った要件を分析し、インフラを設計します...
{
"Resources": {
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "personal-homepage-{random_suffix}",
"WebsiteConfiguration": {
"IndexDocument": "index.html",
"ErrorDocument": "error.html"
}
}
},
"BucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"Properties": {
"Bucket": {
"Ref": "S3Bucket"
},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "S3Bucket"
},
"/*"
]
]
}
}
]
}
}
}
}
}
LLMによるインフラ設計が完了しました。(主要サービス: AWS::S3::Bucket)
--- [試行 1/3] ---
==================================================
Agent: Terraform Coder Agent
Role: インフラ設計からTerraform HCLコードを生成、または構文エラーを修正します。
==================================================
受け取った設計を基に、Terraformコードを生成します...
ユニークな接尾辞 `ac3a1b32` を生成し、設計に適用しました。
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "personal_homepage" {
bucket = "personal-homepage-ac3a1b32"
website {
index_document = "index.html"
error_document = "error.html"
}
}
resource "aws_s3_bucket_policy" "personal_homepage_policy" {
bucket = aws_s3_bucket.personal_homepage.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::personal-homepage-ac3a1b32/*"
}
]
})
}
output "website_url" {
value = aws_s3_bucket.personal_homepage.website_endpoint
}Terraformコードを './terraform_project/main.tf' に生成/修正しました。
==================================================
Agent: Test Agent
Role: Terraformコードの構文を検証します。
==================================================
テスト成功: 構文は正常です。
==================================================
Agent: Deployment Agent
Role: インフラをAWS上に構築し、問題があればエラーを報告します。
==================================================
実行計画を作成しています...
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.personal_homepage will be created
+ resource "aws_s3_bucket" "personal_homepage" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "personal-homepage-ac3a1b32"
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_region = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = "us-west-2"
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
+ website {
+ error_document = "error.html"
+ index_document = "index.html"
}
}
# aws_s3_bucket_policy.personal_homepage_policy will be created
+ resource "aws_s3_bucket_policy" "personal_homepage_policy" {
+ bucket = (known after apply)
+ id = (known after apply)
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "s3:GetObject"
+ Effect = "Allow"
+ Principal = "*"
+ Resource = "arn:aws:s3:::personal-homepage-ac3a1b32/*"
},
]
+ Version = "2012-10-17"
}
)
+ region = "us-west-2"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
~ website_url = "static-website-ba9d4e74.s3-website-us-east-1.amazonaws.com" -> (known after apply)
╷
│ Warning: Argument is deprecated
│
│ with aws_s3_bucket.personal_homepage,
│ on main.tf line 5, in resource "aws_s3_bucket" "personal_homepage":
│ 5: resource "aws_s3_bucket" "personal_homepage" {
│
│ website is deprecated. Use the aws_s3_bucket_website_configuration resource
│ instead.
│
│ (and one more similar warning elsewhere)
╵
╷
│ Warning: Deprecated attribute
│
│ on main.tf line 31, in output "website_url":
│ 31: value = aws_s3_bucket.personal_homepage.website_endpoint
│
│ The attribute "website_endpoint" is deprecated. Refer to the provider
│ documentation for details.
│
│ (and one more similar warning elsewhere)
╵
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
上記の計画でインフラを構築しますか? (yes/no): yes
デプロイを開始します...完了まで数分かかることがあります。
デプロイ失敗: Terraform実行時にエラーが発生しました。
--- [試行 2/3] ---
==================================================
Agent: Architect Agent
Role: 要件に最適なAWSインフラ構成を設計、またはエラーに基づき再設計します。
==================================================
前回の設計で実行時エラーが確認されました。エラーを基に設計を根本から見直します...
再設計の根拠となるエラー:
---
╷
│ Error: putting S3 Bucket (personal-homepage-ac3a1b32) Policy: operation error S3: PutBucketPolicy, https response error StatusCode: 403, RequestID: 587NHJYN0V6AV99B, HostID: Pn1CgSEoTfDSWQY5TrVPHLh70gYPYZMV4gkqqkQTsPyzaKsJa+H1973b2W2SJI7H/0A7JmiHbDeeA4bxR7Uf6w==, api error AccessDenied: User: arn:aws:iam::941891660189:user/StrandsAgents is not authorized to perform: s3:PutBucketPolicy on resource: "arn:aws:s3:::personal-homepage-ac3a1b32" because public policies are blocked by the BlockPublicPolicy block public access setting.
│
│ with aws_s3_bucket_policy.personal_homepage_policy,
│ on main.tf line 14, in resource "aws_s3_bucket_policy" "personal_homepage_policy":
│ 14: resource "aws_s3_bucket_policy" "personal_homepage_policy" {
│
╵
---
{
"Resources": {
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "personal-homepage-{random_suffix}",
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": false,
"IgnorePublicAcls": false,
"BlockPublicPolicy": false,
"RestrictPublicBuckets": false
},
"WebsiteConfiguration": {
"IndexDocument": "index.html",
"ErrorDocument": "error.html"
}
}
},
"BucketPolicy": {
"Type": "AWS::S3::BucketPolicy",
"Properties": {
"Bucket": {
"Ref": "S3Bucket"
},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": {
"Fn::Join": [
"",
[
"arn:aws:s3:::",
{
"Ref": "S3Bucket"
},
"/*"
]
]
}
}
]
}
}
}
}
}
LLMによるインフラ設計が完了しました。(主要サービス: AWS::S3::Bucket)
==================================================
Agent: Terraform Coder Agent
Role: インフラ設計からTerraform HCLコードを生成、または構文エラーを修正します。
==================================================
受け取った設計を基に、Terraformコードを生成します...
ユニークな接尾辞 `0e3390a8` を生成し、設計に適用しました。
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "personal_homepage" {
bucket = "personal-homepage-0e3390a8"
website {
index_document = "index.html"
error_document = "error.html"
}
}
resource "aws_s3_bucket_public_access_block" "public_access" {
bucket = aws_s3_bucket.personal_homepage.id
block_public_acls = false
ignore_public_acls = false
block_public_policy = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_policy" "personal_homepage_policy" {
bucket = aws_s3_bucket.personal_homepage.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::personal-homepage-0e3390a8/*"
}
]
})
}
output "website_url" {
value = aws_s3_bucket.personal_homepage.website_endpoint
}Terraformコードを './terraform_project/main.tf' に生成/修正しました。
==================================================
Agent: Test Agent
Role: Terraformコードの構文を検証します。
==================================================
テスト成功: 構文は正常です。
==================================================
Agent: Deployment Agent
Role: インフラをAWS上に構築し、問題があればエラーを報告します。
==================================================
実行計画を作成しています...
aws_s3_bucket.personal_homepage: Refreshing state... [id=personal-homepage-ac3a1b32]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_s3_bucket.personal_homepage must be replaced
-/+ resource "aws_s3_bucket" "personal_homepage" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
~ arn = "arn:aws:s3:::personal-homepage-ac3a1b32" -> (known after apply)
~ bucket = "personal-homepage-ac3a1b32" -> "personal-homepage-0e3390a8" # forces replacement
~ bucket_domain_name = "personal-homepage-ac3a1b32.s3.amazonaws.com" -> (known after apply)
+ bucket_prefix = (known after apply)
~ bucket_region = "us-west-2" -> (known after apply)
~ bucket_regional_domain_name = "personal-homepage-ac3a1b32.s3.us-west-2.amazonaws.com" -> (known after apply)
~ hosted_zone_id = "Z3BJ6K6RIION7M" -> (known after apply)
~ id = "personal-homepage-ac3a1b32" -> (known after apply)
~ object_lock_enabled = false -> (known after apply)
+ policy = (known after apply)
~ request_payer = "BucketOwner" -> (known after apply)
- tags = {} -> null
~ tags_all = {} -> (known after apply)
~ website_domain = "s3-website-us-west-2.amazonaws.com" -> (known after apply)
~ website_endpoint = "personal-homepage-ac3a1b32.s3-website-us-west-2.amazonaws.com" -> (known after apply)
# (2 unchanged attributes hidden)
- grant {
- id = "08a146eebbd3aa2a6f6ab4503dbe9f699e3cba6c4cb6c4d62d2b3d21a2a9624c" -> null
- permissions = [
- "FULL_CONTROL",
] -> null
- type = "CanonicalUser" -> null
}
- server_side_encryption_configuration {
- rule {
- bucket_key_enabled = false -> null
- apply_server_side_encryption_by_default {
- sse_algorithm = "AES256" -> null
}
}
}
- versioning {
- enabled = false -> null
- mfa_delete = false -> null
}
# (1 unchanged block hidden)
}
# aws_s3_bucket_policy.personal_homepage_policy will be created
+ resource "aws_s3_bucket_policy" "personal_homepage_policy" {
+ bucket = (known after apply)
+ id = (known after apply)
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "s3:GetObject"
+ Effect = "Allow"
+ Principal = "*"
+ Resource = "arn:aws:s3:::personal-homepage-0e3390a8/*"
},
]
+ Version = "2012-10-17"
}
)
+ region = "us-west-2"
}
# aws_s3_bucket_public_access_block.public_access will be created
+ resource "aws_s3_bucket_public_access_block" "public_access" {
+ block_public_acls = false
+ block_public_policy = false
+ bucket = (known after apply)
+ id = (known after apply)
+ ignore_public_acls = false
+ region = "us-west-2"
+ restrict_public_buckets = false
}
Plan: 3 to add, 0 to change, 1 to destroy.
Changes to Outputs:
~ website_url = "personal-homepage-ac3a1b32.s3-website-us-west-2.amazonaws.com" -> (known after apply)
╷
│ Warning: Argument is deprecated
│
│ with aws_s3_bucket.personal_homepage,
│ on main.tf line 5, in resource "aws_s3_bucket" "personal_homepage":
│ 5: resource "aws_s3_bucket" "personal_homepage" {
│
│ website is deprecated. Use the aws_s3_bucket_website_configuration resource
│ instead.
│
│ (and one more similar warning elsewhere)
╵
╷
│ Warning: Deprecated attribute
│
│ on main.tf line 40, in output "website_url":
│ 40: value = aws_s3_bucket.personal_homepage.website_endpoint
│
│ The attribute "website_endpoint" is deprecated. Refer to the provider
│ documentation for details.
│
│ (and one more similar warning elsewhere)
╵
─────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
上記の計画でインフラを構築しますか? (yes/no): yes
デプロイを開始します...完了まで数分かかることがあります。
デプロイが完了しました。
---------- Outputs ----------
Outputs:
website_url = "personal-homepage-0e3390a8.s3-website-us-west-2.amazonaws.com"
-----------------------------
インフラの構築が完了しました。何かキーを押すとリソースの削除に進みます...
==================================================
Agent: Cleanup Agent
Role: インフラをすべて削除します。
==================================================
構築したすべてのリソースを削除しますか? (yes/no): yes
クリーンアップを開始します...完了まで数分かかることがあります。
クリーンアップが完了しました。
すべてのプロセスが正常に完了しました。
非常に長いので要点をまとめます。
要件定義 : UserFacingAgent
UserFacingAgentエージェントは「静的なwebサイトを構築したい」という抽象的な要求を受け取りました。
これだけでは情報が不十分だと判断し、詳細についてヒアリングしたうえで「シンプルで安価な、個人の小規模ホームページ」という具体的な要件を定義しました。設計 : ArchitectAgent
UserFacingAgentが定義した最終要件をもとにインフラを設計しています。
「個人的なホームページの公開」「シンプルで安価な構成」という要件から、シンプルでコスト効率の良いS3単体のアーキテクチャを選択しました。
S3バケットとS3バケットポリシーを含む設計をJSON形式で出力しました。構築 : TerraformCoderAgent
設計にもとづき、Terraformコードを生成しました。テスト : TestAgent
生成されたコードに構文エラーがなかったため、terraform validateは成功しました。デプロイ(失敗): DeploymentAgent
terraform applyを実行したところ、AccessDeniedエラーが発生しデプロイに失敗しました。
S3バケットのパブリックアクセスをブロックしつつパブリックなバケットポリシーを適用しようとしており、設定が矛盾していたことが原因でした。
これは構文エラーではなく設定エラーのため、TestAgentによるterraform validateが通過していました。
terraform applyで発生したエラーを「設計の問題」であると正しく判断し、エラー内容をArchitectAgentにフィードバックしました。自己修復・設計の見直し : ArchitectAgent
AccessDeniedエラーを受け、設計を根本から見直しています。
最初の設計案を破棄し、「PublicAccessBlockConfigurationを明示的に含めパブリックアクセスを許可する」という、新しい設計を再提案しました。
これはAIがエラーの原因を自己分析し、解決のためのアーキテクチャ変更を行ったことを意味します。再構築 : TerraformCoderAgent
新しい設定にもとづき、Terraformコードを再生成しました。テスト : TestAgent
生成されたコードに構文エラーがなかったため、terraform validateは成功しました。再デプロイ(成功): DeploymentAgent
Terraformが1回目の失敗で中途半端に作られたリソースを検知し、それを破棄して新しい設計で作り直しました。
出力(Outputs)として構築されたWebサイトのURLが表示されています。クリーンアップ : CleanupAgent
ユーザーへの確認後、terraform destroyを実行し、2回目の試行で作成されたすべてのAWSリソースを削除しました。
ログを見ると、要件定義から削除まで完遂できたようです。
デプロイ途中でエラーが出ていますが、AI自身が原因を特定して、問題のあった工程からやり直し、エラーをパスしています。
修正方法・内容が本当に正しいかどうかはともかく、AI自身が自律的に問題を解決できたということ自体にかなりインパクトがありました。
Strands Agentsを使ったAI駆動開発について
今回の結果は、単にChatGPTに「Terraformコードを教えて」と聞くのとは根本的に異なるかと思います。
もっとも大きな違いは、AIが自律的に行動し、フィードバックループを閉じている点です。
AIはコードを生成するだけでなく、そのコードを「ツール」を使って実行し、インフラを構築するという目的を完遂しました。
Terraformの実行でエラーが起きれば、そのエラー内容を読み取り、コードを修正して再実行する、といった自己修正のループに発展させることもできました。
これは、エンジニアの役割が「作業者」から「設計者であり監督者」へ本格的にシフトしていく未来を示唆しているかと思います。
「どう作るか(How)」の細部をマイクロマネジメントするのではなく、「何を作りたいか(What)」という目的とゴールをAIに的確に伝える能力、いわば「AIアーキテクト」としての視点がより一層求められるようになりそうです。
そして、こうした自律的なエージェントの進化の鍵となるのが、MCP (Model-Context-Protocol)やA2A(Agent-to-Agent)といった、エージェント間の相互運用性を高めるための標準プロトコルへの対応です。
将来的には、異なる目的や機能を持つエージェント同士が標準化された言語で連携し、より複雑で大規模なタスクを協調して解決する、といった未来もありえるかもしれません。
まとめ
本記事では、Strands AgentsとOpenAI APIを組み合わせ、自然言語の指示だけでAWSインフラを自律的に設計・構築させるプロセスを実証しました。
Strands AgentsはAIが自律的にタスクを実行する単なる生産性向上ツールではなく、プロジェクト全体を俯瞰して自ら課題解決を行うパートナーとして、システム開発の生産性を根底から変える可能性を秘めています。
今回紹介したコードはあくまで一例です。ぜひご自身でAIエージェントを作成してみてください。