5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RAGアプリをOracle APEXの新機能で改善してみた その2 APEX_AIパッケージの活用

Posted at

前回の記事では「対話型ダイアログ」を使ったRAGアプリ実装についてまとめました。
今回は同じく APEX 24.1 の新機能である「APEX_AIパッケージ」を使った実装を扱っていきます。

背景を簡単に再掲すると、以前Qiitaに投稿した23ai+APEXを使ったRAGアプリを APEX 24.1 の新機能で改修する、という内容になります。

APEX_AI パッケージには生成AIを使った文章生成やチャットを実装するための機能が搭載されています。
以前までは APEX_WEB_SERVICE パッケージなどを利用して生成AIのREST APIを直接叩き、帰ってきたJSONを解析する必要がありました。
APEX 24.1 からは APEX_AI パッケージを通してより簡単に生成AIアプリを実装できます。
APEX_AI パッケージの詳細は下記マニュアルをご参照ください。
APIリファレンス - APEX_AI

  • CHATファンクション: チャット実装に使えるファンクション。会話履歴を保持する機能もある
  • GENERATEファンクション: 単発のプロンプトに対する回答を生成するファンクション

ちなみに APEX_AI パッケージで実装したアプリのイメージが下記になります。
改修前のアプリと見た目は全く同じですが、ロジックの実装がシンプルになります。
WS000000-1.JPG

作成したAPEXアプリのexportファイルは以下にありますので、必要に応じてご参照ください。
前者は前回の記事で紹介した対話型ダイアログを使ったアプリ、後者は今回作成するアプリです。
https://github.com/mago1chi/apex/blob/main/apex_2413_rag_chatbot.sql (対話型ダイアログを使った方法)
https://github.com/mago1chi/apex/blob/main/apex_2413_rag_chain.sql (APEX_AIパッケージを使った方法)

以降は実装の流れをまとめていきます。

事前準備

環境構築などの事前準備は、前回の記事にまとめた下記項目を実施してください。

UI実装

UIについては前回の記事とほぼ同じですが、違いとしては以下になります。

  • PX_ANSWER アイテムを残しておく
  • AI_Assistant ボタンは作成しない

最終状態は下記のようになります。
WS000001.JPG

APEX_AIを使った回答生成の実装

回答生成のロジックを実装していきます。
大きくは「ベクトル検索」と「生成AIによる回答生成」に分かれます。

まずページ・デザイナの「プロセス」タブに切り替えます。
作成済みの「回答生成」プロセスを削除します。
WS000001-2.JPG

「プロセス」を右クリックして「プロセスの作成」を選択します。
WS000002.JPG

作成したプロセスを赤枠のように設定します。
ポイントは「タイプ」を「実行チェーン」とすることです。
WS000003.JPG

右ペインを下にスクロールし、赤枠のように設定します。
WS000004.JPG
これによって「検索」ボタンを選択すると、以降で子プロセスとして実装する「ベクトル検索」「回答」が順番に実行されます。

では親プロセスの「回答生成」を右クリックして「子プロセスの追加」を選択します。
WS000005.JPG

作成された子プロセスを赤枠のように設定します。
WS000006.JPG
「PL/SQLコード」には以下を入力します。

DECLARE
    l_context CLOB;
    l_prompt CLOB;
    cohere_params clob;
