
Bedrock Knowledge Baseのメタデータフィルタリング——ドキュメントに属性を付けて検索対象を制御する仕組み
Bedrock Knowledge Baseには、ドキュメントにメタデータを付けて検索対象を絞るメタデータフィルタリングという機能がある。
この記事では「どの段階でフィルターがかかるのか」という仕組みから、S3への配置方法、フィルター構文の全体像まで解説する。実装コードはGenUのRAGに部署別アクセス制御を追加した記事のカスタムLambdaを題材にする。
メタデータフィルタリングとは
Knowledge Baseのデフォルト動作は、登録されている全ドキュメントを対象にベクトル検索する。「部署Aのユーザーには部署Aのドキュメントだけ返したい」という要件はデフォルトでは実現できない。
メタデータフィルタリングは、ドキュメントに department: 営業部 のような属性(メタデータ)を付与しておき、API呼び出し時に絞り込み条件を渡す仕組み。フィルタリングを実行するのはBedrock側で、Lambdaは条件をAPIリクエストに乗せるだけ。
フィルターはいつかかるのか
ここが誤解されやすいポイント。「検索した後に条件に合わないものを捨てる」と思われがちだが、実際はベクトル検索の前段でフィルターが適用される。
Query
↓
metadata filter(ここで検索対象を絞る)
↓
vector search(絞られた範囲だけベクトル検索)
↓
top-k documents
↓
LLM(回答生成)
検索後フィルタの場合、5件取得した後に条件外を捨てると最終的に0件になることがある。検索前フィルタなら最初から対象ドキュメントだけを検索するので、numberOfResults: 5 の指定が意図通りに機能する。
S3のメタデータ構造
Knowledge Baseがインデックスを作るとき、S3上のドキュメントと同名の .metadata.json ファイルを自動的に読み込む。
ファイル配置
s3://your-bucket/
documents/
営業部/
営業部_規程.txt
営業部_規程.txt.metadata.json ← 同名 + .metadata.json
開発部/
開発部_規程.txt
開発部_規程.txt.metadata.json
人事部/
人事部_規程.txt
人事部_規程.txt.metadata.json
命名規則は「ドキュメントのファイル名 + .metadata.json」。同じS3プレフィックスに置く。
metadata.json の中身
{
"metadataAttributes": {
"department": "営業部"
}
}
metadataAttributes の中にキーと値を書く。値は文字列・数値・真偽値が使える。属性を増やすほどフィルター条件の組み合わせが豊富になる。
{
"metadataAttributes": {
"department": "開発部",
"confidential": true,
"year": 2026
}
}
アップロードスクリプト
手動でmetadata.jsonを作るのは現実的でないため、スクリプト化する。2つのモードがある。
# 個別ファイルをアップロード(--department の値がメタデータになる)
python upload.py 営業部_規程.txt --department 営業部 --bucket your-bucket-name
# デモ用サンプル(営業部・開発部・人事部の3ファイル)を一括生成してアップロード
python upload.py --demo --bucket your-bucket-name
実行すると、ドキュメントとmetadata.jsonがペアでS3に配置される。
[OK] document: s3://your-bucket/documents/営業部/営業部_規程.txt
[OK] metadata : s3://your-bucket/documents/営業部/営業部_規程.txt.metadata.json
コアとなる処理は以下の通り。--department で指定した値がそのままmetadata.jsonの中身になり、S3へのアップロードまで自動で行う。
def create_metadata_json(department: str) -> dict:
return {
"metadataAttributes": {
"department": department
}
}
def upload_document_with_metadata(file_path: str, department: str, bucket_name: str):
s3 = boto3.client("s3")
file_name = Path(file_path).name
s3_key = f"documents/{department}/{file_name}"
metadata_key = f"{s3_key}.metadata.json"
# ドキュメント本体とメタデータJSONを両方アップロード
s3.upload_file(file_path, bucket_name, s3_key)
s3.put_object(
Bucket=bucket_name,
Key=metadata_key,
Body=json.dumps(create_metadata_json(department), ensure_ascii=False, indent=2),
ContentType="application/json",
)
アップロード後はKBのsyncが必須
S3にファイルを置いただけではKnowledge Baseには反映されない。AWSコンソールまたはAPIでsync(インデックス再作成)を実行する必要がある。metadata.jsonを更新した場合も同様。
aws bedrock-agent start-ingestion-job \
--knowledge-base-id <KB_ID> \
--data-source-id <DS_ID>
Bedrock APIでのフィルター指定
retrieve_and_generate APIの filter キーに条件を指定すると、Bedrockが検索時に適用する。
response = client.retrieve_and_generate(
input={"text": query},
retrieveAndGenerateConfiguration={
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": knowledge_base_id,
"modelArn": model_arn,
"retrievalConfiguration": {
"vectorSearchConfiguration": {
"numberOfResults": 5,
"filter": {
"equals": {
"key": "department",
"value": department, # JWTから取得した部署名
}
},
}
},
},
},
)
フィルター演算子の一覧
単一条件
| 演算子 | 用途 |
|---|---|
equals |
一致 |
notEquals |
不一致 |
in |
リストのいずれかに一致 |
notIn |
リストのいずれにも不一致 |
startsWith |
前方一致 |
stringContains |
部分一致 |
greaterThan / greaterThanOrEquals |
数値・日付の比較(以上) |
lessThan / lessThanOrEquals |
数値・日付の比較(以下) |
複合条件
| 演算子 | 意味 |
|---|---|
andAll |
すべての条件を満たす(AND) |
orAll |
いずれかの条件を満たす(OR) |
in(複数部署のどれかに一致)
"filter": {
"in": {
"key": "department",
"value": ["営業部", "開発部"]
}
}
andAll(AND条件)
# 開発部かつ機密でないドキュメントだけ
"filter": {
"andAll": [
{"equals": {"key": "department", "value": "開発部"}},
{"equals": {"key": "confidential", "value": False}}
]
}
orAll(OR条件)
"filter": {
"orAll": [
{"equals": {"key": "department", "value": "営業部"}},
{"equals": {"key": "department", "value": "開発部"}}
]
}
andAll と orAll はネストも可能。「(営業部 OR 開発部) AND confidential=false」のような多段階の制御も書ける。
ハマりポイント
chunkに分割されたドキュメントの扱い
Knowledge Baseはingestion時にドキュメントを一定サイズのchunkに分割してベクトル化する。1ファイルが複数chunkになった場合も、全chunkに同じメタデータが付与される。「長いドキュメントの一部にだけメタデータを付けたい」という要件には対応できない。メタデータはファイル単位。
metadata.jsonの値の型に注意
フィルター条件の value の型は、metadata.jsonで登録した型と一致させる必要がある。
{"metadataAttributes": {"year": 2026}}
# NG: 文字列で比較するとエラー
"filter": {"equals": {"key": "year", "value": "2026"}}
# OK
"filter": {"equals": {"key": "year", "value": 2026}}
条件に合うドキュメントが0件のとき
該当ドキュメントが存在しない場合、retrieve_and_generate はエラーではなくnormal responseとして返ってくる。回答文の内容はモデルによって変わるため、citationsの件数が0かどうかで判定するのが確実。
citations = response.get("citations", [])
if not citations:
# フィルター条件に合うドキュメントがなかった
...
まとめ
| ポイント | 内容 |
|---|---|
| フィルタータイミング | ベクトル検索の前に絞る |
| metadata.jsonの配置 | ドキュメントと同名 + .metadata.json をS3に同じ場所に置く |
| sync必須 | S3更新後はKBのインジェストジョブを実行 |
| フィルター構文 | equals / in / andAll / orAll など |
| 型の一致 | metadata.jsonの型とフィルター条件の型を合わせる |
→ retrieve_and_generate APIの構造を解説した記事 | → GenUのRAGに部署別アクセス制御を追加した(ハブ記事)