AutoAI RAGのSDKを試してみた

### 背景
RAGって、難しくないですか？
データを取り込むときのチャンクのサイズや意味をベクトルで表現するために必要なembedding model、回答を生成するLLMなどなど、考慮すべき点は多くあります。それらを毎回全部検証していたら、いつまで経ってもビジネスに生成AIを生かすことなんて、はるか先の未来になってしまいます。
そこで、IBMから、それらの変数を自動で試し、RAGASなどの数値でRAGの結果を表せる指標を用いて、最適な結果を出してくれるAutoAI RAGのSDKがベータ版として出てきたので、試してみました。

### AutoAI RAGの簡単な説明
サボりやガチ勢の私はIBMさんの[ドキュメント](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/autoai-programming-rag.html?context=wx&audience=wdp)をそのまま引用させていただきます。
>AutoAI RAG を使用して実験を実行すると、Tree Parzen Estimator (TPE) と呼ばれる超パラメータ最適化アルゴリズムを使用することで、すべての RAG 設定オプションのテストを回避できます (たとえば、グリッド検索を回避できます)。 以下の図は、16のRAGパターンから選択できるRAGコンフィギュレーション検索空間のサブセットを示している。 実験がそれらすべてを評価した場合、1～16のランク付けが行われ、最高ランクの3つのコンフィギュレーションがベストパフォーマンスとしてタグ付けされる。 TPEアルゴリズムは、どのRAGパターンのサブセットを評価するかを決定し、灰色で示されている他のパターンの処理を停止する。 この処理により、指数関数的な探索空間の探索を回避しつつ、よりパフォーマンスの高いRAGパターンを実際に選択することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2505349/0bf71e31-d1f9-8c3c-5ff1-88b1693fe76f.png)

なるほど、良さそうには見える。もし可能なら設定できる変数を自分で決めたりできるといいですね。あと、まだembedding model自体は日本語対応していないですね。今後BYOMできると最高ですね。
まぁ、どんなもんか試してみますか。

