2024年5月27日月曜日

Oracle Database 23aiのベクトル検索を使ったOracle APEXアプリを作る

Oracle Database 23aiのベクトル検索を使うAPEXのサンプル・アプリを作ってみました。以下の処理を実装しています。
  1. ファイルをアップロードする。
  2. アップロードしたファイルからテキストを取り出す。
  3. 取り出したテキストをチャンクに分割する。
  4. 分割したチャンクよりエンべディングを生成する。
  5. 問合せ文字列から、類似度の高いチャンクを一覧する。
  6. 問合せ文字列と類似度の高いチャンクをプロンプトに組み込み、言語モデルを使って回答文を生成する。
アプリケーションは基本的なワークフローの実装を目的としていて、実用性や精度は求めません。すべてローカルで実行し、費用の発生やセキュリティ上の心配は不要にします。

作成したアプリケーションは以下のように動作します。


アプリケーションの作成と実行には、以下の環境を使っています。
  1. エンべディングの生成と言語モデルの実行に、LM Studioを使います。
  2. 言語モデルとして、mmnga/ELYZA-japanese-Llama-2-7b-fast-instruct-ggufをロードします。
  3. エンべディングには、nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.f16.ggufをロードします。
  4. Oracle Database 23aiはMacbook上のDocker/Colimaのx86_64エミュレーション環境で実行します。非常に遅いです。
  5. Oracle APEXは、23.2をOracle Database 23ai上で実行します。
以下より、サンプル・アプリケーションの作成手順を紹介します。

最初にアップロードしたファイルと、そのファイルに含まれる文字列を分割したチャンクを保存する表を作成します。

ファイルを保存する表はEBMJ_FILES、チャンクを保存する表はEBMJ_CHUNKSとして作成します。ファイルを保存する表EBMJ_FILESには、ファイルから取り出した文字列を列TEXTに保存します。チャンクを保存する表EBMJ_CHUNKSには、チャンクから生成したエンべディングを列EMBED_VECTORに保存します。

クイックSQLの以下のモデルを元に、これらの表を作成します。
# prefix: ebmj
files
    title   varchar2(200) /nn
    content file
    text    clob
    chunks /cascade
        chunk_id num /nn
        chunk_offset num /nn
        chunk_length num /nn
        chunk_data clob
        embed_vector vector

レビューおよび実行を行います。


Oracle APEX 23.2のクイックSQLは、ベクトル型を認識しません。そのため、列EMBED_VECTORについては、以下の記述に置き換えます。

embed_vector vector

実行するDDLは以下になります。



後はDDLを実行し、2つの表を作成します。


アプリケーションの作成を開始します。

名前Sample Vector Searchとし、アプリケーションの作成ウィザードを使用します。


アプリケーション作成ウィザードが開きます。

ページの追加をクリックし、表EBMJ_FILESフォーム付き対話モード・レポートのページを追加します。


対話モード・レポートを選択します。


ページ名Filesとします。表またはビューとしてEBMJ_FILESを選択し、フォームを含めるチェックを入れます。

ページの追加をクリックします。


EBMJ_FILESをソースとしたフォーム付き対話モード・レポートのページが追加されます。


同様の手順で、表EBMJ_CHUNKS対話モード・レポートのページを追加します。手作業での編集は行わないため、フォームは付けません。

ページ名Chunks表またはビューとしてEBMJ_CHUNKSを選択します。

ページの追加をクリックします。


以上のページ構成で、アプリケーションの作成をクリックします。


アプリケーションが作成されます。

これから、それぞれのページに修正を加えていきます。

最初にページ番号の表EBMJ_FILES対話モード・レポートを修正します。

ページ・デザイナで開きます。


アップロードしたファイルから取り出したテキストを保存する列TEXTは長文なので、レポートでは表示できません。コメント・アウトして変更を保存します。


ページ番号の、表EBMJ_FILESを対象としたフォームのページを開きます。

こちらも列TEXTをソースとしたページ・アイテムP3_TEXTコメント・アウトします。


アップロードしたファイルより、テキストの取り出し、チャンク分割、エンべディングの生成までを行うプロセスを実装します。

左ペインでプロセス・ビューを表示します。プロセス作成します。

識別名前Extract, Chunking and Embeddingとします。タイプとして実行チェーンを選択します。

サーバー側の条件タイプリクエストは値に含まれるを選択し、としてCREATE SAVEを指定します。


作成した実行チェーンに子プロセスを追加します。

識別名前ExtractソースPL/SQLコードとして以下を記述します。アップロードしたファイルを列CONTENTから読み出し、取り出したテキストを列TEXTに保存します。



