0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Oracle 26ai の in-DB 埋め込み(VECTOR_EMBEDDING)で Agentic RAG を DB 内で完結させてみた

0
Posted at

1. はじめに

前回、Google の ADK(Agent Development Kit)+ Gemini + MCP Toolbox for Databases の標準構成で、データベースを Oracle Autonomous Database 26ai に差し替えて Agentic RAG を成立させました(ADK + Gemini + MCP Toolbox の標準構成で、DB を Oracle 26ai にして Cloud Run + Vertex AI で Agentic RAG をやってみた)。ただしそのときは、RAG の クエリ側の埋め込みをエージェント(Python)側で Gemini Embedding にかけ、ベクトル文字列を Toolbox に渡して TO_VECTOR で検索する形にしていました。理由は、MCP Toolbox の Oracle ソースが embeddedBy(ツール内での自動埋め込み)に対応しておらず、テキストを渡すだけでは埋め込めなかったからです。

その記事の考察で「本筋は、Oracle 26ai の in-DB 埋め込み(ONNX モデル + VECTOR_EMBEDDING)でクエリも DB 内で埋め込むことだ」と書きました。本記事は、その本筋を実際にやってみた回です。埋め込み・格納・検索のすべてを Oracle の中で完結させ、外部の埋め込み API もエージェント側の埋め込みコードも無くします。

MCP Toolbox から ADB への wallet 接続の細部は、別記事「Google の MCP Toolbox for Databases から Oracle Autonomous Database に wallet 接続してみた」にまとめています。本記事は接続が済んでいる前提で進めます。

1.1. 今回の検証ゴール

# 検証項目 達成条件
1 ADB に ONNX 埋め込みモデルを載せ、DB 内でテキストを埋め込める VECTOR_EMBEDDING(model USING 'テキスト' AS data) がベクトルを返す
2 クエリも DB 内で埋め込んで類似検索が成立する VECTOR_DISTANCE で意味的に近い資料が上位に並ぶ
3 Toolbox ツールをテキスト引数化し、ADK エージェントから RAG が回る LLM がテキストを渡すだけでベクトル検索→Gemini 回答が成立する

1.2. 結論(先出し)

  • 埋め込みも Oracle 内に寄せると、Toolbox の RAG ツールが 「テキストを受け取る」だけになり、エージェントの LLM に直接公開できる。前回必要だった「エージェント内で埋め込んでから渡す」回避策が丸ごと消える
  • 仕組みとしての in-DB 埋め込みは問題なく動く。ただし モデルの言語適合が検索品質を左右する。Oracle が手軽に配布している all-MiniLM-L12-v2 は英語中心で、日本語のコーパスでは意味の近い資料を取りこぼすことがある
  • 同じ 384 次元の多言語モデル multilingual-e5-small に差し替えると、英語モデルが取りこぼした質問でも正解が最上位に来た。差の原因は次元数ではなく「多言語学習の有無」

2. なぜ in-DB 埋め込みか

前回の記事(Gemini 埋め込み版)と本記事(in-DB 埋め込み版)の RAG 検索の流れを並べると、違いがはっきりします。

in-DB 埋め込みのポイントは次の三点です。

  • 外部の埋め込み API が不要。テキストのベクトル化が SQL 関数 VECTOR_EMBEDDING で完結し、データも処理も DB の外に出ない
  • Toolbox の embeddedBy 非対応が無関係になる。埋め込みを SQL 側でやるので、ツールが対応していなくても困らない
  • ツールがテキスト引数で済む。後述するように、これはエージェント設計を大きく単純にする

3. モデルを載せて in-DB 埋め込みで RAG_DOCS を作る

3.1. 検証環境

項目
データベース OCI Autonomous Database 26ai(23.26.2.2.0)、スキーマ SCOTT
埋め込みモデル(英語中心) all-MiniLM-L12-v2(Oracle 提供の prebuilt・384 次元)
埋め込みモデル(多言語) multilingual-e5-small(OML4Py で生成・384 次元)
MCP Toolbox MCP Toolbox for Databases v1.4.0(純 Go ドライバ+wallet 接続)
エージェント ADK + Gemini(gemini-3.1-flash-lite、GEAP 経由)

3.2. ONNX モデルのロード

VECTOR_EMBEDDING でテキストをそのまま渡すには、トークナイザと後処理(プーリング・正規化)を ONNX グラフに内蔵した augmented モデルが必要です。Oracle は all-MiniLM-L12-v2 の augmented 版を配布しているので、まずはこれを使います。

