Amazon S3 Vectors を試してみた — ベクトル検索から Bedrock Knowledge Base 統合まで

記事タイトルとURLをコピーする

2026年3月7日追記: 本記事の初版公開時、S3 Vectors がプレビュー段階であることを前提に記載していましたが、2025年12月2日に一般提供(GA)が開始されていました。GA に伴うリージョン拡大(東京リージョン対応)、制限値の変更(インデックスあたり最大20億ベクトル、topK 上限100件など)を反映して記事を修正しました。誤った情報を掲載してしまい申し訳ございません。

はじめに

2025年7月にプレビューとして発表され、2025年12月に一般提供(GA)が開始された Amazon S3 Vectors を実際に触ってみました。S3 Vectors は、ベクトルデータの保存・クエリに特化した新しいバケットタイプで、インフラのプロビジョニングなしでベクトル検索が使えるのが特徴です。

この記事では以下の2つを試します。

  1. S3 Vectors 単体: Python SDK でベクトル埋め込みの生成・格納・類似検索
  2. Bedrock Knowledge Base 統合: S3 Vectors をベクトルストアとした RAG 構成

前提条件

  • AWS CLI v2 がインストール済み
  • Python 3.x + boto3
  • S3 Vectors 対応リージョン(GA で14リージョンに拡大)を使用
  • Bedrock の Titan Text Embeddings V2 モデルへのアクセスが有効

本記事では us-west-2 を使用します。S3 Vectors は 2025年12月の GA で東京リージョン(ap-northeast-1)を含む14リージョンに対応しました。


Part 1: S3 Vectors 単体でベクトル検索

1-1. ベクトルバケットの作成

aws s3vectors create-vector-bucket \
  --vector-bucket-name test-vector-bucket-kiro \
  --region us-west-2

レスポンス:

{
    "vectorBucketArn": "arn:aws:s3vectors:us-west-2:123456789012:bucket/test-vector-bucket-kiro"
}

1-2. ベクトルインデックスの作成

ベクトルバケットを作成したら、次にベクトルインデックスを作成します。ベクトルインデックスとは、ベクトルデータを格納・検索するための入れ物です。通常の S3 バケットにおけるプレフィックス(フォルダ)のような役割で、用途やデータセットごとにインデックスを分けて管理できます。

1つのベクトルバケットには最大 10,000 個のベクトルインデックスを作成でき、各インデックスには最大 20 億個のベクトルを格納できます(プレビュー時は5,000万個)。例えば、「商品カタログ用」「社内ドキュメント用」「FAQ用」のように、検索対象ごとにインデックスを分けるといった使い方ができます。

ベクトルインデックスを作成する際に、以下の3つを指定します。いずれもインデックス作成後に変更できないため、使用する埋め込みモデルに合わせて慎重に選択する必要があります。

  • データ型(data-type): ベクトル値の数値型。S3 Vectors では現時点で float32(32ビット浮動小数点数)のみ対応。1つの数値を 4 バイトで表現するため、1024 次元のベクトル1件あたり約 4 KB のストレージを使用します。他のベクトル DB では float16(2バイト)や int8(1バイト)をサポートしているものもありますが、S3 Vectors では選択の余地はありません
  • 次元数(dimension): 1つのベクトルに含まれる数値の個数
  • 距離メトリック(distance metric): 2つのベクトルの類似度を計算する方法

順番に見ていきます。

埋め込みモデル: Amazon Titan Text Embeddings V2

まず、ベクトルを生成する埋め込みモデルのスペックを確認します。埋め込みモデルとは、テキストを数値の配列(ベクトル)に変換するモデルです。今回使用する Titan Text Embeddings V2 のスペックは以下の通りです。