子プロセスを追加します。識別名前ChunkingソースPL/SQLコードとして以下を記述します。ファイルから取り出したテキストをチャンクに分割し、表EBMJ_CHUNKSに保存します。ファンクションVECTOR_CHUNKSに与えているパラメータについては、調整の余地が多分にあります。



子プロセスを追加します。識別名前EmbeddingソースPL/SQLコードに以下を記述します。チャンクからエンべディングを生成し、表EBMJ_CHUNKSに保存します。



以上で、ファイルのアップロードからエンべディングを生成するまでの、一連の処理が実装できました。

アプリケーションを実行し、いくつかファイルをアップロードします。

その前にページ番号の表EBMJ_CHUNKSの対話モード・レポートより、列EMBED_VECTORコメント・アウトします。Oracle APEX 23.2のレポートでは、ベクトル型を扱うことができません。


アプリケーションを実行し、Filesのページを開きます。

作成をクリックします。


Titleを入力し、アップロードするファイルを選択します。

作成をクリックします。


ファイルがアップロードされます。


Chunksを開き、分割されたチャンクを確認します。エンべディングは表示されませんが、エラーが発生していなければ正常に生成されています。また、LM Studioのサーバー・ログからも確認できます。

単純にファイルからテキストを取り出して、単語数(日本語だとほとんど文字数)でチャンクに分割すると、さすがに精度がでない感じで分割されるな、とは思います。


ページ番号4の表EBMJ_CHUNKS対話モード・レポートで、問合せ文字列との類似検索を実装します。

類似検索を行う文字列を入力するページ・アイテムを作成します。

識別名前P4_TEXTタイプテキスト・フィールドラベルTextとします。

設定[Enter]を押すと送信オンにします。


類似検索を行うように、対話モード・レポートのソースを置き換えます。

ソースタイプSQL問合せに変更し、SQL問合せとして以下を記述します。FETCH句の部分が決めうちになっています。類似したチャンクを検索するにあたって、調整が必要になるでしょう。

ページ・アイテムP4_TEXTが空白のときは、SELECT文でエラーが発生します。そのため、サーバー側の条件タイプアイテムはNULLではないを選択し、アイテムP4_TEXTを指定します。


以上で、ページ・アイテムP4_TEXTに入力した文字列に類似しているチャンクを検索できます。

選択したエンべディング・モデルが日本語に対応していないのだろうと思いますが、あまり関連の無さそうなチャンクが検索されています。LM Studioで利用可能なエンべディング・モデルに選択肢がほぼ無いので、仕方がありません。実用を考えると、OpenAIやCohereといった外部サービスを呼び出す必要があるでしょう。


最後にホーム・ページに、問合せ文字列から類似検索したチャンクを含めたプロンプトを作成し、言語モデルを呼び出して回答文を生成する機能を実装します。

ページ・デザイナで、ページ番号ホーム・ページを開きます。

Body以下にあるリージョンのページ・ナビゲーションを削除します。


問合せ文字列を入力するページ・アイテムを作成します。

識別名前P1_QUERYタイプテキスト・フィールドラベルQueryとします。

設定[Enter]を押すと送信オンにします。


言語モデルを呼び出して生成した回答文を表示するページ・アイテムを作成します。

識別名前P1_ANSWERタイプテキスト領域ラベルAnswerとします。セッション・ステートデータ型としてCLOBを選択します。


デバッグのために、回答文の生成に使用したプロンプトを表示するページ・アイテムを作成します。

識別名前P1_PROMPTタイプテキスト領域ラベルPromptとします。セッション・ステートデータ型としてCLOBを選択します。


プロセス・ビューを開き、問合せ文字列から回答を生成するプロセスを作成します。

識別名前Generate Answerとします。タイプコードの実行ソースPL/SQLコードとして、以下を記述します。


サーバー側の条件タイプアイテムはNULLではないを選択し、アイテムとしてP1_QUERYを指定します。


以上でアプリケーションは完成です。アプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

ほとんど質問と関連のないチャンクがプロンプトに含まれているにもかかわらず、それっぽい回答文が生成されるので、少々驚きました。言語モデルにELYZAを選択しているからかもしれません。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-vector-search.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2024年5月24日金曜日

DBMS_VECTOR_CHAIN.UTL_TO_SUMMARYとUTL_TO_GENERATE_TEXTの動作を確認する

Oracle Database 23aiに追加されたPL/SQLパッケージDBMS_VECTOR_CHAINには、ドキュメントからサマリーを生成するUTL_TO_SUMMARYおよび文章を生成するUTL_TO_GENERATE_TEXTというファンクションが含まれます。

