2024年6月28日金曜日

Oracle APEX 24.1のAIアシスタントを使ってみる

Oracle APEX 24.1で追加されたAIアシスタント(注:動的アクションはOpen AIアシスタントとなっていますが、AIアシスタントを開くという意味です)を使った、サンプル・アプリケーションを作成してみます。アプリケーションを開発する環境はOracle CloudのAlways FreeのOracle Database 23aiを使用します。

作成するアプリケーションは以下のように動作します。AIアシスタントには、色々とカスタマイズするためのオプションがあります。オプションの設定によって、チャットの使い勝手や動作を変えることができるため、この動画とは異なるユーザー・インターフェースにもできます。

以下の画面では質問文からエンべディングを生成し、データベースに保存した意味的に近いチャンクを取り出し、最初のプロンプトとしてLLMに送信するメッセージに含めています。ユーザーとのチャットによるやり取りは、その後のメッセージとして追加されます。

エンべディング・モデルはCohereのembed-multilingual-light-v3.0、チャットにはcommand-r-plusを使います。これらの機能を呼び出すために、Cohereにユーザー登録を行いTrial Keyを取得しておく必要があります。


CohereのAPIを呼び出すため、ワークスペースにWeb資格証明を作成します。

名前はCOHERE_API_KEY、静的IDもCOHERE_API_KEYとします。認証タイプはHTTPヘッダー、資格証明名(つまりHTTPヘッダー名)としてAuthorizationを指定します。

資格証明シークレットしてBearerで始めて空白で区切り、CohereのTrial Keyを続けた文字列を設定します。

URLに対して有効はhttps://api.cohere.aiとします。


ワークスペース・ユーティリティの生成AIとして、Cohereを作成します。

AIプロバイダはCohere、名前はCohere、静的IDはCOHERE_AIとします。

アプリケービョン・ビルダーで使用はオフ、資格証明は先ほど作成したCOHERE_API_KEYを選択します。AIモデルとしてCohereで最も性能のよいcommand-r-plusを指定します。


以上でAIサービスを使う準備ができました。

これからAPEXアプリケーションを作成します。

最初に、以下のDDLを実行して、チャンクとエンべディングを保存する表EBMJ_EMBEDDINGSを作成します。URLを保存する表とチャンクとエンべディングを保存する表で親子関係の表を作るべきですが、省略して1つの表に保存しています。
create table ebmj_embeddings (
    id                  number generated by default on null as identity
                        constraint ebmj_embeddings_id_pk primary key,
    url                 varchar2(400 char),
    chunk_id            number,
    chunk               varchar2(4000 char),
    embedding           vector
);

APEXアプリケーションを作成します。

名前はAPEX Chatとし、アプリケーション作成ウィザードを起動します。


ページの追加をクリックします。


空白を選択します。


ページ名はDocumentsとします。このページには、PDFを選択してデータベースにロードする機能を実装します。

ページの追加をクリックします。


ページDocumentsが追加されました。再度、ページの追加をクリックします。


表EBMJ_EMBEDDINGSをソースとした、対話グリッドのページを追加します。

対話グリッドを選択します。


ページ名はEmbeddingsとします。表またはビューとしてEBMJ_EMBEDDINGSを選択します。編集を許可を選択します。

ページの追加をクリックします。


以上のページ構成で、アプリケーションの作成を実行します。


APEXアプリケーションが作成されます。


GitHubに、このブログ日日是Oracle APEXの記事のPDF版が置いてあります。今回は、このPDFをデータベースに知識として取り込むことにします。

GitHubのリポジトリを参照するRESTデータ・ソースを作成します。

共有コンポーネントのRESTデータ・ソースを開きます。


作成をクリックします。


RESTデータ・ソースの作成は最初からとします。

次へ進みます。


RESTデータ・ソース・タイプとして簡易HTTPを選択します。名前はGitHub APEXKBとします。PDF版の記事が置いてあるURLエンドポイントは以下です。

https://api.github.com/repos/ujnak/APEXKB/contents/

次へ進みます。


リモート・サーバーがすでに登録されている場合は、そのまま使用します。そうでない場合は、リモート・サーバーは- 新規作成 -として、ベースURLはhttps://api.github.com/repos/、サービスURLパスはujnak/APEXKB/contents/とします。

次へ進みます。


ページ区切りタイプはページ区切りなしとします。

次へ進みます。


公開されているリポジトリなので認証は不要です。

検出をクリックします。


データのプレビューが表示されます。

RESTデータ・ソースの作成をクリックします。


以上でPDF版の記事を一覧するRESTデータ・ソースが作成されました。


このRESTデータ・ソースから記事を選択しデータベースへロードする機能を、ページ番号2のDocumentsのページに実装します。

記事の一覧にはタイプがContent Rowのリージョンを使います。Oracle APEX 24.1より、Content Rowで行選択ができる新機能が追加されました。その機能を使うため、選択された行を保存するページ・アイテムと、すべての行を選択するチェックボックスを作成します。

