Amazon Bedrockで「ユーザがアップロードしたドキュメントから回答を得る」方法のまとめ

# はじめに
生成AIの文脈において、RAG（Retrieval-Augmented Generation）と呼ばれる技術は、生成AIのモデルがプロンプトを解釈し、ナレッジベースから関連情報を検索し、その情報を基にコンテンツ(回答)を生成するために活用されるものです。
RAGを活用すれば、生成AIのモデルが学習していない最新かつインターネットに公開されていない非公開情報を取り入れた回答を提供できるため、回答の正確性と信頼性が向上します。

しかしながら、ナレッジベースを活用したRAGでは、当然ながらナレッジベースを事前に構築し終えておく必要があり、このナレッジベースにも存在しない情報からは回答を生成することができません。
ナレッジベースが常に最新化されているとは限らないため、ユーザのニーズに追いつけないケースも多々あります。

このような背景から、最近は特に、**ユーザがチャットボットを介してドキュメントをアップロードし、その内容に基づく回答を得たい**というニーズが高まっています。

そこで、この記事ではAmazon Bedrockの機能を利用して、ユーザがドキュメントをアップロードし、その内容に基づく回答を得るための方法をまとめます。

但し、単にチャットボットのUIにファイルアップローダを用意して、バックエンドをピタゴラスイッチのように構築した末に実現できましたという話を紹介しても、それは複雑に工夫すればできるという話で、必ずしも開発者にとって嬉しい話とは言えません。

この記事では、現在の**Amazon Bedrockの標準機能で、この「ユーザがドキュメントをアップロードし、その内容に基づく回答を得る」をシンプルに実現する方法**にフォーカスしていきます。

# (1) Anthropic Claude メッセージAPI
Amazon Bedrockのプレイグラウンドで、Anthropic Claudeのモデルファミリーを選択すると、プロンプトを書くフォームの右下に「添付ファイル」を選ぶボタンが配置されています。
このボタンを介してファイルを添付すれば、ファイルの内容に基づいて回答を得られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/f49bf747-c1ef-9cd9-e67c-aa4a1eb35084.png)

この機能を使ってドキュメントから回答を得る方法について深掘りしていきます。

## 要素技術
これは、Anthropic Claudeの「メッセージAPI」の標準機能です。

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html

Converse APIまたはConverse Stream APIにも、ファイルを添付してリクエストを実行できるとの記述があります。

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/conversation-inference-call.html

Converse APIの`messages`引数に指定する、ドキュメントをアップロードして内容を読み込むためのプロンプトを含んだデータ (JSON) の例は以下の通りです。

```Python:boto3
# ドキュメントのパス（ユーザがアップロードしたファイル）
document_path = 'path/to/your/document.pdf'  # ユーザがアップロードしたファイルのパスを指定

# ファイル名と拡張子の取得
file_name = os.path.basename(document_path)
file_extension = os.path.splitext(file_name)[1][1:]  # 拡張子からドットを除去

# サポートされているドキュメント形式のリスト
supported_formats = ['pdf', 'csv', 'doc', 'docx', 'xls', 'xlsx', 'html', 'txt', 'md']

# 拡張子のチェック
if file_extension.lower() not in supported_formats:
    raise ValueError(f"サポートされていないファイル形式です: {file_extension}")

# ドキュメントの読み込み
with open(document_path, 'rb') as doc_file:
    document_bytes = doc_file.read()

# ユーザメッセージの作成
user_message = {
    "role": "user",
    "content": [
        {
            "text": f"ドキュメント（{file_name}）の要約をお願いします。"
        },
        {
            "document": {
                "format": file_extension,
                "name": file_name,
                "source": {
                    "bytes": document_bytes
                }
            }
        }
    ]
}
```

ポイントはバイト列 (bytes) をBase64にエンコードする必要がない点で、公式ドキュメントにも記述があります。

> **bytes**
>The raw bytes for the document. **If you use an AWS SDK, you don't need to encode the bytes in base64.**
>
> Type: Base64-encoded binary data object
>
>Length Constraints: Minimum length of 1.

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

但し、ドキュメントのサイズは**4.5MB未満**である必要があり、これを超えるとエラーが発生します。

## 読み取りの精度
このAPIを利用したドキュメントからの回答の精度は、**高い**です。
以下は、2024/12/24に開催された「[KAGのLT会 〜エンジニア、デザイナー、スクラムマスターの知見を共有したい！〜](https://kddi-agile.connpass.com/event/340541/)」において、私が発表したドキュメントをアップロードして読み込ませた結果ですが、書かれていることの概要を、正確かつ網羅的に読み取れていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/25b756fa-b66a-fdc2-8526-f6ee3189ae69.png)

# (2) Amazon Bedrock Knowledge BaseのRetrieveAndGenerate API
Knowledge Baseの画面には「ドキュメントとチャット」のタブがあり、この画面の「ドキュメントを選択」ボタンから、ドキュメントをアップロードして回答を得ることができます。
(2024/12現在、東京リージョンでは使用できません)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/6b860263-a001-def0-ba6e-d434fa373476.png)