3rd PartyのプロバイダがCohereの場合は、CohereのAPIにsummarizeがあるので(ただし、すでにLegacyの扱い)それを呼び出すことは想定できるのですが、OpenAIのAPIにサマリーの生成はありません。

今回はLM StudioのOpenAI互換のChat Completions APIを、ファンクションUTL_TO_SUMMARYとUTL_TO_GENERATE_TEXTから呼び出し、APIとしてどのようなリクエストが送信されているかを確認してみます。

LM Studioでは、モデルとしてELYZA japanese Llama 2 fast instruct 7B q8_0 ggufを読み込みました。


SQLコマンドから以下のコードを実行します。



LM Studioに出力されたリクエストは以下でした。

プロバイダがOpenAIの場合は、roleがsystemのプロンプトとしてGenerate a summaryを渡すことにより、後続のroleがuserのメッセージのサマリーを生成しているようです。

[2024-05-24 16:48:32.526] [INFO] Received POST request to /v1/chat/completions? with body: { "max_tokens": 260, "model": "gpt-3.5-turbo", "messages": [ { "role": "system", "content": "Generate a summary" }, { "role": "user", "content": "\n吾輩わがはいは猫である。名前はまだ無い。\n どこで生れたかとんと見当けんとうがつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。この書生というのは時々我々を捕つかまえて煮にて食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。ただ彼の掌てのひらに載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始みはじめであろう。この時妙なものだと思った感じが今でも残っている。第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶やかんだ。その後ご猫にもだいぶ逢あったがこんな片輪かたわには一度も出会でくわした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙けむりを吹く。どうも咽むせぽくて実に弱った。これが人間の飲む煙草たばこというものである事はようやくこの頃知った。\n" } ] }

コードに含まれるdbms_vector_chain.utl_to_summaryの行をコメントアウトし、代わりにdbms_vector_chain.utl_to_generate_textを呼び出してみます。


utl_to_generate_textの呼び出しでは、roleがsystemのプロンプトはYou are a helpful assistantになっています。

[2024-05-24 16:53:18.893] [INFO] Received POST request to /v1/chat/completions? with body: { "max_tokens": 260, "model": "gpt-3.5-turbo", "messages": [ { "role": "system", "content": "You are a helpful assistant" }, { "role": "user", "content": "\n吾輩わがはいは猫である。名前はまだ無い。\n どこで生れたかとんと見当けんとうがつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。この書生というのは時々我々を捕つかまえて煮にて食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。ただ彼の掌てのひらに載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始みはじめであろう。この時妙なものだと思った感じが今でも残っている。第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶やかんだ。その後ご猫にもだいぶ逢あったがこんな片輪かたわには一度も出会でくわした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙けむりを吹く。どうも咽むせぽくて実に弱った。これが人間の飲む煙草たばこというものである事はようやくこの頃知った。\n" } ] }

Cohere、GoogleAIといった他の3rd Partyのプロバイダについても、指定したプロバイダに合わせたリクエストになっている模様です。

OpenAIのChat Completions APIを呼び出すにあたって、systemプロンプトが固定というのは、扱いにくいとは思います。

以下は、apex_web_service.make_rest_requestを使って、同じリクエスト(systemプロンプトがGenerate a summary)を発行するコードの例です。



DBMS_VECTOR_CHAIN.UTL_TO_EMBEDDINGおよびUTL_TO_EMBEDDINGSの動作を確認する

Oracle Database 23aiではエンべディング(embedding)の生成に、SQLファンクションのVECTOR_EMBEDDINGと、PL/SQLパッケージDBMS_VECTOR_CHAINに含まれるUTL_TO_EMBEDDINGまたはUTL_TO_EMBEDDINGSを呼び出す方法が提供されています。

今回はLM StudioのOpenAI互換のEmbedding APIを呼び出し、エンべディングを生成してみます。SQLファンクションのVECTOR_EMBEDDINGはONNXモデル向けなので、今回は確認の対象から外します。DBMS_VECTOR_CHAINのUTL_TO_EMBEDDINGおよびUTL_TO_EMBEDDINGSでは、ONNXモデル(providerがdatabase)と、それ以外の外部API呼び出しによるエンべディングの生成に対応しています。

Embeddingモデルとしてnomic-embed-text-v1.5f16.ggufを使用しています。生成されるエンべディングの評価を目的とはしていないため、必ずしもこのモデルを使わなければならない、ということではありません。