BEGIN
    -- OCI GenAI のEmbeddingを利用
    cohere_params := '
    { 
      "provider": "ocigenai",
      "credential_name": "OCI_GENAI", 
      "url": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/embedText",
      "model": "cohere.embed-multilingual-v3.0"
    }';

    -- ベクトル検索結果を一時格納するAPEXコレクションを定義
    APEX_COLLECTION.CREATE_OR_TRUNCATE_COLLECTION(
        p_collection_name => 'CITATION_RAG'
    );
    
    -- ベクトル検索を実行し、RAGに使用するコンテキストを取得
    IF :P9_CATEGORY IS NULL THEN
        IF :P9_DOCNUM IS NULL THEN
            -- カテゴリ、保証ドキュメント数の指定がない場合のクエリ
            FOR rec IN (SELECT doc.doc_filename doc_filename, em.embed_data embed_data
                FROM documents doc, embed em 
                WHERE doc.id = em.doc_id AND TRUNC(doc.doc_lastupd) BETWEEN :P9_BEGIN_DATE AND :P9_END_DATE
                ORDER BY VECTOR_DISTANCE(em.embedding, TO_VECTOR(DBMS_VECTOR.UTL_TO_EMBEDDING(:P9_QUESTION, JSON(cohere_params))), COSINE)
                FETCH APPROXIMATE FIRST :P9_NUM ROWS ONLY)
            LOOP
                -- プロンプトに埋め込むコンテキストを追記
                l_context := l_context || rec.embed_data;
                -- 引用元情報をAPEXコレクションに格納
                apex_collection.add_member(
                      p_collection_name => 'CITATION_RAG',
                      p_c001 => rec.doc_filename,
                      p_c002 => rec.embed_data
                );
            END LOOP;
        ELSE
            -- カテゴリの指定がなく、保証ドキュメント数の指定がある場合のクエリ
            FOR rec IN (SELECT doc.doc_filename doc_filename, em.embed_data embed_data
                FROM documents doc, embed em 
                WHERE doc.id = em.doc_id AND TRUNC(doc.doc_lastupd) BETWEEN :P9_BEGIN_DATE AND :P9_END_DATE
                ORDER BY VECTOR_DISTANCE(em.embedding, TO_VECTOR(DBMS_VECTOR.UTL_TO_EMBEDDING(:P9_QUESTION, JSON(cohere_params))), COSINE)
                FETCH APPROXIMATE FIRST :P9_DOCNUM PARTITIONS BY doc.id, :P9_NUM ROWS ONLY)
            LOOP
                -- プロンプトに埋め込むコンテキストを追記
                l_context := l_context || rec.embed_data;
                -- 引用元情報をAPEXコレクションに格納
                apex_collection.add_member(
                      p_collection_name => 'CITATION_RAG',
                      p_c001 => rec.doc_filename,
                      p_c002 => rec.embed_data
                );
            END LOOP;
        END IF;
    ELSE
        IF :P9_DOCNUM IS NULL THEN
            -- カテゴリの指定があり、保証ドキュメント数の指定がない場合のクエリ
            FOR rec IN (SELECT doc.doc_filename doc_filename, em.embed_data embed_data
                FROM documents doc, embed em 
                WHERE doc.id = em.doc_id AND 
                doc.category IN (SELECT column_value FROM APEX_STRING.SPLIT(:P9_CATEGORY,':')) AND 
                TRUNC(doc.doc_lastupd) BETWEEN :P9_BEGIN_DATE AND :P9_END_DATE
                ORDER BY VECTOR_DISTANCE(em.embedding, TO_VECTOR(DBMS_VECTOR.UTL_TO_EMBEDDING(:P9_QUESTION, JSON(cohere_params))), COSINE)
                FETCH APPROXIMATE FIRST :P9_NUM ROWS ONLY)
            LOOP
                -- プロンプトに埋め込むコンテキストを追記
                l_context := l_context || rec.embed_data;
                -- 引用元情報をAPEXコレクションに格納
                apex_collection.add_member(
                      p_collection_name => 'CITATION_RAG',
                      p_c001 => rec.doc_filename,
                      p_c002 => rec.embed_data
                );
            END LOOP;
        ELSE
            -- カテゴリ、保証ドキュメント数の指定がある場合のクエリ
            FOR rec IN (SELECT doc.doc_filename doc_filename, em.embed_data embed_data
                FROM documents doc, embed em 
                WHERE doc.id = em.doc_id AND 
                doc.category IN (SELECT column_value FROM APEX_STRING.SPLIT(:P9_CATEGORY,':')) AND 
                TRUNC(doc.doc_lastupd) BETWEEN :P9_BEGIN_DATE AND :P9_END_DATE
                ORDER BY VECTOR_DISTANCE(em.embedding, TO_VECTOR(DBMS_VECTOR.UTL_TO_EMBEDDING(:P9_QUESTION, JSON(cohere_params))), COSINE)
                FETCH APPROXIMATE FIRST :P9_DOCNUM PARTITIONS BY doc.id, :P9_NUM ROWS ONLY)
            LOOP
                -- プロンプトに埋め込むコンテキストを追記
                l_context := l_context || rec.embed_data;
                -- 引用元情報をAPEXコレクションに格納
                apex_collection.add_member(
                      p_collection_name => 'CITATION_RAG',
                      p_c001 => rec.doc_filename,
                      p_c002 => rec.embed_data
                );
            END LOOP;
        END IF;
    END IF;

    -- 最終的なコンテキストを非表示アイテムに保存
    :P9_SEARCH_RES := :P9_QUESTION || q'[
回答には次の文章を使ってください。
]' || l_context;
END;

もう一つ子プロセスを作成し、赤枠のように設定します。
WS000007.JPG
ポイントは「タイプ」を「APIの呼出し」とした上で、APEX_AI パッケージの GENERATE ファンクションを実行するようにしている点です。
APEXのAPI呼出し機能によって、回答生成のロジックはPL/SQLを直接書く必要がありません。

GENERATE ファンクションの各パラメータも設定していきます。
「ファンクションの結果」は赤枠のように設定します。
WS000008.JPG

「p_prompt」は赤枠のように設定します。
WS000009.JPG

「p_service_static_id」は赤枠のように設定します。
WS000010.JPG
静的値には前回記事の「連携する生成AIの設定」にて指定した静的IDを入力します。

以上で実装完了です。
チェーンと聞くとLangChainでも同じような実装方式を取りますね。
実行チェーン自体は 24.1 以前からありますが、生成AIアプリの実装と相性の良い機能かもしれません。

動作確認

前回記事と同じように2024年秋アニメのタイトル、あらすじがまとめられたテキストファイルをRAGに使います。
WS000015.JPG

適当な質問でベクトル検索します。
例えば以下のような質問を投げてみると、ベクトル検索結果をコンテキストとして使用した回答が表示されます。

異世界バトルものアニメはありますか?

WS000000-2.JPG

24.1 がリリースされる前は、REST APIを直接実行した際に得られるJSONフォーマットの結果をパースして回答を表示していました。
今回は 24.1 から追加された APEX_AI パッケージの GENERATE ファンクションを使っており、生成AIとの連携や回答の取得が非常に簡素化されています。
そのため実装においては、前述した手順の通りPL/SQLを直接書く必要もなくなり、UI上の設定だけで済みました。

今回の内容は以上になります。
APEX_AI パッケージを使えばチャットボットはもちろん、業務アプリケーションの中に生成AIを組み込むのも容易に実現できそうです。
是非お試しください。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?