この機能を使ってドキュメントから回答を得る方法について深掘りしていきます。

## 要素技術
これは、Bedrock Knowledge Baseの「RetrieveAndGenerate API」の標準機能です。

https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent-runtime_RetrieveAndGenerate.html

以下は RetrieveAndGenerate APIにクエリする部分のコードのサンプルです。

```Python:boto3
import boto3
import mimetypes
import os

# AWSクライアントの設定
region_name = 'us-west-2'
bedrock = boto3.client('bedrock-agent-runtime', region_name=region_name)

# ドキュメントの読み込み
document_path = 'path/to/your/document'
file_name = os.path.basename(document_path)

# ファイルのMIMEタイプを推測
content_type, _ = mimetypes.guess_type(file_name)
if content_type is None:
    raise ValueError(f"未対応のファイル形式です: {file_name}")

# ドキュメントのバイトデータを読み込み
with open(document_path, 'rb') as file:
    document_bytes = file.read()

# バイトデータをBase64エンコード
document_base64 = base64.b64encode(document_bytes).decode('utf-8')

# モデルARNの指定
model_arn = 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0'

# ユーザからのクエリ
user_query = "ドキュメントの要約をお願いします。"

# retrieve_and_generate API の呼び出し
response = bedrock.retrieve_and_generate(
    input={
        'text': user_query
    },
    retrieveAndGenerateConfiguration={
        'type': 'EXTERNAL_SOURCES',
        'externalSourcesConfiguration': {
            'modelArn': model_arn,
            'sources': [
                {
                    'byteContent': {
                        'contentType': content_type,
                        'data': document_base64,
                        'identifier': file_name
                    },
                    'sourceType': 'BYTE_CONTENT'
                }
            ]
        }
    }
)
```

こちらのAPIでリクエストする時は、バイト列 (bytes) をBase64にエンコードする必要があります。
また`contentType`に拡張子ではなくMIMEタイプを指定する必要があり、やや実装の難易度が高めです。
これらの仕様は公式ドキュメントにも書かれています。

> **byteContent (dict)** –
>
>The identifier, contentType, and data of the external source wrapper object.
>
>contentType (string) – [REQUIRED]
>The MIME type of the document contained in the wrapper object.
>
>data (bytes) – [REQUIRED]
>The byte value of the file to upload, **encoded as a Base-64 string.**
>
>identifier (string) – [REQUIRED]
>The file name of the document contained in the wrapper object.

https://boto3.amazonaws.com/v1/documentation/api/1.35.6/reference/services/bedrock-agent-runtime/client/retrieve_and_generate.html

## 読み取りの精度
このAPIを利用したドキュメントからの回答の精度は、先ほどのClaudeメッセージAPIの結果と比較しても分かるように、**非常に高い**です。
先ほどと同じドキュメントを読み込ませても、資料に書かれていることの概要を、正確かつ網羅的に読み取れているだけでなく、詳細を読み取って具体的に文章を生成していることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/0e78fe8f-f9d9-eb1e-4a05-000d602c4ece.png)

# まとめ
このような、ユーザが今まさに参照したい任意のドキュメントから回答を得るニーズは非常に高いです。
ChatGPTやNotebookLMなどの主要なAIチャットサービスでも同様の機能が利用でき、その利便性の高さが注目されています。
Amazon Bedrockを利用して独自のアプリケーションを開発する場合は、今回紹介した2つのAPIを使えば、どちらもナレッジベースを事前に作成したり、S3バケットを介することなく、各APIにリクエストするだけで、ユーザが指定した任意のドキュメントから回答を得ることができます。
複数のサービスをまたぐ実装が必要なく、シンプルに実現できる点は大きな売りで、チャットボットにも組み込みやすいと言えますので、ぜひ活用してみましょう。

## 参考文献

### **Amazon Bedrock ドキュメント**
   - [Anthropic Claude Messages API](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html)
   - [Using the Converse API](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/conversation-inference-call.html)
   - [DocumentSource APIリファレンス](https://docs.aws.amazon.com/ko_kr/bedrock/latest/APIReference/API_runtime_DocumentSource.html)
   - [RetrieveAndGenerate APIリファレンス](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent-runtime_RetrieveAndGenerate.html)

### **Boto3 ドキュメント**
   - [RetrieveAndGenerate API（boto3）](https://boto3.amazonaws.com/v1/documentation/api/1.35.6/reference/services/bedrock-agent-runtime/client/retrieve_and_generate.html)

### **その他**
   - [「KAGのLT会 〜エンジニア、デザイナー、スクラムマスターの知見を共有したい！〜」Connpassイベントページ](https://kddi-agile.connpass.com/event/340541/)