ローカルのMac上にOracle Database 23aiのコンテナ・イメージから作成したOracle APEXの環境を使って、エンべディングの生成を確認します。APEXのワークスペースはAPEXDEV、デフォルトのワークスペース・スキーマはAutonomous Databaseの仕様に合わせてWKSP_APEXDEVとして作成しています。

最初に試験に使用する表TEST_EMBEDDINGSを作成します。

SQLスクリプトとして実行します。列TEXTにエンべディングの生成に使用する文章を入力しています。


TEST_EMBEDDINGSの作成とテスト用のデータの投入が行われます。


DBMS_VECTOR_CHAIN.CREATE_CREDENTIALを呼び出し、OpenAIのAPIを呼び出すために使用するクリデンシャルを作成します。実際に呼び出すのはLM Studioのローカル・サーバーなので、access_tokenには適当な文字列を設定します。
begin
    dbms_vector_chain.create_credential(
        credential_name => 'OPENAI_CRED'
        ,params => JSON(
            json_object(
                key 'access_token' value '適当な文字列'
            )
        )
    );
end;

ORA-27486: 権限が不足していますが発生する場合は、ドキュメントのSQL RAG Exampleにあるように、デフォルトのパーシング・スキーマWKSP_APEXDEVにCREATE CREDENTIAL権限を付与します。

PDBのFREEPDB1にユーザーSYS/SYSDBAで接続し、ユーザーWKSP_APEXDEVにCREATE CREDENTIAL権限を割り当てます。

bash-4.4$ sqlplus / as sysdba


SQL*Plus: Release 23.0.0.0.0 - Production on Mon May 20 08:01:55 2024

Version 23.4.0.24.05


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



Connected to:

Oracle Database 23ai Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free

Version 23.4.0.24.05


SQL> alter session set container = freepdb1;


Session altered.


SQL> grant create credential to wksp_apexdev;


Grant succeeded.


SQL> 


クリデンシャルOPENAI_CREDを作成するために、再度、同じスクリプトを実行します。今度は正常に作成されます。


ビューUSER_CREDENTIALSを検索し、作成したクリデンシャルOPENAI_CREDが有効になっていることを確認します。(ENABLEDがTRUE)

select * from user_credentials


最初にOpenAI互換のEmbedding APIを、直接呼び出してみます。以下のコードを実行します。

IDの行の列TEXTの内容からエンべディングを生成し、列V0に保存しています。


LM StudioのServer logsとして、以下が出力されます。

[2024-05-24 10:07:59.909] [INFO] Received POST request to /v1/embeddings with body: { "model": "text-embedding-3-small", "input": " \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n" }
[2024-05-24 10:07:59.961] [INFO] Returning embeddings (not shown in logs)

同じ処理を、DBMS_VECTOR_CHAIN.UTL_TO_EMBEDDINGを呼び出して実施します。以下のコードを実行します。

IDの行の列TEXTの内容からエンべディングを生成し、列V1に保存しています。


LM StudioのServer logsとして、以下が出力されます。

[2024-05-23 13:44:59.925] [INFO] Received POST request to /v1/embeddings with body: { "input": [ " \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n" ], "model": "text-embedding-3-small" }
[2024-05-23 13:44:59.989] [INFO] Returning embeddings (not shown in logs)

Embedding APIの呼び出しリクエストがほぼ同じ(inputが配列として渡されているところは異なります)なので、生成されているエンべディングも同一になるはずです。確認してみます。2つのベクトルのユークリッド距離を求めます。

select l2_distance(v0, v1) from test_embeddings where id = 1;

結果として0が返されるので、2つのベクトルは同じ値です。


続いて、複数の文章からエンべディングの配列を生成します。以下のコードを実行します。



LM StudioのServer logsとして、以下が出力されます。

[2024-05-23 20:10:53.473] [INFO] Received POST request to /v1/embeddings with body: { "model": "text-embedding-3-small", "input": [ " \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n", " \n何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 \n吾輩はここで始めて人間というものを見た。 \n", " \nしかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。 \nこの書生というのは時々我々を捕つかまえて煮にて食うという話である。 \n" ] }
[2024-05-23 20:10:53.605] [INFO] Returning embeddings (not shown in logs)

同じ処理を、DBMS_VECTOR_CHAIN.UTL_TO_EMBEDDINGSを呼び出して実施します。以下のコードを実行しました。



LM StudioのServer logsとして、以下が出力されます。OpenAIのAPIを直接呼び出したときと、同じリクエストになっています。