### AutoAI RAGのSDKを試す
せっかくなので、IBMさんのwatsonx.ai studio(旧watson studio)で試してみます。
***諸々の設定***
WMLの連携とか諸々をしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2505349/6f729e78-6c10-9dce-ab7e-67e759ffc139.png)
その後、アセットの新規資産から「PythonまたはR notebookでのデータおよびモデルの処理」を選択して、[こちら](https://ibm.box.com/shared/static/yrb963g6olrna7b5e2u66djglraxu5vd.ipynb)をローカル・ファイルから参照してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2505349/1f3bb27f-5f44-db15-7f23-f4e7b5ef88af.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2505349/6d6819a9-35d5-d4cf-846c-db1b156bd347.png)

あとは、Pythonに記載している通りに進めていきます。
まずは、必要なモジュールをインストールしていきます。
```python
!pip install -U wget | tail -n 1
!pip install -U 'ibm-watsonx-ai[rag]>=1.1.11' | tail -n 1
```

その後、WMLインスタンスがあるアカウントのAPIキーを取得＆入力します。
```python
import getpass
from ibm_watsonx_ai import Credentials

credentials = Credentials(
    url="https://us-south.ml.cloud.ibm.com",
    api_key=getpass.getpass("Please enter your WML api key (hit enter): "),
)
```

***RAGのサンプルデータを取り込みます***
今回は、ibm_watsonx_ai Python SDK の zip ファイルを GitHub からダウンロードして使用します。
```python
import wget, zipfile, os

filename = "watsonx-ai-python-sdk"
filename_zip = "watsonx-ai-python-sdk.zip"

if not os.path.isfile(filename_zip): 
    wget.download("https://github.com/IBM/watsonx-ai-python-sdk/archive/refs/heads/gh-pages.zip", out=filename_zip)

with zipfile.ZipFile(filename_zip, "r") as zip_ref:
    zip_ref.extractall(filename)

from ibm_watsonx_ai.helpers import DataConnection, ContainerLocation

container_location_path = "ibm_watsonx_ai_sdk_docs"

container_data_connection = DataConnection(ContainerLocation(path=f"{container_location_path}/"))
container_data_connection.set_client(client)

html_docs_files = []
for root, dirs, files in os.walk(filename):
    if root == f"{filename}/watsonx-ai-python-sdk-gh-pages":
        for file in files:
            if file.endswith('.html'):
                file_path = os.path.join(root, file)
                html_docs_files.append(file_path)

import sys

for ind, html_docs_file in enumerate(html_docs_files):
    container_data_connection.write(html_docs_file, remote_name = html_docs_file.split("/")[-1])
    
    sys.stdout.write(f"\rProgress: {'✓' * (ind+1)}{'.' * (len(html_docs_files)-ind-1)}\r")
    sys.stdout.flush()
```
ICOSに接続します。
```python
datasource_name = 'bluemixcloudobjectstorage'
bucketname = container_data_connection.location.bucket

cos_credentials = client.spaces.get_details(space_id=space_id)['entity']['storage']['properties']

conn_meta_props= {
    client.connections.ConfigurationMetaNames.NAME: f"Connection to Database - {datasource_name} ",
    client.connections.ConfigurationMetaNames.DATASOURCE_TYPE: client.connections.get_datasource_type_id_by_name(datasource_name),
    client.connections.ConfigurationMetaNames.DESCRIPTION: "Connection to external Database",
    client.connections.ConfigurationMetaNames.PROPERTIES: {
        'bucket': bucketname,
        'access_key': cos_credentials['credentials']['editor']['access_key_id'],
        'secret_key': cos_credentials['credentials']['editor']['secret_access_key'],
        'iam_url': 'https://iam.cloud.ibm.com/identity/token',
        'url': cos_credentials['endpoint_url']
    }
}

conn_details = client.connections.create(meta_props=conn_meta_props)
connection_id = client.connections.get_id(conn_details)
```
connectionを作成します。
```python
from ibm_watsonx_ai.helpers import DataConnection, S3Location

input_data_references = [DataConnection(
    connection_asset_id=connection_id,
    location=S3Location(bucket=bucketname,
                        path=container_location_path))]
input_data_references[0].set_client(client)
```

***ベンチマークに使用する json ファイルを COS にアップロードし、このファイルへの接続を定義します。***
この例では、ibm_watsonx_ai SDK ドキュメントの内容を使用しています。
```python
benchmarking_data_IBM_page_content = [
    {
        "question": "How to install ibm-watsonx-ai library?",
        "correct_answer": "pip install ibm-watsonx-ai",
        "correct_answer_document_ids": [
            "install.html"
        ]
    },
    {
        "question": "What is Credentails class parameters?",
        "correct_answer": "url, api_key, name, iam_serviceid_crn, token, projects_token, username, password, instance_id, version, bedrock_url, proxies, verify",
        "correct_answer_document_ids": [
            "base.html"
        ]
    },
    {
        "question": "How to get AutoAI pipeline with number 3?",
        "correct_answer": "get_pipeline(pipeline_name='Pipeline_3')",
        "correct_answer_document_ids": [
            "autoai_working_with_class_and_optimizer.html"
        ]
    },
    {
        "question": "How to get list of Embedding Models?",
        "correct_answer": "client.foundation_models.EmbeddingModels",
        "correct_answer_document_ids": [
            "fm_embeddings.html"
        ]
    },
    {
        "question": "How to retrieve the list of model lifecycle data?",
        "correct_answer": "get_model_lifecycle(url='https://us-south.ml.cloud.ibm.com', model_id='ibm/granite-13b-instruct-v2')",
        "correct_answer_document_ids": [
            "fm_helpers.html"
        ]
    },
    {
        "question": "What is path to ModelInference class?",
        "correct_answer": "ibm_watsonx_ai.foundation_models.inference.ModelInference",
        "correct_answer_document_ids": [
            "fm_model_inference.html"
        ]
    },
    {
        "question": "What is method for get model inferance details?",
        "correct_answer": "get_details()",
        "correct_answer_document_ids": [
            "fm_model_inference.html"
        ]
    }
]
```
その後、Jsonファイルとしてアップロードします。
```pyhon
import json

test_filename = "benchmarking_data_ibm_watson_ai.json"

if not os.path.isfile(test_filename):
    with open(test_filename, "w") as json_file:
        json.dump(benchmarking_data_IBM_page_content, json_file, indent=4)

test_asset_details = client.data_assets.create(name=test_filename, file_path=test_filename)

test_asset_id = client.data_assets.get_id(test_asset_details)
test_asset_id
```
テストデータへの接続情報のコネクションを作成
```
from ibm_watsonx_ai.helpers import DataConnection

test_data_references = [DataConnection(data_asset_id=test_asset_id)]
```

***Milvusへの接続情報を作成する***
```python
import os
import getpass

milvus_connection_id = input("Provide connection asset ID in your space. Skip this, if you wish to type credentials by hand and hit enter: ") or None

if milvus_connection_id is None:
    try:
        username = os.environ["USERNAME"]
    except KeyError:
        username = input("Please enter your Milvus user name and hit enter: ")
    try:
        password = os.environ["PASSWORD"]
    except KeyError:
        password = getpass.getpass("Please enter your Milvus password and hit enter: ")
    try:
        host = os.environ["HOST"]
    except KeyError:
        host = input("Please enter your Milvus hostname and hit enter: ")
    try:
        port = os.environ["PORT"]
    except KeyError:
        port = input("Please enter your Milvus port number and hit enter: ")
    try:
        ssl = os.environ["SSL"]
    except:
        ssl = bool(input("Please enter ('y'/anything) if your Milvus instance has SSL enabled. Skip if it is not: "))

    # Create connection
    milvus_data_source_type_id = client.connections.get_datasource_type_uid_by_name(
        "milvus"
    )
    details = client.connections.create(
        {
            client.connections.ConfigurationMetaNames.NAME: "Milvus Connection",
            client.connections.ConfigurationMetaNames.DESCRIPTION: "Connection created by the sample notebook",
            client.connections.ConfigurationMetaNames.DATASOURCE_TYPE: milvus_data_source_type_id,
            client.connections.ConfigurationMetaNames.PROPERTIES: {
                "host": host,
                "port": port,
                "username": username,
                "password": password,
                "ssl": ssl,
            },
        }
    )

    milvus_connection_id = client.connections.get_id(details)
```
hostには、watsonx.data MilvusのGRPCホスト
portには、watsonx.data Milvuのポート番号
usernameには、ibmlhapikey
passwordには、watsonx.data MilvusのインスタンスがあるアカウントのAPIキー
SSLは"y"(YES)
を入力してください。

接続情報を作成します。
```python
vector_store_references = [DataConnection(connection_asset_id=milvus_connection_id)]
```

さてさて、長くなりましたが、やっとAutoAI RAGを試せます。

***AutoAI RAGを実行***
AutoAI RAGオプティマイザの入力情報を指定していきます。
name - 実験名
description - 実験の説明
max_number_of_rag_patterns - 作成するRAGパターンの最大数
optimization_metrics - 目標とする最適化メトリクス

今回は、以下のように設定しました。
```python
from ibm_watsonx_ai.experiment import AutoAI

experiment = AutoAI(credentials, space_id=space_id)

rag_optimizer = experiment.rag_optimizer(
    name='AutoAI RAG ibm-watsonx-ai SDK documentation',
    description="AutoAI RAG experiemnt trainded on ibm-watsonx-ai SDK documentataion",
    max_number_of_rag_patterns=6,
    optimization_metrics=[AutoAI.RAGMetrics.ANSWER_CORRECTNESS]
)
```
実行します。
```python
run_details = rag_optimizer.run(
    input_data_references=input_data_references,
    test_data_references=test_data_references,
    vector_store_references=vector_store_references,
    background_mode=False
)
```
以下を実行して、'completed'と出れば成功です。
```
rag_optimizer.get_run_status()
```

さて、結果をPandas DataFrameの形で一覧することができるので、見てみましょう。
```python
summary = rag_optimizer.summary()
summary
```
RAGASの指標の結果とともに、使用したembedding model、チャンクサイズなどの結果が出てきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2505349/edc50d9f-82c9-6ec6-2052-b7a59a181d10.png)

じゃあ、一番いいのなんなの？ということで以下のコマンドで教えてもらいました。
```python
best_pattern_name = summary.index.values[0]
print('Best pattern is:', best_pattern_name)

best_pattern = rag_optimizer.get_pattern()
```
Best pattern is: Pattern4
だそうです。

### 感想
GUIで、簡単にできるようになるらしいので、それが出てくるのが楽しみですね。あとはやっぱり、変数をもっと自由に設定できると個人的には嬉しいかな。