Autonomous Database はローカルファイルシステムに直接アクセスできないため、モデルファイルはオブジェクトストレージ経由で読み込みます。事前認証リクエスト(PAR)を作っておけば、クレデンシャルなしで DBMS_VECTOR.LOAD_ONNX_MODEL_CLOUD から直接ロードできます。

BEGIN
  DBMS_VECTOR.LOAD_ONNX_MODEL_CLOUD(
    model_name => 'ALL_MINILM_L12_V2',
    credential => NULL,                 -- PAR 利用時は NULL(ただし引数自体は必須)
    uri        => '<YOUR_PAR_URI>/all_MiniLM_L12_v2.onnx',
    metadata   => JSON('{"function":"embedding","embeddingOutput":"embedding","input":{"input":["DATA"]}}')
  );
END;
/

LOAD_ONNX_MODEL_CLOUDcredential は省略できない引数です(PAR を使うときも credential => NULL を明示的に渡す)。省略すると PLS-00306(引数の数または型が不正)になります。

ロードできたか、384 次元が返るかを確認します。

SELECT model_name, mining_function, algorithm,
       ROUND(model_size/1024/1024,1) AS size_mb
FROM   user_mining_models WHERE model_name = 'ALL_MINILM_L12_V2';
-- ALL_MINILM_L12_V2  EMBEDDING  ONNX  127.1

SELECT VECTOR_DIMENSION_COUNT(
         VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING 'Oracle 26ai のベクトル検索を試す' AS data)
       ) AS dims FROM dual;
-- 384

日本語のテキストを渡すだけで 384 次元のベクトルが DB 内で生成されました。モデルの入手・配置やロードの一般的な手順は Oracle 公式ドキュメント(ONNXパイプライン・モデル: テキスト埋め込み)を確認ください。

3.3. RAG_DOCS を in-DB 埋め込みで投入する

RAG 用の文書テーブルを 384 次元で作り、コーパス(技術トピックの日本語 10 件)を投入します。埋め込みは外部 API を使わず、INSERT ... SELECT の中で VECTOR_EMBEDDING を呼ぶだけです。

CREATE TABLE scott.rag_docs_onnx (
  doc_id    NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  title     VARCHAR2(200),
  content   CLOB,
  embedding VECTOR(384, FLOAT32)
);

INSERT INTO scott.rag_docs_onnx (title, content, embedding)
SELECT t.title, t.content,
       VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING t.content AS data)
FROM ( /* コーパス10件 */ ) t;
COMMIT;

投入した件数と、埋め込みが正常に入った行数を確認します。なお VECTOR 型の列は COUNT() の引数に直接は渡せない(ORA-22849)ので、embedding IS NOT NULLCASE で数えます。

SELECT COUNT(*) AS total,
       COUNT(CASE WHEN embedding IS NOT NULL THEN 1 END) AS embedded
FROM scott.rag_docs_onnx;
-- total=10, embedded=10

クエリも同じ VECTOR_EMBEDDING で DB 内で埋め込み、VECTOR_DISTANCE(COSINE)で近い順に取り出します。

SELECT title,
       ROUND(VECTOR_DISTANCE(embedding,
         VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :q AS data), COSINE), 4) AS cos_dist
FROM   scott.rag_docs_onnx
ORDER  BY cos_dist
FETCH FIRST 3 ROWS ONLY;

代表的な3つの質問(以降「質問1〜3」と呼びます)で試したのが次の結果です(COSINE 距離は小さいほど近い)。

質問 最上位の資料 距離 期待どおりか
質問1:Agentic RAG とは何ですか Agentic RAG 0.40
質問2:Oracle データベースで意味的な類似検索をする機能は? Oracle AI Vector Search 0.48
質問3:テキストを意味ベクトルに変換する埋め込みモデルは? Agent Engine 0.57 ✗(期待は Gemini Embedding)

質問1・2は正解が最上位に来ましたが、質問3でつまずきました。質問の内容そのものを述べている「Gemini Embedding」が最上位にならず、後述するように 10 件中 7 位まで沈んでいます。この点は 6 章で掘り下げます。

4. Toolbox をテキスト引数化して LLM に直接公開する

ここが in-DB 埋め込みの効きどころです。検索のベクトル化が SQL の中で完結するので、Toolbox のツールは テキストを 1 つ受け取るだけで定義できます。

