[Oracle Cloud]Oracle Database 23aiを使ってPDFファイルをEmbeddingし類似性検索してみた。(2024/05/06)

# はじめに

Oracle Database 23ai の AI Vector Search機能を使ってPDFファイルをEmbeddingし、類似性検索をすることができます。
簡単なテストを実施してみました。EmbeddingモデルにはOCI Generative AIサービスとOracle Database 23aiにインポート(ロード)したONNXモデルを使用しました。

# 作業ステップ

- 事前作業
- PDFファイルデータのEmbedding
- 類似性検索

# 事前作業
- ONNXフォーマットでのモデルのエクスポート
[Oracle Database 23ai FreeとAPEXでRAGを使った生成AIアプリをローコード開発してみた (事前準備編)](https://qiita.com/ssfujita/items/34d9e3c39ca730d29351)
こちらの記事の(オプション)の項を参考にHugging Face上にあるモデルをONNXフォーマットにエクスポートします。
sentence-transformers/all-MiniLM-L6-v2をエクスポートした例

```python
$ python3

>>> from oml.utils import EmbeddingModel, EmbeddingModelConfig
>>> config = EmbeddingModelConfig.from_template("text",max_seq_length=512,quantize_model=True)
>>> em = EmbeddingModel(model_name="sentence-transformers/all-MiniLM-L6-v2", config=config)
>>> em.export2file("all-MiniLM-L6-v2",output_dir=".")
```

エクスポートしたファイル(all-MiniLM-L6-v2.onnx)をデータベースサーバー上のディレクトリオブジェクトが作成されているディレクトリにコピーします。



- 作業用データベースユーザの作成
PDBにSYSDBA権限で接続し、データベースユーザを作成し、「DB_DEVELOPER_ROLE」、「create mining model」権限を付与します。エクスポートしたONNXファイルを保管したディレクトリオブジェクトの操作権限を付与します。

```sql
$ export NLS_LANG=American_America.AL32UTF8
$ sqlplus / as sysdba

alter session set container=pdb1;
CREATE USER docuser identified by <password> DEFAULT TABLESPACE USERS quota unlimited on USERS;
GRANT DB_DEVELOPER_ROLE to docuser;
grant create mining model to docuser;
grant read on directory data_pump_dir to docuser;
grant write on directory data_pump_dir to docuser;
```

- PDFファイルの準備、PDFファイルデータの表への格納
操作権限を保持するディレクトリオブジェクトのディレクトリにPDFファイルを配置します。
作成したデータベースユーザでPDFファイル内容を表に取り込みます。

```sql
drop table documentation_tab purge;
CREATE TABLE documentation_tab (id number, data blob);
INSERT INTO documentation_tab values(1, to_blob(bfilename('BFILE_DIR','database-concepts.pdf')));
INSERT INTO documentation_tab values(2, to_blob(bfilename('BFILE_DIR','oracle-ai-vector-search-users-guide.pdf')));
INSERT INTO documentation_tab values(3, to_blob(bfilename('BFILE_DIR','fy24q3-tanshin-jp.pdf')));
commit;
```


# PDFファイルデータのEmbedding

## ONNXフォーマットにしたモデルのインポートしEmbedding
### モデルのインポート(ロード)

DBMS_VECTOR.LOAD_ONNX_MODELプロシージャを使用して、エクスポートしたONNXファイル(all-MiniLM-L6-v2.onnx)をインポート(ロード)します。

引数
- ディレクトリオブジェクト名 ： DATA_PUMP_DIR
- インポートするONNXファイル名 ： all-MiniLM-L6-v2.onnx
- 登録するモデル名 ： minilm_model
- JSON形式のメタ情報
    - function : embedding (モデルの種類)
    - embeddingOutput : emveddingOutput (生成されたエンベディングを保持するモデル出力)
    - input ： モデルの入力マッピングについて説明

```sql
EXECUTE DBMS_VECTOR.LOAD_ONNX_MODEL( 'DATA_PUMP_DIR', 'all-MiniLM-L6-v2.onnx', 'minilm_model', JSON('{"function" : "embedding", "embeddingOutput" : "embedding", "input": {"input": ["DATA"]}}'));
```

登録されたモデルの確認(user_mining_modelsビュー)

```sql
SELECT MODEL_NAME, MINING_FUNCTION, ALGORITHM,ALGORITHM_TYPE, MODEL_SIZE FROM user_mining_models ;


MODEL_NAME
------------------------------------------------------------------------------------------------------------------------
MINING_FUNCTION                ALGORITHM                      ALGORITHM_ MODEL_SIZE
------------------------------ ------------------------------ ---------- ----------
MINILM_MODEL
EMBEDDING                      ONNX                           NATIVE       23026809
```

### Embedding
PDFファイルデータの表のdata列(BLOB)をEmbedding対象列としてEmbeddingし、Embedding結果を表に格納します。

#### Embedding結果を格納する表の作成

```sql
drop table documentation_tab_minilm purge;
CREATE TABLE documentation_tab_minilm (
 DOC number,
 POS number,
 SIZ number,
 TXT CLOB,
 embed_vector VECTOR
);
```

#### Embedding
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108635/a5e96fd5-2cf1-76cc-8144-c02448fb3830.png)

##### ステップ
- data列(BLOB)をテキスト化(DBMS_VECTOR_CHAIN.UTL_TO_TEXTファンクション)
- テキスト化した結果を一定の分量に分割(VECTOR_CHUNKS関数)
- 分割したテキストをEmbedding(VECTOR_EMBEDDING関数)

