Knowledge Bases for Amazon Bedrock でメタデータを自動付与したフィルター検索をやってみた

# はじめに

Knowledge Bases for Amazon Bedrock には、検索精度の向上やセキュリティのコントロールなどに利用できる、メタデータを付与する機能があります。利用のイメージはこんな感じです。

- 営業部署の方が、Knowledge Bases を活用しているアプリケーション経由で RAG を実行
- `eigyo` ディレクトリのみを RAG の対象にするため、関係がないと思われるファイルが除外され、精度向上を期待できる
- 「なんらかのアプリケーション」側で認証認可をうまく実装すると、営業部署以外の方が `eigyo` ディレクトリのデータを利用することを禁止できる

![image-20240504203313478.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/62c5b237-9254-00ec-a22e-aba6ebdfc93e.png)



これを Knowledge Bases で行おうと思ったときには、以下のスライドのように、各ファイルごとに「.metadata.json」が末尾につくメタデータファイルを作らないといけないです。

![image-20240504202743676.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/2da6adf3-e157-1a8d-3a4a-8ab1adc71786.png)



これはなかなか大変ということで、うまく Lambda などを活用しながら自動的にメタデータファイルを生成する実装例を紹介します。今回の記事で紹介した記事をそのまま利用するというわけではなく、環境に合わせてうまくメタデータの付与をコントロールしてみていただけるとよいと思います。



# Lambda 関数の実装

S3 にファイルが置かれたことをトリガーに、`.metadata.json` を自動生成する Lambda 関数を作成します。どういったメタデータを付与するべきか、様々なパターンが考えられます。今回は、単純に格納されたディレクトリの名前を属性としてメタデータファイルを生成する内容とします。

S3 バケットは、ファイル入力用の input バケットと、RAG として利用する rag バケットの 2 種類を利用します。

以下に図解します。

ざっくり説明すると、左側の input バケットに格納したファイルは、自動的に右側の rag バケットにコピーされます。コピーされるのと同時に、metadata ファイルを生成します。この属性に「eigyo」や「keiri」といったディレクトリ名を属性として付与します。

![image-20240504171418894.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/10f0a234-0508-41ae-07a9-3956ac4a6a81.png)



これを実現する Lambda 関数のソースコードサンプルがこちらです。ファイルの削除や、エラーハンドリングなどは考慮外で実装しているので、本番環境のときには、これを参考にしながら実装の変更をお願いします。

```python
import json
import boto3
from logging import getLogger, INFO
from urllib.parse import unquote

logger = getLogger(__name__)
logger.setLevel(INFO)


def lambda_handler(event, context):
    # S3 クライアントの初期設定
    s3_client = boto3.client('s3')

    # ログ出力
    print("============ logger.info の出力 ============")
    logger.info(json.dumps(event))

    # Lambda 関数に渡される Event から、input のバケット名とファイルに紐づくキー名を取得
    source_bucket = event['Records'][0]['s3']['bucket']['name']
    source_key_encoded = event['Records'][0]['s3']['object']['key']
    source_key = unquote(source_key_encoded)  # 日本語のファイル名に対応するためデコード

    # コピー先のバケット名を指定 (環境に合わせて変更する)
    destination_bucket = 'knowledge-base-virginia-bucket01'

    # RAG に利用するファイルそのものをコピーする
    copy_source = {
        'Bucket': source_bucket,
        'Key': source_key
    }
    s3_client.copy(copy_source, destination_bucket, source_key)

    # メタデータのファイルを作成
    directory_name = source_key.split('/')[0]
    metadata_content = {
        "metadataAttributes": {
            "directory": directory_name,
        }
    }
    metadata_key = f"{source_key}.metadata.json"
    s3_client.put_object(Bucket=destination_bucket,
                     Key=metadata_key, Body=json.dumps(metadata_content, indent=4))

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "File copied successfully",
        }),
    }
```



「# メタデータのファイルを作成」の部分は、特に環境ごとに付与すべき属性は変わってくると思うので、適宜変更をお願いします。例えば、「提案書」「見積」「会議議事録」といったカテゴリを用意しておいて、生成 AI をつかって分類分けをしても面白いかもしれません。



