Amazon Bedrock Knowledge Basesで同期したメタデータがAmazon OpenSearch Serviceにどう格納されるか確認してみた

Amazon Bedrock Knowledge Basesで同期したメタデータがAmazon OpenSearch Serviceにどう格納されるか確認してみた

2025.12.20

はじめに

こんにちは、スーパーマーケットが大好きなコンサルティング部の神野です。

Bedrock Knowledge Basesでメタデータファイル(.metadata.json)を設定した際に、Amazon OpenSearch Serviceへメタデータも同期されますよね。

このメタデータを活用して、通常はRetriveRetriveAndGenerateコマンドでメタデータフィルタリングを行いますが、以下のようなケースではOpenSearchに直接クエリを発行したいことがあります。少なくとも私はありました・・・!

  • Bedrockへのリクエスト前に、独自ロジックでメタデータによる絞り込みを行って、後続で独自の処理を行いたい
  • ドキュメントの集計やグルーピングなど、対応していない処理を行いたい

こういったケースの際に.metadata.jsonで定義した情報が、Knowledge BasesでOpenSearchに同期した場合、フィールドにどう表現されているのかわからないとOpenSearchのクエリをどう書いたらいいかわからないですよね。

CleanShot 2025-12-20 at 22.19.48@2x

なので、今回は確認して実際に設定したメタデータに対してクエリを実行して挙動を確認することにしました。

今回やること

S3に配置したメタデータがOpenSearchにどう同期されるかを確認し、
OpenSearchのダッシュボードでDev Toolsから直接クエリを発行して挙動を確認してみます。

環境

  • Amazon Bedrock Knowledge Bases(OpenSearch Serverlessをベクトルストアとして使用)
  • AWS CDKで構築

CDKのサンプルコードは以下に配置しています。

https://github.com/yuu551/sample-knowledge

CDKで作成されるリソース

今回のCDKスタックでは@cdklabs/generative-ai-cdk-constructsを使用して以下のリソースを作成しています。

リソース 説明
S3バケット ドキュメント格納用。サンプルドキュメントも自動でデプロイされます
Bedrock Knowledge Bases OpenSearch Serverlessをベクトルストアとして自動作成
S3データソース Knowledge BaseとS3バケットを紐付け
OpenSearch Serverless Collection Knowledge Base作成時に自動で構築されるベクトルストア

Knowledge Baseの主な設定値は以下のとおりです。

設定項目
Embedding Model Amazon Titan Text Embeddings V2(1024次元)
Vector Store OpenSearch Serverless
Chunking Strategy Fixed Size
Max Tokens 500
Overlap Percentage 20%

generative-ai-cdk-constructsVectorKnowledgeBaseを使って、OpenSearch Serverlessのコレクションやインデックス、データアクセスポリシーなどを作成します。

サンプルドキュメント

検証用に3種類のドキュメントとメタデータファイルを用意しています。

ファイル document_type department is_public 備考
annual-report-2024.txt report finance false 年次報告書。year属性あり
tech-blog-aws-bedrock.txt blog engineering true 技術ブログ。tags属性(STRING_LIST)あり
product-manual-v2.txt manual product true 製品マニュアル。version属性あり

それぞれのドキュメントに異なるメタデータ属性を設定して、型ごとの挙動を確認できるようにしています。
またサンプルのドキュメントメタデータもCDKのデプロイを実施するとアップロードされます。

S3メタデータファイルの形式

Bedrock Knowledge Basesでメタデータを設定するには、対象ドキュメントと同じS3パスにファイル名.拡張子.metadata.jsonという名前でメタデータファイルを配置します。
例えばannual-report-2024.txtに対してはannual-report-2024.txt.metadata.jsonを同じ場所に置く形ですね。

今回使用したメタデータファイルの例に見てみます。

{
  "metadataAttributes": {
    "document_type": {
      "value": { "type": "STRING", "stringValue": "report" }
    },
    "priority": {
      "value": { "type": "NUMBER", "numberValue": 95 }
    },
    "is_public": {
      "value": { "type": "BOOLEAN", "booleanValue": false }
    },
    "department": {
      "value": { "type": "STRING", "stringValue": "finance" }
    },
    "year": {
      "value": { "type": "NUMBER", "numberValue": 2024 }
    },
    "tags": {
      "value": {
        "type": "STRING_LIST",
        "stringListValue": ["aws", "bedrock", "generative-ai"]
      }
    }
  }
}

