Autonomous DatabaseのSELECT AIを使用して、他のOracle Databaseに格納されているデータに対して自然言語で問合せを行う

# はじめに

Autonomous DatabaseのSELECT AI機能を使用して、他のOracle Databaseに格納されているデータに対して自然言語で問合せを行うサンプルアプリを作成してみました。

ここでは、Computeインスタンス（Oracle Linux 8）にApacheとPHP、php-oci8をインストールしてアプリケーションサーバとしました。

https://qiita.com/500InternalServerError/items/cdb333949f8e0ff76fae

また、クエリの実行対象となるOracle Databaseとして、Base Database Serviceを使用しました。

アプリケーションサーバはAutonomous Database、Base Database ServiceのOracle Databaseにネットワーク的に接続できる必要がありますが、Autonomous DatabaseとBase Database ServiceのOracle Databaseとの間はネットワーク的に接続されている必要はありません。

構成イメージは、以下の図のようになります。
![スクリーンショット 2025-01-29 21.48.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/77dbc65d-ee6f-d737-ed32-815d24f4dd34.png)


なお、こちらの記事の内容はあくまで個人の実験メモ的な内容のため、こちらの内容を利用した場合のトラブルには一切責任を負いません。
また、こちらの記事の内容を元にしたOracleサポートへの問い合わせはご遠慮ください。

# 1. 事前準備

Base Database ServiceのOracle Databaseに接続します。
```
[oracle@db23ai ~]$ sqlplus / as sysdba

SQL*Plus: Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems on Wed Jan 29 18:16:47 2025
Version 23.6.0.24.10

Copyright (c) 1982, 2024, Oracle.  All rights reserved.


Connected to:
Oracle Database 23ai EE High Perf Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems
Version 23.6.0.24.10

SQL>
```
Base Database ServiceのOracle DatabaseのPDBに接続します。
```sql
SQL> ALTER SESSION SET CONTAINER = "DB23AI_PDB1";                  

Session altered.

SQL>
```
ユーザadminを作成し、権限を付与します。
```sql
SQL> GRANT CONNECT, RESOURCE, UNLIMITED TABLESPACE TO admin IDENTIFIED BY Demo#1Demo#1;

Grant succeeded.

SQL>
```
Base Database ServiceのOracle Databaseから切断します。
```sql
SQL> exit
Disconnected from Oracle Database 23ai EE High Perf Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems
Version 23.6.0.24.10
[oracle@db23ai ~]$
```
adminユーザとして、Base Database ServiceのOracle DatabaseのPDBに接続します。
```sql
[oracle@db23ai admin]$ sqlplus admin/Demo#1Demo#1@basedb_pdb1

SQL*Plus: Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems on Wed Jan 29 18:20:30 2025
Version 23.6.0.24.10

Copyright (c) 1982, 2024, Oracle.  All rights reserved.


Connected to:
Oracle Database 23ai EE High Perf Release 23.0.0.0.0 - for Oracle Cloud and Engineered Systems
Version 23.6.0.24.10

SQL> 
```
表empを作成します。
```sql
SQL> CREATE TABLE emp (
  2    empno NUMBER(4) PRIMARY KEY,
  3    ename VARCHAR2(10),
  4    job VARCHAR2(9),
  5    mgr NUMBER(4),
  6    hiredate DATE,
  7    sal NUMBER(7,2),
  8    comm NUMBER(7,2),
  9    deptno NUMBER(2)
 10  );

Table created.

SQL> 
```
表empにデータをINSERTします。
```sql
SQL> INSERT INTO EMP VALUES (7369,'SMITH','CLERK',7902,to_date('17-12-1980','dd-mm-yyyy'),800,NULL,20);

1 row created.

SQL> INSERT INTO EMP VALUES (7499,'ALLEN','SALESMAN',7698,to_date('20-2-1981','dd-mm-yyyy'),1600,300,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7521,'WARD','SALESMAN',7698,to_date('22-2-1981','dd-mm-yyyy'),1250,500,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7566,'JONES','MANAGER',7839,to_date('2-4-1981','dd-mm-yyyy'),2975,NULL,20);

1 row created.

SQL> INSERT INTO EMP VALUES (7654,'MARTIN','SALESMAN',7698,to_date('28-9-1981','dd-mm-yyyy'),1250,1400,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7698,'BLAKE','MANAGER',7839,to_date('1-5-1981','dd-mm-yyyy'),2850,NULL,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,to_date('9-6-1981','dd-mm-yyyy'),2450,NULL,10);

1 row created.

SQL> INSERT INTO EMP VALUES (7788,'SCOTT','ANALYST',7566,to_date('13-JUL-87','dd-mm-rr')-85,3000,NULL,20);

1 row created.

SQL> INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,to_date('17-11-1981','dd-mm-yyyy'),5000,NULL,10);

1 row created.

SQL> INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,to_date('8-9-1981','dd-mm-yyyy'),1500,0,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7876,'ADAMS','CLERK',7788,to_date('13-JUL-87', 'dd-mm-rr')-51,1100,NULL,20);

1 row created.

SQL> INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,to_date('3-12-1981','dd-mm-yyyy'),950,NULL,30);

1 row created.

SQL> INSERT INTO EMP VALUES (7902,'FORD','ANALYST',7566,to_date('3-12-1981','dd-mm-yyyy'),3000,NULL,20);

1 row created.

SQL> INSERT INTO EMP VALUES (7934,'MILLER','CLERK',7782,to_date('23-1-1982','dd-mm-yyyy'),1300,NULL,10);

1 row created.

SQL> COMMIT;

Commit complete.

SQL>
```
表empのレコード数を確認します。
```sql
SQL> SELECT COUNT(*) FROM emp;

  COUNT(*)
----------
	    14

SQL>
```
adminユーザとして、Autonomous Databaseに接続します。
```
[oracle@oracle23ai ~]$ sqlplus admin/Demo#1Demo#1@atp23ai

SQL*Plus: Release 23.0.0.0.0 - Production on Wed Jan 29 18:28:32 2025
Version 23.4.0.24.05

Copyright (c) 1982, 2024, Oracle.  All rights reserved.

Last Successful login time: Wed Jan 29 2025 18:27:30 +09:00

Connected to:
Oracle Database 23ai Enterprise Edition Release 23.0.0.0.0 - Production
Version 23.7.0.25.02

SQL>
```
表empを作成します。
```sql
SQL> CREATE TABLE emp (
  2    empno NUMBER(4) PRIMARY KEY,
  3    ename VARCHAR2(10),
  4    job VARCHAR2(9),
  5    mgr NUMBER(4),
  6    hiredate DATE,
  7    sal NUMBER(7,2),
  8    comm NUMBER(7,2),
  9    deptno NUMBER(2)
 10  );

Table created.

SQL>
```
今回は対象となる表が1つだけなのでDDLを実行して作成しましたが、expdpでCONTENT=METADATA_ONLYを指定して検索対象のDBからメタデータのみをエクスポートし、impdpでインポートすることも可能です。

