1.はじめに
1.1.背景
2025/7/15、AWSから S3 Vectorsと呼ばれる機能がプレビューされました。
Amazon S3 Vectors
早速ですが AWS CLIを利用して、とりあえず動くものを作りながら学びを深めたいと思います。
1.2.構成

2.ハンズオン
2.1.前提
2.1.1.実行環境
環境 | 設定 | バージョン |
---|---|---|
環境 | AWS CloudShell | 2.27.55 |
2.2.ベクトル S3作成
1.CloudShellアップデート
- `2.27.51`よ`S3 Vectors`に対応しているとのことで、最新の CLIを取得しておく# aws cliバージョンアップ
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install --update
# バージョン確認
aws --version
# S3 Vectorsテスト
aws s3vectors help
2.変数設定
# 環境変数設定
export AWS_REGION="us-east-1"
export VECTOR_BUCKET_NAME="sangokushi-rag-bucket-$(date +%s)"
export VECTOR_INDEX_NAME="sangokushi-index"
3.ベクトル S3作成
ベクトル(数値の配列)を効率的に保存・検索するためのバケット。 ベクトル検索に最適化された専用ストレージで、中にベクトルインデックスを複数作成することが可能。# ベクトルS3作成
aws s3vectors create-vector-bucket --vector-bucket-name "${VECTOR_BUCKET_NAME}" --region "${AWS_REGION}"
# 構築確認
aws s3vectors list-vector-buckets --region us-east-1
4.変数設定
# 環境変数設定
export VECTOR_BUCKET_NAME="sangokushi-rag-bucket-1752990275"
export VECTOR_INDEX_NAME="sangokushi-index"
5.ベクトルインデックス作成
実際のベクトルデータを格納し、類似検索を実行する。 次元数(1024)や距離計算方法(コサイン)を定義し、ここにベクトル化したデータを投入する。# ベクトルインデックス作成
aws s3vectors create-index \
--vector-bucket-name "${VECTOR_BUCKET_NAME}" \
--index-name "${VECTOR_INDEX_NAME}" \
--data-type "float32" \
--dimension 1024 \
--distance-metric "cosine" \
--metadata-configuration '{"nonFilterableMetadataKeys":["text"]}' \
--region us-east-1
2.2.データ配置スクリプト 作成
今回のサンプルデータとして Wikipedia
から要約された三国志関連での情報を取得する
1.keywordsテキストの作成
cat > keywords.txt << 'EOF'
# 三国志関連人物(#はコメント行)
劉備
曹操
孫権
諸葛亮
関羽
張飛
趙雲
司馬懿
周瑜
呂布
EOF
2.メインスクリプトの作成
Wikipedia REST API
を利用してWikipediaの要約ページ内容を取得
取得した内容 右記項目(。!?、・\s()()「」『』【】])で分割。
# 読点分割版スクリプト作成
cat > wikipedia_data_loader_detailed.py << 'EOF'
#!/usr/bin/env python3
"""
Wikipedia データ収集・詳細分割・ベクトル化・S3 Vectors投入ツール
使用方法: python3 wikipedia_data_loader_detailed.py
"""
import boto3
import json
import os
import uuid
import re
import requests
import time
from typing import List, Dict
# 環境変数確認
VECTOR_BUCKET_NAME = os.environ.get('VECTOR_BUCKET_NAME')
VECTOR_INDEX_NAME = os.environ.get('VECTOR_INDEX_NAME')
AWS_REGION = os.environ.get('AWS_REGION', 'us-east-1')
KEYWORDS_FILE = 'keywords.txt'
def load_keywords() -> List[str]:
"""keywords.txtからキーワード読み込み"""
keywords = []
with open(KEYWORDS_FILE, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
keywords.append(line)
print(f"[INFO] {len(keywords)}個のキーワードを読み込み")
return keywords
def get_wikipedia_summary(keyword: str) -> Dict:
"""Summary APIでWikipedia記事取得"""
url = f"https://ja.wikipedia.org/api/rest_v1/page/summary/{keyword}"
try:
response = requests.get(url, timeout=10)
if response.status_code == 404:
print(f"[WARN] {keyword}: 記事が見つかりません")
return None
if response.status_code != 200:
print(f"[ERROR] {keyword}: HTTPエラー {response.status_code}")
return None
data = response.json()
extract = data.get('extract', '')
if len(extract) < 20:
print(f"[WARN] {keyword}: 記事が短すぎるためスキップ ({len(extract)}文字)")
return None
print(f"[SUCCESS] {keyword}: {len(extract)}文字の要約を取得")
return {
'title': data.get('title', keyword),
'text': extract,
'url': data.get('content_urls', {}).get('desktop', {}).get('page', ''),
'description': data.get('description', '')
}
except Exception as e:
print(f"[ERROR] {keyword}: 取得エラー {e}")
return None
def split_into_detailed_chunks(article: Dict) -> List[Dict]:
"""詳細分割でチャンク作成"""
text = article['text']
title = article['title']
# より細かい分割(読点、中黒、括弧なども含む)
parts = re.split(r'[。!?、・\s()\(\)「」『』【】]', text)
chunks = []
current_chunk = ""
chunk_id = 1
for part in parts:
part = part.strip()
if not part:
continue
# 短すぎる部分は結合
if len(part) < 3:
current_chunk += part
else:
# 現在のチャンクを保存
if current_chunk and len(current_chunk) >= 5:
chunks.append({
'title': title,
'text': current_chunk.strip(),
'url': article['url'],
'description': article['description'],
'chunk_id': f"{title}-detailed-{chunk_id}"
})
chunk_id += 1
# 新しいチャンク開始
current_chunk = part
# 最後のチャンク
if current_chunk and len(current_chunk) >= 5:
chunks.append({
'title': title,
'text': current_chunk.strip(),
'url': article['url'],
'description': article['description'],
'chunk_id': f"{title}-detailed-{chunk_id}"
})
return chunks
def generate_embeddings(chunks: List[Dict]) -> List[List[float]]:
"""チャンクをベクトル化"""
bedrock = boto3.client('bedrock-runtime', region_name=AWS_REGION)
embeddings = []
print(f"[INFO] {len(chunks)}個のチャンクをベクトル化中...")
for i, chunk in enumerate(chunks, 1):
try:
body = json.dumps({"inputText": chunk['text']})
response = bedrock.invoke_model(
modelId='amazon.titan-embed-text-v2:0',
body=body
)
result = json.loads(response['body'].read())
embedding = result['embedding']
embeddings.append(embedding)
if i % 5 == 0:
print(f"[INFO] 進捗: {i}/{len(chunks)}")
except Exception as e:
print(f"[ERROR] ベクトル化エラー ({i}/{len(chunks)}): {e}")
embeddings.append(None)
continue
valid_embeddings = [emb for emb in embeddings if emb is not None]
print(f"[SUCCESS] ベクトル化完了: {len(valid_embeddings)}個")
return valid_embeddings
def upload_to_s3vectors(chunks: List[Dict], embeddings: List[List[float]]) -> bool:
"""S3 Vectorsにアップロード"""
s3vectors = boto3.client('s3vectors', region_name=AWS_REGION)
vectors = []
for chunk, embedding in zip(chunks, embeddings):
if embedding is None:
continue
vector = {
'key': str(uuid.uuid4()),
'data': {'float32': embedding},
'metadata': {
'text': chunk['text'],
'title': chunk['title'],
'source': 'wikipedia_detailed_chunked',
'url': chunk.get('url', ''),
'description': chunk.get('description', ''),
'chunk_id': chunk.get('chunk_id', '')
}
}
vectors.append(vector)
print(f"[INFO] {len(vectors)}個のベクトルをS3 Vectorsにアップロード中...")
try:
response = s3vectors.put_vectors(
vectorBucketName=VECTOR_BUCKET_NAME,
indexName=VECTOR_INDEX_NAME,
vectors=vectors
)
print("[SUCCESS] アップロード完了!")
return True
except Exception as e:
print(f"[ERROR] アップロードエラー: {e}")
return False
def main():
print("=== Wikipedia 詳細分割データ収集・投入ツール ===")
print(f"バケット: {VECTOR_BUCKET_NAME}")
print(f"インデックス: {VECTOR_INDEX_NAME}")
print()
# 1. キーワード読み込み
keywords = load_keywords()
# 2. Wikipedia要約取得
print(f"\n[INFO] Wikipedia要約取得開始...")
articles = []
for keyword in keywords:
article = get_wikipedia_summary(keyword)
if article:
articles.append(article)
if not articles:
print("[ERROR] 取得できた記事がありません")
exit(1)
print(f"[SUCCESS] {len(articles)}個の要約を取得完了")
# 3. 詳細分割
print(f"\n[INFO] 詳細分割開始...")
all_chunks = []
for article in articles:
chunks = split_into_detailed_chunks(article)
all_chunks.extend(chunks)
print(f"[INFO] {article['title']}: {len(chunks)}個のチャンクに分割")
print(f"[SUCCESS] 総チャンク数: {len(all_chunks)}個")
# 4. ベクトル化
print(f"\n[INFO] ベクトル化開始")
embeddings = generate_embeddings(all_chunks)
# 5. S3 Vectorsアップロード
print(f"\n[INFO] S3 Vectorsアップロード開始")
valid_chunks = []
valid_embeddings = []
for chunk, embedding in zip(all_chunks, embeddings):
if embedding is not None:
valid_chunks.append(chunk)
valid_embeddings.append(embedding)
if valid_chunks and valid_embeddings:
success = upload_to_s3vectors(valid_chunks, valid_embeddings)
if success:
print(f"\n[SUCCESS] 完了! {len(valid_embeddings)}個の詳細チャンクベクトルを投入しました")
else:
print(f"\n[ERROR] アップロード失敗")
else:
print(f"\n[ERROR] アップロードできるデータがありません")
if __name__ == "__main__":
main()
EOF
3.上記スクリプトの実行画面
# スクリプト実行
python wikipedia_data_loader_detailed.py
# レスポンス
=== Wikipedia 詳細分割データ収集・投入ツール ===
バケット: sangokushi-rag-bucket-1752990275
インデックス: sangokushi-index
[INFO] 10個のキーワードを読み込み
[INFO] Wikipedia要約取得開始...
[SUCCESS] 劉備: 34文字の要約を取得
[SUCCESS] 曹操: 79文字の要約を取得
[SUCCESS] 孫権: 32文字の要約を取得
[SUCCESS] 諸葛亮: 44文字の要約を取得
[SUCCESS] 関羽: 85文字の要約を取得
[SUCCESS] 張飛: 123文字の要約を取得
[SUCCESS] 趙雲: 67文字の要約を取得
[SUCCESS] 司馬懿: 150文字の要約を取得
[SUCCESS] 周瑜: 111文字の要約を取得
[SUCCESS] 呂布: 87文字の要約を取得
[SUCCESS] 10個の要約を取得完了
[INFO] 詳細分割開始...
[INFO] 劉備: 2個のチャンクに分割
[INFO] 曹操: 7個のチャンクに分割
[INFO] 孫権: 3個のチャンクに分割
[INFO] 諸葛亮: 2個のチャンクに分割
[INFO] 関羽: 7個のチャンクに分割
[INFO] 張飛: 9個のチャンクに分割
[INFO] 趙雲: 4個のチャンクに分割
[INFO] 司馬懿: 9個のチャンクに分割
[INFO] 周瑜: 10個のチャンクに分割
[INFO] 呂布: 6個のチャンクに分割
[SUCCESS] 総チャンク数: 59個
[INFO] ベクトル化開始
[INFO] 59個のチャンクをベクトル化中...
[INFO] 進捗: 5/59
[INFO] 進捗: 10/59
[INFO] 進捗: 15/59
[INFO] 進捗: 20/59
[INFO] 進捗: 25/59
[INFO] 進捗: 30/59
[INFO] 進捗: 35/59
[INFO] 進捗: 40/59
[INFO] 進捗: 45/59
[INFO] 進捗: 50/59
[INFO] 進捗: 55/59
[SUCCESS] ベクトル化完了: 59個
[INFO] S3 Vectorsアップロード開始
[INFO] 59個のベクトルをS3 Vectorsにアップロード中...
[SUCCESS] アップロード完了!
[SUCCESS] 完了! 59個の詳細チャンクベクトルを投入しました
4.ベクトルデータ確認
# 投入されたベクトル確認
aws s3vectors list-vectors --vector-bucket-name "${VECTOR_BUCKET_NAME}" --index-name "${VECTOR_INDEX_NAME}" --region us-east-1 --max-results 10
# レスポンス
{
"nextToken": "2GDD6KniBdXLB-J6DuvcP32-nZOuVCcwPJ8ZcmqgcDdySyTGBEXaPdsYOAOTSaTd__tfK4aQheBO-LzL4MwllGp0yGtq3FdSkRsLPv8x5dbROh9gQDH3_qzrYX1R_g",
"vectors": [
{
"key": "a969e08f-1eda-4afc-802f-6cf166ef8efe"
},
...以下省略(同様の Keyが10個表示される)
2.3.データ検索スクリプト 作成
RAGに質問(質問内容をベクトル化)する スクリプトを作成
1.検索スクリプト作成
# 検索スクリプト作成
cat > query_sangokushi.py << 'EOF'
#!/usr/bin/env python3
"""
三国志RAGシステム クエリツール
使用方法: python3 query_sangokushi.py "劉備について教えて"
"""
import boto3
import json
import os
import sys
# 環境変数
VECTOR_BUCKET_NAME = os.environ.get('VECTOR_BUCKET_NAME')
VECTOR_INDEX_NAME = os.environ.get('VECTOR_INDEX_NAME')
AWS_REGION = os.environ.get('AWS_REGION', 'us-east-1')
def embed_question(question: str):
"""質問をベクトル化"""
bedrock = boto3.client('bedrock-runtime', region_name=AWS_REGION)
body = json.dumps({"inputText": question})
response = bedrock.invoke_model(
modelId='amazon.titan-embed-text-v2:0',
body=body
)
result = json.loads(response['body'].read())
return result['embedding']
def search_vectors(query_vector, top_k=3):
"""ベクトル検索"""
s3vectors = boto3.client('s3vectors', region_name=AWS_REGION)
response = s3vectors.query_vectors(
vectorBucketName=VECTOR_BUCKET_NAME,
indexName=VECTOR_INDEX_NAME,
queryVector={'float32': query_vector},
topK=top_k,
returnMetadata=True,
returnDistance=True
)
return response['vectors']
def main():
if len(sys.argv) < 2:
print("使用方法: python3 query_sangokushi.py '質問文'")
print("例: python3 query_sangokushi.py '劉備について教えて'")
sys.exit(1)
question = sys.argv[1]
print(f"[INFO] 質問: {question}")
print(f"[INFO] ベクトル化中...")
query_vector = embed_question(question)
print(f"[INFO] 検索中...")
results = search_vectors(query_vector)
print(f"\n=== 検索結果 ===")
for i, result in enumerate(results, 1):
metadata = result.get('metadata', {})
distance = result.get('distance', 0)
print(f"\n{i}. タイトル: {metadata.get('title', 'N/A')}")
print(f" 類似度: {1-distance:.3f}")
print(f" 内容: {metadata.get('text', 'N/A')}")
print(f" URL: {metadata.get('url', 'N/A')}")
if __name__ == "__main__":
main()
EOF
3.検索実行
3.1.検索実行
1.検索実行
# 検索する質問
python query_sangokushi.py "蜀の武将は?"
=== 検索結果 ===
1. タイトル: 張飛
類似度: 0.663
内容: 中国後漢末期から三国時代の蜀の将軍
URL: https://ja.wikipedia.org/wiki/%E5%BC%B5%E9%A3%9B
2. タイトル: 趙雲
類似度: 0.603
内容: 中国後漢末期から三国時代にかけての蜀漢の武将
URL: https://ja.wikipedia.org/wiki/%E8%B6%99%E9%9B%B2
3. タイトル: 諸葛亮
類似度: 0.524
内容: 中国後漢末期から三国時代の蜀漢の政治家武将軍師
URL: https://ja.wikipedia.org/wiki/%E8%AB%B8%E8%91%9B%E4%BA%AE
3.2.検索改善
分割数を分ければ分けるほど、検索類似度も上昇が見込める。
※ MeCab
などの品詞によるベクトル分割までは未実施。単純な分割でのベクトル化を実施。
項番 | チャンク分け方 | 分割数(10偉人を挿入しての分割された数) | 検索類似度(上位3つの平均) |
---|---|---|---|
1 | 無分割 | 10分割 | ≒0.39 |
2 | [。! ?、] | 21分割 | ≒0.43 |
3 | [。!?、・\s()()「」『』【】] | 59分割 | ≒0.60 |
4.リソース削除
リソース削除
# ベクトルインデックス削除
aws s3vectors delete-index \
--vector-bucket-name "${VECTOR_BUCKET_NAME}" \
--index-name "${VECTOR_INDEX_NAME}" \
--region us-east-1
# ベクトルバケット削除
aws s3vectors delete-vector-bucket \
--vector-bucket-name "${VECTOR_BUCKET_NAME}" \
--region us-east-1
5.おわりに
5.1.得られた知見
- S3 VectorsDB作成方法
5.2.今後の課題
- 品詞による詳細なベクトル分割の実装
- ベクトルDBより取得できた内容を集約して返信するLLMの拡張