項目
モデル ID amazon.titan-embed-text-v2:0
最大入力トークン数 8,192
最大入力文字数 50,000
出力ベクトル次元数 1,024(デフォルト)、512、256 から選択可能
出力の正規化 デフォルトで有効(normalize=true
対応言語 英語(100以上の言語をプレビューサポート、日本語含む)

入力の制限について補足します。トークンとは、モデルがテキストを処理する際の最小単位で、単語や単語の一部に分割されたものです。英語の場合、平均して 1 トークン ≒ 4.7 文字程度になります(例: "embeddings" は "embed" + "dings" の2トークン)。一方、日本語では1文字が1トークン以上になることが多く、同じトークン数でも文字数は少なくなります。

入力テキストは「8,192 トークン」と「50,000 文字」の両方の制限を同時に満たす必要があり、どちらか先に到達した方が実質的な上限になります。英語ではトークン数が先に上限に達しやすく、日本語でも同様にトークン数がボトルネックになる傾向があります。

次元数(dimension)

次元数とは、1つのベクトルに含まれる数値の個数です。例えば 1024 次元なら [0.123, -0.456, 0.789, ...] のように 1024 個の数値で1つのテキストの意味を表現します。ベクトルインデックスの次元数は、埋め込みモデルの出力次元数と一致させる必要があります。

Titan Text Embeddings V2 では、リクエスト時に dimensions パラメータで 1024 / 512 / 256 から選択できます。次元数が大きいほど、テキストの意味の違いをより細かく区別できます(例: 「犬が走る」と「犬が歩く」のような微妙なニュアンスの差を捉えやすくなる)。一方で、ストレージとクエリのコストも増えます。今回はデフォルトの 1024 を使用します。

距離メトリック(distance metric)

距離メトリックとは、2つのベクトルがどれだけ似ているかを計算する方法です。類似検索では、クエリのベクトルとインデックス内の各ベクトルの「距離」を計算し、距離が近いものから順に結果を返します。

S3 Vectors では以下の2種類から選択できます。

メトリック 計算方法 特徴
Cosine ベクトル間の角度のコサイン値 「ベクトルの向きがどれだけ違うか」を測る。ベクトルの長さには影響されない
Euclidean ベクトル間の直線距離 「2点間の直線距離」を測る。ベクトルの長さも結果に影響する

ここで、Titan Text Embeddings V2 の「正規化」が関係してきます。正規化とは、ベクトルの長さ(ノルム)を 1 に揃える処理です。

なぜ正規化するかというと、埋め込みモデルの出力ベクトルには「意味の方向」と「ベクトルの長さ」の2つの情報が含まれています。方向はテキストの意味を表しますが、長さはテキストの長さや頻出単語の影響などで変わり、意味の類似度とは直接関係ありません。長さを 1 に揃えることで、純粋に「方向の違い」だけで類似度を比較できるようになります。

具体的には、各要素をベクトル全体のノルム(長さ)で割ります。例えばベクトルが [3, 4] の場合、ノルムは √(3² + 4²) = 5 なので、正規化後は [3/5, 4/5] = [0.6, 0.8](長さ = 1)になります。ここでの「長さ」は 0.6 + 0.8 = 1.4 ではなく、ユークリッドノルム √(0.6² + 0.8²) = √(0.36 + 0.64) = 1.0 です(ピタゴラスの定理と同じ計算)。1024 次元でも同じ計算です。Titan Text Embeddings V2 はデフォルトで正規化が有効(normalize=true)なので、この処理は出力時に自動で行われます。

ところで、cosine similarity の計算式は (A · B) / (|A| × |B|) で、分母でベクトルの長さの積で割っています。つまり cosine を使う場合、長さが揃っていなくても計算結果は同じです。では事前に正規化する意味は何かというと、パフォーマンスの最適化です。正規化済み(長さ = 1)のベクトル同士なら、分母が常に 1 × 1 = 1 になるため、内積の計算だけで cosine similarity が求まります。数億ベクトルに対してクエリする場合、毎回分母を計算するコストが省けるのは大きな差になります。

すべてのベクトルの長さが 1 に揃っている場合、Euclidean distance の大小関係は Cosine distance と数学的に一致します。長さの違いがなくなるため、方向の違いだけが距離に反映されるからです。つまり、Titan Text Embeddings V2 をデフォルト設定で使う限り、どちらを選んでも検索結果の順序は同じです。

今回は S3 Vectors の公式チュートリアルに合わせて cosine を選択しました。なお、リクエスト時に normalize=false を指定して非正規化ベクトルを使う場合は、用途に応じて適切なメトリックを選ぶ必要があります。

CLI コマンド

以上を踏まえて、ベクトルインデックスを作成します。

aws s3vectors create-index \
  --vector-bucket-name test-vector-bucket-kiro \
  --index-name test-vector-index \
  --data-type float32 \
  --dimension 1024 \
  --distance-metric cosine \
  --region us-west-2

ポイント: CLI のサブコマンドは create-vector-index ではなく create-index です。また --data-type パラメータが必須です。ブログ記事のコード例だけ見ていると見落としがちなので注意。

1-3. Python で埋め込み生成 → 格納 → クエリ

映画の説明文3件をベクトル化して S3 Vectors に格納し、類似検索を行います。コードを機能ごとに分けて見ていきます。

クライアントの初期化

import boto3
import json

REGION = "us-west-2"
VECTOR_BUCKET = "test-vector-bucket-kiro"
VECTOR_INDEX = "test-vector-index"
EMBED_MODEL = "amazon.titan-embed-text-v2:0"

bedrock = boto3.client("bedrock-runtime", region_name=REGION)
s3vectors = boto3.client("s3vectors", region_name=REGION)

boto3 で2つのクライアントを作成します。bedrock-runtime は埋め込みモデルの呼び出し用、s3vectors はベクトルの格納・クエリ用です。S3 Vectors は既存の s3 クライアントとは別の専用クライアント(s3vectors)を使う点に注意してください。

埋め込み生成関数

def get_embedding(text: str) -> list[float]:
    """Bedrock Titan Text Embeddings V2 でベクトル埋め込みを生成"""
    response = bedrock.invoke_model(
        modelId=EMBED_MODEL,
        body=json.dumps({"inputText": text}),
    )
    return json.loads(response["body"].read())["embedding"]

Bedrock の invoke_model API にテキストを渡すと、レスポンスの embedding フィールドに 1024 次元の float 配列が返ってきます。リクエストボディに "dimensions": 512"normalize": false を追加すれば、次元数の変更や正規化の無効化も可能です。今回はデフォルト(1024次元、正規化あり)をそのまま使います。

テストデータの準備と格納

texts = [
    ("v1", "Star Wars: A farm boy joins rebels to fight an evil empire in space", "scifi"),
    ("v2", "Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong", "scifi"),
    ("v3", "Finding Nemo: A father fish searches the ocean to find his lost son", "family"),
]

vectors = []
for key, text, genre in texts:
    emb = get_embedding(text)
    vectors.append({
        "key": key,
        "data": {"float32": emb},
        "metadata": {"source_text": text, "genre": genre},
    })

s3vectors.put_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    vectors=vectors,
)