# 動作確認 : メタデータの自動生成

Input 用の S3 バケットにファイルをアップロードします。ちなみにアップロードするファイルは、Claude 3 に適当に作ってもらったサンプルファイルです。

![image-20240504175848926.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d22b9ae8-25a8-ae45-dec2-d6a0ada43eb0.png)



input 用の bucket にファイルがアップロードされています。

![image-20240504180052348.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6da504e9-f2e9-0d39-ac78-cc3b2b3f58bf.png)



Lambda 関数が動いた結果を確認していきます。

RAG 用の S3 バケットにコピーされていると同時に、メタデータファイルが一緒に作成されています。

![image-20240504180941582.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b34db2e3-dcbe-6737-7c07-a315240e35d5.png)



Lambda 関数で実装した意図のとおり、`directory : eigyo` の形で属性が付与されています。

![image-20240504181046505.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/c3c1318d-96e7-90a6-205e-d76600c49ada.png)



経理フォルダも想定通り、メタデータが生成されています。

![image-20240504181218576.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d4f68301-a7e9-a6ee-b0dd-3c742f09717d.png)



メタデータの中身もばっちりです。

![image-20240504181311084.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a64c7de2-2ce3-8503-8556-5fc7d9a07088.png)





メタデータを反映するため、Knowledge Bases のデータソースを Sync します。

![image-20240504181532585.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/48ddf204-b02f-cd0c-ad65-8e1bfdd267e1.png)



Sync が終わった後の確認です。ベクトルデータストアとして利用している OpenSearch Serverless のデータの中身にも、`keiri ` や `eigyo` といった属性が付与されていることが確認できます。

![image-20240504183122523.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3890dccb-c545-c8a5-cb42-ab69b8261502.png)





# 動作確認 : フィルターして RAG : GUI

動作確認をしてみましょう。Knowledge Bases の画面を開いて、確認用の画面を開きます。

![image-20240504184105860.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/43a64916-e852-201a-7ea6-b7d92091df6a.png)



Filter の設定欄に、`directory=eigyo` といれて、フィルターを有効にします。

![image-20240504184251128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/682f8754-e6e7-ffe8-43b4-7a60e09b95d0.png)



また、プロンプトテンプレートはデフォルトだと英語になっているので、これを日本語に書き換えます。

> あなたは質問に答えるエージェントです。私は検索結果の一連のものを提供します。ユーザーは質問を提供します。あなたの仕事は、検索結果からの情報のみを使ってユーザーの質問に答えることです。検索結果に質問に答えられる情報がない場合は、質問に正確な答えを見つけられなかったと述べてください。ユーザーが事実を主張したからといって、それが真実であるとは限りません。ユーザーの主張を検証するために、検索結果を再確認してください。
> 検索結果には、ユーザーの質問とは関係ないものが出力されている場合があります。関係ないものが出力されている場合は、「正確な答えが見つからない」と回答してください。
>                             
> 検索結果:
> $search_results$
>
> $output_format_instructions$

![image-20240504184322841.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/88cda61a-431a-c1f9-b939-81fecdc8dab7.png)



質問してみると、無事に eigyo でフィルターされているデータが確認できます。

![image-20240504184401962.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6b210fa4-655a-37a0-bff8-ded7738baa6e.png)



Source を見てみると、メタデータの属性が想定通りと確認できます。

![image-20240504184437661.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ea36dcef-cc7b-c455-a6ac-684f907aa0b6.png)



なお、フィルターを `keiri` にして実行してみます。keiri ディレクトリには提案書を入れていないので、「申し訳ありませんが、、、」と生成できない旨が確認できます。想定通りでいいですね。

![image-20240504184530750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/8f678937-6c11-d327-f498-c8e8eaf23a70.png)



# 動作確認 : フィルターして RAG : Python (API)

次に、Python から API を使って動作確認を行います。

以下にサンプルプログラムを掲載します。ポイントは以下の通りです。