typeにはSTRINGNUMBERBOOLEANSTRING_LISTの4種類が指定可能です。

なお、メタデータには制約があります。属性名は最大50個まで、文字列は最大2048文字まで、STRING_LISTの要素数は最大10個までとなっています。

https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent_DocumentMetadata.html

https://docs.aws.amazon.com/ja_jp/bedrock/latest/APIReference/API_agent_MetadataAttributeValue.html

デプロイ手順

前提・使ったバージョン

  • Node.js v24.10.0

インストール

git clone https://github.com/yuu551/sample-knowledge.git
cd sample-knowledge
pnpm install

パラメータ設定

lib/parameter.tsでOpenSearch Serverlessダッシュボードへのアクセス権限を設定します。
自分のIAMユーザーまたはロールのARNを追加してください。

lib/parameter.ts
export const parameter = {
  /**
   * OpenSearch Serverlessダッシュボードにアクセスを許可するIAMプリンシパル
   */
  opensearchAccessPrincipals: [
    'arn:aws:iam::123456789012:user/your-username',
    // 'arn:aws:iam::123456789012:role/Admin',
  ] as string[],

  // ... その他の設定
};

デプロイ

初回の場合はBootstrapが必要です。
deployコマンドを実行してデプロイを実施します。

# 初回のみ
cdk bootstrap

# デプロイ
cdk deploy

デプロイ完了後、以下の出力が表示されます。

Outputs:
SampleKnowledgeStack.KnowledgeBaseId = XXXXXXXXXX
SampleKnowledgeStack.DataSourceId = XXXXXXXXXX
SampleKnowledgeStack.DocumentBucketName = sampleknowledgestack-docbucket...
SampleKnowledgeStack.OpenSearchCollectionArn = arn:aws:aoss:...

これでインフラ環境一式は作成できました!Knowledge Bases の画面から同期を行います。

データの同期

デプロイ後、コンソールからデータソースの同期を実行してください。
実行することでデータがOpenSearchへベクトル化されてデータが保存されます。

  1. Bedrockコンソール → Knowledge bases → 作成されたKnowledge Baseを選択
  2. Data source → 同期(Sync)を実行

CleanShot 2025-12-17 at 22.08.46@2x

OpenSearchダッシュボードにアクセスする

データソースの同期が完了したら、OpenSearch Serverlessのダッシュボードからメタデータがどう格納されているか確認してみます。

AWSコンソールでOpenSearch Serviceを開き、左メニューから Serverless > Collectionsを選択します。CDKで作成したコレクションがあるので、それを選択してDashBoardをクリックします。

CleanShot 2025-12-20 at 19.26.38@2x

ダッシュボードにアクセスできたら、左メニューからDev Toolsを開きます。
ここでOpenSearchに対して直接クエリを発行できます。

CleanShot 2025-12-20 at 19.29.06@2x

左側のエディタにクエリを入力して、再生ボタンを押すと右側に結果が表示されます。

CleanShot 2025-12-20 at 19.30.11@2x

ダッシュボードにアクセスできない場合は、データアクセスポリシーに自分のIAMロールが追加されているか確認してください。
今回のCDKではparameter.tsで指定したロールに対してアクセス権限を付与しています。

OpenSearchへの格納形式を確認

それでは実際にメタデータがどのような形式でOpenSearchに格納されているか見ていきましょう。

マッピングを確認する

まずはインデックスのマッピングを確認します。Dev Toolsで以下のクエリを実行してどういったマッピングでデータが登録されるのかを確認してみます!

GET /bedrock-knowledge-base-default-index/_mapping

CleanShot 2025-12-20 at 19.32.05@2x

結果を見ると、Bedrockが自動で作成するフィールドに加えて、メタデータで定義した属性がフィールドとして追加されていることがわかります。