https://docs.oracle.com/cd/G11854_01/sutil/oracle-data-pump-export-utility.html#GUID-F7994C75-CC86-4A5D-AA9A-361A94FD9C03

COMMENT文でemp表の各列に、それぞれどのようなデータが格納されているかのコメントを追加します。

```sql
SQL> COMMENT ON COLUMN emp.empno IS 'this column contains 従業員番号';

Comment created.

SQL> COMMENT ON COLUMN emp.ename IS 'this column contains 従業員名';

Comment created.

SQL> COMMENT ON COLUMN emp.job IS 'this column contains 役職';

Comment created.

SQL> COMMENT ON COLUMN emp.hiredate IS 'this column contains 入社日';

Comment created.

SQL> COMMENT ON COLUMN emp.sal IS 'this column contains 年棒';

Comment created.

SQL> COMMENT ON COLUMN emp.comm IS 'this column contains コミッション';

Comment created.

SQL> COMMENT ON COLUMN emp.deptno IS 'this column contains 部門番号';

Comment created.

SQL>
```

Autonomous Database側のemp表には、データが格納されていないことを確認します。
```sql
SQL> SELECT COUNT(*) FROM emp;

  COUNT(*)
----------
	     0

SQL>
```
AIプロファイル「OCI_GENAI」を作成します。
```sql
SQL> BEGIN
  2    DBMS_CLOUD_AI.CREATE_PROFILE(
  3  	 profile_name => 'OCI_GENAI',
  4  	 attributes => '{"provider": "oci",
  5  			 "credential_name": "OCI$RESOURCE_PRINCIPAL",
  6  			 "object_list": [{"owner": "admin", "name": "emp"}],
  7  			     "region": "ap-osaka-1",
  8  			     "model": "meta.llama-3.1-70b-instruct",
  9  					     "comments":"true" }'
 10    );
 11  END;
 12  /

PL/SQL procedure successfully completed.

SQL>
```

# 2. HTMLフォームとPHPプログラムの作成
PHPプログラムにプロンプトを渡すためのHTMLフォームを準備します。
今回は、入力されたプロンプトをpromptというパラメータとしてPOSTメソッドで送信します。

