RAG構築のためだけのAzure OpenAI クライアントライブラリ入門【Javascript/Node.js】

昨今めざましいほどの進化をとげている、chatGPTをはじめとした生成AIたち。それに伴って、Azure OpenAI Serviceを利用するためのクライアントライブラリも、とてつもなく早いスパンでアップデートが繰り返され、「3ヶ月前に書かれた記事の内容はもう使い物にならない、、、」そんな状況です。(破壊的更新という異名までついています)

社内向けチャットボットを作成する作業の中で、公式リファレンスを読み漁り、なんとか思い描いていた機能だけは実装できたので、今回は「RAG構築のためだけのAzure OpenAI クライアントライブラリ入門」と題して、JavascriptでのOpenAIクライアントライブラリの使い方を必要なところだけ解説していこうと思います。

なお、今回RAGに要する文書等は、既に前処理が済んだ状態でAI Searchに投げられている状態であることを前提とします。

APIの認証など

const { OpenAIClient, AzureKeyCredential } = require('@azure/openai');

// for GPT
const endpoint = '';
const apiKey = '';
const deploymentName = '';

// for RAG
const azureSearchEndpoint = '';
const azureSearchAdminKey = '';
const azureSearchIndexName = '';

const client = new OpenAIClient(endpoint, new AzureKeyCredential(apiKey));

定数値はAzure Portalを参照して各自の値を入れてください。

ChatCompletions

chatCompletionsにはlistChatCompletionsやgetChatCompletions,streamChatCompletionsがありますが、まずGPT3.5-turboからはlistChatCompletionsは使えなくなっているので、後者2つのいずれかを使うのをお勧めします。この記事ではstreamChatCompletionsを使います。

const events = await client.streamChatCompletions(deploymentName, messages, {
            maxTokens: 1000,
            azureExtensionOptions: {
                extensions: [
                    {
                        type: 'azure_search',
                        endpoint: azureSearchEndpoint,
                        indexName: azureSearchIndexName,
                        strictness: 3, // 厳格度
                        topNDocuments: 5, // 参照するドキュメント数
                        roleInformation: '', // システムメッセージ
                        authentication: {
                            type: 'api_key',
                            key: azureSearchAdminKey
                        },
                    }
                ]
            },
            temperature: 0.5, // 温度
            topP: 0.5 // 上位P
        });

extensions内のオプションに関する公式のリファレンスはこちらです:AzureSearchChatExtensionConfiguration

小話ですが、streamChatCompletionsのリファレンスからextensions内のオプションのリファレンスに辿り着くまでには、元のリンクが貼られていないエイリアスをコピペして調べる必要があるのですが、そこで誤ってC#のリファレンスに遷移してしまったことで大量の時間を溶かされました..。 「オプションの名前がjavascriptっぽくないよな」という違和感に早く気づくべきでしたが、これをきっかけに命名規則に名前があることを知って勉強になったので、趣旨とは関係ありませんがそちらも共有しておきます。 命名規則についてまとめてみた(キャメルケース,パスカルケース,スネークケース,ケバブケース)

streamChatCompletionsの返り値

for await (const event of events) {
    for (const choice of event.choices) {
        const delta = choice.delta?.content;
        if (delta !== undefined) {
            res += delta;
        }
        if (choice.delta?.context !== undefined) {
            for (const citata of choice.delta.context.citations) {
                // for MarkDown
                citation.push('[' + citata.filepath + ']' + '(' + citata.url + ')\n\n');
                // for Slack
                citation.push('<' + citata.url + '|' + citata.filepath + '>\n\n');
             }
         }
    }
}

こちらはresという変数に生成された回答を格納、citationという変数に回答を生成する際に参照した文書のURLやファイル名を格納する処理です。

実際に出力が行われた際、SlackのみMarkDown形式のハイパーリンクの処理ができないので、Slackでチャットボットを作成する際などはfor Slackの方の形式にする必要があります。

ここまでできたら、あとは各自の形式に従ってresやcitationを出力してあげれば、RAGを搭載したチャットボットの応答が完成します。

より詳しく調べたい方

まずは公式リファレンス:

ネットの記事を参考にする場合、できるだけ更新されたばかりの記事(目安としては2ヶ月以内)の情報を優先的に取り入れた方が無難です。当たり前ですが、英語の方が圧倒的に情報量は多いので、基本的に英語で調べることをおすすめします。

また、古典的ですが、console.log()で挙動や返り値を自身で把握しておくことが、理解の近道になります。