2024年10月28日月曜日

Microsoft AI Chat Protocolのクライアントを使ってブラウザよりローカルLLMを呼び出す

Oracle APEXのページにMicrosoft AI Chat Protocolのクライアントを読み込んで、ローカルLLMをブラウザから直接呼び出してみます。

作成したAPEXアプリケーションは以下のように動作します。

macOSのLM Studioに、モデルとしてmmnga/Llama-3-ELYZA-JP-8B-gguf/Llama-3-ELYZA-JP-8B-Q8_0.ggufをロードしてローカルLLM(ローカル・サーバー)を実行しています。

Oracle APEXのアプリケーションは別のホストよりロードしているため、localhostで動作しているLLMを呼び出そうとするとCORSのエラーが発生します。そのため、LM Studioの設定のCross-Origin-Resource-Sharing (CORS)ONにしています。


LM Studioは上記の設定によりAPEXのアプリケーションからローカルLLMを呼び出せたのですが、OllamaではCORSを許可する方法が分かりませんでした。

せっかくブラウザから直接LLMを呼び出すのでストリーミングを実装するつもりだったのですが(Microsoft AI Chat ProtocolはgetStreamedCompletion APIでストリーミングをサポートしています)、コードに以下のように記載があり、エンドポイントURLの末尾が/streamであることが必須のようでした。そのため、ストリーミングは断念してgetCompletion APIを呼び出しています。
    const response = await asStream(
      this.client.path(`${this.basePath}/stream`).post(request),
    );
以下より実装について紹介します。

空のAPEXアプリケーションを作成し、チャットの呼び出しはデフォルトで作成されるホーム・ページに実装しています。

ローカルLLMとやり取りするダイアログは、インライン・ダイアログとして実装しています。

タイプ静的コンテンツレイアウトスロットDialogs, Drawers and Popupsを選択し、外観テンプレートInline Dialogを選択します。これで、動的アクションでリージョンを開くまでは、画面にダイアログは表示されません。

テンプレート・オプションAuto Heightをチェックし、SizeとしてMedium (600x400)を選択しています。


ページ・プロパティJavaScriptCSSに、Microsoft AI Chat Protocolのクライアントを呼び出すために必要な設定や、メッセージの見かけを良くするためのCSSクラスを設定します。

JavaScriptファイルURLに以下を指定します。Microsoft AI Chat Protocolを扱うクライアント・ライブラリを読み込みます。

https://cdn.jsdelivr.net/npm/@microsoft/ai-chat-protocol/dist/iife/index.min.js

ファンクションおよびグローバル変数の宣言に以下を記述します。
var client;      /* Microsoft AI Chat Protocolのクライアント */
var messages;    /* 送信するメッセージの配列 */
const contentDiv = document.getElementById("content"); /* チャット履歴を表示する要素 */

/* 表示されたチャット履歴を初期化 */
function removeAllChildren(element) {
    while (element.firstChild) {
        element.removeChild(element.firstChild);
    }
}
CSSインラインに以下を記述します。
.bubble-left {
  max-width: 70%;              /* 吹き出しの幅 */
  padding: 10px 15px;          /* 内側の余白 */
  margin: 10px 0;              /* 吹き出しの上下の間隔 */
  background-color: #e1f5fe; /* 吹き出しの背景色 */
  text-align: left;            /* 文字を左寄せ */
  position: relative;          /* 吹き出しの位置を相対的に */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

.bubble-right {
  max-width: 70%;               /* 吹き出しの幅 */
  padding: 10px 15px;           /* 内側の余白 */
  margin: 10px 0;               /* 吹き出しの上下間隔 */
  background-color: #c8e6c9;  /* 吹き出しの背景色(薄い緑色) */
  text-align: left;             /* 文字は左寄せ */
  position: relative;           /* 吹き出しの位置を相対的に */
  margin-left: auto;            /* 吹き出しを右寄せ */
  border-radius: 15px 15px 15px 15px; /* 角を丸める */
}

インライン・ダイアログのリージョンを開くボタンOPENを作成し、ボタンをクリックしたときに実行される動的アクションを作成します。


ボタンOPENを押した時に、最初に以下のJavaScriptコードを実行します。メッセージの履歴を消去し、ローカルLLMとのチャットを初期化します。

以下のURLよりLM StudioのローカルLLMを呼び出します。

http://localhost:8080/v1/chat/completions
/* AI Chat Protocolのクライアントを作成する */
client = new ChatProtocol.AIChatProtocolClient(
    "http://localhost:8080/v1/chat/completions",
    {
        "allowInsecureConnection": true
    }
);

/* メッセージとチャット履歴を表示するDIV要素も初期化。 */
messages = [];
removeAllChildren(contentDiv);

初期化を行なった後に、リージョンChatを開きます


リージョンChatには、チャットのやり取りを表示するリージョンとしてContent、ユーザーのメッセージを入力するページ・アイテムとしてP1_MESSAGE、メッセージをローカルLLMに送信するボタンSUBMIT、および、ダイアログを閉じるボタンCLOSEを作成します。

リージョンContent静的コンテンツとして作成し、ソースHTMLコードとして以下を記述します。

<div id="content"></div>

修飾を最低限にするため、外観テンプレートとしてBlank with Attributesを選択します。CSSクラスh400を設定し、リージョンの高さを400ピクセルに固定します。

詳細カスタム属性としてstyle="overflow-y: scroll;"を設定し、400ピクセルで表示できないメッセージはスクロールさせて表示するようにします。


メッセージを入力するページ・アイテムP1_MESSAGEは、テキスト領域として作成しています。


ボタンSUBMITをクリックしたときに、以下のJavaScriptコードを実行します。



ボタンCLOSEをクリックしたしたときは、TRUEアクションとしてリージョンを閉じるを呼び出すようにします。


以上でアプリケーションは完成です。実行すると記事の先頭のGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-microsoft-ai-chat-protocol-call-local-llm.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。