Bedrockのナレッジベースにパラメータの多いCSVは向いてない？

分析用データなど、パラメータ数が多くなりがちなファイルを用いて日々手動でレポートを作成するのは負担が大きいため、生成AIを活用して自動化したいと考えるケースがあります。
そういったケースの解決策として、Bedrockのナレッジベースを活用してみたところ、期待した精度の回答が得られないことがあります。

上記問題について、本記事の前半でチャンクの観点から原因と思わしきない点がないか深堀をしていきます。
また、後半では「ナレッジベースを用いたチャンクありのパターン」と「ConverseAPIを用いたチャンクなしのパターン」による切り分けを行います。

結果として、パラメータの多いCSVファイルを取り扱う場合、ナレッジベースではうまくいかない際の代替案としてConverseAPIが使えそうでした。

| CSVの内容 | ナレッジベース | ConverseAPI |
| ----| ---- | ---- |
| パラメータが少ない| 〇 | 〇 |
| パラメータが多い| × | 〇 |

# ナレッジベースを活用した方法
## 現在の状況
### 取り込んでいるファイル
マスクした状態かつ一部抜粋したものですが、下記のようなファイルになります。

```
date_time,parameter1,parameter2,parameter3,Value,parameter4,parameter5,parameter6,parameter7,parameter8,parameter9,parameter10,parameter11,parameter12,parameter13,parameter14
2024-07-04 00:00:00,1,0,0.9583401811072437,4.2399215270502735,558.6741476736654,33.083413886627476,59.02975315539411,10.783458839327125,0.7535578933603982,0.1610194036838556,0.2637825313230583,32.124070051110685,10.184445145711077,21.47831513426158,22.600696530317087
2024-07-04 00:30:00,0,0,0.13546568964970218,6.077937824288026,875.5476108129924,30.707115993195423,176.23663771648043,10.044459929367914,0.9257873805978653,0.19397310657305622,0.4219514411544937,24.13090262780977,21.23953008056985,23.65785036955724,24.378293256369798
2024-07-04 01:00:00,0,0,0.01311348696146708,4.13306959184654,528.5339416144764,33.80992351224609,325.1800824141899,11.539542423786678,0.7409699008402154,0.42539404889725374,0.2991146306554542,17.73275566417008,14.316420132470386,12.061445651145483,18.04625561604794
2024-07-04 01:30:00,0,2,0.2454693043494034,4.243831290218708,268.0558816072691,23.39687563370505,246.24751104695403,13.082975383452606,0.6596247278447952,0.06948735861158978,0.6812308940248375,21.71839701829203,17.40078203687051,13.843091606507398,19.79422360561057
2024-07-04 02:00:00,0,1,0.07196115632132549,5.617626681485765,709.0645296360196,26.921533498383468,131.50117510445236,12.393785913503025,0.4859165243153867,0.32741202467002556,0.8970174413271748,22.725306424956294,27.108634091030552,21.59312278420611,18.167085504152197
2024-07-04 02:30:00,1,0,0.15221490465644527,7.130404681814783,666.1037012544036,32.28641645296709,226.37192575415227,11.181006853111327,0.8968974366450246,0.24645780387293115,0.30317417310324823,21.15621435285717,27.502680890901683,29.05052761315094,25.208764718860007
2024-07-04 03:00:00,2,1,0.9760554736374016,9.835733658651034,511.9638364671384,22.42824445663246,274.73779528371523,16.70999814352542,0.9413627190532832,0.4402707546295691,0.005405625847359374,33.91957813237978,16.15203299963857,25.16536822680007,27.53195685936703
2024-07-04 03:30:00,2,0,0.09009314766613374,5.924254134775627,804.4890187262522,12.827781822714323,178.78138697819548,16.47299286736471,0.9350254072087554,0.002609200555093283,0.25802645490587095,34.719278798202126,15.527047715074612,17.707883515097734,11.376094421923185
2024-07-04 04:00:00,2,2,0.5960047709838371,7.388109200201541,689.2035123972055,28.14507132409545,137.52367522601008,12.964355016174556,0.8740473989261637,0.36120515184213825,0.5814993846206701,21.419859440449066,18.21110550451872,25.194730336195942,24.26263114186939
```
### ナレッジベースの実行結果
- モデル: Claude 3.5 Sonnet  v1
- 埋め込みモデル: Embed Multilingualv3
- システムプロンプトはなしで、下記のユーザープロンプトのみとしています。

◆ユーザープロンプト
> Valueが一番高い日時教えて

◆回答
> 提供された情報からは、Valueが一番高い日時を正確に特定することはできません。データには複数の日時のエントリーがありますが、Valueの値が明示的に示されていないため、最高値を判断することができません。データには様々な数値が含まれていますが、それらがValueを表しているかどうかは不明です。

このようにCSVにValueがあるのに、見つけてくれないという状況です。
ナレッジベースはデータを取り込むと、チャンクという単位に分割して処理を進めていきます。
なので、チャンクの観点から原因と思わしき点がないかを深堀をしていきます。

## チャンクとは？
### チャンクの概要

「チャンク」とは、長いテキストやデータを小さな部分に分割したものです。
AWS Bedrockでは、大規模言語モデル(LLM)を活用してテキストの生成や解析を行いますが
モデルに渡すテキストにはサイズ制限があります。
このため、長文テキストを一定サイズの「チャンク」に分割し、それぞれのチャンクを順番に処理することで
大きなデータを効率的に扱うことができます。

![bedrock-チャンクイメージ図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/a79657c2-e199-76d2-ebba-0c360acd40e3.png)