各ベクトルは以下の3つの要素で構成されます。

  • key: ベクトルの一意な識別子(文字列)。同じ key で再度 put_vectors すると上書きされます
  • data: {"float32": [...]} の形式でベクトル値を指定。インデックス作成時に指定した data-type と一致させる必要があります
  • metadata: ベクトルに付随する補足情報をキーバリューペアで添付できます。ベクトル検索は「意味が近いものを探す」処理ですが、実際のアプリケーションでは「意味が近い、かつ特定の条件を満たすもの」を探したいケースが多くあります。例えば、映画検索なら「SF映画の中から似たものを探す」、ECサイトなら「価格帯が1万円以下の商品から似たものを探す」といった具合です。メタデータにカテゴリや日付、価格などを入れておくと、クエリ時に filter パラメータでこれらの条件を指定して絞り込めます

put_vectors は1回の呼び出しで複数のベクトルをまとめて格納できます。

類似検索(query_vectors)

query_text = "List the movies about adventures in space"
query_emb = get_embedding(query_text)

result = s3vectors.query_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    queryVector={"float32": query_emb},
    topK=3,
    returnDistance=True,
    returnMetadata=True,
)

for v in result["vectors"]:
    meta = v.get("metadata", {})
    print(f"key={v['key']}, distance={v.get('distance'):.4f}, genre={meta.get('genre')}")
    print(f"  -> {meta.get('source_text')}")

検索テキストも同じ埋め込みモデルでベクトル化し、query_vectors に渡します。主要なパラメータは以下の通りです。

  • queryVector: 検索クエリのベクトル。格納時と同じ {"float32": [...]} 形式
  • topK: 返す結果の最大件数。1〜100 の範囲で指定します(GA で上限が30から100に拡大)。S3 Vectors の QueryVectorsAPI リファレンスで "approximate nearest neighbor search"(近似最近傍探索、ANN)と明記されています。ANN とは、全ベクトルとの距離を総当たりで計算するのではなく、インデックス構造を使って高速に「だいたい近いもの」を返すアルゴリズムです。厳密な最近傍ではないため理論上は取りこぼしがありえますが、実用上はほぼ問題になりません。topK を大きくすると探索範囲が広がり、レスポンスに含まれるデータ量も増えます。RAG 用途では 3〜10 程度で十分なことが多いです。なお、S3 Vectors のドキュメントでは "subsecond latency for infrequent queries and as low as 100 milliseconds for more frequent queries"(低頻度のクエリで1秒未満、高頻度のクエリで最短 100 ミリ秒)とされています
  • returnDistance: True にすると各結果に距離値が含まれる。cosine distance なので 0 に近いほど類似度が高い
  • returnMetadata: True にすると格納時に付与したメタデータが結果に含まれる

