社内AI活用推進のためにAWS S3 Vectorsを使ってみました!(データのベクトル化と投入、検索まで)
はじめに
株式会社フクロウラボで情シスを担当している高島です。
最近はゼロトラスト推進やSaaS運用・改善にとどまらず、事業をドライブするために「攻めの情シス」業務に注力しようとしています。AI推進を行うべく、社内に点在する膨大なナレッジ(Slack、Notion、PDF資料など)をAIエージェントに学習させ、業務効率化を図るミッションを掲げています。
最新のLLMも社内の固有ルールは知らないため、当然「RAG (Retrieval-Augmented Generation)」アーキテクチャの採用を決定しました。RAGアーキテクチャを採用する上で、誰もが直面するのが「ベクトルDBをどうするか」という問題です。
自前でOpenSearchを構築・運用するのはインフラコストが重すぎますし、PineconeのようなフルマネージドSaaSは(非常に優秀ですが)外部サービス連携の手間やAPIキー管理、セキュリティ承認という別のハードルがあります。
そんな中、2025年7月15日AWSよりAmazon S3 Vectorsのプレビュー版がリリースされました!
これはなんだか便利そうだな?ということで早速使用してみましたので、
本記事は、このS3 Vectorsを早速使い、OpenAIでベクトル化したデータをS3に投入 (put_vectors) し、CLIで投入確認 (list_vectors)、Pythonで検索 (query_vectors) するまでの詳細をお伝えさせていただきます。
【Step 1】準備:S3コンソールでインデックスを作成する
通常のS3でのバケット作成と同様にこの作業はAWSコンソールから数クリックで完了し、コードもサーバー構築も不要です。
まず、AWSのサービス一覧からAmazon S3を選択します。
サイドメニューから、「ベクトルバケット」を選択しベクトルバケット名を入力、作成します。

次に、作成したベクトルバケットをクリックし、ベクトルバケットにインデックスを作成します。