ページ・デザイナでページDocumentsを開きます。


ページ上で選択した記事を、データベースにロードするボタンを作成します。

識別のボタン名はLOAD、ラベルはLoadとします。外観のホットをオン、テンプレート・オプションではWidthにStretchを指定します。動作のアクションはデフォルトのページの送信です。


レポートに一覧されている記事を、すべて選択するチェックボックスとなるページ・アイテムを作成します。

識別の名前はP2_SELECT_ALL、タイプはチェックボックです。ラベルはすべて選択とします。セッション・ステートのストレージとしてリクエストごと(メモリーのみ)を選択します。


レポート上で選択された記事を保持するページ・アイテムを作成します。

識別の名前はP2_SELECTED、タイプは非表示とします。ブラウザ上の処理で値が変更されるため、設定の保護された値はオフにします。セッション・ステートのデータ型はCLOB、ストレージはリクエストごと(メモリーのみ)を指定します。


記事の一覧と選択を行うリージョンを作成します。

識別の名前はDocumentsとします。タイプにContent Rowを選択します。

ソースの位置はRESTソース、RESTソースとして先ほど作成したGitHub APEXKBを選択します。


列DOWNLOAD_URLを選択し、ソースの主キーをオンにします。行が選択されたときに、ページ・アイテムP2_SELECTEDに主キーの値、つまりDOWNLOAD_URLが保存されます。


リージョンDocumentsの属性タブを開きます。

外観の表示は複数(レポート)です。設定のTitleは&NAME.、Descriptionは<a href="&DOWNLOAD_URL.">&DOWNLOAD_URL.</a>とします。

記事のエントリ自体を小さくするため、AppearanceのStyleにCompactを選択し、Remove Paddingをオンにします。

行選択のタイプに複数選択を選び、現在の選択のページ・アイテムにP2_SELECTED、すべてのページ・アイテムの選択にP2_SELECT_ALLを指定します。

ページ区切りのタイプをスクロールに変更し、総数の表示をオンにします。


ブレッドクラムを選択し、タイプをスマート・フィルタに変更します。

ソースのフィルタ済リージョンにDocumentsを指定します。デフォルトで行検索に使用するP2_SEARCHが作成されます。


以上でページ上に配置するコンポーネントは作成できました。

ボタンLOADをクリックしたときに実行するプロセスを作成します。

識別の名前はLoad、ソースのPL/SQLコードとして以下を記述します。選択された複数の記事のダウンロードURLが、ページ・アイテムP2_SELECTEDに:(コロン)区切りで渡されます。それぞれのダウンロードURLごとにPDFの取得、テキストの抽出およびチャンク分割を行います。分割されたチャンクをまとめてCohereのEmbed APIを呼び出してエンべディングを生成し、表EBMJ_EMBEDDINGSに保存しています。

サーバー側の条件のボタン押下時にLOADを指定します。


以上でPDF記事を選択して、データベースにロードする機能の実装は完了です。

タイトルにGoogleが含まれている記事を、データベースにロードしてみます。

スマート・フィルタでGoogleを検索し、検索されたすべての行を選択したのち、Google Fontsの記事を選択から外しています。その後に選択した記事すべてをデータベースにロードしています。


これから、本記事の本題であるAIアシスタントを実装します。

ページ・デザイナでホーム・ページを開き、デフォルトで作成されているページ・ナビゲーションを削除します。


AIへの質問を入力するページ・アイテムを作成します。

識別の名前はP1_QUESTION、タイプはテキスト領域とします。ラベルは質問とします。


AIとのチャットを開始するボタンを作成します。

識別のボタン名はASK、ラベルは問い合わせるとします。動作のアクションは動的アクションで定義を選択します。ボタンをクリックしたときに、動的アクションとして実装されているOpen AIアシスタントを実行します。


AIアシスタントの表示形式として、ダイアログとインラインの2種類を選択できます。今回は、まず最初にインラインを実装します。そのために、AIアシスタントとの会話を扱うリージョンを作成します。

識別の名前はAIチャットとします。タイプは静的コンテンツです。リージョンとしての装飾は不要なので、外観のテンプレートにBlank with Attributesを選択します。

詳細の静的IDとしてchatを指定します。AIアシスタントには、このリージョンchatを会話に使うように指示します。


AIアシスタントとの会話の結果、採用した回答を保存するページ・アイテムを作成します。

識別の名前はP1_RESP、タイプはテキスト領域とします。ラベルは回答とします。

セッション・ステートのデータ型はVARCHAR2、ストレージはセッションごと(永続)を選択します。


データベースに保存されたチャンクより、質問の内容に近いものを検索した結果を保存するページ・アイテムを作成します。

識別の名前はP1_PROMPT、タイプはMarkdownエディタを選択します。ただし、これは検索結果を確認するために、画面に表示されるページ・アイテムを選んでいます。通常は非表示にするのが適切でしょう。

ラベルは知識とし、セッション・ステートのデータ型はCLOB、ストレージはリクエストごと(メモリーのみ)を選択します。


