watsonx.data Milvus + watsonx.ai RAGハンズオン Part1: Excelをベクトル化してベクトルDB Milvusに入れよう

この内容は2024年11月27日(水)にホテル雅叙園東京で開催された「[IBM TechXchange Japan 2024](https://www.linkedin.com/posts/ibm_ibmtechxchange-activity-7270719294588542976-rA6u?utm_source=share&utm_medium=member_desktop)」で実施したwatsonxハンズオン「さわってみよう ベクトル・データベース watsonx.dataでRAG体験」の内容です。QiitaではPart1, Part2, Part3, Part4の四部構成で、　この投稿は**Part1**となります。

- **Part1: Excelをベクトル化してベクトルDB Milvusに入れよう (当投稿)**
- [Part2: ベクトルDB Milvusに入ったデータで類似検索してみよう!](https://qiita.com/nishikyon/items/9531d3cbbc893822e494)
- [Parr3: ベクトルDB Milvusとwatsonx.ai LLMでRAGを構成して、質問をしてみよう!](https://qiita.com/nishikyon/items/a275d7a557e11d1b2d6b/)
- [Part4: ベクトルDB Milvusとwatsonx.ai LLMでRAGを構成して、チャットアプリを作成してみよう!](https://qiita.com/nishikyon/items/7cd7949cd993cc449ca9/)


座学部分の資料はこちらです:
- https://speakerdeck.com/kyokonishito/ibmtechxchangejp-2024-handson8-1

使用したハンズオン資料はこちらです:
- https://github.com/IBM/japan-technology/tree/main/techxchange/2024-watsonx-handson-1

# 0. ハンズオン内容　および　前提
## 0.1: ハンズオン内容
TechXchange JapanのWebサイトの内容のソースであるExcelファイルを使って、RAGを構成し、 TechXchange JapanについてQ&Aができるchatbotを作ります。<br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/c99ccaeb-f01a-2ebd-e594-0bc02ef58336.png)


chatbotのイメージ:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/6ff7c59c-00f1-cddf-fcef-474df714aeb6.png)


- このハンズオンでは watsonx.dataのベクトル・データベース　Milvusへのアクセス、データの追加をし、watsonx.aiの生成AIを組み合わせてRAGを体験していただくのが目的です。
- RAGのフレームワークとして[LangChain](https://www.ibm.com/jp-ja/topics/langchain)を使います。
- chatbotのGUI構築にはpythonの機械学習モデルのデモを行うWebアプリケーションを簡単に作ることができるPythonのライブラリー[gradio](https://www.gradio.app/)を使います。
- RAGを体験していただくのが目的のため、Milvusのindex typeやmetricsなどの詳細、LangChainの使い方詳細、gradioの使い方詳細など Coding詳細はこのハンズオンでは扱いません。

## 0.2: ハンズオン前提
当ハンズオンを実施するには以下の環境が必要です:
- watsonx.dataのMilvus または　なんらかのMilvusが使える状況にあり、接続に必要なhostname, port, APIKEY情報が入手可能
- watsonx.ai Studioでプロジェクト作成済み　かつ　Watson Machine Learning と関連付けされていて、APIKEY, Project Id情報が入手可能
- pythonが実行できるjupyter notebook環境があること(Local環境でもOK)


# 1. Excelをベクトル化してベクトルDB Milvusに入れよう！
この内容はjupyter notebook https://github.com/IBM/japan-technology/blob/main/techxchange/2024-watsonx-handson-1/notebooks/techxchange_handson_01_vectordb.ipynb  とほぼ同じです。Link先notebookはwatsonx.ai Studioでの実行を前提としていますが、当内容はLocalも含め一般的なpython実行環境が前提です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/486dc8ee-c832-733d-fd50-9670660a5331.png)

### 1. 必要なライブラリーのインストール
pipにて以下のライブラリーを導入します:
```sh
pip install 'ibm-watsonx-ai>=1.1.15'
pip install 'langchain>=0.3.3'
pip install 'langchain-ibm>=0.3.1'
pip install 'langchain-milvus>=0.1.6'
pip install 'langchain-community>=0.3.2'
pip install 'pymilvus>=2.4.8'
```

### 2. apikeyの設定
apikey をセットしてください。
IBM Cloud環境でのAPIKEYの取得方法は[こちら](https://qiita.com/nishikyon/items/fa8b951267efbe911fe7)を参考にしてください。

またwatsonx.dataとwatsonx.ai Studioは同じAPIKEYである前提で同じ変数を使用していますので、もし違う場合は2つ変数を準備するようにしてください。
```py
apikey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
```

### 3. Milvus接続情報の設定
Milvus接続情報の値をセットしてください。
- Milvus Host名
- Milvus Port番号

Milvus接続情報の詳細取得手順は[こちら](https://github.com/IBM/japan-technology/tree/main/techxchange/2024-watsonx-handson-1/watsonx_data_get_milvus_info.md) を参照してください。

またmy_connection_argsに設定するkeyの詳細は[pymilvusのMilvusClientのパラメータ](https://milvus.io/api-reference/pymilvus/v2.5.x/MilvusClient/Client/MilvusClient.md)を参照してください。



```py
milvus_host="xxx.xxx.xxx.com"
milvus_port="YYYY"

# Milvus接続情報パラメータののセット
my_connection_args ={
 'uri': f'https://{milvus_host}:{milvus_port}', 
 'token': f'ibmlhapikey:{apikey}'
}
```

### 4. watsonx.ai Studio Project idの設定
watsonx.ai Studioで実行する場合は、このノートブックが実行されるプロジェクトからProject idを取得します。 watsonx.ai Studio以外で実行する場合は、Project idを入力してください。

**Hint**: project_id はプロジェクトを表示し、管理タブから project_id を取得可能です.

```py
import os
try:
    project_id = os.environ["PROJECT_ID"]
except KeyError:
    project_id = "<ProjrctID>"
```

### 5.watsonx.aiのAuthentication用のエンドポイントのURLの設定
Waston Machine Learningのインスタンスを作成したリージョンで決まります。 https://ibm.github.io/watson-machine-learning-sdk/setup_cloud.html#authentication より

Dallas: https://us-south.ml.cloud.ibm.com
London: https://eu-gb.ml.cloud.ibm.com
Frankfurt: https://eu-de.ml.cloud.ibm.com
Tokyo: https://jp-tok.ml.cloud.ibm.com

```py
watsonx_url = "https://us-south.ml.cloud.ibm.com" #ダラスの場合
```

#### 6. Excelデータの取得
TechXchange Confrence Japan 2024の情報の入ったExcelを取得します。

尚ファイルは[こちら](https://github.com/IBM/japan-technology/raw/refs/heads/main/techxchange/2024-watsonx-handson-1/data/TechXchangeJapan2024.xlsx)にありますので、お手持ちのPCで見たい場合はダウンロードしてExcelで開いて見てみてください。

```sh
wget https://github.com/IBM/japan-technology/raw/refs/heads/main/techxchange/2024-watsonx-handson-1/data/TechXchangeJapan2024.xlsx -O TechXchangeJapan2024.xlsx
```

参考までにこんな感じの内容です:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/a7a06e50-4f92-52c1-a4aa-210b77382e54.png)


### 7. 必要ライブラリーのImport

```py
import pandas as pd
from langchain.schema.document import Document
import json
from langchain_milvus import Milvus
import os
from ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames
from langchain_ibm import WatsonxEmbeddings
from ibm_watsonx_ai.foundation_models.utils.enums import EmbeddingTypes
```

### 8, 9, 10で以下を行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/17b40f28-fbef-2cbe-4cef-41be4efbe9eb.png)

### 8. Excelファイルの内容を pandas Daraframeに読み込む
```py
path="./"
filename='TechXchangeJapan2024.xlsx'
excel_file = path+filename

df_list = []

# 全てのシートを読み込み、リストdf_listに格納
for sheet_name in pd.ExcelFile(excel_file).sheet_names:
    df = pd.read_excel(excel_file, sheet_name=sheet_name)
    df_list.append(df)
    print(f"\nExcelシート名: {sheet_name}")
    display(df.head()) #各シート最初の5　行　表示
```
出力として各シート最初の5行を表示するようにしてあります:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/84917081-671c-50b4-ecba-e28f91ffbfa2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/0673842b-9411-b60d-3c19-7fcc194987c2.png)




### 9. 行をJSON化し、metadataとして`Category`,`ID`を抜き出す
ベクトルDBにデータを入れる際、どの単位でどのようにベクトル化するかというのは、のちのちの類似検索の結果に関わってきますので重要です。

今回はやりませんが、PDFならページ単位に文字を抜き出して、1ページ分を1ベクトルににするとか、さらに細かく切っておおよそ1000文字単位でうまく文章の切れ目で切って1ベクトルにするとか、いろいろ考えられます。

今回はExcelファイルなので、シート単位で1シート1ベクトルとか、一行1ベクトルとかが考えられます。
今回は一行1ベクトルにしてみます。
さらに一行のベクトル化する元の文字列ですが、列名をいれたJSON形式の文字列にしてみます。

Milvusにはベクトルデータの他に、Keyとなる値を列として持つことができます。RDBのように列で検索も可能です。

[LangChain](https://python.langchain.com/docs/introduction/)を使って、ベクトルDBにインサートする際、列データはDocumentオブジェクトのmetadataとしてJSONで指定します。 こちらのmetadataのJSON文字列もここで作成します。

```py
import numpy as np
json_doc_list=[]
json_meta_list=[]

for df in df_list:
    # 各シートのデータフレームに対する処理
    
    # 行をJSONフォーマットに変換
    json_doc_string = json.loads(df.to_json(orient='records', force_ascii=False))
   
    # metaデータとして'Category','ID'を抜き出し, JSONに変換
    json_meta_string =  json.loads(df[['Category','ID']].to_json(orient='records', force_ascii=False), parse_int=str)

    #　各シートのJSON　Listを1つのListに結合
    json_doc_list.extend(json_doc_string)
    
    json_meta_list.extend( json_meta_string)

#中身確認 最初の5行
print("ベクトル化するデータ 最初の5行")
for index, item in enumerate(json_doc_list[0:5]):
    print(index + 1, item)

print("\nmetaデータ 最初の5行")
for index, item in enumerate(json_meta_list[0:5]):
    print(index + 1, item)
```
出力:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/77ff4932-4693-344e-e742-8d8ee898502c.png)


### 10. 1行の情報をlangchainのDocumentにし、Listを作成
- page_contentはjson_doc_listの一行分のjson
    - ベクトル化されるデータ
- metadataはjson_meta_listのの一行分のjson
    - ベクトルDBに列の項目として入るデータ

```py
# DocumentのListをjson_doc_listとjson_meta_listから作成
# page_contentはjson_doc_listの一行分のjson
# metadataはjson_meta_listのの一行分のjson

# 以下と同じコード
# docs = []
# for doc_str, meta_str in zip(json_doc_list, json_meta_list):
#     docs.append(Document(page_content=json.dumps(doc_str, ensure_ascii=False), metadata=meta_str)) 
    
docs = [Document(page_content=json.dumps(doc_str, ensure_ascii=False), metadata=meta_str) 
        for doc_str, meta_str in zip(json_doc_list, json_meta_list)]

#中身確認 最初の二行
print(docs[0], "\n\n")

print(docs[1], "\n\n")
```
出力：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/1ec2ba2e-29d0-70a0-c7e7-574d9287ece9.png)


### 11, 12で以下を行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/0ef09b8b-c71a-cd6b-1ae3-2d62b6eba426.png)

### 11. Embeddingモデルを作成
ここでは`intfloat/multilingual-e5-large`を使います

- https://huggingface.co/intfloat/multilingual-e5-large
- https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx&locale=ja#multilingual-e5-large

LangChainで使用できるwatsonx.aiのEmbeddingモデルintfloat/multilingual-e5-largeがあるので、今回はこちらを使用します:
- https://python.langchain.com/docs/integrations/text_embedding/ibm_watsonx/


> 尚、`intfloat/multilingual-e5-large`はオープンソースで公開されているので、watsonx.aiのEmbeddingモデルを使用しなくとも、ローカルにダウンロードすることで使用可能です。 その場合のコードはこちらです(今回は使用しません)
> ```py
> from langchain_huggingface import HuggingFaceEmbeddings
>from tqdm.autonotebook import tqdm
>
>embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large")
>```

<br>
下記は`apikey`はMilvusと同じものを使用しています。異なる場合は変数apikeyを変更してください。

```py
# watsonx.aiのEmbeddingモデル取得
embed_params = {
    EmbedTextParamsMetaNames.TRUNCATE_INPUT_TOKENS: 512,
    EmbedTextParamsMetaNames.RETURN_OPTIONS: {"input_text": True},
}

embeddings = WatsonxEmbeddings(
    model_id="intfloat/multilingual-e5-large",
    url=watsonx_url,
    apikey=apikey,
    project_id=project_id
    )
```

### 12. ベクトルDB Milvusにデータの挿入
参考: LangChain ドキュメント:  [Vector stores](https://python.langchain.com/docs/concepts/vectorstores/)

Milvus参考:

- metric_type https://milvus.io/docs/ja/metric.md?tab=floating 参照
- index_type https://milvus.io/docs/index.md?tab=floating　参照
- paramsは　index_type HNSWのパラメーター https://milvus.io/docs/index.md?tab=floatingのHNSWの記載参照

コレクション(Milvusのテーブル名のようなもの)の名前は`techxchange_line_data`とします。

```py
# パラメータの設定 
index_params = {
    "metric_type": "COSINE", #コサイン類似度
    "index_type": "HNSW", 
    "params": { "M": 16,"efConstruction": 200,"efSearch": 16 }
}

# techxchange_line_data に　データの挿入
vector_store = Milvus.from_documents(
    docs,
    embeddings,
    connection_args=my_connection_args,
    index_params = index_params,
    drop_old=True, #追加の場合はここをFalseに
    collection_name = 'techxchange_line_data' # コレクション名
)
```

上記はベクトル化＋データの挿入を実施していますが、もし既にデータの入っているMilvusのCollectionに接続するのみの場合は以下のように接続してください：
```py
# 既存データを使う場合はこちらを実行
from langchain_milvus import Milvus

vector_store = Milvus(
    embeddings,
    connection_args=my_connection_args,
    collection_name = 'techxchange_line_data'
)
```

### 13. 挿入データの確認
Milvus DBにロードした内容をDataFrameにダンプして表示させます。

[Attu](https://github.com/zilliztech/attu)などのGUIツールがあればそれを使用すれば見えますが、今回のハンズオンでは環境がないため、ダンプして表示してみます。

```py
from pymilvus import MilvusClient
import pandas as pd

pk_list=vector_store.get_pks(expr="pk > 0")

client = MilvusClient(uri=my_connection_args['uri'], token=my_connection_args['token'])

res = client.get(
    collection_name="techxchange_line_data",
    ids=pk_list
)

for i, milvus_rec in enumerate(res):
    vecter_data = milvus_rec['vector']
    res[i]['vector']= "["+", ".join(map(str, vecter_data))+"]"
    res[i]['pk']= str(milvus_rec['pk'])

# DataFrame df_s にダンプしたデータが入ります
df_s = pd.DataFrame.from_dict(res).reindex(columns=['ID', 'Category', 'text', 'pk', 'vector'])

# notebookの場合は以下で表示してきます
df_s

```

notebookでの表示例：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/1227f6e8-8d7b-0111-4403-ae532697862a.png)

以上で「Part1: Excelをベクトル化してベクトルDB Milvusに入れよう」は完了です。

次の[Part2: ベクトルDB Milvusに入ったデータで類似検索してみよう!](https://qiita.com/nishikyon/items/9531d3cbbc893822e494)に進んでください。