ベクトル・データベースPineconeのサービスを提供しているPinecone Systems Inc. が、RAG(Retrieval Augmented Generation)の実装を容易にするCanopyというフレームワークをオープン・ソースで提供しています。
Introducing Canopy: An easy, free, and flexible RAG framework powered by Pinecone https://www.pinecone.io/blog/canopy-rag-framework/
CanopyはPineconeのベクトル・データベースとOpenAIのサービスを呼び出します。そのため、Canopyの実行にはPineconeのAPIキーとOpenAIのAPIキーの両方が必要です。それぞれの準備方法については、PineconeまたはOpenAIのサイトを参照してください。Pineconeについてはインデックスが1つまでは無料で利用できます。そのため、Canopyを利用するにあたって必ずしも有料アカウントにアップグレードする必要はありません。OpenAIについては、ユーザー登録時に付与される無料クレジットを使い切っている場合は、有料アカウントへのアップグレードが必要です。
Canopyの2023年11月29日時点でのバージョン(v.0.2.0)では、RAGで使用するベクトル埋め込み(embedding)を生成するために、OpenAIのEmbeddings APIを呼び出しています。次元数が1536なので、モデルおそらくはtext-embedding-ada-002 でしょう。また、回答を生成するためにOpenAIのCompletions APIを呼び出しています。
Canopy自体はOpenAIのCompletions APIと互換性のあるREST APIを提供しているため、OpenAIのCompletions APIを呼び出すクライアントであれば、Canopyへの問い合わせはエンドポイントを変更するだけで動作するはずです。
CanopyはOracle CloudのAlways Freeのコンピュート・インスタンスに実装します。Canopyを呼び出すアプリケーションは、これもAlways FreeのAutonomous DatabaseのAPEXで作成します。
Oracle CloudのAlways FreeのAutonomous Databaseから外部サービスを呼び出す場合は、HTTPS(SSL)かつDNSで名前解決ができるという制限があるため、作成したコンピュート・インスタンスに付与されているパブリックIPアドレスは、DNSにホスト名とともに設定しておく必要があります。
以下より、Canopyのインストールと構成およびAPEXのアプリケーション作成について紹介します。
Always Freeのコンピュート・インスタンスは、イメージ はOracle Linux 9 、Shape はVM.Standard.E2.1 Micro で作成しました。Canopy自体はPineconeやOpenAIのAPIを呼び出すだけで重い処理をするわけではないので、計算リソースはそれほど必要ないでしょう。
コンピュート・インスタンスが作成されたら、sshで接続してCanopyの実装を始めます。
Canopyのインストールに必要なPython 3.11 とCanopyのSSL対応に使用するnginx をインストールします。作業はユーザーopc で行います。
sudo dnf -y install python3.11 python3.11-pip nginx
[opc@canopy-rag ~]$ sudo dnf -y install python3.11 python3.11-pip nginx
Ksplice for Oracle Linux 9 (x86_64) 385 kB/s | 158 kB 00:00
Oracle Linux 9 OCI Included Packages (x86_64) 24 MB/s | 59 MB 00:02
Oracle Linux 9 BaseOS Latest (x86_64) 14 MB/s | 17 MB 00:01
Oracle Linux 9 Application Stream Packages (x86_64) 19 MB/s | 26 MB 00:01
Oracle Linux 9 Addons (x86_64) 468 kB/s | 335 kB 00:00
Oracle Linux 9 UEK Release 7 (x86_64) 14 MB/s | 24 MB 00:01
Dependencies resolved.
========================================================================================
Package Arch Version Repository Size
========================================================================================
Installing:
nginx x86_64 1:1.20.1-14.0.1.el9_2.1 ol9_appstream 48 k
python3.11 x86_64 3.11.5-1.el9_3 ol9_appstream 30 k
python3.11-pip noarch 22.3.1-4.el9 ol9_appstream 4.3 M
Installing dependencies:
libnsl2 x86_64 2.0.0-1.el9 ol9_appstream 30 k
mpdecimal x86_64 2.5.1-3.el9 ol9_appstream 85 k
nginx-core x86_64 1:1.20.1-14.0.1.el9_2.1 ol9_appstream 587 k
nginx-filesystem noarch 1:1.20.1-14.0.1.el9_2.1 ol9_appstream 8.4 k
oracle-logos-httpd noarch 90.2-1.0.4.el9 ol9_baseos_latest 37 k
python3.11-libs x86_64 3.11.5-1.el9_3 ol9_appstream 12 M
python3.11-pip-wheel noarch 22.3.1-4.el9 ol9_appstream 1.4 M
python3.11-setuptools-wheel noarch 65.5.1-2.el9 ol9_appstream 712 k
Installing weak dependencies:
python3.11-setuptools noarch 65.5.1-2.el9 ol9_appstream 2.3 M
Transaction Summary
========================================================================================
Install 12 Packages
[中略]
Installed:
libnsl2-2.0.0-1.el9.x86_64
mpdecimal-2.5.1-3.el9.x86_64
nginx-1:1.20.1-14.0.1.el9_2.1.x86_64
nginx-core-1:1.20.1-14.0.1.el9_2.1.x86_64
nginx-filesystem-1:1.20.1-14.0.1.el9_2.1.noarch
oracle-logos-httpd-90.2-1.0.4.el9.noarch
python3.11-3.11.5-1.el9_3.x86_64
python3.11-libs-3.11.5-1.el9_3.x86_64
python3.11-pip-22.3.1-4.el9.noarch
python3.11-pip-wheel-22.3.1-4.el9.noarch
python3.11-setuptools-65.5.1-2.el9.noarch
python3.11-setuptools-wheel-65.5.1-2.el9.noarch
Complete!
[opc@canopy-rag ~]$
CanopyにAPEXから接続するにはHTTPS(SSL)が必須であるため、Nginxで一旦HTTPSの接続を受け付けて、HTTPに変換してCanopyにリクエストを転送するようにします。NginxをHTTPSで構成するために、Let's Encryptより証明書を取得します。
証明書の取得に使用する
certbot をインストールします。
sudo dnf --enablerepo=ol9_developer_EPEL -y install certbot [opc@canopy-rag ~]$ sudo dnf --enablerepo=ol9_developer_EPEL -y install certbot
Oracle Linux 9 EPEL Packages for Development (x86_64) 23 MB/s | 47 MB 00:02
Last metadata expiration check: 0:00:34 ago on Wed 29 Nov 2023 01:46:02 AM GMT.
Dependencies resolved.
========================================================================================
Package Arch Version Repository Size
========================================================================================
Installing:
certbot noarch 2.6.0-1.el9 ol9_developer_EPEL 25 k
Installing dependencies:
fontawesome-fonts noarch 1:4.7.0-13.el9 ol9_appstream 205 k
python3-acme noarch 2.6.0-1.el9 ol9_developer_EPEL 268 k
python3-certbot noarch 2.6.0-1.el9 ol9_developer_EPEL 1.0 M
python3-configargparse noarch 1.7-1.el9 ol9_developer_EPEL 56 k
python3-josepy noarch 1.13.0-1.el9 ol9_developer_EPEL 101 k
python3-parsedatetime noarch 2.6-5.el9 ol9_developer_EPEL 133 k
python3-pyrfc3339 noarch 1.1-11.el9 ol9_developer_EPEL 36 k
Transaction Summary
========================================================================================
Install 8 Packages
[中略]
Installed:
certbot-2.6.0-1.el9.noarch fontawesome-fonts-1:4.7.0-13.el9.noarch
python3-acme-2.6.0-1.el9.noarch python3-certbot-2.6.0-1.el9.noarch
python3-configargparse-1.7-1.el9.noarch python3-josepy-1.13.0-1.el9.noarch
python3-parsedatetime-2.6-5.el9.noarch python3-pyrfc3339-1.1-11.el9.noarch
Complete!
[opc@canopy-rag ~]$
サービスhttp 、https およびポート8000/tcp での接続を許可するよう、firewalld を構成します。
sudo firewall-cmd --add-service=http sudo firewall-cmd --add-service=https sudo firewall-cmd --add-port=8000/tcp sudo firewall-cmd --runtime-to-permanent sudo firewall-cmd --reload sudo firewall-cmd --list-all
[opc@canopy-rag ~]$ sudo firewall-cmd --add-service=http
success
[opc@canopy-rag ~]$ sudo firewall-cmd --add-service=https
success
[opc@canopy-rag ~]$ sudo firewall-cmd --add-port=8000/tcp
success
[opc@canopy-rag ~]$ sudo firewall-cmd --runtime-to-permanent
success
[opc@canopy-rag ~]$ sudo firewall-cmd --reload
success
[opc@canopy-rag ~]$ sudo firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: ens3
sources:
services: dhcpv6-client http https ssh
ports: 8000/tcp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
[opc@canopy-rag ~]$
SELinuxの設定で、Nginxによるリバース・プロキシを許可します。
sudo setsebool -P httpd_can_network_connect 1 [opc@canopy-rag ~]$ sudo setsebool -P httpd_can_network_connect 1
[opc@canopy-rag ~]$
Canopyをインストールします。Canopyのインストール手順は、GitHubに記載されています。今回はVirutal Environmentの構成はスキップします。
pip3.11 install canopy-sdk [opc@canopy-rag ~]$ pip3.11 install canopy-sdk
Defaulting to user installation because normal site-packages is not writeable
Collecting canopy-sdk
Using cached canopy_sdk-0.2.0-py3-none-any.whl (72 kB)
Collecting fastapi<0.93.0,>=0.92.0
Using cached fastapi-0.92.0-py3-none-any.whl (56 kB)
Collecting gunicorn<22.0.0,>=21.2.0
Using cached gunicorn-21.2.0-py3-none-any.whl (80 kB)
Collecting jsonschema<5.0.0,>=4.2.0
Using cached jsonschema-4.20.0-py3-none-any.whl (84 kB)
Collecting openai<2.0.0,>=1.2.3
Using cached openai-1.3.6-py3-none-any.whl (220 kB)
Collecting pandas-stubs<3.0.0.0,>=2.0.3.230814
Using cached pandas_stubs-2.1.1.230928-py3-none-any.whl (153 kB)
Collecting pinecone-client<3.0.0,>=2.2.2
[中略]
Running setup.py install for wget ... done
Successfully installed aiobotocore-2.7.0 aiohttp-3.9.1 aioitertools-0.11.0 aiosignal-1.3.1 anyio-3.7.1 attrs-23.1.0 botocore-1.31.64 cachetools-5.3.2 canopy-sdk-0.2.0 certifi-2023.11.17 charset-normalizer-3.3.2 click-8.1.7 decorator-5.1.1 distro-1.8.0 dnspython-2.4.2 fastapi-0.92.0 frozenlist-1.4.0 fsspec-2023.10.0 gcsfs-2023.10.0 google-api-core-2.14.0 google-auth-2.23.4 google-auth-oauthlib-1.1.0 google-cloud-core-2.3.3 google-cloud-storage-2.13.0 google-crc32c-1.5.0 google-resumable-media-2.6.0 googleapis-common-protos-1.61.0 gunicorn-21.2.0 h11-0.14.0 httpcore-1.0.2 httpx-0.25.2 idna-3.6 jmespath-1.0.1 joblib-1.3.2 jsonschema-4.20.0 jsonschema-specifications-2023.11.1 loguru-0.7.2 mmh3-3.1.0 multidict-6.0.4 nltk-3.8.1 numpy-1.25.2 oauthlib-3.2.2 openai-1.3.6 packaging-23.2 pandas-2.1.3 pandas-stubs-2.0.3.230814 pinecone-client-2.2.4 pinecone-datasets-0.6.2 pinecone-text-0.7.0 prompt-toolkit-3.0.41 protobuf-4.25.1 pyarrow-11.0.0 pyasn1-0.5.1 pyasn1-modules-0.3.0 pydantic-1.10.13 python-dateutil-2.8.2 python-dotenv-1.0.0 pytz-2023.3.post1 pyyaml-6.0.1 referencing-0.31.0 regex-2023.10.3 requests-2.31.0 requests-oauthlib-1.3.1 rpds-py-0.13.1 rsa-4.9 s3fs-2023.10.0 six-1.16.0 sniffio-1.3.0 sse-starlette-1.8.2 starlette-0.25.0 tenacity-8.2.3 tiktoken-0.3.3 tqdm-4.66.1 types-jsonschema-4.20.0.0 types-pytz-2023.3.1.1 types-pyyaml-6.0.12.12 types-tqdm-4.66.0.5 typing-extensions-4.8.0 tzdata-2023.3 urllib3-2.0.7 uvicorn-0.20.0 wcwidth-0.2.12 wget-3.2 wrapt-1.16.0 yarl-1.9.3
[opc@canopy-rag ~]$
環境変数の設定を行います。
export PINECONE_API_KEY="<PINECONE_API_KEY>"
export PINECONE_ENVIRONMENT="<PINECONE_ENVIRONMENT>"
export OPENAI_API_KEY="<OPENAI_API_KEY>"
export INDEX_NAME="<INDEX_NAME>"
PineconeのAPIキー とEnvironment の値は、API keys のページより取得します。
INDEX_NAME は任意ですが、今回は
canopy-101 としました。
こちらのCanopyの
セットアップ動画 でインデックス名を
canopy-101 としているのですが、実際にPineconeに作成されるインデックスの名前は
canopy-- が接頭辞として付加されるため、
canopy--canopy-101 になります。
以上の環境変数を設定した後に、Pineconeにインデックスを作成します。無料の範囲で利用する場合は、作成済みのインデックスがあれば削除しておく必要があります。
canopy new
[opc@canopy-rag ~]$ canopy new
Canopy is going to create a new index: canopy--canopy-101
Do you want to continue? [y/N]: y
Success!
[opc@canopy-rag ~]$
canopy new が成功するとPineconeのインデックスが作成されています。Pineconeのコンソールより、作成されたインデックスを確認できます。
Githubに記載されているCanopyのインストール手順では、この後より知識として扱われるドキュメントをPineconeのインデックスに登録する作業が続きます。
今回はAPEXのアプリケーションにRAGで使用するドキュメントの登録機能を実装します。そのため、Canopyのインストール作業は以上で完了です。
これより、HTTPSによる接続を受け付けるリバース・プロキシをNginxで構成します。DNSでパブリックIPアドレスの解決ができるようになっていることが前提です。
Oracle Cloudの仮想クラウド・ネットワークのパブリック・ネットワークのイングレス・ルールで、ポート80と443への接続が許可されていることを確認します。
Let's Encryptを使って証明書を発行します。
sudo certbot certonly --standalone [opc@canopy-rag ~]$ sudo certbot certonly --standalone
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): <申請者のメール・アドレス>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter 'c' to cancel): <DNSに登録したホスト名>
Requesting a certificate for host-name
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/host-name/fullchain.pem
Key is saved at: /etc/letsencrypt/live/host-name/privkey.pem
This certificate expires on 2024-02-27.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[opc@canopy-rag ~]$
certbot の実行に成功するとディレクトリ
/etc/letsencrypt/live/ホスト名/ 以下に、秘密キーのファイルとして
privkey.pem 、発行された証明書を保存するファイルとして
fullchain.pem が作成されます。
Nginxの構成ディレクトリである/etc/nginx/conf.d/ に、server.conf として以下を記述したファイルを作成します。<ホスト名> の部分は実際のホスト名 に置き換えます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/<ホスト名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<ホスト名>/privkey.pem;
server_name <ホスト名>;
root /usr/share/nginx/html;
index index.html;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_send_timeout 10;
proxy_read_timeout 60;
}
}
以上の設定を行い、nginxを起動します。
sudo systemctl start nginx
[opc@canopy-rag ~]$ sudo systemctl start nginx
[opc@canopy-rag ~]$
Canopyを起動します。Canopyを起動する前に、必ず環境変数を設定しておきます。(以下の例では環境変数の設定をmac.env というファイルに書き込んでいます。)
canopy start
[opc@canopy-rag ~]$ . mac.env
[opc@canopy-rag ~]$ canopy start
🚨 Note 🚨
For debugging only. To run the Canopy server in production run the command:
gunicorn canopy_server.app:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers <num_workers>
Starting Canopy server on 0.0.0.0:8000
INFO : Started server process [ 3900 ]
INFO : Waiting for application startup.
2023-11-29 03:25:43,526 - MainProcess - canopy_server.app [INFO ]: Did not find config file. Initializing engines with default configuration
INFO : Application startup complete.
INFO : Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
手元のブラウザより、https://ホスト名/docs にアクセスします。HTTPSのリクエストはNginxが受け付けますがCanopyに転送されているため、CanopyのAPI定義情報が表示されます。
この時点で、Canopyのサーバーが提供するREST APIを、Oracle APEXのアプリケーションから呼び出せる状態になりました。
このCanopyのサーバーに問合せを行う、Oracle APEXのアプリケーションを作成します。
最初に知識となる文章を保存する表CANOPY_DOCUMENTS を作成します。以下のDDLを実行します。
create table canopy_documents (
id number generated by default on null as identity
constraint canopy_documents_id_pk primary key,
source varchar2(80 char) not null,
text clob not null
);
SQLワークショップ のSQLコマンド で実行します。
ドキュメントはPineconeのインデックスに登録され検索もPineconeで実施されるため、本来はAPEX側で表を作成してデータを保持する必要はありません。Oracle APEXでは、表があった方が速くアプリケーションを作成できるため、表を作成しています。
続いて以下のコードを実行し、パッケージ
UTL_CANOPY_API を作成します。CanopyへのRAGを使った問い合わせ(Completions APIの呼び出し)、Pineconeのインデックスの検索(Query API)、RAGの知識となるドキュメントの登録と削除(Upsert/Delete API)を呼び出すプロシージャを作成しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
create or replace package utl_canopy_api
as
/**
* CanopyのCompletions APIを呼び出す。
*/
procedure completions(
p_user_content in clob
,p_stream in boolean default false
,p_canopy_url in varchar2
,p_assistant_content out clob
,p_response out clob
);
/**
* CanopyのQuery APIを呼び出す。
*/
procedure query(
p_query_text in clob
,p_top_k in number default 3
,p_namespace in varchar2 default ''
,p_max_tokens in number default 8192
,p_canopy_url in varchar2
,p_query_result out clob
,p_response out clob
);
/**
* CanopyのUpsertまたはDelete APIを呼び出す。
* APEXのフォームから呼び出すことを想定しているため、Document IDなどは
* 多くても1つだけを処理する。
*/
procedure manage_document(
p_row_status in varchar2
,p_id in number
,p_source in varchar2
,p_text in clob
,p_batch_size in number default 200
,p_canopy_url in varchar2
,p_response out clob
);
end utl_canopy_api;
/
create or replace package body utl_canopy_api
as
procedure completions(
p_user_content in clob
,p_stream in boolean
,p_canopy_url in varchar2
,p_assistant_content out clob
,p_response out clob
)
as
l_request json_object_t;
l_messages json_array_t;
l_message json_object_t;
l_request_clob clob;
e_call_api_failed exception;
l_response_json json_object_t;
l_choices json_array_t;
begin
l_message := json_object_t();
l_message.put('role','user');
l_message.put('content', p_user_content);
l_messages := json_array_t();
l_messages.append(l_message);
l_request := json_object_t();
l_request.put('messages', l_messages);
l_request.put('model','');
l_request.put('stream',false);
l_request_clob := l_request.to_clob();
/*
* Canopyのcompletionsを呼び出す。
*/
apex_web_service.clear_request_headers;
apex_web_service.set_request_headers('Content-Type','application/json', p_reset => false);
apex_web_service.set_request_headers('Accept','application/json', p_reset => false);
p_response := apex_web_service.make_rest_request(
p_url => p_canopy_url || '/chat/completions'
,p_http_method => 'POST'
,p_body => l_request_clob
);
if apex_web_service.g_status_code <> 200 then
raise e_call_api_failed;
end if;
/* */
l_response_json := json_object_t(p_response);
l_choices := l_response_json.get_array('choices');
p_assistant_content := '';
for i in 1..l_choices.get_size()
loop
l_message := treat(l_choices.get(i-1) as json_object_t).get_object('message');
if l_message.get_string('role') = 'assistant' then
p_assistant_content := p_assistant_content || l_message.get_string('content');
end if;
end loop;
end completions;
procedure query(
p_query_text in clob
,p_top_k in number
,p_namespace in varchar2
,p_max_tokens in number
,p_canopy_url in varchar2
,p_query_result out clob
,p_response out clob
)
as
l_request json_object_t;
l_queries json_array_t;
l_query json_object_t;
l_request_clob clob;
e_call_api_failed exception;
l_response_json json_object_t;
l_content clob;
l_snippets json_array_t;
l_snippet json_object_t;
begin
l_query := json_object_t();
l_query.put('text', p_query_text);
l_query.put('namespace', p_namespace);
-- l_query.put('matadata_filter', '{}');
l_query.put('top_k', p_top_k);
-- l_query.put('query_params', '{}');
l_queries := json_array_t();
l_queries.append(l_query);
l_request := json_object_t();
l_request.put('queries', l_queries);
l_request.put('max_tokens', p_max_tokens);
l_request_clob := l_request.to_clob();
/*
* Canopyのqueryを呼び出す。
*/
apex_web_service.clear_request_headers;
apex_web_service.set_request_headers('Content-Type','application/json', p_reset => false);
apex_web_service.set_request_headers('Accept','application/json', p_reset => false);
p_response := apex_web_service.make_rest_request(
p_url => p_canopy_url || '/context/query'
,p_http_method => 'POST'
,p_body => l_request_clob
);
if apex_web_service.g_status_code <> 200 then
raise e_call_api_failed;
end if;
/* */
l_response_json := json_object_t(p_response);
l_content := l_response_json.get_string('content');
l_queries := json_array_t(l_content);
p_query_result := '';
for i in 1..l_queries.get_size()
loop
l_query := treat(l_queries.get(i-1) as json_object_t);
l_snippets := l_query.get_array('snippets');
for j in 1..l_snippets.get_size()
loop
l_snippet := treat(l_snippets.get(j-1) as json_object_t);
p_query_result := p_query_result || 'Source: ' || l_snippet.get_string('source');
p_query_result := p_query_result || apex_application.LF || apex_application.LF;
p_query_result := p_query_result || l_snippet.get_string('text');
p_query_result := p_query_result || apex_application.LF || apex_application.LF;
end loop;
end loop;
end query;
procedure manage_document(
p_row_status in varchar2
,p_id in number
,p_source in varchar2
,p_text in clob
,p_batch_size in number
,p_canopy_url in varchar2
,p_response out clob
)
as
l_request json_object_t;
l_request_clob clob;
l_documents json_array_t;
l_document json_object_t;
l_metadata json_object_t := json_object_t();
l_response clob;
e_call_api_failed exception;
begin
case
when p_row_status in ('C','U') then
l_request := json_object_t();
l_documents := json_array_t();
l_document := json_object_t();
l_document.put('id', p_id);
l_document.put('text', p_text);
l_document.put('source', p_source);
l_document.put('metadata', l_metadata);
l_documents.append(l_document);
l_request.put('documents', l_documents);
l_request.put('batch_size', p_batch_size);
--
l_request_clob := l_request.to_clob();
l_response := apex_web_service.make_rest_request(
p_url => p_canopy_url || '/context/upsert'
,p_http_method => 'POST'
,p_body => l_request_clob
);
if apex_web_service.g_status_code <> 200 then
raise e_call_api_failed;
end if;
-- response should be "Success"
when p_row_status = 'D' then
l_request := json_object_t();
l_documents := json_array_t();
l_documents.append(p_id);
l_request.put('document_ids', l_documents);
--
l_request_clob := l_request.to_clob();
l_response := apex_web_service.make_rest_request(
p_url => p_canopy_url || '/context/delete'
,p_http_method => 'POST'
,p_body => l_request_clob
);
if apex_web_service.g_status_code <> 200 then
raise e_call_api_failed;
end if;
end case;
end manage_document;
end utl_canopy_api;
/
アプリケーション作成ウィザード を開始します。アプリケーションの名前 はCanopy App とします。
ホーム・ページ を削除 し、先ほど作成した表CANOPY_DOCUMENTS のフォーム付き対話モード・レポート のページを追加します。
ページの追加 をクリックし、対話モード・レポート を選択します。
ページ名はDocuments 、表またはビュー にCANOPY_DOCUMENTS を選択し、フォームを含める をチェック します。
ページの追加 をクリックします。
アプリケーションの作成 をクリックします。
アプリケーションが作成されます。
最初にアプリケーション定義 の置換 に、置換文字列 としてG_CANOPY_URL 、置換値 としてCanopyが動作しているサーバーのエンドポイントURL を設定します。URLの末尾は/v1 とします。
https://Canopyが動作しているホスト名/v1
ページ番号
1 の表
CANOPY_DOCUMENTS の
対話モード・レポート のページを開きます。
RAGのデータとして与えるドキュメントに含まれる改行が適切に表示されるように、列TEXT のタイプ をリッチ・テキスト に変更します。設定 の書式 はマークダウン です。
表CANOPY_DOCUMENTSへのドキュメントの挿入、更新、削除は、このままでも実行できます。表CANOPY_DOCUMENTSへのドキュメントの挿入、更新、削除を行うと同時に、同じドキュメントをCanopyに挿入、更新(Upsert APIなので挿入と更新は同じ処理になります)、削除を実行するプロセスを作成します。
ページ番号2 のフォームのページを開きます。
プロセス・ビュー から作成済みのプロセスプロセス・フォームCanopy Document を重複 させます。このプロセスは、表CANOPY_DOCUMENTSへの操作が実装されています。
新たに作成されたプロセスの識別 の名前 をManage Canopy Document 、設定 のターゲット・タイプ をPL/SQLコード に変更し、挿入/更新/削除するPL/SQLコード として以下を記述します。
declare
l_response clob;
begin
utl_canopy_api.manage_document(
p_row_status => :APEX$ROW_STATUS
,p_id => :P2_ID
,p_source => :P2_SOURCE
,p_text => :P2_TEXT
,p_canopy_url => :G_CANOPY_URL
,p_response => l_response
);
end;
失われた更新の防止 はオフ 、行のロック はいいえ を指定します。
以上で、RAGで使用する知識となるドキュメントを登録できるようになりました。
アプリケーションを実行して、ドキュメントを登録してみます。
ナビゲーション・メニュー よりDocuments を開き、作成 をクリックします。
Oracle APEX 23.2の新機能のページをコピペして登録してみました。
プロセスの実行が成功していれば、表CANOPY_DOCUMENTSへの書き込みと同時にPineconeのインデックスcanopy--canopy-101への書き込みが行われています。
Pineconeのコンソールよりインデックスcanopy--canopy-101を確認すると、登録したドキュメントは1つですが、チャンク分割が行われて34のベクトルが登録されていることが確認できます。
登録した知識を使った問い合わせを行うページを作成します。
ページの作成 を実行します。
空白ページ を選択します。
ページ番号 は
3 、
名前 は
Completions とします。それ以外はデフォルトのまま変更しません。
ページの作成 をクリックします。
ページが作成されます。
問い合わせとなる文章を入力するページ・アイテムを作成します。
識別 の名前 はP3_USER_CONTENT 、タイプ はテキスト領域 、ラベル はUser Content とします。
送信ボタンを作成します。
識別 のボタン名 はSUBMIT 、ラベル は送 信とします。外観 のホット をオン にし、テンプレート・オプション のWidth としてStretch を選択します。
動作 のアクション はデフォルトのページの送信 です。
問い合わせ結果を表示するページ・アイテムを作成します。
識別 の名前 はP3_ASSISTANT_CONTENT 、タイプ はテキスト領域 、ラベル はAssistant Content とします。
送信ボタンを押した時に実行されるプロセスを作成します。左ペインでプロセス・ビュー を開き、新規にプロセスを作成します。
作成したプロセスの識別 の名前 をCall Completions とします。タイプ にAPIの呼出し を選択し、設定 のパッケージ としてUTL_CANOPY_API 、プロシージャまたはファンクション としてCOMPLETIONS を選択します。
サーバー側の条件 のボタン押下時 にSUBMIT を指定します。
パラメータ の
p_canopy_url を選択し、
値 の
アイテム として置換文字列
G_CANOPY_URL を指定します。
パラメータ
p_response を選択し、
パラメータ の
出力を無視 を
オン にします。
パラメータp_response には、CanopyのCompletions APIを呼び出した際に受け取った、生のJSONの応答を戻します。デバッグなどのために画面に表示したい場合は、ページ・アイテムを新規に作成して出力先に設定すると良いでしょう。
以上で実装は完了です。ページの保存と実行を行います。
User Contentとして「Oracle APEXの新機能を教えてください。 」と入力して、送信 をクリックしてみました。
納得いかない回答が返されました。
Pineconeのインデックスの検索結果を確認するページを作成します。
このページもCompletions と同じく空白のページ を作成し、質問文を入力するページ・アイテム、送信ボタン、検索結果を返すページ・アイテムを作成します。送信ボタンのクリックでUTL_CANOPY_API.QUERYを呼び出すプロセスを作成します。
作成するページのページ番号 は4 、名前 はQuery とします。
検索文を入力するページ・アイテムは
名前 が
P4_QUERY_TEXT 、
タイプ は
テキスト領域 、
ラベル は
Query Text とします。
送信 ボタンは、Completionsのページの送信ボタンと同じです。
検索結果を返すページ・アイテムは、
名前 が
P4_QUERY_RESULT 、
タイプ は
テキスト領域 、
ラベル は
Query Result とします。
Queryには追加でTop Kを指定するページ・アイテムを作成します。
識別 の名前 はP4_TOP_K 、タイプ として数値フィールド を選択します。ラベル はTop K とします。
検証 の必須の値 をオン にします。デフォルト のタイプ に静的 を選択し、静的値 として3 を設定します。
送信ボタンを押した時に実行されるプロセスを作成します。
作成したプロセスの識別 の名前 をCall Query とします。タイプ にAPIの呼出し を選択し、設定 のパッケージ としてUTL_CANOPY_API 、プロシージャまたはファンクション としてQUERY を選択します。
サーバー側の条件 のボタン押下時 にSUBMIT を指定します。
パラメータのp_canopy_url とp_response が赤く表示されます。Completions のページと同じく、p_canopy_url にはアイテム としてG_CANOPY_URL を設定し、p_response はパラメータ の出力を無視 します。
以上で実装は完了です。ページの保存と実行を行います。
Query Textとして「Oracle APEXの新機能を教えてください。 」と入力して、送信 をクリックしてみました。
パラメータのp_max_tokensとして十分に大きい値が設定されている必要があるようですが、Top Kで指定した数だけ、類似したベクトルが返されました。
大きなドキュメントを登録してCanopyにチャンク分割を任せたのが、納得いかない回答が生成される理由ではないかと推測しています。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/canopy-app.zip
Oracle Linux 9上に実装したnginxおよびCanopyの双方ともに、自動起動は実装していないため、作業をする際にはサーバーを毎回起動する必要があります。
以上になります。
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完