フィルタ付きクエリ

result_filtered = s3vectors.query_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    queryVector={"float32": query_emb},
    topK=3,
    filter={"genre": "scifi"},
    returnDistance=True,
    returnMetadata=True,
)

filter パラメータにメタデータの条件を辞書で渡すと、条件に一致するベクトルのみを対象に類似検索が行われます。この例では genrescifi のベクトルだけに絞り込んでいます。ベクトル検索とメタデータフィルタリングを組み合わせることで、カテゴリやタグで絞った上でのセマンティック検索が実現できます。

コード全文

上記をまとめた全文です。そのまま実行できます。

import boto3
import json

REGION = "us-west-2"
VECTOR_BUCKET = "test-vector-bucket-kiro"
VECTOR_INDEX = "test-vector-index"
EMBED_MODEL = "amazon.titan-embed-text-v2:0"

bedrock = boto3.client("bedrock-runtime", region_name=REGION)
s3vectors = boto3.client("s3vectors", region_name=REGION)


def get_embedding(text: str) -> list[float]:
    """Bedrock Titan Text Embeddings V2 でベクトル埋め込みを生成"""
    response = bedrock.invoke_model(
        modelId=EMBED_MODEL,
        body=json.dumps({"inputText": text}),
    )
    return json.loads(response["body"].read())["embedding"]


# --- テストデータの埋め込み生成 & 格納 ---
texts = [
    ("v1", "Star Wars: A farm boy joins rebels to fight an evil empire in space", "scifi"),
    ("v2", "Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong", "scifi"),
    ("v3", "Finding Nemo: A father fish searches the ocean to find his lost son", "family"),
]

vectors = []
for key, text, genre in texts:
    emb = get_embedding(text)
    print(f"  {key}: {text[:40]}... (dim={len(emb)})")
    vectors.append({
        "key": key,
        "data": {"float32": emb},
        "metadata": {"source_text": text, "genre": genre},
    })

s3vectors.put_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    vectors=vectors,
)
print("格納完了!")

# --- 類似検索 ---
query_text = "List the movies about adventures in space"
query_emb = get_embedding(query_text)

result = s3vectors.query_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    queryVector={"float32": query_emb},
    topK=3,
    returnDistance=True,
    returnMetadata=True,
)

print(f"\n=== クエリ: '{query_text}' ===")
for v in result["vectors"]:
    meta = v.get("metadata", {})
    print(f"  key={v['key']}, distance={v.get('distance'):.4f}, genre={meta.get('genre')}")
    print(f"    -> {meta.get('source_text')}")

# --- フィルタ付きクエリ (scifi のみ) ---
print("\n=== フィルタ付きクエリ (genre=scifi) ===")
result_filtered = s3vectors.query_vectors(
    vectorBucketName=VECTOR_BUCKET,
    indexName=VECTOR_INDEX,
    queryVector={"float32": query_emb},
    topK=3,
    filter={"genre": "scifi"},
    returnDistance=True,
    returnMetadata=True,
)

for v in result_filtered["vectors"]:
    meta = v.get("metadata", {})
    print(f"  key={v['key']}, distance={v.get('distance'):.4f}, genre={meta.get('genre')}")
    print(f"    -> {meta.get('source_text')}")

1-4. 実行結果

=== 検索結果 ===
  key=v1, distance=0.7958, genre=scifi
    -> Star Wars: A farm boy joins rebels to fight an evil empire in space
  key=v2, distance=0.9222, genre=scifi
    -> Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong
  key=v3, distance=0.9227, genre=family
    -> Finding Nemo: A father fish searches the ocean to find his lost son

=== フィルタ付きクエリ (genre=scifi) ===
  key=v1, distance=0.7958, genre=scifi
    -> Star Wars: A farm boy joins rebels to fight an evil empire in space
  key=v2, distance=0.9222, genre=scifi
    -> Jurassic Park: Scientists create dinosaurs in a theme park that goes wrong

