[Amazon Bedrock]  AgentCoreをVPCにデプロイして自然言語でAuroraに問い合わせてみました

[Amazon Bedrock] AgentCoreをVPCにデプロイして自然言語でAuroraに問い合わせてみました

2026.04.14

1 はじめに

製造ビジネステクノロジー部の平内(SIN)です。

「先月の売上トップ5は?」——こんな質問を投げるだけで、AI エージェントが SQL を生成し、データベースに問い合わせて、結果を日本語で返してくれます。

$ agentcore invoke '{"prompt": "売上トップ5の商品は?"}'

売上トップ5の商品をお調べしました!

1. スマートウォッチ(電子機器)— 売上合計:1,339,200円
2. カシミヤセーター(衣類)— 売上合計:790,000円
3. ダウンベスト(衣類)— 売上合計:752,000円
4. 黒毛和牛すき焼きセット(食品)— 売上合計:704,000円
5. リネンシャツ(衣類)— 売上合計:470,820円
$ agentcore invoke '{"prompt": "先月の食品の売上状況をまとめて"}'

## 先月(2026年3月)の食品売上状況まとめ

### 📊 全体概況
- **総売上金額**: 426,420円
- **総販売数量**: 84個
- **販売商品数**: 6商品
- **注文件数**: 23件
- **平均単価**: 5,011円

### 🏆 商品別売上ランキング

1. **黒毛和牛すき焼きセット** - 192,000円
   - 販売数量: 15個
   - 注文件数: 5件
   - 平均単価: 12,800円

2. **魚沼産コシヒカリ 5kg** - 75,620円
   - 販売数量: 19個
   - 注文件数: 6件
   - 平均単価: 3,980円

3. **有機抹茶パウダー** - 66,960円
   - 販売数量: 27個
   - 注文件数: 8件
   - 平均単価: 2,480円

### 💡 特徴
- 高単価商品の「黒毛和牛すき焼きセット」が売上No.1
- 「有機抹茶パウダー」は最も多くの数量が販売された人気商品
- 全商品が売上を記録しており、食品カテゴリ全体で好調な売上

本記事では、AgentCore を VPC に配置して、同じ VPC 内の Aurora Serverless v2 に自然言語で問い合わせる Text-to-SQL エージェントを作ってみました。

ソースコードは GitHub で公開しています。
https://github.com/furuya02/agentcore-vpc-text-to-sql

2 AgentCore から Aurora へのアクセス

AgentCoreをデプロイするモードによって、Auroraへのアクセス方法が変わります。

PUBLIC VPC
Aurora へのアクセス Data API(AWS API 経由) VPC 内直接通信
DB 接続方式 boto3 rds-data クライアント PostgreSQL ドライバ(psycopg2)
セキュリティ IAM ポリシーで制御 SG でネットワークレベル制御
レイテンシ 数十〜数百ミリ秒 数ミリ秒
結果サイズ 1 MB 制限 制限なし
構築の手軽さ 簡単(VPC 設定不要) VPC / VPC Endpoint の設定が必要

検証やプロトタイプなら PUBLIC + Data API の方が手軽そうですが、本番運用では、以下のような理由から VPC + 直接接続がオススメかも知れないということで、今回は、こちらを採用してみました。

  • セキュリティ: VPC内に閉じ、Security Group でアクセス元を限定できる
  • Data API の制限: 大量データの集計やバッチ処理では 1MB 制限が問題になるかも知れません
  • アーキテクチャの一貫性: 通常のアプリからのアクセスと統一できる(Private Subnetからのみの接続)

3 構成

AgentCore 及び、Aurora Serverless v2は、VPC(Private Subnet)に配置されており、必要なサービスへは、VPC Endpoint経由で接続されています。

Aurora Serverless v2 は、最小ACU が0となっていて、アクセスがなければ利用費ゼロですが、最初のクエリにはコールドスタート(数十秒)が発生します。
なお、未使用でも、VPC Endpointの利用費は発生するのでご注意ください。

001

4 インフラ構築

(1) CDK デプロイ

VPC、Aurora、VPC Endpoint などのインフラは CDK(TypeScript)で構築しています。

git clone https://github.com/furuya02/agentcore-vpc-text-to-sql.git
cd agentcore-vpc-text-to-sql
cd cdk
npm install
npx cdk deploy

デプロイ完了時に表示される、CDK Outputs の SubnetIdsAgentCoreSecurityGroupIdSecretArn は後のステップで利用するのでコピーしておいてください。

Outputs:
text-to-sql-stack.SubnetIds = subnet-xxx,subnet-yyy
text-to-sql-stack.AgentCoreSecurityGroupId = sg-zzz
text-to-sql-stack.SecretArn = arn:aws:secretsmanager:ap-northeast-1:...
text-to-sql-stack.ClusterArn = arn:aws:cloudformation:...

CDK の主なリソースは以下のとおりです。

リソース 設定
VPC Private Isolated Subnet 、NAT Gateway なし
VPC Endpoint bedrock-runtime, logs, secretsmanager の 3 つ
Aurora Serverless v2 PostgreSQL 16.4、最小 ACU: 0、最大 ACU: 1
Security Group AgentCore SG → Aurora SG の TCP 5432 のみ許可