{
  "bedrock-knowledge-base-default-index": {
    "mappings": {
      "properties": {
        "AMAZON_BEDROCK_METADATA": {
          "type": "text",
          "index": false
        },
        "AMAZON_BEDROCK_TEXT_CHUNK": {
          "type": "text"
        },
        "bedrock-knowledge-base-default-vector": {
          "type": "knn_vector",
          "dimension": 1024,
          "method": {
            "engine": "faiss",
            "space_type": "l2",
            "name": "hnsw",
            "parameters": {}
          }
        },
        "document_type": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        },
        "department": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        },
        "priority": { "type": "float" },
        "is_public": { "type": "boolean" },
        "year": { "type": "float" },
        "tags": {
          "type": "text",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 }
          }
        }
      }
    }
  }
}

なるほどこんな形で登録されるんですね。
AMAZON_BEDROCK_TEXT_CHUNKがチャンク分割されたドキュメント本文、bedrock-knowledge-base-default-vectorがそのベクトル表現です。そしてdocument_typepriorityといった、メタデータで定義した属性が個別のフィールドとして展開されています。

Bedrockがデータソースの同期時に.metadata.jsonを読み取って、自動でフィールドを作成してくれる仕組みになっていることがこれでわかりますね。

実データを確認する

次に実際のドキュメントがどう格納されているか見てみます。

GET /bedrock-knowledge-base-default-index/_search

_sourceフィールドにメタデータが個別の属性として格納されていることが確認できます。

{
  "_source": {
    "document_type": "report",
    "department": "finance",
    "priority": 95,
    "is_public": false,
    "year": 2024,
    "AMAZON_BEDROCK_TEXT_CHUNK": "ドキュメント本文...",
    "bedrock-knowledge-base-default-vector": [...],
    "tags": ["aws", "bedrock", "generative-ai", "rag"]
  }
}

メタデータの値がそのままフィールド値として入っていました!
通常のOpenSearchクエリでフィルタリングや集計ができますね。

型のマッピングについて

メタデータの型がOpenSearchでどのようにマッピングされるか整理しておきます。

メタデータの型 OpenSearchのマッピング
STRING text型(keywordサブフィールド付き)
NUMBER float型
BOOLEAN boolean型
STRING_LIST text型(keywordサブフィールド付き)

ポイントは文字列系の型はtext型になるという点です。

OpenSearchのtext型は全文検索用にトークン分割されるため、完全一致で検索したい場合は.keywordサブフィールドを使う必要があります。例えばdocument_typereportのドキュメントを検索したい場合は、document_type.keywordに対してクエリを発行します。

また、date型への直接マッピングはサポートされていません。日付を扱いたい場合はNUMBER型でUNIXタイムスタンプやYYYYMMDD形式の数値として格納する形になります。今回のサンプルではyearを数値として定義しています。

Dev Toolsでクエリを試す

それでは実際にクエリを発行して、メタデータでフィルタリングできるか試してみます。

完全一致検索

まずはdocument_typereportで、かつis_publicfalseのドキュメントを検索してみます。

GET /bedrock-knowledge-base-default-index/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "document_type.keyword": "report" } },
        { "term": { "is_public": false } }
      ]
    }
  }
}

ここで注意が必要なのが、文字列フィールドには.keywordを付ける必要があるという点です。先ほどのマッピングで見たとおり、文字列はtext型で格納されているため、完全一致で検索したい場合はkeywordサブフィールドを使います。これを忘れると意図した結果が返ってこないので気をつけたいですね。

実行結果を見ると、期待どおり年次報告書のドキュメントが1件ヒットしました。

{
  "hits": {
    "total": { "value": 1, "relation": "eq" },
    "hits": [
      {
        "_source": {
          "x-amz-bedrock-kb-source-uri": "s3://sampleknowledgestack-docbucket.../annual-report-2024.txt",
          "year": 2024,
          "is_public": false,
          "priority": 95,
          "department": "finance",
          "AMAZON_BEDROCK_TEXT_CHUNK": "2024年度 年次報告書  概要 本報告書は、2024年度における当社の事業活動、財務状況、および今後の展望についてまとめたものです。...",
          "document_type": "report"
        }
      }
    ]
  }
}