"adventures in space" というクエリに対して、Star Wars が distance=0.7958 で最も類似度が高い結果になりました(cosine distance なので値が小さいほど類似)。filtergenre=scifi を指定すると、Finding Nemo が除外されて scifi の2件のみが返ってきます。

ここでの「類似度の判定」がどこで行われているかを補足します。処理の流れは以下の通りです。

  1. 埋め込みモデル(Titan Text Embeddings V2): テキストを 1024 個の数値(ベクトル)に変換する。「似ている/似ていない」の判定はしない
  2. S3 Vectors: 格納済みのベクトルとクエリのベクトルの間の cosine distance を数学的に計算し、距離が近い順に返す

つまり、モデルは「テキストの意味を数値に変換する」役割で、「どれが似ているか」の判定は S3 Vectors 側の距離計算で行われています。Star Wars が最上位に来たのは、モデルが "adventures in space" と "farm boy joins rebels to fight an evil empire in space" を意味的に近いベクトルに変換し、S3 Vectors がその距離を計算した結果です。モデルが良い埋め込みを生成するからこそ、距離計算の結果が人間の感覚に近い類似度と一致します。


Part 2: Bedrock Knowledge Base との統合

S3 Vectors をベクトルストアとして Bedrock Knowledge Base を構築し、ドキュメントに対する RAG 検索を試します。

構成図

① データ取り込み(Ingestion)

flowchart LR
    A[S3 バケット] -- "1" --> B[Knowledge Base] -- "2" --> C[S3 Vector Bucket]
  1. Knowledge Base が S3 バケットからドキュメントを読み取り、テキストをチャンク(断片)に分割して埋め込みモデルでベクトルに変換
  2. 生成されたベクトルを S3 ベクトルバケットのインデックスに格納

この処理は start-ingestion-job コマンドで実行します。

② 検索(Retrieve)

flowchart LR
    D[ユーザー] -- "1" --> E[Knowledge Base] -- "2" --> F[S3 Vector Bucket]
    F -- "3" --> E -- "4" --> D
  1. ユーザーが質問テキストを送信
  2. Knowledge Base が質問を同じ埋め込みモデルでベクトル化し、S3 ベクトルバケットに類似検索を実行
  3. ベクトルインデックスから距離が近いチャンクが返される
  4. 検索結果(チャンクのテキスト + 元ドキュメントの S3 URI)をユーザーに返却

この構成では、2種類の S3 リソースを使い分けています。

リソース 用途 格納するもの
S3 バケット(通常) データソース 元のドキュメント(テキスト、PDF など)。人間が読める形式のファイルをそのまま置く
S3 ベクトルバケット ベクトルストア ドキュメントから生成されたベクトル埋め込み(数値の配列)。類似検索に使う

処理の流れは2段階あります。

まず、データの取り込み(Ingestion)時には、Knowledge Base が S3 バケットからドキュメントを読み取り、テキストをチャンク(断片)に分割し、埋め込みモデルでベクトルに変換して、S3 ベクトルバケットのインデックスに格納します。この処理は start-ingestion-job で実行します。

次に、検索(Retrieve)時には、ユーザーの質問テキストを同じ埋め込みモデルでベクトルに変換し、S3 ベクトルバケット内で類似検索を行い、距離が近いチャンクを返します。返されるチャンクには元のドキュメントの S3 URI も含まれるため、どのファイルから取得された情報かを追跡できます。

2-1. Knowledge Base 用ベクトルインデックスの作成

Part 1 で作ったインデックスとは別に、Knowledge Base 用のインデックスを新たに作成します。

Knowledge Base と統合する場合、インデックス作成時に「非フィルタメタデータキー」の設定が必要です。これは何かというと、Knowledge Base が Ingestion 時にベクトルと一緒に保存する補足データの置き場所です。具体的には以下の2つを登録します。

キー名 Knowledge Base が格納する内容
AMAZON_BEDROCK_TEXT ドキュメントから分割されたテキストチャンク(検索結果として返される文章)
AMAZON_BEDROCK_METADATA ソースファイルの URI やデータソース ID など、Knowledge Base が管理する内部メタデータ

Part 1 で説明した通り、S3 Vectors のメタデータはデフォルトでフィルタ可能(クエリ時の絞り込み条件に使える)ですが、上記2つはテキスト本文や内部管理情報なので、フィルタ条件として使うものではありません。そのため「非フィルタ」として登録し、フィルタ対象から除外します。