tools:
  match_documents_onnx:
    kind: oracle-sql
    source: oracle-adb
    statement: |
      SELECT title, content
      FROM scott.rag_docs_onnx
      ORDER BY VECTOR_DISTANCE(
        embedding,
        VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :1 AS data),
        COSINE)
      FETCH FIRST 3 ROWS ONLY
    description: |
      質問テキストに意味的に近い資料を上位3件返す。ベクトル化は SQL 内で行うため、
      呼び出し側はテキストを渡すだけでよい。
    parameters:
      - name: query_text
        type: string
        description: "調べたい質問・トピック(自然言語のテキスト)"

toolsets:
  oracle_rag_onnx:
    - match_documents_onnx

バインド変数が :1 になっているのは、MCP Toolbox の Oracle ソースが位置プレースホルダ(:1, :2, …)を使う作法だからです。parameters に並べた順に :1 へ入ります。3.3 章で単体の SQL を確認したときの名前付きバインド :q とは書き方が違う点に注意してください(そのまま貼り替えると動きません)。

前回はツールがベクトル引数だったため、エージェント内に「クエリを Gemini Embedding で埋めてから Toolbox を叩く」Python ツールを噛ませる必要がありました。in-DB 埋め込みではその回避策が不要になり、ツールをそのままエージェントの LLM に公開できます。

Toolbox を起動して、テキスト引数で叩けることを確認します。

# v1.4.0 では --config はルートコマンドのフラグ(serve サブコマンドではない)
toolbox --config tools_onnx.yaml --enable-api --address 127.0.0.1 --port 5000
curl -s http://127.0.0.1:5000/api/tool/match_documents_onnx/invoke \
  -H "Content-Type: application/json" \
  -d '{"query_text":"Agentic RAG とは何ですか。検索して回答する仕組みを教えて"}'
# → Agentic RAG / Agent Engine / Agent Development Kit (ADK)(3.3 章の直 SQL と同じ並び)

テキストを渡すだけで、DB 内で埋め込み→ベクトル検索が走り、3.3 章の SQL と同じ上位3件が返りました。

5. ADK エージェントから RAG

エージェント側は、RAG ツールを ToolboxToolset で読み込んで LLM に渡すだけです。埋め込み用のコードは一切ありません。

from google.adk import Agent
from google.adk.tools.toolbox_toolset import ToolboxToolset

# テキスト引数なので、RAG ツールを LLM に直接公開できる
toolset = ToolboxToolset(server_url="http://127.0.0.1:5000", toolset_name="oracle_rag_onnx")

root_agent = Agent(
    name="oracle_onnx_rag_assistant",
    model="gemini-3.1-flash-lite",
    instruction="知識を問う質問には match_documents_onnx に質問テキストを渡し、その content を根拠に答えてください。",
    tools=[toolset],
)

実行すると、エージェントは質問テキストをそのまま match_documents_onnx に渡し、取得した資料を根拠に回答しました。

=== User: Agentic RAG とは何か、資料を基に説明して。
  [tool call] match_documents_onnx({'query_text': 'Agentic RAG とは何か'})
Agent: Agentic RAGとは、エージェントが自律的に検索ツールを呼び出し、根拠となる文書を
取得してから回答を生成するRAGの発展形です。従来のRAGと異なり、検索(retrieve)と生成
(generate)の実行をエージェント自身が判断する点が特徴です。

ベクトル化のコードを 1 行も書かずに、テキスト→ベクトル検索→Gemini による回答という Agentic RAG が成立しました(ゴール3)。

6. 日本語の壁とモデル選定:all-MiniLM から multilingual-e5 へ

3.3 章でつまずいた質問3を掘り下げます。all-MiniLM-L12-v2 での全順位は次のとおりでした。

順位 資料(質問3:テキストを意味ベクトルに変換する埋め込みモデルは?) 距離
1 Agent Engine 0.57
2 Gemini モデルファミリ 0.59
7 Gemini Embedding(本来の正解) 0.71

質問は「テキストをベクトルに変換する埋め込みモデル」で、これはまさに「Gemini Embedding」の説明文の内容です。それでも 7 位まで沈み、しかも上位の距離が 0.57〜0.84 に固まっていて、資料どうしの区別がほとんどついていません。

理由は単純で、Oracle が手軽に配布している all-MiniLM-L12-v2 は 英語中心のモデルだからです。日本語の意味的な近さをうまく数値化できず、資料ごとの距離の差が小さくなりがちです。

6.1. 同じ 384 次元の多言語モデルに替える

そこで、多言語対応の intfloat/multilingual-e5-small に差し替えます。このモデルも 384 次元なので、次元数を固定したまま「英語中心 vs 多言語」だけを比較できます(差が出れば、その原因は次元数ではなくモデルの言語適合だと切り分けられる)。