[2024-05-24 10:12:51.502] [INFO] Received POST request to /v1/embeddings with body: { "input": [ " \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n", " \n何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 \n吾輩はここで始めて人間というものを見た。 \n", " \nしかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。 \nこの書生というのは時々我々を捕つかまえて煮にて食うという話である。 \n" ], "model": "text-embedding-3-small" }
[2024-05-24 10:12:51.592] [INFO] Returning embeddings (not shown in logs)

引数dataとして与えるVECTOR_ARRAY_Tの内容は、DBMS_VECTOR_CHAIN.UTL_TO_CHUNKSの出力形式に合わせます。

OpenAIのAPIを直接呼び出して取得したエンべディングと、DBMS_VECTOR_CHAIN.UTL_TO_EMBEDDINGSを呼び出して取得したエンべディングのユークリッド距離を確認します。

select id, l2_distance(v0, v1) from test_embeddings order by id asc

すべて0となり、同一のエンべディングが作成できていることが確認できます。


ドキュメントではbatch sizeと記載されていますが、正確にはbatch_sizeです。batch_sizeに指定された数の文章をinputに含め、複数回のAPI呼び出しによりすべてのエンべディングが生成されます。

batch_sizeに1を指定すると、LM StudioのServer logsに以下のように出力され、複数回のAPI呼び出しが行われていることが確認できます。

[2024-05-24 10:46:41.451] [INFO] Received POST request to /v1/embeddings with body: { "input": [ " \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n" ], "model": "text-embedding-3-small" }
[2024-05-24 10:46:41.503] [INFO] Returning embeddings (not shown in logs)
[2024-05-24 10:46:41.537] [INFO] Received POST request to /v1/embeddings with body: { "input": [ " \n何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 \n吾輩はここで始めて人間というものを見た。 \n" ], "model": "text-embedding-3-small" }
[2024-05-24 10:46:41.552] [INFO] Returning embeddings (not shown in logs)
[2024-05-24 10:46:41.584] [INFO] Received POST request to /v1/embeddings with body: { "input": [ " \nしかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。 \nこの書生というのは時々我々を捕つかまえて煮にて食うという話である。 \n" ], "model": "text-embedding-3-small" }
[2024-05-24 10:46:41.604] [INFO] Returning embeddings (not shown in logs)

ドキュメントに記載されていないようですが、引数dataにCLOB型の値を渡し(つまりUTL_TO_EMBEDDINGSを呼び出しますが、対象となる文章は1つだけ)、戻り値の方をVECTOR_ARRAY_TとしてUTL_TO_EMBEDDINGSを呼び出せるようです。

この場合は戻り値のVECTOR_ARRAY_T(これはTABLE OF CLOBとして定義されています)であるため、1つだけCLOBの要素が返されます。内容はJSONになっていました。


一つだけの要素の内容は以下です。
{"embed_id":"1","embed_data":" \n吾輩わがはいは猫である。名前はまだ無い。 \nどこで生れたかとんと見当けんとうがつかぬ。 \n","embed_vector":"[0.07786194980144501,0.002595797646790743,-0.14850468933582306,0.01288861595094204,0.02572513557970524,0.03886890783905983,-0.02595830149948597,-0.006078328471630812,-0.030467288568615913,0.0006770426989533007,-0.06241714581847191,-0.01921042799949646, [中略] -0.009982775896787643,0.017772963270545006,0.010874804109334946,-0.01866289973258972,0.03308163210749626,-0.004839506931602955,-0.006120910868048668,-0.008617770858108997,0.032157570123672485,0.059361301362514496,0.01376398466527462,0.02976307086646557,-0.01138649694621563,0.019222760573029518,-0.024225575849413872,0.00522598996758461,0.036297716200351715,0.0064460234716534615,-0.08576755225658417]"}
戻り値となるJSONには、仕様にそってembed_id(なぜか値は文字列)、embed_dataが含まれていて、embed_vectorとしてエンべディングがJSON配列の文字列として返されています。UTL_TO_EMBEDDING、UTL_TO_EMBEDDINGSともに、出力がベクトルのみの方が扱いやすいのですが、ドキュメントの記載とは違っているようにも思います。

Oracle AI Vector Search User's GuideのConvert File to Text to Chunks to Embeddingsには、以下のコード例が載っています。
SELECT et.* from 
  documentation_tab dt,
  dbms_vector_chain.utl_to_embeddings(
    dbms_vector_chain.utl_to_chunks(dbms_vector_chain.utl_to_text(dt.data)),
    json(:embed_params)) et;
これと同じ構造の以下のコードを実行してみます。

JSONの配列ではなくベクトルの配列が返されます。プロバイダ(databaseか3rd Partyか)または、3rd Partyのプロバイダの種類に依存して、返される値も異なるのかもしれません。