aws s3vectors create-index \
  --vector-bucket-name test-vector-bucket-kiro \
  --index-name kb-vector-index \
  --data-type float32 \
  --dimension 1024 \
  --distance-metric cosine \
  --metadata-configuration '{"nonFilterableMetadataKeys":["AMAZON_BEDROCK_TEXT","AMAZON_BEDROCK_METADATA"]}' \
  --region us-west-2

ポイント: この非フィルタメタデータキーの設定を忘れると、Ingestion 時にエラーになります。

2-2. データソース用 S3 バケットの作成 & ドキュメント配置

aws s3 mb s3://test-kb-datasource-kiro-2025 --region us-west-2
aws s3 cp kb_test_docs/ s3://test-kb-datasource-kiro-2025/docs/ --recursive --region us-west-2

今回はテスト用に S3 Vectors の概要と Bedrock 統合手順を記載した2つのテキストファイルを配置しました。

2-3. IAM ロールの作成

Knowledge Base が Bedrock モデル呼び出し、S3 データソース読み取り、S3 Vectors 操作を行うためのロールが必要です。

信頼ポリシー:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "bedrock.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "<アカウントID>"
        }
      }
    }
  ]
}

権限ポリシー:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BedrockInvokeModel",
      "Effect": "Allow",
      "Action": "bedrock:InvokeModel",
      "Resource": "arn:aws:bedrock:us-west-2::foundation-model/amazon.titan-embed-text-v2:0"
    },
    {
      "Sid": "S3DataSourceAccess",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::<データソースバケット名>",
        "arn:aws:s3:::<データソースバケット名>/*"
      ]
    },
    {
      "Sid": "S3VectorsAccess",
      "Effect": "Allow",
      "Action": [
        "s3vectors:CreateIndex", "s3vectors:DeleteIndex",
        "s3vectors:GetIndex", "s3vectors:ListIndexes",
        "s3vectors:PutVectors", "s3vectors:GetVectors",
        "s3vectors:DeleteVectors", "s3vectors:QueryVectors",
        "s3vectors:ListVectors", "s3vectors:GetVectorBucket",
        "s3vectors:GetVectorBucketPolicy", "s3vectors:ListVectorBuckets"
      ],
      "Resource": [
        "arn:aws:s3vectors:us-west-2:<アカウントID>:bucket/<ベクトルバケット名>",
        "arn:aws:s3vectors:us-west-2:<アカウントID>:bucket/<ベクトルバケット名>/index/*"
      ]
    }
  ]
}
aws iam create-role \
  --role-name BedrockKB-S3Vectors-Role \
  --assume-role-policy-document file://kb_trust_policy.json

aws iam put-role-policy \
  --role-name BedrockKB-S3Vectors-Role \
  --policy-name BedrockKB-S3Vectors-Policy \
  --policy-document file://kb_permissions_policy.json

2-4. Knowledge Base の作成

ここまでで準備したリソース(ベクトルインデックス、データソース用 S3 バケット、IAM ロール)を組み合わせて、Knowledge Base を作成します。CLI では2つの設定ファイルを用意して渡します。

knowledge-base-configuration(create_kb.json)

Knowledge Base がドキュメントをベクトル化する際に使う埋め込みモデルの設定です。

{
  "type": "VECTOR",
  "vectorKnowledgeBaseConfiguration": {
    "embeddingModelArn": "arn:aws:bedrock:us-west-2::foundation-model/amazon.titan-embed-text-v2:0",
    "embeddingModelConfiguration": {
      "bedrockEmbeddingModelConfiguration": {
        "dimensions": 1024
      }
    }
  }
}
  • type: Knowledge Base の種類。ベクトル検索を使う場合は VECTOR
  • embeddingModelArn: 埋め込みモデルの ARN。Part 1 で使ったのと同じ Titan Text Embeddings V2 を指定
  • dimensions: 出力ベクトルの次元数。ベクトルインデックス作成時に指定した 1024 と一致させる必要がある

storage-configuration(create_kb_storage.json)

ベクトルの格納先の設定です。ここで S3 Vectors を指定します。

