Azure Prompt Flow で OpenAI Embedding/ChatGPT を使った RAG を評価する

### はじめに

Azure Machine Learning プロンプトフローのパブリックプレビューが開始され、大規模言語モデル (LLM)を使った AI アプリケーションを Azure Machine Learning 上で開発、評価、デプロイまでできるようになりました。
プロンプトフローには開発する上で便利なツールやサンプルが提供されていますが、ここでは評価にフォーカスを当てて、プロンプトフローを利用してみたいと思います。

### 評価対象アプリケーション

ここでの評価対象は、独自のデータを利用して ChatGPT などの大規模言語モデル(LLM)から回答を生成する、いわゆる RAG (Retrieval Augmented Generation) を実装したアプリケーションです。
RAG は下記のような流れで実装されますが、1 ～ 2 は既に処理済みで Vector Store に保存されているものとし、3 ～ 4 の部分を実装したアプリケーションを評価対象とします。

1. 独自データを LLM が扱える小さいチャンクに分割する。
2. チャンクを検索可能な形式に変換し、保存する。
3. 質問を受け付けると、質問に関連するチャンクを検索する。
4. 検索したチャンクをプロンプトに含める形式で LLM から回答を得る。

既に Python で実装したアプリケーションがローカル環境では動作しており、これをプロンプトフロー上に移植します。

### 独自データ、評価データ

独自データは下記のような架空のオフィスの FAQ が書かれた CSV ファイルとします。  
カラムには「question」、「answer」が存在しています。これをそのまま評価にも利用します。
![スクリーンショット 2023-07-27 171943.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/fd4de849-6e2b-ce56-a1dd-cece387961df.png)

### 準備

ローカル環境で動作させる上でも LLM が必要ですが、Azure OpenAI リソースを作成し、埋め込み(text-embedding-ada-002)と完了(ChatGPT:gpt-35-turbo)のモデルをデプロイしておきます。

プロンプトフローを利用するためには、Azure Machine Learning workspace を作成する必要があります。ここではランタイムをカスタマイズするため、カスタマイズしたイメージを格納するためのコンテナーレジストリも合わせて作成しておきます。  
作成したリソースから Machine Learning Studio を開くと、プロンプトフローのメニューが表示されます。

![スクリーンショット 2023-07-27 160441.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/ef2bdcd4-1c3d-3ce0-be99-6bae892b6d98.png)

ここでは利用しませんが、評価方法によっては、Azure OpenAIへの接続が必要になるものがあります。  
プロンプトフローメニューを選択し、「接続」タブで作成ボタンを押し、「Azure OpenAI」を選択、作成した Azure OpenAI リソースのキーやエンドポイントの情報を入力して保存しておきます。

#### ランタイムのカスタマイズ

実装済のアプリケーションでライブラリを利用していたり、外部依存のコードがある場合、標準のランタイムでは動作しない場合があります。  
下記 MS Learn のドキュメントに従って、カスタマイズしたイメージをコンテナーレジストリに格納し、そこからランタイムを作成します。  
environment_build.yaml と environment.yaml を使って 2 回環境を作成するような手順となっています。最初に environment_build.yaml を使って作成したコンテナーレジストリのイメージを environment.yaml の image に指定して再度環境を作成する必要があります。

https://learn.microsoft.com/ja-jp/azure/machine-learning/prompt-flow/how-to-customize-environment-runtime?view=azureml-api-2

環境を作成後、プロンプトフローのランタイムを追加します。  
「コンピューティング インスタンス ランタイムの追加」を選択し、コンピューティングインスタンスを作成、環境としては「カスタマイズされた環境を使用する」オプションから先程作成した環境を選択します。
![スクリーンショット 2023-07-27 162614.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/8c224580-1096-2eec-81fc-117ff42139fd.png)

### フローの作成

プロンプトフローの作成ボタンを押し、「標準フロー」を作成します。

![スクリーンショット 2023-07-27 163400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/096cd55c-6ad8-39e3-9966-4f69a512ea90.png)

上図の通り、

input -> hello_prompt -> echo_my_prompt -> outputs

という単純なフローとなっています。

`input`の名前は`text`となっていますが、評価データのカラムと名称を合わせるとマッピングされるため、`question`に変更しておきます。