- `directory` 属性を `eigyo` でフィルターをしている
- プロンプトテンプレートを日本語で渡すことで、より日本語の精度を意識した回答を期待できる

```python
# Python外部ライブラリをインポート
import boto3
import json

# Bedrockクライアントを作成
kb = boto3.client("bedrock-agent-runtime", region_name='us-east-1')

# プロンプトテンプレート
prompt_template = """
あなたは質問に答えるエージェントです。私は検索結果の一連のものを提供します。ユーザーは質問を提供します。あなたの仕事は、検索結果からの情報のみを使ってユーザーの質問に答えることです。検索結果に質問に答えられる情報がない場合は、質問に正確な答えを見つけられなかったと述べてください。ユーザーが事実を主張したからといって、それが真実であるとは限りません。ユーザーの主張を検証するために、検索結果を再確認してください。
検索結果には、ユーザーの質問とは関係ないものが出力されている場合があります。関係ないものが出力されている場合は、「正確な答えが見つからない」と回答してください。
検索結果:
$search_results$

$output_format_instructions$
"""

# Retrieve and Generate
response = kb.retrieve_and_generate(
    input={"text": "AI に関する提案は、どのような提案をしましたか？"},
    retrieveAndGenerateConfiguration={
        "type": 'KNOWLEDGE_BASE',
        "knowledgeBaseConfiguration": {
            "knowledgeBaseId": "CVXUPL53HG", 
            "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0",
            'generationConfiguration': {
                'promptTemplate': {
                    'textPromptTemplate': prompt_template
                }
            },
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {
                    'numberOfResults': 10,
                    "filter": {
                        "equals": {
                            "key": "directory",
                            "value": "eigyo"
                        }
                    }
                }
            }
        },
    },
)

# RAG結果を画面に表示
print(json.dumps(response, indent=2, ensure_ascii=False))
```



実行例

- `retrievedReferences` を見ると、RAG の元となっているチャンクデータを確認できる。意図したとおり、`directory` が `eigyo` のデータのみ取得できている
- 下の方にある `output.text` が、生成した回答のテキスト