{
  "type": "S3_VECTORS",
  "s3VectorsConfiguration": {
    "vectorBucketArn": "arn:aws:s3vectors:us-west-2:<アカウントID>:bucket/<ベクトルバケット名>",
    "indexArn": "arn:aws:s3vectors:us-west-2:<アカウントID>:bucket/<ベクトルバケット名>/index/kb-vector-index"
  }
}
  • type: ベクトルストアの種類。S3 Vectors の場合は S3_VECTORS。他にも OPENSEARCH_SERVERLESSRDS などが選択可能
  • vectorBucketArn: 2-1 で作成したベクトルバケットの ARN
  • indexArn: 2-1 で作成したベクトルインデックスの ARN

CLI コマンド

上記2つのファイルと IAM ロールの ARN を指定して Knowledge Base を作成します。

aws bedrock-agent create-knowledge-base \
  --name "test-kb-s3vectors" \
  --description "S3 Vectors test knowledge base" \
  --role-arn "arn:aws:iam::<アカウントID>:role/BedrockKB-S3Vectors-Role" \
  --knowledge-base-configuration file://create_kb.json \
  --storage-configuration file://create_kb_storage.json \
  --region us-west-2

ポイント: s3VectorsConfigurationindexArn を指定する場合、indexName は含めないでください。両方指定すると ValidationException になります。

2-5. データソースの追加 & Sync(Ingestion)

このステップでは2つのことを行います。

まず、Knowledge Base にデータソースを登録します。データソースとは、ドキュメントが置いてある S3 バケットのことです。

次に、Ingestion(データ取り込み)ジョブを実行します。これは構成図①で説明した処理で、以下を一括で行います。

  1. S3 バケットからドキュメントを読み取り
  2. テキストをチャンク(断片)に分割
  3. 各チャンクを埋め込みモデルでベクトルに変換
  4. ベクトルインデックスに格納
# データソース追加
aws bedrock-agent create-data-source \
  --knowledge-base-id <KB_ID> \
  --name "test-docs" \
  --data-source-configuration '{
    "type": "S3",
    "s3Configuration": {
      "bucketArn": "arn:aws:s3:::<データソースバケット名>",
      "inclusionPrefixes": ["docs/"]
    }
  }' \
  --region us-west-2

# Ingestion 開始
aws bedrock-agent start-ingestion-job \
  --knowledge-base-id <KB_ID> \
  --data-source-id <DS_ID> \
  --region us-west-2

Ingestion ジョブの結果:

{
    "status": "COMPLETE",
    "statistics": {
        "numberOfDocumentsScanned": 2,
        "numberOfNewDocumentsIndexed": 2,
        "numberOfDocumentsFailed": 0
    }
}

2ドキュメントが正常にインデックスされました。

2-6. Retrieve API でセマンティック検索

aws bedrock-agent-runtime retrieve \
  --knowledge-base-id <KB_ID> \
  --retrieval-query '{"text":"What distance metrics does S3 Vectors support?"}' \
  --region us-west-2

結果(抜粋):

{
    "retrievalResults": [
        {
            "content": {
                "text": "Amazon S3 Vectors Overview ... Distance Metrics: - Cosine: Measures the cosine of the angle between two vectors - Euclidean: Measures the straight-line distance between two vectors ...",
                "type": "TEXT"
            },
            "location": {
                "s3Location": {
                    "uri": "s3://test-kb-datasource-kiro-2025/docs/s3_vectors_overview.txt"
                },
                "type": "S3"
            },
            "score": 0.8315
        },
        {
            "content": {
                "text": "Bedrock Knowledge Bases with S3 Vectors Integration ...",
                "type": "TEXT"
            },
            "score": 0.7274
        }
    ]
}

"distance metrics" に関するクエリに対して、距離メトリックの説明を含むドキュメントが score=0.8315 で最上位にヒットしました。ソースの S3 URI も正しく返ってきています。

2-7. 日本語データで試してみる

ここまでは英語のテストドキュメントで動作確認しましたが、日本語のデータでも試してみます。日向坂46のシングルディスコグラフィー(楽曲名・センター・作曲者)をテキストファイルにまとめて、データソースに追加しました。

1st キュン / センター: 小坂菜緒 / 作曲: 野村陽一郎 / 2019年3月27日
2nd ドレミソラシド / センター: 小坂菜緒 / 作曲: 野村陽一郎 / 2019年7月17日
...
16th クリフハンガー / センター: 大野愛実 / 作曲: 杉山勝彦 / 2026年1月28日

S3 にアップロードして Ingestion を実行します。

aws s3 cp hinatazaka46.txt s3://test-kb-datasource-kiro-2025/docs/ --region us-west-2