```html:form.html
<!doctype html>
<html>
<head>  
    <title> NL2SQL for BaseDB</title>
</head>
<body>
    <form name="HTMLFormElements" action="selectai.php" method="post" id="HTMLFormElements">
          <table border="1" cellpadding="5">
            <tr>
                <td>
                   質問:<br />
                      <input type="text" name="prompt" size="100" />
                </td>
            </tr>
            <tr>
                <td>
                    <input type="reset" name="submitbutton" value=
                            "cancel"/>
                    <input type="submit" name="submitbutton"
                           value="submit" />
                </td>
            </tr>
          </table>
    </form>
</body>
</html>
```
以下の処理を行うPHPプログラムselectai.phpを作成します。
1.POSTメソッドで送信されたpromptを表示
2.Autonomous Databaseに接続
3.SELECT AI（DBMS_CLOUD_AI.GENERATEファンクション）を使用して、プロンプトからSQLを生成
4.生成されたSQLを表示
5.SQL実行対象のOracle DBに接続
6.SQL実行対象のOracle DBに対して、生成されたSQLを実行
7.SQLの実行結果を表示

```php:selectai.php
<?php
// POSTメソッドで送信されたプロンプトを表示
echo "プロンプト：".$_POST["prompt"]."<br><br>";
// Autonomous Databaseに接続
$conn_adb = oci_connect('admin', 'Demo#1Demo#1', 'atp', 'AL32UTF8');
if (!$conn_adb) {
    $e = oci_error();
    trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
// SELECT AI機能（DBMS_CLOUD_AI.GENERATEファンクション）を使用して、プロンプトからSQLを生成
$adb_sql = "SELECT TO_CHAR(DBMS_CLOUD_AI.GENERATE(prompt => '".$_POST["prompt"]."', profile_name => 'OCI_GENAI', action => 'showsql')) FROM dual";
$stid_adb = oci_parse($conn_adb, $adb_sql);
oci_execute($stid_adb);

while (oci_fetch($stid_adb)) {
    $generated_sql = oci_result($stid_adb, 1);
}
// 生成されたSQLを表示
echo "生成されたSQL：".$generated_sql."<br><br>";
// リソースを解放し、Autonomous Databaseへの接続をクローズ
oci_free_statement($stid_adb);
oci_close($conn_adb);
// Base Database ServiceのPDBに接続
$conn_basedb = oci_connect('admin', 'Demo#1Demo#1', 'basedb', 'AL32UTF8');
if (!$conn_basedb) {
    $e = oci_error();
    trigger_error(htmlentities($e['message'], ENT_QUOTES), E_USER_ERROR);
}
// Base Database ServiceのPDBに対して、生成されたSQLを実行
$stid_basedb = oci_parse($conn_basedb, $generated_sql);
oci_execute($stid_basedb);
// 生成されたSQLの実行結果を表示
echo '<table border=1>';
echo '<tr>';
$ncols = oci_num_fields($stid_basedb);
for ($i = 1; $i <= $ncols; ++$i) {
    $colname = oci_field_name($stid_basedb, $i);
    echo '<td>'.$colname.'</td>';
}
echo '</tr>';
while (($row = oci_fetch_array($stid_basedb, OCI_ASSOC + OCI_RETURN_NULLS)) != false) {
    echo '<tr>';
    foreach ($row as $item) {
        echo '<td>'.$item.'</td>';
    }
    echo '</tr>';
}
echo '</table>';
echo "\n";
// リソースを解放し、Base Database ServiceのPDBへの接続をクローズ
oci_free_statement($stid_basedb);
oci_close($conn_basedb);
?>
```

# 3. 動作確認

フォームのHTMLにアクセスします。
![スクリーンショット 2025-01-29 19.51.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/96055162-76c4-08f8-9d1a-97ba0d03bc10.png)
「質問」にプロンプトを入力して、「submit」をクリックします。

![スクリーンショット 2025-01-29 21.07.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/f7d0e30d-a901-bb00-0db8-6287d5201a3b.png)

入力したプロンプトからSQLが生成され、生成されたSQLの実行結果が取得できました。

![スクリーンショット 2025-01-29 21.06.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/30e90ff0-7223-0ee9-70a4-250da45af2db.png)

さらに別の質問も実行してみます。
![スクリーンショット 2025-01-29 19.51.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/89345bd5-b036-a35d-adc9-6c9d6fdf67ed.png)
入力したプロンプトからSQLが生成され、生成されたSQLの実行結果が取得できました。

![スクリーンショット 2025-01-29 19.54.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784824/3f2576bd-44d0-3a26-dab7-f618ecce439e.png)

# 参考情報
[DBMS_CLOUD_AI.GENERATEファンクション](https://docs.oracle.com/cd/E83857_01/paas/autonomous-database/serverless/adbsb/dbms-cloud-ai-package.html#GUID-7B438E87-0E9A-4318-BA01-3BE1A5851229)