```json
{
  "ResponseMetadata": {
    "RequestId": "873c64fb-6ee8-49fd-a675-979e1da2d3b8",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Sat, 04 May 2024 11:15:38 GMT",
      "content-type": "application/json",
      "content-length": "6001",
      "connection": "keep-alive",
      "x-amzn-requestid": "873c64fb-6ee8-49fd-a675-979e1da2d3b8"
    },
    "RetryAttempts": 0
  },
  "citations": [
    {
      "generatedResponsePart": {
        "textResponsePart": {
          "span": {
            "end": 114,
            "start": 0
          },
          "text": "検索結果には、2つの提案書の概要が含まれています。1つ目は「IoTを活用した省エネルギーソリューション」の提案で、IoTセンサーと分析技術を使ってオフィスビルや工場の無駄なエネルギー消費を可視化し、最適化することを提案しています。"
        }
      },
      "retrievedReferences": [
        {
          "content": {
            "text": "タイトル:\r 「ABC株式会社 新規事業提案書」\r \r はじめに:\r ABC株式会社の皆様へ\r \r この度は、弊社の新規事業提案の機会を賜り、誠にありがとうございます。弊社では、貴社の更なる発展と収益向上を目指し、以下の新規事業を提案させていただきます。\r \r 提案概要:\r 弊社では、ABC株式会社様向けに「IoTを活用した省エネルギーソリューション」の導入を提案いたします。本ソリューションでは、IoTセンサーと高度な分析技術を組み合わせ、オフィスビルや工場などの施設における無駄なエネルギー消費を可視化し、最適化を行います。\r \r 主な特長:\r \r リアルタイムの消費データ収集と分析\r AIによる需要予測と自動制御\r 省エネルギー効果の継続的なモニタリング\r カスタマイズ可能なダッシュボードとレポーティング\r 期待される効果:\r 本ソリューションの導入により、以下の効果が期待できます。\r \r 年間エネルギーコストの20%削減\r CO2排出量の大幅な削減による環境負荷の低減\r 設備の最適運用による資産の長期化\r 省エネ活動の「見える化」による社員の意識向上\r 実績:\r 弊社の同様のソリューションは、これまでに多くの企業に導入されており、高い省エネルギー効果と顧客満足度を実現してまいりました。\r \r まとめ:\r IoTを活用した省エネルギーソリューションの導入により、ABC株式会社様の経営課題である「コスト削減」と「環境対策」の両立が可能になります。ぜひ、この提案をご検討くださいますよう、よろしくお願いいたします。\r \r 以上が架空の提案書のテキストとなります。必要に応じて内容を調整していただければと思います。"
          },
          "location": {
            "s3Location": {
              "uri": "s3://knowledge-base-virginia-bucket01/eigyo/提案資料01.txt"
            },
            "type": "S3"
          },
          "metadata": {
            "x-amz-bedrock-kb-source-uri": "s3://knowledge-base-virginia-bucket01/eigyo/提案資料01.txt",
            "directory": "eigyo"
          }
        }
      ]
    },
    {
      "generatedResponsePart": {
        "textResponsePart": {
          "span": {
            "end": 229,
            "start": 116
          },
          "text": "2つ目の提案は「AIチャットボットの導入」に関するものです。自然言語処理と機械学習を組み合わせたAIチャットボットを導入し、24時間365日の無人対応、複数言語対応、自己学習機能による回答精度向上などのメリットを提案しています。"
        }
      },
      "retrievedReferences": [
        {
          "content": {
            "text": "題名:\r 「AIチャットボットの導入提案」\r \r 概要:\r この度は、AIチャットボットの導入に関する提案の機会を賜り、誠にありがとうございます。弊社では、お客様の利便性向上と業務効率化を目指し、最先端のAI技術を活用したチャットボットシステムの導入を提案させていただきます。\r \r 提案内容:\r 弊社が提案するAIチャットボットは、自然言語処理と機械学習の技術を組み合わせた高度なシステムです。質問に対して的確な回答を行うだけでなく、対話の文脈を理解し、お客様一人ひとりに合わせた最適なサポートを提供します。\r \r 主な特長:\r \r 24時間365日の無人対応が可能\r 複数の言語に対応\r 自己学習機能により、回答精度が継続的に向上\r AIとオペレーターの連携で、高度な問題にも対応\r 期待される効果:\r AIチャットボットの導入により、以下の効果が期待できます。\r \r コールセンター業務の大幅な効率化と人件費削減\r お客様対応の質と満足度の向上\r FAQ対応の自動化による社員の業務負荷軽減\r AIが蓄積したデータを活用した新サービス開発\r 実績:\r 弊社のAIチャットボットは、すでに多くの企業に導入されており、業務効率化と顧客満足度の向上に大きく貢献しています。実際の導入事例もご紹介できますので、ご要望がありましたらお知らせください。\r \r まとめ:\r AIチャットボットの導入により、お客様へのサポート品質が大幅に向上するとともに、業務効率化とコスト削減が実現できます。ぜひこの提案をご検討くださいますよう、よろしくお願いいたします。"
          },
          "location": {
            "s3Location": {
              "uri": "s3://knowledge-base-virginia-bucket01/eigyo/提案資料02.txt"
            },
            "type": "S3"
          },
          "metadata": {
            "x-amz-bedrock-kb-source-uri": "s3://knowledge-base-virginia-bucket01/eigyo/提案資料02.txt",
            "directory": "eigyo"
          }
        }
      ]
    }
  ],
  "output": {
    "text": "検索結果には、2つの提案書の概要が含まれています。1つ目は「IoTを活用した省エネルギーソリューション」の提案で、IoTセンサーと分析技術を使ってオフィスビルや工場の無駄なエネルギー消費を可視化し、最適化することを提案しています。 2つ目の提案は「AIチャットボットの導入」に関するものです。自然言語処理と機械学習を組み合わせたAIチャットボットを導入し、24時間365日の無人対応、複数言語対応、自己学習機能による回答精度向上などのメリットを提案しています。"
  },
  "sessionId": "77bdbb60-d069-4479-9ab1-da438796b021"
}
```