multilingual-e5-small は Oracle の変換対応モデル一覧(事前トレーニング済モデルのONNXモデルへの変換)に含まれていますが、all-MiniLM のような「そのまま載る」prebuilt は配布されていません。OML4Py(Oracle Machine Learning for Python)クライアントで HuggingFace のモデルを augmented ONNX に変換して用意します。

from oml.utils import EmbeddingModel
EmbeddingModel(model_name="intfloat/multilingual-e5-small").export2file(
    "multilingual_e5_small", output_dir=".")
# → multilingual_e5_small.onnx(117MB / 384次元 / augmented)

生成した .onnx を all-MiniLM と同じ手順(オブジェクトストレージ+PAR)で MULTILINGUAL_E5_SMALL としてロードし、同じコーパスを別テーブル rag_docs_e5 に投入します。1

6.2. 比較結果

3.3 章と同じ3つの質問で、英語モデルと多言語モデルの最上位ヒットを並べます(どちらも 384 次元・COSINE 距離。e5 は passage:/query: を付けた版)。

質問 all-MiniLM(英語) multilingual-e5(多言語)
質問1:Agentic RAG とは何ですか Agentic RAG(0.40)◯ Agentic RAG(0.07)◯
質問2:Oracle データベースで意味的な類似検索をする機能は? Oracle AI Vector Search(0.48)◯ Oracle AI Vector Search(0.11)◯
質問3:テキストをベクトル化する埋め込みモデルは? Agent Engine(0.57)✗ Gemini Embedding(0.10)◯

英語モデルが取りこぼした質問3で、多言語モデルは正解を最上位に持ってきました。さらに、正解の距離が 0.06〜0.18 と小さく、正解とそれ以外の距離の差も大きいため、どれが近いかを判別しやすくなっています(英語モデルは 0.40〜0.84 に固まっていた)。同じ次元・同じデータ・同じ SQL で、変えたのはモデルだけなので、改善はモデルの言語適合によるものと言えます。

6.3. E5 のプレフィックスについて

E5 系のモデルは、本来「文書側に passage: 、質問側に query: を付ける」前提で学習されています。プレフィックスは SQL 側で文字列結合するだけで付けられます。

-- 文書側:格納時に passage: を付けて埋め込む
VECTOR_EMBEDDING(MULTILINGUAL_E5_SMALL USING 'passage: ' || t.content AS data)

-- 質問側:検索時に query: を付けて埋め込む
VECTOR_EMBEDDING(MULTILINGUAL_E5_SMALL USING 'query: ' || :q AS data)

今回はプレフィックスを付けた版と付けない版の両方を作って比べましたが、この小さめのコーパス(10 件)では順位に差は出ませんでした(付けない版でも全問正解で、距離もほぼ同じ)。プレフィックスは大規模・非対称な検索で効くとされるため、本検証の規模では決定的ではなかったと考えられます。実運用で規模が大きくなる場合は、モデルの作法どおりプレフィックスを付けておくのが無難です。

7. 考察

7.1. Gemini 埋め込み版と比べてどうか

前回の Gemini 埋め込み版と本記事の in-DB 埋め込み版を、手数と依存関係で並べます。