(2) サンプルデータ

Aurora の Data API を使って、サンプルデータを投入します。EC サイトの注文データを想定した 4 テーブル構成です。

cd cdk
chmod +x scripts/seed-data.sh
./scripts/seed-data.sh
テーブル 件数 内容
customers 50 顧客(氏名、メール、都道府県)
products 30 商品(5 カテゴリ x 6 商品)
orders 200 注文(過去 90 日分)
order_items 500 注文明細(各注文に平均 2.5 明細)

5 エージェントコードの実装

(1) 全体構成

エージェントのエントリポイントは agent/texttosql/app/texttosql/main.py です。

処理の流れは以下です。

  1. ユーザーのプロンプトを Bedrock に送信(converse API)
  2. Claude が tool_uselist_tables を要求 → テーブル構造を取得して結果を返送
  3. Claude が SQL を生成し tool_useexecute_query を要求 → SQL を実行して結果を返送
  4. Claude が最終応答テキストを返却(stop_reason: end_turn

(2) DB 認証情報の取得

DB の認証情報は Secrets Manager から取得しています。CDK が Aurora クラスター作成時に自動生成したシークレットを、環境変数 DB_SECRET_ARN で参照しています。

_db = None

def db_config():
    global _db
    if _db: return _db
    s = json.loads(boto3.client("secretsmanager", region_name="ap-northeast-1")
                   .get_secret_value(SecretId=os.environ["DB_SECRET_ARN"])["SecretString"])
    _db = dict(host=s["host"], port=int(s.get("port", 5432)),
               dbname=os.environ.get("DB_NAME", "ecommerce"),
               user=s["username"], password=s["password"])
    return _db

(3) ツールの実装

2 つのツールを実装しています。

list_tablesinformation_schema からテーブル構造を取得します。execute_query は SELECT 文のみ実行を許可し、SQL インジェクションのリスクを軽減しています。

def list_tables(_):
    conn = psycopg2.connect(**db_config())
    cur = conn.cursor()
    cur.execute("SELECT table_name, column_name, data_type FROM information_schema.columns "
                "WHERE table_schema='public' ORDER BY table_name, ordinal_position")
    # ... 省略(テーブル名: カラム名 (型) の形式で整形)

def execute_query(inp):
    sql = inp.get("sql", "")
    if not sql.strip().upper().startswith("SELECT"):
        return "エラー: SELECT文のみ実行可能です"
    # ... 省略(psycopg2 で SQL を実行し、結果をテキストで返却)

(4) Tool Loop の実装

boto3 の converse API を使って、ツール呼び出しのループを実装しています。stop_reasontool_use の間はツールを実行して結果を返し、end_turn で最終応答を返却します。

def run(client, prompt):
    msgs = [{"role": "user", "content": [{"text": prompt}]}]
    for _ in range(10):
        r = client.converse(modelId=MODEL_ID, system=[{"text": SYSTEM}],
                            messages=msgs, inferenceConfig={"maxTokens": 2000}, toolConfig=TOOL_CONFIG)
        c = r["output"]["message"]["content"]
        msgs.append({"role": "assistant", "content": c})
        if r["stopReason"] == "end_turn":
            return next((b["text"] for b in c if "text" in b), "")
        msgs.append({"role": "user", "content": [
            {"toolResult": {"toolUseId": b["toolUse"]["toolUseId"],
                            "content": [{"text": TOOLS[b["toolUse"]["name"]](b["toolUse"].get("input", {}))}]}}
            for b in c if "toolUse" in b]})
    return ""

詳細なコードは GitHub リポジトリの main.py をご覧ください。

(5) Inference Profile ID について

ap-northeast-1 では、一部のモデルでオンデマンド呼び出しが使えないため、APAC Inference Profile ID を使用しています。

MODEL_ID = "apac.anthropic.claude-sonnet-4-20250514-v1:0"

6 AgentCore デプロイ

(1) プロジェクトの作成

agentcore CLI(v0.8)でプロジェクトを作成します。これにより agentcore.json や CDK 定義などの雛形が生成されます。

cd agent
agentcore create \
  --name texttosql \
  --framework Strands \
  --model-provider Bedrock \
  --memory none \
  --network-mode VPC \
  --subnets "(SubnetIds の値1),(SubnetIds の値2)" \
  --security-groups "(AgentCoreSecurityGroupId の値)" \
  --skip-python-setup

生成された main.py を、前章で実装したエージェントコードに差し替えます。

(2) 環境変数の設定

agent/texttosql/agentcore/agentcore.jsonenvVars に環境変数を追加します。

"envVars": [
  { "name": "DB_SECRET_ARN", "value": "(SecretArn の値)" },
  { "name": "DB_NAME", "value": "ecommerce" }
]

(3) デプロイ

cd agent/texttosql
agentcore deploy

(4) IAM 権限の追加

agentcore deploy で自動作成される実行ロールには、Bedrock の呼び出し権限は含まれていますが、Secrets Manager へのアクセス権限は含まれていません。エージェントコードから Secrets Manager を呼び出すため、手動で権限を追加します。

aws iam put-role-policy \
  --role-name "(実行ロール名)" \
  --policy-name "SecretsManagerReadAccess" \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "(SecretArn)*"
    }]
  }'