上記の Python プログラムのうち、フィルターの部分を `keiri` に変更して実行してみます。

```python
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {
                    'numberOfResults': 10,
                    "filter": {
                        "equals": {
                            "key": "directory",
                            "value": "keiri"
                        }
                    }
                }
            }
```



実行結果

- 意図したとおり、`keiri` 属性がついているデータのみ取得している
- その結果、正常に回答ができないので、「回答ができない」旨の結果が生成されている

```json
{
  "ResponseMetadata": {
    "RequestId": "6a7a22a3-3d4b-44eb-a2a3-9b6a61ff206c",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Sat, 04 May 2024 11:22:41 GMT",
      "content-type": "application/json",
      "content-length": "2701",
      "connection": "keep-alive",
      "x-amzn-requestid": "6a7a22a3-3d4b-44eb-a2a3-9b6a61ff206c"
    },
    "RetryAttempts": 0
  },
  "citations": [
    {
      "generatedResponsePart": {
        "textResponsePart": {
          "span": {
            "end": 121,
            "start": 0
          },
          "text": "申し訳ありませんが、提供された検索結果には AI に関する提案についての情報がありません。検索結果は会計処理の基本原則と手順、主な勘定科目について説明しているだけです。したがって、あなたの質問に対する正確な答えを見つけることができませんでした。"
        }
      },
      "retrievedReferences": [
        {
          "content": {
            "text": "【会計処理の基本原則】\r \r 発生主義の原則\r 収益や費用は、現金が入ったり出たりした時点ではなく、発生した時点で計上する必要があります。\r \r 継続企業の原則\r 企業は継続して事業を行うものと仮定し、会計処理を行います。\r \r 重要性の原則\r 財務諸表には、利用者の判断に影響を与える重要な事項を開示する必要があります。\r \r 期間区分の原則\r 企業の活動は適切な期間に区分して測定し、報告する必要があります。\r \r 【主な会計処理の手順】\r \r 仕訳\r 取引の内容を借方と貸方に分けて記録します。\r \r 転記\r 仕訳を総勘定元帳に転記し、勘定ごとに集計します。\r \r 試算表の作成\r 借方と貸方の合計金額が一致することを確認します。\r \r 決算整理\r 期末の収益・費用の計上や、債権債務の確定などを行います。\r \r 財務諸表の作成\r 損益計算書、貸借対照表、キャッシュフロー計算書などを作成します。\r \r 【主な勘定科目】\r \r 資産の部\r 現金、売掛金、棚卸資産、有形固定資産、無形固定資産など\r \r 負債の部\r 買掛金、未払費用、長期借入金など\r \r 純資産の部\r 資本金、利益剰余金など\r \r 収益の部\r 売上高、受取利息など\r \r 費用の部\r 売上原価、販売費及び一般管理費など"
          },
          "location": {
            "s3Location": {
              "uri": "s3://knowledge-base-virginia-bucket01/keiri/会計処理マニュアル.txt"
            },
            "type": "S3"
          },
          "metadata": {
            "x-amz-bedrock-kb-source-uri": "s3://knowledge-base-virginia-bucket01/keiri/会計処理マニュアル.txt",
            "directory": "keiri"
          }
        }
      ]
    }
  ],
  "output": {
    "text": "申し訳ありませんが、提供された検索結果には AI に関する提案についての情報がありません。検索結果は会計処理の基本原則と手順、主な勘定科目について説明しているだけです。したがって、あなたの質問に対する正確な答えを見つけることができませんでした。"
  },
  "sessionId": "e00950e6-ff84-4122-b4f1-34eaea54abc0"
}
```





# 検証を通じてわかったこと

- `.metadata.json` に含める属性は、日本語で指定するとうまく動かなかった。基本的には英語で属性を指定するとよい。
- Knowledge Bases で指定した S3 バケットにサブディレクトリが存在する場合、再起的にサブディレクトリのファイル群が読みこまれる。



# 参考 URL

https://qiita.com/hayao_k/items/f486b7d1385c8d5f897c