ここで求められるのは、主に以下の3点です。
-
インデックス名: 任意(例:
slack-index) -
ベクトルの次元数: 使用するEmbeddingモデルに合わせる
今回はOpenAIの最新モデルtext-embedding-3-largeを使うため、次元数は3072を指定しました。
※text-embedding-3-smallを使う場合は、次元数は1536を指定します。 -
距離メトリック:
コサインを選択(テキストの「意味」の類似性(方向)を測るのに最適なため、RAGにおいては主流)
これだけです。VPCやセキュリティグループの設定に悩む必要は一切ありません。このS3バケットはベクトルデータを格納・検索できる状態になりました。
【Step 2】データ投入:Python (boto3) で put_vectors を叩く
次に、Slackから取得したナレッジ(テキストデータ)をベクトル化し、S3 Vectorsに投入します。今回はPythonスクリプトを実行しました。
核心部は、boto3 (AWS SDK for Python) の s3vectors クライアントを使って、put_vectors APIを直接呼び出す部分です。
(SlackReaderでのデータロードや OpenAIEmbedding でのベクトル化の前処理は完了しているものとします)
import boto3
import logging
import os
import hashlib
# --- (前処理:Slackデータロードとベクトル化は完了済みと仮定) ---
# ...
# nodes = [...] # (例: LlamaIndexでチャンク分割されたSlackメッセージNode)
# vectors = [...] # (例: OpenAIでベクトル化されたベクトルのリスト)
# ...
# --- 1. ★ S3 Vectors クライアントの初期化 ---
# S3 Vectors プレビュー対応リージョンを指定
AWS_REGION = "us-east-1"
s3vectors_client = boto3.client("s3vectors", region_name=AWS_REGION)
logging.basicConfig(level=logging.INFO) # シンプルなログ設定
logger = logging.getLogger(__name__)
# --- 2. ★ S3 Vectors投入形式への整形 ---
vector_entries = []
for i, node in enumerate(nodes):
# ★ 検索時に一緒に返してほしいリッチな情報を metadata に格納
metadata = node.metadata.copy() # (Slackの 'ts', 'channel', 'url' など)
metadata["text_snippet"] = node.get_content()[:500] # 回答生成に使うスニペット
entry = {
"key": f"slack#{metadata.get('channel')}#{metadata.get('ts')}#{i}", # 一意なキー
"data": {"float32": [float(x) for x in vectors[i]]}, # ベクトル本体
"metadata": metadata # ★ 重要なメタデータ
}
vector_entries.append(entry)
# --- 3. ★ `put_vectors` APIの実行 ---
if vector_entries:
logger.info(f"Putting {len(vector_entries)} vector entries...")
# (実際には100件ずつのバッチ処理が必要です)
s3vectors_client.put_vectors(
vectorBucketName=os.environ.get("VECTOR_BUCKET_NAME"),
indexName=os.environ.get("VECTOR_INDEX_NAME"),
vectors=vector_entries
)
logger.info("✅ 投入完了。")
else:
logger.info("投入するベクトルがありませんでした。")
【Step 3】投入確認:AWS CLI で list-vectors を試す
Step 2でデータが正常に投入された(はずな)ので、インデックスの中身を直接確認します。
get-vectors(キー指定)でも確認できますが、ここではインデックス内のデータを一覧表示する list-vectors コマンドを使ってみました。
aws s3vectors list-vectors \
--vector-bucket-name test-bucket \
--index-name slack-index \
--return-data \
--return-metadata
実行結果
コマンドを実行すると、インデックスに格納されているキーと、その中身(ベクトルとメタデータ)が一覧で返ってきました。
{
"vectors": [
{
"key": "slack#C01234#1678886400.000100#0",
"data": {
"float32": [0.012, -0.045, ... (3072次元続く) ...]
},
"metadata": {
"ts": "1678886400.000100",
"channel": "C01234",
"source": "slack",
"url": "[https://your-slack.com/archives/C01234/p1678886400000100](https://your-slack.com/archives/C01234/p1678886400000100)",
"text_snippet": "【総務】経費精算の締め日は毎月25日です。遅れないようご注意ください。"
}
},
{
"key": "slack#C05678#1678000000.000200#0",
"data": { "...": "..." },
"metadata": { "...": "..." }
}
],
"nextToken": "..."
}
【Step 4】検索テスト:query_vectors の呼び出し (Python)
インデックスにデータがあることを確認できたので、いよいよRAGの心臓部である「意味による類似検索」を行います。
Step 3ではCLIを使いましたが、ここではAIエージェント(Webアプリ)のバックエンドで動かすことを想定し、Python (boto3) で query_vectors APIを呼び出すサンプルコードを紹介します。
import boto3
import os
from llama_index.embeddings.openai import OpenAIEmbedding
# --- (クライアントとモデルの初期化は完了済みと仮定) ---
AWS_REGION = "us-east-1"
s3vectors_client = boto3.client("s3vectors", region_name=AWS_REGION)
embed_model = OpenAIEmbedding(model="text-embedding-3-large", api_key=os.environ.get("OPENAI_API_KEY"))
# 環境変数から取得する代わりに、サンプルとしてハードコード
VECTOR_BUCKET_NAME = "test-bucket"
VECTOR_INDEX_NAME = "slack-index"
def search_knowledge(query_text: str):
"""
ユーザーの質問テキストを受け取り、S3 Vectorsで検索して
関連するノード(メタデータ含む)を返す関数
(エラーハンドリング省略版)
"""
if not query_text:
return "質問が入力されていません。"
# --- 1. 質問をベクトル化 ---
query_vector = embed_model.get_text_embedding(query_text)
# --- 2. ★ `query_vectors` APIの実行 ---
response = s3vectors_client.query_vectors(
vectorBucketName=VECTOR_BUCKET_NAME,
indexName=VECTOR_INDEX_NAME,
queryVector={'float32': query_vector}, # 検索ベクトルを指定
topK=5, # 上位5件を取得
returnMetadata=True # ★ metadataを必ずTrueにする
)
nodes = response.get('vectors', [])
if not nodes:
return "参照ソースが見つかりませんでした。"
# 検索結果(ノードのリスト)を返す
return nodes
# --- 実行デモ ---
user_question = "経費精算の締め日はいつ?"
search_results = search_knowledge(user_question)
# 検索結果(JSONライクな辞書のリスト)を表示
print(search_results)
実行結果 (コンソール出力例)
上記の print(search_results) を実行すると、Step 3のCLI実行時とほぼ同じJSON構造(key, score, metadata を持つ辞書のリスト)がPythonで取得できます。
[
{
"key": "slack#C01234#1678886400.000100#0",
"score": 0.895,
"metadata": {
"ts": "1678886400.000100",
"channel": "C01234",
"text_snippet": "【総務】経費精算の締め日は毎月25日です。遅れないようご注意ください。"
}
},
... (他4件の結果) ...
]
最後に
最後までお読みいただきありがとうございました!
今回S3 Vectorsを試してみて、AI推進としてRAG構築を始めるハードルが、思ったよりもずっと低かったと感じました。
理由の一つとして、開発・認証がAWSエコシステムで完結する点にあります。
-
boto3で完結: 開発者は、使い慣れたboto3(AWS SDK) でclient("s3vectors")を呼び出すだけです。 -
IAMロールで完結: 認証もIAMロール/ポリシーで一元管理できます。
Pineconeのような外部SaaSや、自前でOpenSearchクラスターを導入する場合、新しいSDKの学習、APIキーの管理、あるいはDB自体のインフラ運用といった「RAGの本質ではない作業」が発生してしまいます。
S3 Vectorsを選択すると、これら「別のベクトルDBを学び、設置する」必要がなくなり、設計が非常にシンプルになりました。
その結果、本来やるべき「どのデータをベクトル化するか」「どういうmetadataを持たせるか」「どう検索(query_vectors)するか」といった、AIエージェントの精度向上に直結する業務にフォーカスすることができるのではないでしょうか。
(本記事執筆時点では)S3 Vectorsはまだ us-east-1 us-east-2 us-west-2 eu-central-1 ap-southeast-2 でのみ利用可能ですが、AWSネイティブでRAGを構築したいチームにとって、非常に強力な選択肢になると存じます。(個人的にはこれからのベクトルDBの主流になるのではと感じています!)
この記事が、AI推進を担当されている情シスの方や、RAG構築に悩むエンジニアの方の一助となれば幸いです。
Discussion