ボタンASKに動的アクションを作成します。

識別の名前はonClick Start AI Assistantとします。ボタンのデフォルトである、クリックのタイミングで実行します。


最初にサーバー側で、質問文に類似したチャンクを取り出しプロンプトとする処理を実行します。

TRUEアクションとしてサーバー側のコードを選択し、設定のPL/SQLコードとして以下を記述します。

送信するアイテムとして質問文であるP1_QUESTION、戻すアイテムとしてプロンプトであるP1_PROMPTを指定します。実行の初期化時に実行はオフ、結果を待機はオンにします。


TRUEアクションとしてOpen AIアシスタントを作成します。


生成AIのサービスにCohereを選択します。

システム・プロンプトに以下を記述します。

あなたはOracle APEXの専門家です。

この文字列はOpenAIのChat Completions APIの呼び出しではroleがsystemのcontentになります。

"messages": [
      { 
             "role" : "system",
             "content" : "ここの文字列"
      }
 ]

システム・プロンプトにはアプリケーション・アイテムやページ・アイテムの置換文字列を含めることができますが、これはページ・レンダリングの前に値が設定されている必要があります。

ようこそメーセージに以下を記述します。

Oracle APEXの質問に答えます。

この文字列はAIアシスタントに表示されるだけで、API呼び出しのメッセージには含まれません。

外観の表示形式はインラインとダイアログを選択できます。ダイアログを選択すると、ダイアログのタイトルが追加の設定項目になります。タイトルをAIチャットとした場合、以下のような画面に変わります。


今回は表示形式はインライン、コンテナ・セレクタに#chatを指定します。


最初のプロンプトのタイプにはなし、アイテム、JavaScript式のどれかを選択できます。

OpenAIのChat Completions APIの呼び出しでは、roleがsystemのメッセージの直後に追加するroleがuserのcontentを設定しています。

"messages": [
      { 
             "role" : "system",
             "content" : "プロンプト"
      },
      { 
             "role" : "user",
             "content" : "最初のプロンプト"
      }              
 ]

今回はタイプがアイテム、アイテムとしてページ・アイテムP1_PROMPTを指定します。

表示メッセージとして以下を記述しています。名前の通り表示だけで、API呼び出しには含まれません。

### 前提知識を追加しました。 ###

即時アクション・プロンプトとして&P1_QUESTION.を指定しています。即時アクション・プロンプトの指定があるときは、この値をroleがuserのcontentとしてメッセージに含め、APIを呼び出します。

即時アクション・プロンプトが無指定の場合は、システム・プロンプトと最初のプロンプトは設定されていますが、APIの呼び出しは行われていず、ユーザーの追加入力待ちになります。

この状態のときにクイック・アクションが表示されます。クイック・アクションは2つまで設定できます。

例えば今回の実装で、即時アクション・プロンプトを空白にして、クイック・アクションのメッセージ1に&P1_QUESTION.、メッセージ2に今までの要約をお願いします。を設定します。


この場合、ボタン問い合わせるをクリックすると選択肢としてOracle APEXからGoogleのサービスを呼び出せますか?と今までの要約をお願いします。が表示されます。

どちらかをクリックすると、roleがuserのcontentにクイック・アクションの文字列が追加され、APIが呼び出されます。


最初のプロンプトとしてJavaScript式を選択すると、APIに含めるメッセージと会話として表示するデータを別々に制御できます。

例えば以下を記述します。
{
    fullContent: apex.items.P1_PROMPT.value + "###" + apex.items.P1_QUESTION.value,
    content: "**" + apex.items.P1_QUESTION.value + "**",
    type: "markdown",
    commit: true
}

fullContentはAPIに含まれる文字列です。contentがチャット画面に表示される文字列です。typeとしてmarkdownやhtmlを選択できるため、画面上に表示される文字列を修飾できます。commitがtrueの場合は、すぐにAPI呼び出しを行う、falseの場合はAPI呼び出しは行いません。


レスポンスの使用のタイプにアイテム、アイテムにP1_RESP、JavaScript検出式として以下を記述します。

this.fullContent.toLowerCase().includes( "json" ) ? this.fullContent : ""

this.fullContentはAIからのレスポンスで、そのレスポンスに文字列jsonが含まれている場合に、this.fullContentを戻すようにしています。この検出ができたときにAIのレスポンスにはボタン・ラベルで指定されたラベル(無指定の場合はこれを使用)のボタンが表示されます。


そのボタンを押すとJavaScript検出式で返された文字列が、ページ・アイテムP1_RESPに設定されます。また、この処理を持ってAIチャットは終了し、チャット画面が初期化されます。


レスポンスの使用のタイプをJavaScriptコードにすると、受け取ったAIのレスポンスをコードで処理することができます。以下のコードはタイプがアイテム、アイテムとしてP1_RESPを設定したときと同じ動きをするJavaScriptコードになります。

apex.items.P1_RESP.value = this;


今回の記事は以上になります。

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

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

完