`echo_my_prompt`が Python のコードとなっているので、ここに Python コードを移植します。  
エントリポイントとなっている`my_python_tool`など基本の形式はそのまま残すことにします。  
プロンプトは前段の`hello_prompt`から受け取る形式になっていますが、プロンプトの切り出しが難しいようなら`hello_prompt`を省略して`input`を`echo_my_prompt`の入力とします。ただし、この場合、プロンプトのバリアントを作って比較するといったことができなくなります。

```python
from promptflow import tool
# RAG実装
# ...
@tool
def my_python_tool(input1: str) -> str:
  # RAG実装
  # ...
  return # 処理結果を返す
```

コードの移植が終われば、上部の「ランタイム」欄に準備したランタイムが設定されていることを確認し、入力の値に独自データに関する質問を入力して、「実行」を行います。  
問題なく移植できていれば、echo_my_prompt の「出力」に適切な回答が出力されていると思います。

### 一括テスト

ここからが本題ですが、プロンプトフローの「一括テスト」ボタンを押して評価を開始します。

任意の実行名(自動で入力されます)、準備したランタイムを設定後、評価データのアップロードを行います。  
「Upload new data」ボタンを押し、任意の「Name」を入力、「Choose a file」欄で評価データの CSV を選択して「Add」を実行すると、上部 5 行のプレビューが表示されます。
![スクリーンショット 2023-07-27 171836.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/70821215-0974-98ed-b2e7-318de4b8de43.png)

「次へ」ボタンを押すと評価方法の選択画面となります。

評価方法が複数表示されますが、「QnA f1 scores Evaluation」を選択してみます。  
「詳細の表示」ボタンを押すと評価内容の詳細が表示されますが、共通するトークンを使って f1 スコアを算出するようです。  
「評価入力マッピング」欄では、正解(ground_truth)と出力(answer)のマッピングを設定します。下図のように設定します。
![スクリーンショット 2023-07-27 172740.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/bd978624-86f6-514f-a2c7-ba302ab7c1c8.png)
設定後、「送信」を行うと、ジョブに一括テストが追加されます。

### 評価結果の確認

Machine Learning Studio の「ジョブ(job)」メニューを選択すると開始した一括テストが表示されます。  
対象の一括テストを選択し、「状態」が「完了」となったものを選択します。  
「評価履歴」のリンクを選択すると、評価のフローが表示されます。  
プロンプトフロー同様、左側のフラットビューと右側のグラフビューで構成されています。
左側の「f1_score」のコードからは、評価の詳細なコードを確認することができます。
右側には下図のようなフローが表示されますが、「aggregate_variants_results.output」の Details を開くと、各評価の f1 スコアと、集計結果が表示されます。  

![スクリーンショット 2023-07-27 174955.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/cd2c1ff0-f0cc-1268-727c-5fcbcce1a1b8.png)

今回は単純な内容であったためか、結果が「1」となりました。

### 評価データの更新

評価データを少し変更してみます。  
元のデータから少し聞き方を変えた質問、回答を評価データとして追加してみます。

元の質問、応答

```
ゴミの収集日はいつですか。
-> 毎週火曜日と金曜日の2回です。
ゴミはどこに捨てればよいですか。
-> 紙ゴミは近くの緑色のゴミ箱に捨ててください。ペットボトルやびん、缶はオフィスの東北角にあるゴミ箱の分類に従って捨ててください。
```

追加した質問、応答

```
水曜日にゴミは収集されますか。
-> いいえ、収集されません。
緑色のゴミ箱には何を捨てればいいですか。
-> 紙ゴミを捨ててください。
```

CSV を変更し、再度一括テストを実行します。「Upload new data」ボタンで変更後の CSV をアップロードし、他は前回と同じ条件で実行します。  
結果、f1 スコアは 0.88 となりました。

実際出力された内容は「出力」タブから確認することができます。回答としては問題ないように思いますが、トークン数、母数が少ないため数値が大きく下がったように思われます。
![スクリーンショット 2023-07-27 182210.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/4bb2ade7-a984-6da1-b3c2-cfe3de0c50fe.png)

### まとめ

評価方法の選択や正解となるデータの作成には難しいところがあると思いますが、結果を容易に数値化できるのは重宝しそうです。
今回はプロンプトのバリアントを利用していませんが、プロンプトをいろいろ試したい場合にはさらに有用と思われます。  
プロンプトフロー上で開発、デプロイする予定のない方でも評価に利用することを検討してみてはいかがでしょうか。