【watsonx.ai】自分の好みに合わせたワインをレコメンドさせるRAGシステム

## 概要
IBMの生成AIプラットフォーム**watsonx.ai**を活用して、内部情報を含むPDF書類から必要情報を抽出し、問い合わせ回答文章を生成するRAGシステムを構築してみました。本記事では、そのシステムの構築手順や使用した技術、生成結果について紹介していきます。

## 背景
最近の生成AIはウェブ検索機能を搭載し、ネット上の情報を効果的に回答結果に含めることが可能になっています。

ChatGPTでも2024年12月からウェブ検索機能「**ChatGPT search**」が無料ユーザに開放され、ネット上にある情報であればユーザインターフェース版のChatGPTに質問することで手っ取り早く欲しい情報を得られます。もはや「ググる」という概念は廃れつつあり、「とりあえずGPTに聞いてみる」というノリの方が多いのではないでしょうか。

一方でウェブに存在しない情報、例えば社内にある内部文書を検索対象とする場合には自前でRAGシステムの構築が必要になってくるかと思います。
LLMファインチューニングで機密情報を学習させるという手もありますが、データの準備や再学習のコストが都度かかることを踏まえると、実装に踏み切りにくいでしょう。社内情報に関する問い合わせをセキュアな環境で実現し、より高い回答精度を求める場合は初手RAG構築が効果的かなと思います。

さてさて今回はRAGのパフォーマンスをチェックするという意図も込めて、**IBMのwatsonx.ai**を用いて、内部文書を参照しながら回答を生成するシステムをサクッと構築してみました。watsonx.aiは多数の基盤モデルを活用可能で、セキュリティ面でも優れた生成AIプラットフォームです。RAGで参照できるナレッジベースには様々な形式がサポートされているので、こちらが求める要件通りのRAGシステムを迅速に構築できるという心強いソリューションですね。

https://www.ibm.com/jp-ja/watsonx

## 目的
今回の目的は以下2点とします。
1. IBM watsonx.aiでRAGシステムを簡単に構築する
2. 内部文書への問い合わせ精度をチェックする

## 今回想定するRAG問い合わせケース
さすがに社内の内部情報をここに載せるわけにはいかないので、今回は以下のケースを想定してみました。

>ワインの小売業をしているあるお店では、お店の内部情報である**販売ワインリスト**と**購入者のワイン選好状況データ**を組み合わせて、より効果的にお客様にワインを提示したいと考えています。ワイン一つ一つを定量化し、統計的にレコメンドをすることも考えましたが、数学的知識がないので断念してしまいました。
そこで自然言語で簡単に問い合わせができるRAGシステムを使って、同じようなレコメンドシステムを構築できないでしょうか、


今回は特別に私の㊙︎ワイン選好ドキュメントPDFを内部文書として扱い、それを元におすすめワインをレコメンドするシステムを作ってみようと思います。

![recommend.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958389/95a2d779-ba9e-246c-0ac2-ce6fa96487ff.jpeg)
ワインリストも事前にPDFファイルで用意したので、その中から選ばせる算段です。