### チャンクの処理の流れ(デフォルトのチャンキング戦略の場合)

Bedrock->ナレッジベースのデフォルトだと、「コンテンツを約300トークンのテキストチャンクに分割します。」

https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/kb-chunking-parsing.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/38380c77-71bc-0049-acfc-928eb321fffc.png)

なので、ファイルを取り込んでからチャンクに分かれる部分を簡単に流れにすると

**1. ファイル内の単語をトークンに変換** 

まず、文章は「トークン」というモデルが扱いやすい最小単位に分解されます。
トークンは単語全体やその一部、記号、スペースなどを含みます。
例えば、 Hello, world! という文章では、次のようにトークンに分けられることがあります。

・Hello
・,
・world
・!

**2. 300トークンごとにチャンクに分割** 

次にBedrockのデフォルトのチャンキング戦略に合わせて
300トークンごとにチャンクを分けられていきます。

・チャンク1：最初の300トークン
・チャンク2：次の300トークン
・チャンク3：さらに次の300トークン そして、モデルはこれらのチャンクを順番に処理します。

**3. 分割されたチャンクをベースにプロンプトへの回答**

ナレッジベースのプロンプトに対して、使用できるチャンク数の最大値は100※1のためトークン数から考えると
100チャンク * 300トークン ＝ 約 30,000トークン
までのデータを取り込むことができます。

※1 チャンク数：最大値100について

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/db61130e-7f87-da63-73f3-b09b21be6c1f.png)

https://aws.amazon.com/jp/about-aws/whats-new/2024/03/knowledge-bases-amazon-bedrock-customize-prompts-number-retrieval-results/

## 取り込んでいたCSVファイルのトークン数の確認

利用しているCSVファイルのトークン数をプレイグラウンドのチャットから確認してみると
約6,000トークンとなっていました。
→ 30,000トークンを超えてないので、サイズ制限としては問題なさそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3790709/98f00079-9b26-40df-f367-3896135609e2.png)

## ナレッジベースを活用した方法のまとめ

チャンクのサイズ制限は問題ないのに回答の精度がいまいちという結果になっています。
となると、そもそもパラメータの多いCSVはあまりナレッジベースで扱うのには向いていない可能性があるので
切り分けとして、次にチャンクを使用していないパターンとしてナレッジベースを使用せず、ConverseAPIを活用したやり方を実施してみます。

# ConverseAPIを活用した方法

## ConveseAPIとは

ConverseAPI は、AWS Bedrockの「プレイグラウンド → Chat / Text」で使用されているAPIで、プログラムから直接プロンプトを送信し、生成AIモデルの応答を取得するためのものです。

## ConverseAPIサンプルのソースコード

```
import boto3
import json
import csv
import io

def read_csv(csv_path):
    """CSVファイルを読み込んでテキスト形式で返す"""
    csv_content = ""
    try:
        with open(csv_path, 'r', encoding='utf-8') as csv_file:
            # CSVの内容を読み込んで整形された文字列として返す
            reader = csv.reader(csv_file)
            for row in reader:
                csv_content += ",".join(row) + "\n"
        return csv_content
    except Exception as e:
        print(f"CSVファイル読み込みエラー: {e}")
        return None

def chat_with_csv(prompt, csv_path):
    # Bedrock clientの初期化
    bedrock_runtime = boto3.client(
        service_name='bedrock-runtime',
        region_name='us-east-1'  # あなたのリージョンに応じて変更してください
    )

    # CSVファイルの読み込み
    csv_content = read_csv(csv_path)
    if csv_content is None:
        return "CSVファイルの読み込みに失敗しました。"

    # リクエストボディの作成（CSVの内容をプロンプトに含める）
    combined_prompt = f"以下はCSVデータです:\n\n{csv_content}\n\n{prompt}"
    
    body = {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1000,
        "messages": [
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": combined_prompt
                    }
                ]
            }
        ]
    }

    try:
        # APIリクエストの実行
        response = bedrock_runtime.invoke_model(
            modelId='anthropic.claude-3-5-sonnet-20240620-v1:0',  # 使用するモデルに応じて変更
            body=json.dumps(body)
        )
        
        # レスポンスの解析
        response_body = json.loads(response['body'].read())
        return response_body['content'][0]['text']

    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return None

# 使用例
if __name__ == "__main__":
    csv_path = "sample.csv"  # CSVファイルのパスを指定
    prompt = "このCSVデータを分析して、Valueの一番高い"
    
    response = chat_with_csv(prompt, csv_path)
    if response:
        print("応答:", response)
```

## ConverseAPIの動作状況

◆ユーザープロンプト
> Valueが一番高い日時教えて

◆回答
> データを確認したところ、Valueが最も高い値を示しているのは2024-07-04 03:00:00で、値は9.835733658651034です。

このように、チャンクの制限がないConverseAPIで実施することで日時とkwhも合ってる結果を取得することができました。

## 5. まとめ

ConverseAPIでうまくいって、ナレッジベースだとうまくいかなかったことを踏まえると
今回のように多数のパラメータを持つCSVファイルの場合は
ナレッジベースベースよりもチャンクの制約がないConverseAPIを活用したほうがいいケースがあるかもしれません。

大量データを用いた日々のレポート作成などを生成AIをかつ効率化したいものの、大量のパラメータがネックとなって回答精度がいまひとつな場合には、ConverseAPIの活用も検討してみてください。

| CSVの内容 | ナレッジベース | ConverseAPI |
| ----| ---- | ---- |
| パラメータが少ない| 〇 | 〇 |
| パラメータが多い| × | 〇 |