Azure Chatを利用したRAGの試し方

RAGを試すために`Azure Chat`を利用した所、自分で作ったインデックスに合わせてコードを修正する必要のある箇所がいくつかあったので、`Azure Chat`をローカルにデプロイしてRAGを試すための手順をまとめます。

実行時点と執筆時点で`Azure Chat`の構成がだいぶ変わっていたため、実際に試す場合には、以下のように過去のコミットに戻してお試しください。

```sh
git checkout 601086b
```

### Azure Chatとは

`Azure Chat`は`Microsoft`によって提供されているオープンソースのWebアプリで、`Azure OpenAI`を利用したプライベートな`Chat GPT`を用意することができます。

https://github.com/microsoft/azurechat

### 実行時環境

- macOS Monterey 12.7.4
- Node.js 18.19.0

### 必要なモノの準備

`Azure Chat`を利用するには以下のリソースを用意する必要があります。

- Azure OpenAI
- Azure AI Search
- Azure Cosmos DB

#### Azure OpenAI

`GPTモデル`を用いた回答の生成と、必要な情報をベクトル検索するために、プロンプトをベクトル化する`埋め込みモデル`を使用します。
これらのプロセスを実装するために、`Azure OpenAI`を利用します。

以下を参考にリソースを作成し、`GPT-4`もしくは`GPT-3`のモデルと`埋め込みモデル`をデプロイします。
- [Azure OpenAI Service リソースを作成してデプロイする](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/create-resource?pivots=web-portal)

`GPTモデル`、`埋め込みモデル`については以下を参考にしてください。
- [Azure OpenAI Service モデル](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/models)

#### Azure AI Search

回答を生成するのに必要な情報をもつドキュメントを検索するために使います。

以下を参考にサービスを作成し、ドキュメントの内容をベクトル化したフィールドを持つインデックスを作成します。
- [ポータルで Azure AI Search サービスを作成する](https://learn.microsoft.com/ja-jp/azure/search/search-create-service-portal)
- [クイックスタート: 垂直統合 (プレビュー)](https://learn.microsoft.com/ja-jp/azure/search/search-get-started-portal-import-vectors)

#### Azure Cosmos DB

プロンプトと回答の履歴を残すために使います。

以下を参考に`Azure Cosmos DBアカウント`を作成します。
- [クイックスタート: Azure portal を使って Azure Cosmos DB のアカウント、データベース、コンテナー、項目を作成する](https://learn.microsoft.com/ja-jp/azure/cosmos-db/nosql/quickstart-portal)


### 構築手順

#### 1. 以下実行

```bash
git clone https://github.com/microsoft/azurechat.git azurechat
cd azurechat/src
cp .env.example .env.local
npm install
```

#### 2. `.env.local`の以下定数記入
```bash
# [Azure OpenAI] -> [キーとエンドポイント]の[キー１]もしくは[キー2]
OPENAI_API_KEY 

# Azure OpenAIリソース名
AZURE_OPENAI_API_INSTANCE_NAME

# GPTモデル名(GPTモデルをデプロイした際に付けた名前)
AZURE_OPENAI_API_DEPLOYMENT_NAME

# 埋め込みモデル名(埋め込みモデルをデプロイした際に付けた名前)
AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME

# [Azure Cosmos DB] -> [概要]の[URI]
AZURE_COSMOSDB_URI

# [Azure Cosmos DB] -> [キー] -> [Read-write Keys]タブの[PRIMARY KEY]もしくは[SECONDARY KEY]
AZURE_COSMOSDB_KEY

# [Azure AI Search] -> [キー]の[プライマリ管理者キー]、[セカンダリ管理者キー]、[クエリキー]のいずれか
AZURE_SEARCH_API_KEY

# Azure AI Searchサービス名
AZURE_SEARCH_NAME

# Azure AI Searchで作成したインデックス名
AZURE_SEARCH_INDEX_NAME
```

#### 3. コード調整

検索対象インデックスのフィールド名に合わせて調整。  

```src/features/chat/chat-services/chat-api-data.ts
	// ChatAPIData関数
	const context = relevantDocuments
	.map((result, index) => {
		// result.[インデックスのドキュメント内容を保持するフィールド名].replace()に修正
		const content = result.chunk.replace(/(\r\n|\n|\r)/gm, "");
		const context = `[${index}]. file name: ${result.metadata} \n file id: ${result.id} \n ${content}`;
		return context;
    })

	// findRelevantDocuments関数
	const relevantDocuments = await
	similaritySearchVectorWithScore(query, 10, {
		filter: "", // インデックスのフィールド名に合うフィルターを記述。ここではフィルターなし。
	});
```

```src/features/chat/chat-services/azure-cog-search/azure-cog-vector-store.ts
	// Azure AI Search からのレスポンスに合わせる
	export interface AzureCogDocumentIndex {
		id: string;
		chunk: string; // 上記ChatAPIData関数修正の[インデックスのドキュメント内容を保持するフィールド名]に合わせる(コード上のエラーが気になるようであれば)
		embedding?: number[];
		user: string;
		chatThreadId: string;
		metadata: string;
	}

	// similaritySearchVectorWithScore関数
	const searchBody: AzureCogRequestObject = {
		search: filter?.search || "*",
		facets: filter?.facets || [],
		filter: filter?.filter || "",
		vectors: [
			{
				value: embeddings.data[0].embedding,
				fields: "vector", // fieldsの値を[インデックスのドキュメント内容のベクトルを保持するフィールド名]に合わせる
				k: k
			},
		],
		top: filter?.top || k,
    };
```

#### 4. ローカルサーバーを起動し、立ち上がったサイトにアクセス

```
npm run dev
```

#### 5. RAGモードに切り替えて質問開始

`＋マーク`をクリックし、ルームを立ち上げ、`How would you like to chat?`を`File`に切り替えてプロンプトを送信する。

![スクリーンショット 2024-04-02 9.19.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115549/b6da9a36-79e4-fcd6-eb06-90de4225e782.png)

### まとめ

ここでは回答の精度にはこだわらず単純に動く状態にしただけなので、回答に不満がある場合にはインデックスやプロンプトの改良などを行い、納得がいく回答が返ってくるよう調整してみてください。