Azure Machine Learning の Prompt flow で Azure Cognitive Search をベクトルストアとして RAG を実行する

Azure Machine Learning に Prompt flow が搭載され、パブリックプレビューが開始されました。Prompt flow は大規模言語モデル (LLM) を利用した AI アプリケーションの開発サイクル全体を合理化するように設計された新時代の開発ツールです。

https://learn.microsoft.com/en-us/azure/machine-learning/prompt-flow/overview-what-is-prompt-flow?view=azureml-api-2

# Azure AI Studio 
[Azure AI Studio](https://www.youtube.com/watch?v=DaIYrlMOj7I) は Azure OpenAI Studio の Chat Playground や Azure Machine Learning の Prompt flow を包含するサービスというイメージですが、まだ全貌は明らかになっておりません。 Prompt flow は現状は Azure Machine Learning の中の 1 機能として公開されましたが、今後は Azure AI Studio なる UI からも呼べるようになるのではないかと思います。

Azure AI Studio は [Copilot stack](https://qiita.com/nohanaga/items/c3e92c9adeca7c84a8d5) における AI オーケストレーションの層を担う重要なコンポーネントです。
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/1876d006-65c4-a36b-df45-2b0409dacd01.png)
さらに、Prompt flow の中で使用できる機能スタックにブレークダウンします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/cd5e8a68-61d6-8dc8-143c-5645f73dc469.png)
基本的には Azure ML のコンピューティングインスタンス(VM)上の Python 実行基盤となり、OpenAI モデルや Azure Cognitive Search、Faiss のベクトルインデックスなどを接続して Grounding することができます。Prompt flow ではプロンプトエンジニアリングの評価・比較を行ったり、完成したオーケストレーターをマネージドオンラインエンドポイントにデプロイして、UI から呼び出せるようにできます。

# Azure Cognitive Search
Azure Cognitive Search は Prompt flow にネイティブ対応しており、「[Vector DB Lookup](https://learn.microsoft.com/azure/machine-learning/prompt-flow/tools-reference/vector-db-lookup-tool?view=azureml-api-2)」ツールを利用してベクトル検索することができます。モデルが知らない内容を外部検索システムから検索した情報を付加して質問などに回答させる Retrieval Augmented Generation(RAG) を構築可能です。

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/1540345c-3983-8fb2-3551-123c7eae67c1.png)
本日はこのようなアーキテクチャ（の RAG 部分）を Prompt flow を使って実装していきましょう。

# Prompt flow の構築
## コネクションの作成
### Azure OpenAI との接続
Azure OpenAI リソースを作成し、Embeddingsモデル「text-embedding-ada-002」とチャット系の「gpt-35-turbo」モデルをデプロイしておきます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/81a47743-bc16-f606-aa97-c710536083a9.png" alt="003.png" width="400"/>

「接続」タブから「AzureOpenAI」を選択します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/ce459a46-23e3-8a66-426d-0b5672ed1411.png" alt="004.png" width="400"/>

Azure OpenAI の接続情報を入力します。API version に最新 GA 版の `2023-05-15` と入れると、Prompt flow の「deployment_name」ドロップダウンに選択肢が表示されない現象が出ました。（自分だけ？）一度デフォルトの `2023-03-15-preview` でフローを作成してから、接続設定を変更するとギリいけました💦 裏技…

### Azure Cognitive Search との接続
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/62a2df62-62a4-abd3-79c8-79b11544c782.png" alt="005.png" width="400"/>

「接続」タブから「CognitiveSearch」を選択し、Azure Cognitive Search への接続情報を入力します。ベクトルインデックスはいつもの[戦国武将データ](https://github.com/nohanaga/busho-index/blob/main/TextChunking_Embeddings_RegisterIndex.ipynb)を使っています。

# ランタイムの作成
Prompt flow の実行基盤であるコンピューティングインスタンス(VM)を作成し、ランタイムとしてアタッチします。ちょっとしたテストにしか使いませんので、お安い「STANDARD_DS2_V2」にしました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/7ad75994-1cf8-f2d6-8037-4fd45ee97632.png" alt="006.png" width="600"/>

# Prompt flow の作成
「フロー」タブから「＋作成」ボタンを押して、ギャラリーから「Bring Your Own Data QnA」を選んで「詳細の表示」を押してから「複製」ボタンを押せば自動的に展開されます。
![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/9d5a6755-f7f9-1554-c754-aabc01ac74c3.png)

## 入力
ランタイム設定のところに、作成済みのランタイムをアタッチします。次にフローの一番上の「入力」にテストする質問を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/2d320024-2e72-b29c-a7e1-300410e20514.png)