CleanShot 2025-12-20 at 21.33.12@2x

CleanShot 2025-12-20 at 21.34.18@2x

数値フィールドでの絞り込み

次はyearフィールドで2024年のドキュメントを検索してみます。

GET /bedrock-knowledge-base-default-index/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "year": 2024 } }
      ]
    }
  }
}

数値フィールドの場合は.keywordは不要です。NUMBER型で定義したメタデータはfloat型としてマッピングされているので、そのまま数値で検索できます。

CleanShot 2025-12-20 at 21.34.51@2x

CleanShot 2025-12-20 at 21.35.27@2x

しっかりとメタデータが2024年度のデータが取得できましたね。

STRING_LISTの検索

tagsのようなSTRING_LIST型のフィールドも試してみましょう。departmentengineeringで、tagsawsを含むドキュメントを検索します。

GET /bedrock-knowledge-base-default-index/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "department.keyword": "engineering" } },
        { "term": { "tags.keyword": "aws" } }
      ]
    }
  }
}

STRING_LISTも内部的にはtext型の配列として格納されているので、完全一致検索には.keywordが必要です。
配列の中のいずれかの要素にマッチすればヒットするので、「awsタグを含むドキュメント」という検索が実施できます。

{
  "hits": {
    "total": { "value": 1, "relation": "eq" },
    "hits": [
      {
        "_source": {
          "x-amz-bedrock-kb-source-uri": "s3://sampleknowledgestack-docbucket.../tech-blog-aws-bedrock.txt",
          "is_public": true,
          "priority": 70,
          "department": "engineering",
          "AMAZON_BEDROCK_TEXT_CHUNK": "技術ブログ: Amazon Bedrockで始める生成AI  はじめに Amazon Bedrockは、AWSが提供するフルマネージドの生成AIサービスです。...",
          "document_type": "blog",
          "tags": ["aws", "bedrock", "generative-ai", "rag"]
        }
      }
    ]
  }
}

CleanShot 2025-12-20 at 21.35.53@2x

CleanShot 2025-12-20 at 21.37.01@2x

集計クエリ

OpenSearchは集計クエリも活用できます。
ドキュメントのグルーピングや統計情報の取得ができます。

GET /bedrock-knowledge-base-default-index/_search
{
  "size": 0,
  "aggs": {
    "by_type": {
      "terms": { "field": "document_type.keyword" }
    },
    "by_department": {
      "terms": { "field": "department.keyword" }
    },
    "avg_priority": {
      "avg": { "field": "priority" }
    }
  }
}

size: 0を指定すると検索結果のドキュメント自体は返さず、集計結果だけを取得できます。
ドキュメントの種類別・部署別の件数や、優先度の平均値といった情報を取得するのにも活用できますね。

下記のように取得できます。

{
  "took": 80,
  "timed_out": false,
  "_shards": {
    "total": 0,
    "successful": 0,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  },
  "aggregations": {
    "avg_priority": {
      "value": 81.66666666666667
    },
    "by_department": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "engineering",
          "doc_count": 1
        },
        {
          "key": "finance",
          "doc_count": 1
        },
        {
          "key": "product",
          "doc_count": 1
        }
      ]
    },
    "by_type": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "blog",
          "doc_count": 1
        },
        {
          "key": "manual",
          "doc_count": 1
        },
        {
          "key": "report",
          "doc_count": 1
        }
      ]
    }
  }
}

おわりに

今回はBedrock Knowledge BasesのメタデータがOpenSearch Serverlessにどう格納されるかを確認してみました!

Knowledge Basesが.metadata.jsonの内容を読み取って、自動でOpenSearchのフィールドに展開してくれる仕組みになっています。文字列の完全一致検索には.keywordサブフィールドを使う必要があるのには注意したいですね。

OpenSearchに直接クエリを発行するケースなどではどういった構造でデータが格納されているか押さえておく必要があったので、勉強になりました!
本記事が少しでも参考になりましたら幸いです!最後までご覧いただきありがとうございましたー!!

この記事をシェアする

FacebookHatena blogX

関連記事