```sql
INSERT INTO documentation_tab_minilm
SELECT D.id doc, C.chunk_offset pos, C.chunk_length siz, C.chunk_text txt, VECTOR_EMBEDDING(MINILM_MODEL USING C.chunk_text as data) embed_vector
FROM documentation_tab D, VECTOR_CHUNKS(dbms_vector_chain.utl_to_text(D.data)
BY words
MAX 150
OVERLAP 0
SPLIT BY sentence
LANGUAGE japanese NORMALIZE all) C;

COMMIT;
```

# 類似性検索

```sql
SELECT doc, pos, txt
FROM 
  documentation_tab_minilm ORDER BY VECTOR_DISTANCE(embed_vector,TO_VECTOR(VECTOR_EMBEDDING(MINILM_MODEL USING '&Keyword' as data)), EUCLIDEAN) fetch first 5 rows only;
Enter value for keyword: Performance
```

検索結果

```
       DOC        POS
---------- ----------
TXT
--------------------------------------------------------------------------------
         1    1642523
Chapter 22

Topics for Database Administrators

The Oracle performance method is based on i
dentifying and eliminating bottlenecks i
n the
database, and developing efficient SQL statements. Ap
plying the Oracle performance method
involves the following tasks:
<以下略>

         1     659880
Dynamic performance views contain information such as the fo
llowing:
<以下略>

         1    1641693
Typical problems include:

~
CPU bottlenecks

~
Undersized memory structures
<以下略>

         1    1673740
designed data model answer the most common queries a
s efficiently as possible. For
example, the data model should use the type of i
ndexes that provide the best
performance. Tuning after deployment is undesirabl
<以下略>


         1    1649806
Besides reporting potential performance issues, ADDM documen
ts areas of the database
that are not problems. Subcomponents such as I/O and m
emory that are not significantly
<以下略>


5 rows selected.
```

## OCI Generative AIのEmbeddingモデルを使ったEmbedding

- ネットワークACLの設定 
データベースユーザーにホストに対する接続権限を付与

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

- credentialの作成
OCI Generative AIサービスを利用するための credentialを作成します。
OCI CLIの構成ファイル情報を使います。
    - OCIユーザOCID
    - テナンシーOCID
    - コンパートメントOCID
    - pem形式の秘密鍵(ファイルの内容を使用)
    - API Keyフィンガープリント

```sql
exec dbms_vector.drop_credential('OCI_GENAI_CRED');
declare
  jo json_object_t;
begin
  jo := json_object_t();
  jo.put('user_ocid','ocid1.user.oc1..aaaaaaaa');
  jo.put('tenancy_ocid','ocid1.tenancy.oc1..aaaaaaaa');
  jo.put('compartment_ocid','ocid1.compartment.oc1..aaaaaaaa');
  jo.put('private_key','aaaaaaaa');
  jo.put('fingerprint','aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa:aa');
  dbms_output.put_line(jo.to_string);
  dbms_vector.create_credential(
    CREDENTIAL_NAME   => 'OCI_GENAI_CRED',
    PARAMS        => json(jo.to_string));
end;
/
```

#### Embedding結果を格納する表の作成
```sql
drop table documentation_tab_ocigenai purge;
CREATE TABLE documentation_tab_ocigenai (
 DOC number,
 TXT CLOB,
 embed_id NUMBER,
 embed_data VARCHAR2(4000),
 embed_article VECTOR
);
```

#### PDFデータのテキスト化(dbms_vector_chain.utl_to_text)と分割(VECTOR_CHUNKS)

```sql
INSERT INTO documentation_tab_genai(DOC,embed_id,embed_data) 
SELECT D.id doc, C.chunk_offset pos, C.chunk_text
FROM documentation_tab D, VECTOR_CHUNKS(dbms_vector_chain.utl_to_text(D.data)
BY words
MAX 150
OVERLAP 0
SPLIT BY sentence
LANGUAGE japanese NORMALIZE all) C;
```

VECTOR_CHUNKS後のデータをInputにEmbeddingします。

操作を簡略するためにEmbedding用のファンクションを作成します。

```sql
CREATE OR REPLACE FUNCTION vector_get(keywd IN CLOB)
RETURN vector IS
 v vector;
BEGIN
 select et.embed_vector into v
  from dbms_vector.utl_to_embeddings(
           keywd, 
           json('{
	        "provider": "ocigenai", 
	        "credential_name": "OCI_GENAI_CRED", 
	        "url": "https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/embedText", 
	        "model": "cohere.embed-multilingual-v3.0"
	        }')
	) t, 
  JSON_TABLE(
      t.column_value, 
      '$[*]' COLUMNS (
        embed_id NUMBER PATH '$.embed_id', 
        embed_data VARCHAR2(4000) PATH '$.embed_data', 
        embed_vector CLOB PATH '$.embed_vector'
        )
    ) et;

    RETURN v;
END;
/
```

```sql
update documentation_tab_genai
set embed_vector = vector_get(EMBED_DATA);
```

# おわりに

AI Vector Searchには「バイナリデータのテキスト化」、「テキストデータの分割」、「Embedding」用の関数が用意されており、外部ファイルのEmbedding化から類似性検索までの一連の操作が できました。

## 参考情報
- [Generate and Use Embeddings for End-to-End Search](https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/generate-and-use-embeddings-end-end-search.html)
- [SQL Quick Start](https://docs.oracle.com/en/database/oracle/oracle-database/23/vecse/sql-quick-start.html)