## 内部情報データの準備
内部情報ファイルは以下のPDFファイル2つです。
1. **ワイン選好ファイル**: 自分のワイン診断結果（好みの特徴）を記載したPDF
    - エノテカさんの[ワイン診断](https://www.enoteca.co.jp/feature/app_wineshindan)を実施して、結果をPDFにしたものです。
    - 一応触れますが、私の好みは**ニューカベ**というジャンルらしく、深みのあるタンニンが強めに出て香りが重厚なザ・赤ワインの部類が好みだそうです。カベルネ・ソーヴィニヨンという品種はよく聞きますね。
    ```txt:ワイン診断結果.pdf
    ワイン診断結果
    ニュー・カベ
    New World Cabernet Sauvignon
    ニューワールド（新世界）で造られる、ブドウ品種「カベルネ・ソーヴィニヨン」のワインがお好みの
    あなた。
    黒系ベリーの濃厚な香りと、ぶ厚い果実味。どんな時にも満足感たっぷりのこちらのタイプは、お
    肉料理のお供にもぴったりです。
    
    産地
    ニューワールド
    フランスなど伝統的な産地に対し、アメリカやチリなどの産地を指します。ブドウ本来の果実味が
    ダイレクトに感じられる魅力に溢れたワインが生み出されています。
    ブドウ品種
    カベルネ・ソーヴィニヨン
    若いうちはパワフルなタンニンが特徴で、熟成させることで他にはない優美で複雑な風味を生む
    高級な赤ワインの代名詞的品種で高い人気を誇っています。
    
    香り
    ● ブラックベリー
    ● バニラ
    ● チョコレート
    
    近しいワイン
    メルロ
    Merlot
    ブドウ品種「メルロ」のワインがお好みのあなた。チェリーのような香りと、みずみずしいフルーツ
    感。口当たりのやさしい「ニュー・メルロ」は、どんな場面にもすっと寄り添ってくれる頼もしいワイ
    ンです。
    ネッビオーロ
    Piemonte Nebbiolo
    イタリアで造られる、ブドウ品種「ネッビオーロ」のワインがお好みのあなた。赤系果実やバラの香
    りが優雅に広がり、しっかりとした酸味と豊富なタンニンを持つ味わいは、まさに高貴という言葉
    がぴったりの風格溢れるワインです。
    ```
    
2. **ワインリスト**: ワインリストもエノテカさんから適当に7本選んで特徴をPDF文書にまとめました
    ```txt:ワインリスト.pdf
    ワインリスト
    ピノタージュ・シラーズ
    レオパーズ・リープ
    PINOTAGE SHIRAZ LEOPARD'S LEAP
    
    味わい
    外観は深い紫の色調です。プラムやフルーツコンポート、ハーブなどの豊かな香りと、樽由来のバニラ
    のニュアンスが見事に調和。複雑な風味の中に溶け込んだ黒コショウやモカ、黒系果実の深みのある
    味わいが広がります。ビーフシチューやラム肉のグリル、ステーキなどのお肉料理との相性抜群な1本
    です。
    
    商品の紹介
    こちらのピノタージュ・シラーズは、上質なピノタージュとシラーズをブレンドした、果実味溢れる赤ワイ
    ン。南アフリカでシラーズとピノタージュの栽培に最も適したスワートランド地区とウェリントン地区の畑か
    ら厳選したブドウを使用しています。
    .
    .
    .
    
    ```
実際のシステムではここで外部公開できない機密情報を用意or抽出することになると思います。

## 環境構築
1. Python環境の構築
    - 必要なライブラリをインストール。
    ```python:title.requirements.txt
    ibm-watson==9.0.0
    ibm-watson-machine-learning==1.0.367
    langchain-community==0.3.13
    pypdf==5.1.0
    sentence-transformers==3.3.1
    ```

2. IBM watsonx.aiのセットアップ
    - APIキーの取得
    - プロジェクト作成とサービスリソースの紐付け

APIキーの取得とプロジェクトの作成方法は以下が参考になります。

https://qiita.com/i_toma/items/c83f850261cdd0484cc2#%E6%96%87%E6%9B%B8%E3%81%AE%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E5%8C%96embedding


## 実装手順
### 1. モデルの設定
以下のコードでwatsonx.aiの基盤モデルを設定します。
```python
from ibm_watson_machine_learning.foundation_models.utils.enums import ModelTypes
from ibm_watson_machine_learning.foundation_models import Model
from ibm_watson_machine_learning.metanames import GenTextParamsMetaNames as GenParams
from ibm_watson_machine_learning.foundation_models.extensions.langchain import WatsonxLLM

URL        = "<URL>"
API_KEY    = "<API_KEY>"
PROJECT_ID = "PROJECT_ID"

credentials = {
    "url"   : URL,
    "apikey": API_KEY,
}

# 使用するLLMのパラメータ
generate_params = {
    GenParams.MAX_NEW_TOKENS    : 500,
    GenParams.MIN_NEW_TOKENS    : 0,
    GenParams.DECODING_METHOD   : "greedy",
    GenParams.REPETITION_PENALTY: 1
}

# モデルの初期化
model = Model(
    model_id    = "meta-llama/llama-3-8b-instruct", #使用するLLM名
    credentials = credentials,
    params      = generate_params,
    project_id  = PROJECT_ID
)

# LangChainで使うLLM
custom_llm = WatsonxLLM(model=model)
```

### 2. PDFファイルの読み込み
`PyPDFLoader`を使用してPDFを読み込ませます。
```python
from langchain.document_loaders import PyPDFLoader
loader_wine = PyPDFLoader("./ワインリスト.pdf")
loader_user = PyPDFLoader("./ワイン診断結果.pdf")
```

### 3. ベクトルストアの構築
続いて`langchain`の`VectorstoreIndexCreator`を用いてベクトルインデックスを作成します。

`VectorstoreIndexCreator`はインデックス化（データを検索しやすい形に構造化して、迅速な検索を実現）まで自動で行ってくれるので少ないコードでRAGシステムを構築できます。

```python
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.indexes import VectorstoreIndexCreator

index = VectorstoreIndexCreator(embedding=HuggingFaceEmbeddings()).from_loaders([loader_wine, loader_user])
```
### 4. 回答生成
最後に検索を実際にしてみて、LLMの回答を見てみましょう。
「**### 指示**」のように指示内容を明示的にすることで回答の精度が上がるそうです。（プロンプトチューニングの一種）
```python
query ="""
以下の指示を満たす応答を書きなさい。

### 指示:
Aさんに対して、「ワインアイテムリスト」からワインを一本おすすめしてください。

### 応答:
"""
answer = index.query(llm=custom_llm, question=query)
print(answer)
```

回答結果は以下のようになりました。
>I'd like to recommend the "ARP A C ABERNET SA UVIGNON IL BORRO" from the wine item list. This wine has a rich, intense flavor profile with notes of blackberry, raspberry, and spices, which would pair well with a variety of meat dishes, such as grilled steak or lamb. Its bold tannins and acidity would also complement the richness of the wine, making it a great choice for a special occasion. Would you like to try this wine?

英語で回答してきたのでDeepLで翻訳します。

>（DeepL訳）
>ワインのアイテムリストから「**ARP A C ABERNET SA UVIGNON IL BORRO**」をお勧めしたい。このワインは、ブラックベリー、ラズベリー、スパイスの香りを持つ濃厚な味わいで、グリルしたステーキやラム肉など、様々な肉料理と相性が良い。大胆なタンニンと酸味もワインのリッチさを引き立ててくれるので、特別な日にぴったりのワインだ。このワインを試してみませんか？

ワイン名に妙な半角区切りが見られますが、**ARP A C ABERNET SA UVIGNON IL BORRO**というカベルネのワインを提示していますね。カベルネ好きであるということが反映されていて嬉しい結果です。

ちゃんと選好データを考慮に入れて、レコメンドしている雰囲気はありますね。

## まとめ
このように**watsonx.ai**を活用して簡単にPDFを参照したRAGシステムを実装することができました。
各ワインの特徴量を作ることなく、自然言語ベースでレコメンドが実現できるのも生成AIの強みだと感じました。
基盤モデルは**InstructLab**というプラットフォームを使うことで簡単にファインチューニングすることも可能らしいので、今後はそちらの検証もしてみようと思います。

## 参考
https://qiita.com/i_toma/items/c83f850261cdd0484cc2#%E6%96%87%E6%9B%B8%E3%81%AE%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E5%8C%96embedding

https://qiita.com/yanagih/items/60d3263518da7acc126e

https://qiita.com/tmiyata25/items/bfb7f4f5f22ec659c791#vectorestoreindexcreator