7 動作確認

(1) テーブル一覧の取得

$ agentcore invoke '{"prompt": "テーブル一覧を教えて"}'

ECサイトのデータベースには以下の4つのテーブルがあります:

1. customers(顧客テーブル)
   - customer_id:顧客ID(整数)
   - name:顧客名(文字列)
   - email:メールアドレス(文字列)
   - prefecture:都道府県(文字列)
   - created_at:登録日時(タイムスタンプ)
  ...(省略)

エージェントが list_tables ツールを呼び出し、Aurora に接続してスキーマ情報を取得できています。VPC Endpoint 経由で Bedrock API と Secrets Manager にアクセスし、VPC 内で Aurora と直接通信しています。

(2) 売上の集計クエリ

$ agentcore invoke '{"prompt": "先月の売上トップ3の商品を教えて"}'

先月の売上トップ3の商品をお答えします:

**1位:スマートウォッチ**
- 売上金額:297,600円
- 販売数量:12個

**2位:ダウンベスト**
- 売上金額:263,200円
- 販売数量:14個

**3位:Bluetoothスピーカー**
- 売上金額:256,000円
- 販売数量:20個

list_tables でテーブル構造を確認した後、execute_query で集計 SQL(JOIN + GROUP BY + ORDER BY)を生成・実行しています。1 回の invoke で Bedrock の Converse API を 3 回呼び出しています(テーブル構造確認 → SQL 実行 → 結果整形)。

(3) 生成された SQL の確認

今回のエージェントコードにはログ出力を入れていません。しかし、Bedrock のモデル呼び出しログを有効化していると、Converse API のリクエスト/レスポンスに tool_use の内容(生成された SQL)が含まれるので、そちらで確認が可能です。

002

  • 生成された SQL

  SELECT
      p.name AS 商品名,
      SUM(oi.quantity * oi.unit_price) AS 売上金額,
      SUM(oi.quantity) AS 販売数量
  FROM order_items oi
  JOIN orders o ON oi.order_id = o.order_id
  JOIN products p ON oi.product_id = p.product_id
  WHERE DATE_TRUNC('month', o.order_date) = DATE_TRUNC('month', CURRENT_DATE
  - INTERVAL '1 month')
  GROUP BY p.product_id, p.name
  ORDER BY 売上金額 DESC
  LIMIT 3
  • 3 回の呼び出しの流れ:

    Turn 入力 出力 レイテンシ
    1 ユーザープロンプト tool_use: list_tables 6,825ms
    2 テーブル構造(tool_result) tool_use: execute_query + 上記 SQL 9,238ms
    3 SQL 結果(tool_result) 最終回答テキスト(end_turn) 6,367ms

CloudWatch Log

8 ハマりどころ

作業中、いくつかハマったポイントがありましたので、共有させてください。

(1) Strands SDK で応答が返らなかった

当初、Strands Agents SDK でエージェントを実装していたのですが、私の環境では VPC モードで 2 回目以降の Bedrock API 呼び出しの応答が返ってきませんでした。1 回目の呼び出し(テーブル構造の確認まで)は成功するのですが、ツール結果を含む 2 回目の呼び出しで応答が返らない状態でした。

設定の問題かもしれませんが、切り分けの結果、converse を直接呼び出す方式では問題なく動作できたため、今回はこちらを採用しました。

(2) AgentCore の名前にハイフンが使えない

agentcore create でプロジェクト名に text-to-sql を指定したところ、エラーになりました。AgentCore のプロジェクト名には英数字のみ使用可能(ハイフン・アンダースコア不可)です。texttosql に変更しました。

(3) 実行ロールに Secrets Manager の権限が必要

agentcore deploy で自動作成される IAM 実行ロールには、Secrets Manager へのアクセス権限が含まれていません。aws iam put-role-policysecretsmanager:GetSecretValue の権限を追加する必要がありました。

(4) ENI の解放遅延によるスタック削除失敗

agentcore destroycdk destroy の順で実行したところ、CDK スタックの削除が失敗しました。AgentCore が VPC 内に作成した ENI が、destroy 後もすぐには解放されず、ENI が Security Group に紐付いているため、CDK が SG を削除出来ない状態です。数時間後に、削除できましたが、注意が必要そうです。

9 最後に

今回は、AgentCore を VPC モードで Aurora Serverless v2 に接続する Text-to-SQL エージェントを構築しました。

VPC Endpoint の設定や ENI の解放遅延など、PUBLIC モードにはないハマりどころがありましたが、
AgentCore の VPC モードは、普段のアプリケーションと同じように Private Subnet から DB に直接接続できるので、既存のネットワーク構成にそのまま載せることも出来そうだなと感じました。

参考リンク

この記事をシェアする

関連記事