OCI Generative AI AgentsとADBを使ってRAG対応チャットを実装してみた

本記事は[Oracle Cloud Infrastructure Advent Calendar 2024](https://qiita.com/advent-calendar/2024/oci)の五日目の記事となります。

OCI Generative AI AgentsはAIエージェントのマネージドサービスです。
本サービスを使うとベクトル検索を使ったRAGを効率的に実装できます。
リランクなど精度向上に関わる工夫がサービス内部で自動的に行われる、という特徴を持っています。

OCI Generative AI Agentsはナレッジベース (ベクトル検索のソース) としてObject Storage、OCI Search Service with OpenSearch、Base Database Service (BaseDB)、Autonomous Database (ADB) を選択できます。
今回はADBとの連携を試してみました。

## VCNの作成

ADBなどのプライベート・エンドポイントを配置する仮想ネットワークを作成します。
具体的な手順は下記をご参照ください。

https://oracle-japan.github.io/ocitutorials/beginners/creating-vcn/

NSGを作成し、同じプライベート・サブネット内からのADB接続を許可します。
具体的な手順は下記をご参照ください。

https://docs.oracle.com/ja-jp/iaas/Content/Network/Concepts/create-nsg.htm

以下の通りイングレス・ルールを作成します。
- ソースタイプ：CIDR
- ソースCIDR：プライベート・サブネットのCIDR
- IPプロトコル：TCP
- ソース・ポート範囲：All
- 宛先ポート範囲：1521-1522

## ADBの作成

ADBを23aiで作成します。
作成する際に「ネットワーク・アクセスの選択」は「プライベート・エンドポイント・アクセスのみ」を選びます。
具体的な手順は下記をご参照ください。

https://oracle-japan.github.io/ocitutorials/adb/adb101-provisioning/

https://oracle-japan.github.io/ocitutorials/adb/adb109-private-endpoint/#1-2-adb%E3%81%AE%E4%BD%9C%E6%88%90

なおプライベート・エンドポイントはプライベート・サブネットに作成してください。
mTLS認証の設定はオフにします。

## DBユーザの作成

プライベート・サブネットにADBを作成したため、DBログインするにはBastionを使います。
Bastion経由でADBへアクセスする手順は下記をご参照ください。

https://qiita.com/letian/items/38b968cee3a9619c63e5#2-%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E7%B5%8C%E7%94%B1%E3%81%A7adb%E3%81%AB%E6%8E%A5%E7%B6%9A

:::note info
※もしホーム・リージョンにADBを作成した場合は、下記手順でCloud ShellからADBに直接アクセス可能です。
https://qiita.com/letian/items/f28aa9ab2a3e5b49b42d
:::

DBログインできたらユーザを作成し、必要な権限を付与します。
今回は `agents` という名前のDBユーザを作成しました。

```sql
CREATE USER agents IDENTIFIED BY "your password" QUOTA UNLIMITED ON data;
GRANT DWROLE to agents;
GRANT DB_DEVELOPER_ROLE to agents;
GRANT CREATE MINING MODEL TO agents;
GRANT EXECUTE ON DBMS_CLOUD TO agents;
GRANT EXECUTE ON DBMS_VECTOR TO agents;
```

REST API実行できるように設定します。

```sql
BEGIN
    DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
    host => '*',
    ace => xs$ace_type(privilege_list => xs$name_list('connect'),
    principal_name => 'agents',
    principal_type => xs_acl.ptype_db));
END;
/
```

## Object Storageアクセスの設定

本検証ではRAGに使うファイルをOCI Object Storageに配置します。
そのためObject Storageにアクセスするための資格証明を作成します。
まずは下記を参照し、認証トークンを取得します。

https://oracle-japan.github.io/ocitutorials/adb/adb102-dataload/#2-1-oci%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%81%B8%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97

次に資格証明を作成します。
なお、これ以降は先ほど作成したDBユーザで作業します。
もしデータベース・アクションのSQLからコマンド実行する場合は、下記をご参考に、先ほど作成した検証用DBユーザのRESTを有効化してください。

https://oracle-japan.github.io/ocitutorials/adb/adb101-provisioning/#3-2-%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86database-actions

> 3-2. データベース・ユーザーを作成してみよう(Database Actions)

```sql
BEGIN
    DBMS_CLOUD.CREATE_CREDENTIAL(
      credential_name => 'OBJ_CRED',
      username => 'your user name',
      password => 'your auth token'
    );
END;
/
```

Object Storageのバケットを作成します。
作成手順は下記をご参照ください。

https://oracle-japan.github.io/ocitutorials/beginners/object-storage/

## Embeddingの設定

Object Storageに配置した非構造化データをベクトル化するために、Embeddingモデル用の資格証明を作ります。
今回はOCI Generative AIで提供されているモデル `cohere.embed-multilingual-v3.0` を使います。
そのためOCI向けの資格証明を作成します。
JSONの各項目はご自分の情報を入力してください。

```sql
DECLARE
  jo json_object_t;
BEGIN
  jo := json_object_t();
  jo.put('user_ocid','your user ocid');
  jo.put('tenancy_ocid','your tenancy ocid');
  jo.put('compartment_ocid','your compartment ocid');
  jo.put('private_key','your private key');
  jo.put('fingerprint','your fingerprint');
  DBMS_VECTOR.CREATE_CREDENTIAL(
    CREDENTIAL_NAME   => 'OCI_GENAI',
    PARAMS        => json(jo.to_string));
end;
/
```

`private_key`、`fingerprint` の箇所に入力するAPIキー情報の取得方法は下記をご参照ください。

https://docs.oracle.com/ja-jp/iaas/Content/API/Concepts/apisigningkey.htm#two

なおADBはプライベート・サブネットに配置しているため、OCI Generative AIサービスへアクセスするには事前にサービス・ゲートウェイを作成し、ルート表を更新する必要があります。

https://docs.oracle.com/ja-jp/iaas/Content/generative-ai/private-network-access.htm

## 表の作成

ベクトルデータを格納する表を作ります。

```sql
CREATE TABLE "DOCUMENTS" 
 (	"DOCID" NUMBER GENERATED ALWAYS AS IDENTITY, 
    "BODY" VARCHAR2(4000),
    "VECTOR" VECTOR,
    "CHUNKID" NUMBER,
    "URL" VARCHAR2(512),
    "TITLE" VARCHAR2(512),
    CONSTRAINT "DOCUMENTS_PK" PRIMARY KEY ("DOCID") USING INDEX ENABLE
 );
```

なおOCI Generative AI Agentsと連携するためには、下記マニュアル記載の要件を満たす表を作成する必要があります。

https://docs.oracle.com/ja-jp/iaas/Content/generative-ai-agents/oracle-db-guidelines.htm

データベースの要件 → データベース表 をご参照ください。

## Object Storage上のファイルをベクトル化

まずは作成したバケットにRAGで使いたい任意のファイルをアップロードしておきます。

そしてバケット内のオブジェクトをベクトル化するプロシージャを作成します。
※`bucket_url` の値はご自分の情報を入力してください。

```sql
CREATE OR REPLACE PROCEDURE embed_files
IS
    chunk_params clob;
    cohere_params clob;
    l_blob BLOB := NULL;
    bucket_url CLOB := 'https://<your private endpoint prefix>-<your namespace>.private.objectstorage.us-chicago-1.oci.customer-oci.com/n/<your namespace>/b/<your bucket name>/o/';
BEGIN
    -- チャンク分割する際のパラメータ
    chunk_params := '
    {
      "max": "200",
      "overlap": "40"
    }';
    
    -- OCI GenAI のEmbeddingを利用
    cohere_params := '
    { 
      "provider": "ocigenai",
      "credential_name": "OCI_GENAI", 
      "url": "https://inference.generativeai.ap-osaka-1.oci.oraclecloud.com/20231130/actions/embedText",
      "model": "cohere.embed-multilingual-v3.0"
    }';    
    
    -- DOCUMENTS表をリセット
    DELETE FROM documents;
    
    -- Object Storage上の各オブジェクトに対してEmbeddingしたデータを保存
    FOR rec IN (SELECT OBJECT_NAME FROM DBMS_CLOUD.LIST_OBJECTS('OBJ_CRED', bucket_url))
    LOOP
        -- オブジェクトを取得
        l_blob := DBMS_CLOUD.GET_OBJECT(
          credential_name => 'OBJ_CRED',
          object_uri => bucket_url || rec.object_name);
        
        -- チャンク分割したテキストをDOCUMENTS表に保存
        INSERT INTO documents (body, url, title)
            SELECT chunk_t.chunk_data, rec.object_name, rec.object_name
            FROM
            DBMS_VECTOR_CHAIN.UTL_TO_CHUNKS(REPLACE(DBMS_VECTOR_CHAIN.UTL_TO_TEXT(l_blob), CHR(10)), JSON(chunk_params)) chunk_jt,
            JSON_TABLE(chunk_jt.column_value, '$[*]' COLUMNS (chunk_id NUMBER PATH
            '$.chunk_id', chunk_data VARCHAR2(4000) PATH '$.chunk_data')) chunk_t;
        
        -- DOCUMENTS表に新規保存された各テキストについて、Cohereの embed-multilingual-v3.0 を使ってEmbeddingし、DOCUMENTS表に保存
        FOR rec2 IN (SELECT docid, body FROM documents)
        LOOP
            UPDATE documents
            SET
                vector = DBMS_VECTOR.UTL_TO_EMBEDDING(rec2.body, JSON(cohere_params))
            WHERE docid = rec2.docid;
        END LOOP; 
    END LOOP; 
    
    COMMIT;
END;
/
```

こちらのプロシージャはバケット内のオブジェクトをすべて読みこんでベクトル化し、DOCUMENTS表に保存します。
プロシージャを作成できましたら実行します。

```sql
exec embed_files;
```

## OCI Generative AI Agentsが利用するファンクションの作成

Generative AI AgentsがADB内でベクトル検索を行い、結果を取得するためのファンクションを作成します。

```sql
CREATE OR REPLACE FUNCTION retrieval_func (
  p_query IN VARCHAR2,
  top_k IN NUMBER
)
RETURN SYS_REFCURSOR IS
    v_results SYS_REFCURSOR;
    cohere_params clob;
    query_vec VECTOR;
BEGIN
    -- OCI GenAI のEmbeddingを利用
    cohere_params := '
    { 
      "provider": "ocigenai",
      "credential_name": "OCI_GENAI", 
      "url": "https://inference.generativeai.ap-osaka-1.oci.oraclecloud.com/20231130/actions/embedText",
      "model": "cohere.embed-multilingual-v3.0"
    }';    
    
    query_vec := DBMS_VECTOR.UTL_TO_EMBEDDING(p_query, JSON(cohere_params));
    
    OPEN v_results FOR
        SELECT docid, body, VECTOR_DISTANCE(vector, query_vec, COSINE) as score, chunkid, title
        FROM documents
        ORDER BY score
        FETCH FIRST top_k ROWS ONLY;
    
    RETURN v_results;
END;
/
```

なおファンクションが満たすべき要件やサンプルは下記マニュアルをご参照ください。

https://docs.oracle.com/ja-jp/iaas/Content/generative-ai-agents/oracle-db-guidelines.htm

データベースの要件 → データベース関数

## DB接続の設定

Generative AI AgentsとADBが連携するために、いくつかOCIコンソール上で作業する必要があります。
まずはADBにネットワーク・セキュリティ・グループを設定し、下記マニュアルに記載されている要件を満たすようにNW許可設定を入れます。

https://docs.oracle.com/ja-jp/iaas/Content/generative-ai-agents/oracle-db-guidelines.htm

ネットワークおよびセキュリティー要件 → ネットワーキング要件

次にDBユーザのパスワードを格納するためのボールトを作成します。
以下を参考にボールト、およびキーを作成します。

https://oracle-japan.github.io/ocitutorials/intermediates/certificate/#vault%E3%81%AE%E4%BD%9C%E6%88%90

以下にある「接続」サービスへアクセスします。
開発者サービス → データベース・ツール → 接続

｢接続の作成」を選択します。
![WS000006.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/badb48db-443e-a50f-7328-c083b5b261a6.jpeg)

以下を参考に入力します。
![WS000007.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/b366ec94-ea12-be52-2991-e8ef3973212b.jpeg)

｢パスワード・シークレットの作成」を選択します。
先ほど作成したボールトを選択し、冒頭で作成したDBユーザのパスワードを入力します。
![WS000008.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/138afd65-d0b5-e1b3-4da5-ae11d211df9e.jpeg)

元の入力画面に戻りましたら、接続文字列を入力します。
以下は入力例です。
`host` と `service_name` はご自分の情報を入力します。

```text
(description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)
(host=<private-endpoint-URL>)(connect_data=(service_name=xxx_high.adb.oraclecloud.com))
(security=(ssl_server_dn_match=no)))
```

その他は以下を参考に入力します。
![WS000009.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/3cb425d1-9eeb-6386-c52c-d9fe6c753a17.jpeg)

｢プライベート・エンドポイントの作成」を選択します。
ADBのエンドポイントを配置したプライベート・サブネットを入力します。
![WS000010.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/432e0f22-f264-9097-cdfb-6b369f8b7f6c.jpeg)

最後に「作成」を選択します。
![WS000011.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/332a25dc-4594-d945-def7-8f7c4dd92534.jpeg)

## エージェントの作成

Generative AI Agentsでエージェントを作成します。
以下の通りGenerative AI Agentsのトップ画面へアクセスします。
アナリティクスとAI → AIサービス → 生成AIエージェント
![WS000001.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/8cd3f406-72e3-7309-a146-845f81aa36b6.jpeg)

左メニューの「エージェント」を選択し、「エージェントの作成」を押下します。
![WS000002.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/897c54d5-806b-b3d2-8512-097d9a6c04a0.jpeg)

以下を参考に入力します。
![WS000003.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/d94dfc20-92d9-79a1-edd9-a6f1ca04b418.jpeg)

｢ナレッジ・ベースの作成」を選択します。
先ほど作成した接続、ADB内に作成したベクトル検索用のファンクションを入力します。
｢接続のテスト」を選択し、成功したら「作成」を押下します。
![WS000004.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/97775ff1-39d1-e429-818a-e88487724974.jpeg)

｢このエージェントのエンドポイントの自動作成」をチェックし、「作成」を押下します。
![WS000005.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/7c3c2d30-fd08-4331-2f78-f066b76340cd.jpeg)

## チャットを試す

Generative AI Agentsの左にあるメニューから「チャット」を選択します。
｢エージェント」と「エージェント・エンドポイント」をプルダウンから入力します。
あとはアップロードしたファイルの内容と関連する質問を投げかけます。
すると以下のように、ベクトル検索およびリランクした結果がログとして表示され、最終回答が返ってきます。
![WS000000.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/8b2609b3-c79e-4bb0-dbff-eb97edb5320e.jpeg)

ログの内容を展開するとベクトル検索の結果を確認できます。
![WS000000-2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33182/12d05fe1-32a2-4442-6206-f58b045e6804.jpeg)

以上、OCI Generative AI Agentsを使ったRAG対応チャットの実装でした。
LLMとベクトルDBの連携がマネージドサービスとして提供されるため、より効率的にRAGを実装できますね。