## Vector DB Lookup ツール
デフォルトのフローではベクトルインデックスが **FAISS のインデックス** になってしまっているのでこれをゴミ箱ボタンを押して削除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/6ff3a05e-12b4-5e21-a821-330257a5f7e2.png)
次に「＋その他のツール」から「[Vector DB Lookup](https://learn.microsoft.com/en-us/azure/machine-learning/prompt-flow/tools-reference/vector-db-lookup-tool?view=azureml-api-2)」ツールを選択し、以下のように作成済みベクトルインデックスへのマッピングを行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/4cde3033-296b-0f2a-dea9-209aa1da3525.png)
重要なのは `vector` クエリフィールドですね。ここにはひとつ前のノードである「embed_the_question」ノードの出力結果 `${embed_the_question.output}` を指定する必要があります。Azure Machine Learning のフローは有向非巡回グラフを形成し、前後のノードの関連性は**データの入出力**によって決定されます。

## Source フィールドについて
デフォルトでは、「ファイル名」のフィールドが無いですね…　これは次のノードである「generate_prompt_context」コードを以下で置き換えることで取得することができます。

```python
from typing import List
from promptflow import tool
from embeddingstore.core.contracts import SearchResultEntity

@tool
def generate_prompt_context(search_result: List[dict]) -> str:
    def format_doc(doc: dict):
        return f"Content: {doc['Content']}\nSource: {doc['Source']}"

    SOURCE_KEY = "sourcepage"
    #URL_KEY = "url"

    retrieved_docs = []
    for item in search_result:

        entity = SearchResultEntity.from_dict(item)
        content = entity.text or ""

        source = ""
        if entity.original_entity is not None:
            if SOURCE_KEY in entity.original_entity:
                source = entity.original_entity[SOURCE_KEY] or ""
                #if URL_KEY in entity.original_entity[SOURCE_KEY]:
                #    source = entity.original_entity[SOURCE_KEY][URL_KEY] or ""

        retrieved_docs.append({
            "Content": content,
            "Source": source
        })
    doc_string = "\n\n".join([format_doc(doc) for doc in retrieved_docs])
    return doc_string
```
コードを置き換えたら、以下のように**前のノードの出力**を入力にセットすることで、ノード間の関連が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/05bd32be-9f8b-56b5-dc05-168b5ecee16e.png)
Python コード内で `print` した場合は「出力」の「ログ」タブに標準出力が表示されます。

## Vector DB Lookup ノードの前後の関係が完成
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/409acbfa-20ee-68b1-11d7-361809e23450.png" alt="006.png" width="400"/>

## Prompt variants でプロンプトエンジニアリングの比較を実施
プロンプトエンジニアリングの評価・比較ができる目玉機能です。「Prompt_variants」ノードを見るとデフォルトで 3 種類のプロンプトが比較対象として登録されています。variants に登録しておけば、同じインプットデータで結果がどう変わるのかを一覧で比較できるのです。

## Variant_0 英語の system プロンプト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/e239633e-5bc2-ca0f-f01d-22cc50f4f834.png)
### Variant_1 日本語の system プロンプト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/caf23d6e-99b8-832e-1f50-953216d0194a.png)

＋αで Few-shot サンプルを入れた variant も加えます。プロンプトフローの評価のみを行う場合は評価フローテンプレートを利用できます。利用可能な評価メトリクスは以下を参考にしてください。

https://qiita.com/nohanaga/items/b68bf5a65142c5af7969

# フローの実行
右上の「実行」ボタンを押してバリアントを実行するノードを選択して「送信」ボタンを押すとフロー全体の処理が実行されます。「実行が完了しました」と表示されたら、以下の「出力の表示」ボタンを押して結果を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/0799f7cf-0c9a-bf34-0f9c-98ae8f8d6ae0.png)

用意した 3 種類のプロンプト variant の実行結果がそれぞれ表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/128f71d3-0cd0-2bb1-15bc-af62c5a01d2c.png)
なんか output 欄小さくない？気づいた点はフィードバックしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/26fa6427-4681-46da-1180-be1987131472.png)

ブラウザの DevTools のようなトレース機能もついており、処理時間をノードごとに可視化できます。

# デプロイ
完成したフローをリアルタイム推論用のマネージド オンライン エンドポイントへデプロイできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674344/86b7dfe2-bba9-5d6d-7440-d31204533939.png)

# さいごに
プロンプトエンジニアリングの評価を簡単に行えるようになったというのは非常に便利ですね。テストデータを用意して一括テストする機能もあるので、今後 LLM 開発の重要なツールになりそうです。
パブリックプレビュー開始時点では、Build の[動画]()にあった Semantic Kernel ボタンや LangChain ボタンがありませんね。こちらは登場まで待つ必要があるかもしません。