Azure OpenAI on your data の REST API で ChatGPT をカスタマイズする

### はじめに

Azure OpenAI on your data は ChatGPT や GPT-4 といった LLM に独自のデータを追加できるサービスです。  
[前回の記事](https://qiita.com/fsdg-honda/items/06323590ecb8fea204d9)では、Azure OpenAI Studio の ChatGPT プレイグラウンドから動作を確認しました。  
今回は本サービスの REST API の動作を確認します。

### Completions extensions API のリクエスト

Completions API の拡張となる下記 Completions extensions API を使用します。

https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference#completions-extensions

上記 MS Learn のドキュメントに記載がありますが、少しわかりづらい箇所がありますので、説明を補足したものを下記に記載します。  
{}で囲んだ箇所に説明を記載しています。

```
curl -i -X POST {Azure OpenAIのエンドポイント(ドメイン名まで)}/openai/deployments/{デプロイ名}/extensions/chat/completions?api-version=2023-06-01-preview \
-H "Content-Type: application/json" \
-H "api-key: {Azure OpenAIのAPIキー}" \
-H "chatgpt_url: {Azure OpenAIのCompletion APIのURL(api-versionのクエリまで)}" \
-H "chatgpt_key: {Azure OpenAIのAPIキー}" \
-d \
'
{
    "dataSources": [
        {
            "type": "AzureCognitiveSearch",
            "parameters": {
                "endpoint": "{Cognitive Searchのエンドポイント(ドメイン名まで)}",
                "key": "{Cognitive Searchの管理者キー}",
                "indexName": "{Cognitive Searchのデータ格納先としたインデックス}"
            }
        }
    ],
    "messages": [
        {通常のCompletion APIと同様、roleとcontentからなるmessage}
    ]
}
'
```

例:

```
curl --location 'https://xxx.openai.azure.com/openai/deployments/gpt-35-turbo/extensions/chat/completions?api-version=2023-06-01-preview' \
--header 'api-key: xxx' \
--header 'chatgpt_url: https://xxx.openai.azure.com/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-03-15-preview' \
--header 'chatgpt_key: xxx' \
--header 'Content-Type: application/json' \
--data '{
    "dataSources": [
        {
            "type": "AzureCognitiveSearch",
            "parameters": {
                "endpoint": "https://xxx.search.windows.net",
                "key": "xxx",
                "indexName": "sample"
            }
        }
    ],
    "messages": [
        {
            "role": "system",
            "content": "You are an AI assistant that helps people find information. You retrieve Japanese documents, and you should read them carefully in Japanese and answer in Japanese."
        },
        {
            "role": "user",
            "content": "オフィスの入退室はどうすればいいですか。"
        }
    ]
}'
```

わかりづらいと感じるのはヘッダの部分で、api-key と chatgpt_key は同じ値を指定することになります。  
また、chatgpt_url は、エンドポイントのドメイン名だけでなく、Completion API の URL を最後まで指定する必要があります。  
ChatGPT プレイグラウンドのコードの表示で「エンドポイント」として表示される URL をそのまま指定すればいいだけなのですが、URL のクエリで「api-version=2023-06-01-preview」を指定する一方で、chatgpt_url に「api-version=2023-03-15-preview」の値を指定するのはおかしいのでは？と考えた私は、最初ドメイン名のみ指定していたため、HTTP 500 応答が返ってくる結果となっていました。  
API として見た場合、chatgpt_url、chatgpt_key あたりは冗長な気がしますので、今後変更されるかもしれません。

また、リクエストボディの key は管理者キーを指定する必要があります。Cognitive Search にはクエリキーというものもありますが、こちらを指定すると HTTP 500 応答が返ってきます。

### Completions extensions API のレスポンス

前回と記事同様、ChatGPT プレイグラウンドで架空のオフィスに関する FAQ データをアップロードして Cognitive Search にインデックスを作成し、FAQ データにある質問を messages に含めて REST API を送信すると、下記のようなレスポンスが返ってきました。

```
{
    "id": "xxx",
    "model": "gpt-35-turbo",
    "created": xxx,
    "object": "chat.completion",
    "choices": [
        {
            "index": 0,
            "messages": [
                {
                    "index": 0,
                    "role": "tool",
                    "content": "{\"citations\": [{\"content\": \"\基\本\的\なFAQ\ペ\ー\ジ\\n\オ\フ\ィ\ス\の\入\退\室\は\ど\う\す\れ\ば\い\い\で\す\か\。\\n\ド\ア\の\横\に\あ\る\装\置\に\セ\キ\ュ\リ\テ\ィ\カ\ー\ド\を\か\ざ\し\て\く\だ\さ\い\。\音\が\鳴\り\、\ロ\ッ\ク\が\解\除\さ\れ\ま\す\。\入\室\時\、\退\出\時\と\も\に\忘\れ\ず\か\ざ\し\て\く\だ\さ\い\。\\n\出\張\時\に\セ\キ\ュ\リ\テ\ィ\カ\ー\ド\が\な\い\場\合\は\ど\う\す\れ\ば\い\い\で\す\か\。\\n\人\事\部\に\連\絡\く\だ\さ\い\。\一\時\利\用\の\セ\キ\ュ\リ\テ\ィ\カ\ー\ド\を\お\渡\し\し\ま\す\。\\n\受\付\の\電\話\を\受\け\る\に\は\ど\う\す\れ\ば\い\い\で\す\か\。\\n\近\く\で\鳴\っ\て\い\る\電\話\の\受\話\器\を\取\り\、\「*1\」\を\押\し\て\く\だ\さ\い\。\\n\荷\物\を\発\送\す\る\に\は\ど\う\す\れ\ば\い\い\で\す\か\。\\n\人\事\部\横\の\棚\に\あ\る\発\送\伝\票\や\梱\包\材\を\使\っ\て\く\だ\さ\い\。\\n\ゴ\ミ\は\ど\こ\に\捨\て\れ\ば\よ\い\で\す\か\。\\n\オ\フ\ィ\ス\の\角\に\あ\る\ゴ\ミ\箱\の\分\類\に\従\っ\て\捨\て\て\く\だ\さ\い\。\\n\ゴ\ミ\の\収\集\日\は\い\つ\で\す\か\。\\n\毎\週\水\曜\日\で\す\。\", \"id\": null, \"title\": \"\基\本\的\なFAQ\ペ\ー\ジ\", \"filepath\": \"faq_basic.docx\", \"url\": \"https://xxx.blob.core.windows.net/fileupload-sample/faq_basic.docx\", \"metadata\": {\"chunking\": \"orignal document size=451. Scores=7.8840075 and None.Org Highlight count=92.\"}, \"chunk_id\": \"0\"}], \"intent\": \"\オ\フ\ィ\ス\の\入\退\室\は\ど\う\す\れ\ば\い\い\で\す\か\。\"}",
                    "end_turn": false
                },
                {
                    "index": 1,
                    "role": "assistant",
                    "content": "オフィスの入退室に関する情報は、[doc1]のFAQページにあります。入室時には、ドアにあるカードリーダーにカードをかざしてください。退室時には、カードをかざす必要はありません。カードリーダーから音が鳴り、ロックが解除されます。カードがない場合は、人事部に連絡してください。また、受付には一時利用のカードがあります。[doc1]",
                    "end_turn": true
                }
            ]
        }
    ]
}
```

role は従来"system"、"user"、"assistant"の 3 種類でしたが、ここでは"tool"というものが使われています。
content には JSON が含まれていますが、抜き出すと下記のような形式になっています。

```
{
    "citations": [
        {
            "content": "基本的なFAQページnオフィスの入退室はどうすればいいですか。nドアの横にある装置にセキュリティカードをかざしてください。音が鳴り、ロックが解除されます。入室時、退出時ともに忘れずかざしてください。n出張時にセキュリティカードがない場合はどうすればいいですか。n人事部に連絡ください。一時利用のセキュリティカードをお渡しします。n受付の電話を受けるにはどうすればいいですか。n近くで鳴っている電話の受話器を取り、「*1」を押してください。n荷物を発送するにはどうすればいいですか。n人事部横の棚にある発送伝票や梱包材を使ってください。nゴミはどこに捨てればよいですか。nオフィスの角にあるゴミ箱の分類に従って捨ててください。nゴミの収集日はいつですか。n毎週水曜日です。",
            "id": null,
            "title": "基本的なFAQページ",
            "filepath": "faq_basic.docx",
            "url": "https://xxx.blob.core.windows.net/fileupload-sample/faq_basic.docx",
            "metadata": {
                "chunking": "orignal document size=451. Scores=7.8840075 and None.Org Highlight count=92."
            },
            "chunk_id": "0"
        }
    ],
    "intent": "オフィスの入退室はどうすればいいですか。"
}
```

citations の要素に含まれる各キーは Cognitive Search のインデックスのフィールド名と一致しています。Cognitive Search で検索した結果がここに含まれていると思われます。

![スクリーンショット 2023-06-23 194532.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/b64d2463-2485-18ff-01f2-43d3e9dc0fdb.png)

role が"assistant"のメッセージの中に含まれる"[doc1]"といった表記は citations の要素を参照していると推測されますが、ドキュメントには記載されていないようです。  
Microsoft のサンプルでこの表記を処理している箇所があったので、ソースを確認したところ、docN は citations の N 番目の要素として扱っているようでした。

https://github.com/microsoft/sample-app-aoai-chatGPT/blob/main/frontend/src/components/Answer/AnswerParser.tsx

呼び出し側でデータの加工が必要な箇所がいくつかありますが、Completions extensions API を使って、Cognitive Searchに格納した独自データからChatGPTによる回答を得られることが確認できました。