aws bedrock-agent start-ingestion-job \
  --knowledge-base-id <KB_ID> \
  --data-source-id <DS_ID> \
  --region us-west-2
{
    "status": "COMPLETE",
    "statistics": {
        "numberOfDocumentsScanned": 3,
        "numberOfNewDocumentsIndexed": 1,
        "numberOfDocumentsFailed": 0
    }
}

3ドキュメントがスキャンされ、新規の1件(日向坂46のファイル)がインデックスされました。マネジメントコンソールでも3ドキュメントすべてが INDEXED になっていることを確認できます。

日本語でクエリしてみます。

aws bedrock-agent-runtime retrieve \
  --knowledge-base-id <KB_ID> \
  --retrieval-query '{"text":"クリフハンガーのセンターと作曲者は?"}' \
  --region us-west-2

結果(抜粋):

{
    "retrievalResults": [
        {
            "content": {
                "text": "/ センター: 金村美玖、小坂菜緒 / 作曲: 石崎光 / 2025年9月17日 16th クリフハンガー / センター: 大野愛実 / 作曲: 杉山勝彦 / 2026年1月28日"
            },
            "score": 0.7927
        }
    ]
}

「16th クリフハンガー / センター: 大野愛実 / 作曲: 杉山勝彦」が score=0.79 で正しくヒットしました。日本語のセマンティック検索もちゃんと動いています。

マネジメントコンソールの「ナレッジベースをテスト」画面からも検索できます。


S3 Vectors の主な制限値(GA 時点)

ドキュメントに記載されている主な制限値をまとめます。

項目 上限
リージョンあたりのベクトルバケット数 10,000
ベクトルバケットあたりのインデックス数 10,000
インデックスあたりのベクトル数 20 億
ベクトルの次元数 1〜4,096
ベクトルあたりのメタデータ合計 40 KB(フィルタ可能 + 非フィルタ)
ベクトルあたりのフィルタ可能メタデータ 2 KB
ベクトルあたりのメタデータキー数 50
インデックスあたりの非フィルタメタデータキー数 10
PutVectors 1回あたりのベクトル数 500
QueryVectors の topK 上限 100
インデックスあたりの書き込みスループット PutVectors + DeleteVectors で 1,000 リクエスト/秒、2,500 ベクトル/秒

その他の特性

  • 書き込みは強整合性(strongly consistent)。PutVectors 直後からクエリで検索可能
  • ベクトルバケットは Block Public Access が常に有効で無効化不可
  • IAM ポリシーの名前空間は s3 ではなく s3vectors。既存の S3 ポリシーとは別に設計が必要
  • AWS PrivateLink(インターフェース VPC エンドポイント)に対応

クリーンアップ

# Knowledge Base
aws bedrock-agent delete-knowledge-base --knowledge-base-id <KB_ID> --region us-west-2

# S3 Vectors
aws s3vectors delete-index --vector-bucket-name test-vector-bucket-kiro --index-name test-vector-index --region us-west-2
aws s3vectors delete-index --vector-bucket-name test-vector-bucket-kiro --index-name kb-vector-index --region us-west-2
aws s3vectors delete-vector-bucket --vector-bucket-name test-vector-bucket-kiro --region us-west-2

# データソース用 S3 バケット
aws s3 rb s3://test-kb-datasource-kiro-2025 --force --region us-west-2

# IAM ロール
aws iam delete-role-policy --role-name BedrockKB-S3Vectors-Role --policy-name BedrockKB-S3Vectors-Policy
aws iam delete-role --role-name BedrockKB-S3Vectors-Role

まとめ

S3 Vectors は、ベクトルバケットを作ってすぐにベクトルの格納・検索ができる手軽さが印象的でした。OpenSearch Serverless のようなクラスタ管理が不要で、API を叩くだけで使えます。

Bedrock Knowledge Base との統合も、非フィルタメタデータキーの設定さえ押さえておけばスムーズに構築できました。クエリ頻度が低い RAG ワークロードであれば、コスト面で大きなメリットがありそうです。

2025年12月の GA でインデックスあたり最大 20 億ベクトル、14リージョン対応と大幅にスケールアップしたので、本番ワークロードでも十分使えるレベルになっています。

参考ドキュメント

山本 哲也 (記事一覧)

多分インフラエンジニアです。データ分析に興味あります。

山を走るのが趣味です。今年は 100 マイルレース完走します。