観点 Gemini 埋め込み版(前回) in-DB 埋め込み版(本記事)
クエリ埋め込みの場所 エージェント(Python)側 データベース内(VECTOR_EMBEDDING
外部の埋め込み API 必要(Gemini Embedding) 不要
Toolbox ツールの引数 ベクトル文字列 テキスト
エージェント側の埋め込みコード 必要(自前の検索ツール) 不要(ツールを LLM に直接公開)
データの所在 埋め込み時に外部 API へテキスト送信 DB 内で完結

in-DB 埋め込みは、外部依存とコードを減らし、データを DB の外に出さずに済む点で運用がすっきりします。一方で、回答生成は引き続き Gemini を使っているので「完全に閉じている」わけではありません。生成まで含めて閉域化したい場合は、別のローカル LLM 構成が必要です(本記事の範囲外)。

7.2. 「載せ方」より「選び方」が品質を決める

今回いちばんはっきりしたのは、in-DB 埋め込みの仕組みは素直に動く一方で、検索品質はモデル選定で決まるということです。Oracle が手軽に配布している all-MiniLM-L12-v2 は英語中心で、日本語コーパスでは意味の近さを取りこぼすことがありました。同じ 384 次元でも多言語モデルの multilingual-e5-small にすると、英語モデルが外した質問で正解が最上位になりました。

Oracle の変換対応モデル一覧には、ほかにも paraphrase-multilingual-mpnet-base-v2(768 次元)や intfloat/multilingual-e5-base(768 次元)、ibm-granite/granite-embedding-278m-multilingual といった多言語モデルが含まれています。日本語主体の RAG なら、英語専用(名前に en が付くもの)ではなく多言語モデルを選ぶのが出発点になります。なお、どのモデルを使うにせよ、ベクトルの次元(VECTOR(n))はモデルの出力次元に合わせて作り直す必要があります。

7.3. in-DB 埋め込みのデメリットと、外部埋め込みが向く場面

ここまでは利点を中心に見てきましたが、in-DB 埋め込みには弱点もあります。

  • モデルの入手と更新に手間がかかる — そのまま載る prebuilt は実質 all-MiniLM 一択で、多言語モデルなどは OML4Py で自前変換が要る(本記事でも環境構築に手数がかかった)。モデルを新しくするたびに再生成・再ロードが必要で、次元が変わればテーブルも作り直しになる
  • 使えるモデルの幅と新しさは外部 API に劣る — Gemini Embedding のような API 型は提供側が継続的に更新し、次元縮約も柔軟である。in-DB は Oracle が対応する ONNX と自分で変換できる範囲に限られ、載せた分だけ DB のストレージとメモリを使う
  • 埋め込み計算は DB のリソースを使う — ベクトル化は DB の CPU を使うため、単一インスタンス構成では負荷が primary に乗る。Oracle は Active Data Guard の standby へ推論(埋め込み生成)をオフロードする手段を公式に用意しており、生成したベクトルは DML リダイレクトで primary に書き戻される2。読み取り側の検索も True Cache や RAC でスケールできる。ただしこれらの手段はいずれも Oracle 固有で、後述のコストを伴う(本記事の単一構成では計測していない)
  • Oracle へのロックインとコストが大きい — 埋め込み・格納・検索を DB 内に寄せるほど、RAG 基盤が Oracle 前提になる。上のスケール/オフロード手段(Active Data Guard・RAC・True Cache・Exadata など)もいずれも Oracle 固有で、有償オプションや追加インフラのコストがかかる。外部 API 埋め込み+可搬なベクトルストアに比べ、他基盤への移行やコスト最適化の自由度は下がる
  • 利用できる DB バージョンが限られる — 26ai 以降の機能で、テキストを直接渡せる augmented ONNX が前提になる(生の HuggingFace モデルはそのまま載らない)

逆に、最新・大型の埋め込みモデルを使いたい、モデルを頻繁に切り替えたい、埋め込みの負荷やコストを DB から切り離したい、特定 DB へのロックインを避けたい、といった場合は外部 API 型の埋め込みが向きます。データを DB 内に閉じたい・外部依存を減らしたいなら in-DB 埋め込み、という住み分けになります。

8. まとめ

ポイント 内容
in-DB 埋め込みの効果 クエリのベクトル化が VECTOR_EMBEDDING で完結し、外部埋め込み API もエージェント側の埋め込みコードも不要になる
アーキテクチャ上の利点 Toolbox ツールがテキスト引数になり、エージェントの LLM に直接公開できる(前回の回避策が消える)
ロードの要点 ADB はローカル FS 不可。オブジェクトストレージ+PAR で LOAD_ONNX_MODEL_CLOUDcredential => NULL は必須)
品質を決めるのはモデル選定 英語中心の all-MiniLM は日本語を取りこぼす。同次元の多言語 multilingual-e5 で正解が最上位に改善
次元の注意 モデルを替えたら VECTOR(n) の次元も合わせて作り直す
デメリット モデル更新の手間、DB リソース消費、Oracle へのロックインとコスト。負荷は Active Data Guard 等でオフロードできるが手段はいずれも Oracle 固有・有償。外部 API 埋め込みとの住み分けで選ぶ

参考

  1. OML4Py クライアントは Linux x86_64 専用です。Oracle Container Registry の公式コンテナを使う手もありますが、レジストリのログインが SSO パスワード認証で、多要素認証(MFA)が有効なアカウントでは通りません。その場合はクライアントの wheel をダウンロードし、WSL などの Linux 環境(Python 3.13)に入れて実行すると回避できます。

  2. Oracle MAA ブログ「Application and AI Scalability with Oracle Active Data Guard」(2025 年 10 月)。モデルを primary にロードすれば standby でも推論に使えてオフロードでき、生成したベクトルやメタデータは DML リダイレクトで primary に透過的に書き戻される、と述べられている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?