
Bedrock Knowledge Basesの階層チャンキングで、メタデータが想定と違う形式で格納されていた話
はじめに
Bedrock Knowledge Basesを使ったRAGシステムで、検索結果にドキュメントの作成日時などのカスタムメタデータを表示しようとしたところ、OpenSearchに格納されているメタデータの形式が想定と全く違っていてハマりました。
S3上のサイドカー .metadata.json にはメタデータを入れていたので「OpenSearchから取り出すだけ」と思っていたのですが、 階層チャンキング(Hierarchical chunking) を使っている場合は事情が異なります。
この記事では、階層チャンキング使用時にOpenSearch Serverlessに格納される AMAZON_BEDROCK_METADATA の実際の構造と、カスタムメタデータを正しく取得する方法を紹介します。
前提・環境
- Amazon Bedrock Knowledge Bases
- チャンキング戦略: 階層チャンキング(Hierarchical chunking)
- ベクトルストア: Amazon OpenSearch Serverless
- データソース: S3上のドキュメント + サイドカー
.metadata.json - バックエンド: Python
S3側のメタデータ構造
Bedrock KBでは、S3上のドキュメントにサイドカーファイル(.metadata.json)を置くことでカスタムメタデータを付与できます。例えば以下のような形式です。
{
"metadataAttributes": {
"category": "FAQ",
"priority": "high",
"created_on": "2024-04-01T00:49:42Z",
"author": "yamada"
}
}
想定していたメタデータ構造
OpenSearchに格納される AMAZON_BEDROCK_METADATA フィールドには、サイドカーの metadataAttributes がそのまま入ると思っていました。
# 想定していたコード
meta = chunk.get("metadata") # AMAZON_BEDROCK_METADATA
attrs = meta.get("metadataAttributes")
created_on = attrs.get("created_on") # "2024-04-01T00:49:42Z" が取れるはず
実際にデプロイしてみると、複数の検索結果のうち一部はメタデータが取得できるのに、残りでは取得できないという状況になりました。
実際のメタデータ構造

ログを仕込んで確認したところ、AMAZON_BEDROCK_METADATA の実際の中身はこうでした。
{
"source": "s3://my-bucket/document-001.md",
"parentText": "# ドキュメントタイトル\n\n## 基本情報\n- 作成日: 2024-04-01T00:49:42Z\n- カテゴリ: FAQ\n..."
}
metadataAttributes は含まれていません。 代わりに source(S3 URI)と parentText(該当する親チャンクのテキスト)の2フィールドだけが格納されていました。
これは階層チャンキング特有の構造です。階層チャンキングでは、子チャンクがベクトル化・検索され、検索にヒットすると対応する親チャンクがLLMに渡されます。OpenSearchのドキュメントには、子チャンクの content と、親チャンクのテキストが parentText として AMAZON_BEDROCK_METADATA に格納されます。
なお、Bedrock KBの Retrieve API を使った場合は、レスポンスの metadata フィールドにサイドカーの metadataAttributes が含まれます。しかし、OpenSearchに直接格納されるドキュメントの AMAZON_BEDROCK_METADATA フィールドには含まれないため、OpenSearchを直接クエリする場合は注意が必要です。
なぜ一部だけ取得できたのか
最初のコードでは metadataAttributes からの取得に失敗した場合のフォールバックとして、子チャンクの content に対して正規表現で 作成日: を検索していました。
# フォールバック: 子チャンクのcontentから正規表現で抽出
cm = re.search(r"作成日[::]\s*(\S+)", chunk.get("content", ""))
ドキュメントの先頭付近に 作成日: を含むセクションがあります。一部の子チャンクではこのセクションがたまたま content に含まれていたのでフォールバックが成功しました。しかし、子チャンクがそのセクションを含まない箇所で分割されていた場合は、フォールバックも失敗していました。
修正: parentTextも検索対象にする
parentText は親チャンクの全文なので、必ず該当のセクションを含んでいます。フォールバックの検索対象に parentText を追加することで、すべてのドキュメントで確実に created_on を取得できるようになりました。
_CREATED_ON_PATTERN = re.compile(r"作成日[::]\s*(\S+)")
def _extract_created_on(chunks: list[dict]) -> dict[str, str]:
"""チャンクのリストから、ドキュメントごとの作成日を抽出する"""
result: dict[str, str] = {}
for chunk in chunks:
source = chunk.get("source_uri", "")
if source in result:
continue
# 1. metadataAttributes から直接取得
meta = chunk.get("metadata")
if isinstance(meta, dict):
attrs = meta.get("metadataAttributes", meta)
raw = attrs.get("created_on", "")
else:
raw = ""
if not raw:
# 2. 子チャンクの content → parentText の順で正規表現抽出
parent_text = meta.get("parentText", "") if isinstance(meta, dict) else ""
for text in (chunk.get("content", ""), parent_text):
cm = _CREATED_ON_PATTERN.search(text)
if cm:
raw = cm.group(1)
break
if raw:
result[source] = raw
return result
ポイントは3段構えの抽出戦略です:
metadataAttributes.created_onを直接取得(カスタムインデクサーでメタデータを正しく格納した場合)- 子チャンクの
contentから正規表現で抽出(子チャンクが該当セクションを含む場合) parentTextから正規表現で抽出(親チャンクは必ず全文を含むので確実)
学んだこと
Bedrock KBの階層チャンキングにおける AMAZON_BEDROCK_METADATA の構造:
| フィールド | 内容 |
|---|---|
source |
S3上のドキュメントURI |
parentText |
親チャンクのテキスト |
サイドカー .metadata.json の metadataAttributes は、OpenSearchの AMAZON_BEDROCK_METADATA には含まれません。Retrieve APIレスポンスには含まれるため、混同しやすい点に注意してください。
まとめ
- Bedrock KBの階層チャンキングでは、OpenSearchの
AMAZON_BEDROCK_METADATAにサイドカーのmetadataAttributesが直接格納されない - 代わりに
source(S3 URI)とparentText(親チャンクの全文)が格納される - カスタムメタデータを取得したい場合は、
parentTextのテキストから正規表現等で抽出するフォールバックを用意するのが確実 - 想定と実際のデータ構造が異なる場合は、推測で対処せずログを仕込んで実データを確認するのが最短ルート





