ankuro.dev
← ブログ一覧に戻る
Bedrock Knowledge Baseのメタデータフィルタリング——ドキュメントに属性を付けて検索対象を制御する仕組み
2026-03-19#AWS#Bedrock#Knowledge Base#RAG#GenU

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": "開発部"}}
    ]
}

andAllorAll はネストも可能。「(営業部 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に部署別アクセス制御を追加した(ハブ記事)