3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OCI Generative AI Agents Service で既存 DB を活用したRAGツールを構築する【前編:環境準備】

3
Last updated at Posted at 2026-07-30

はじめに

本稿は2026年4月時点の情報をもとに作成しています。
記事公開後の仕様変更や更新により、記事の内容と参考URL先の情報が異なる場合があります。
作業を実施する際は、必ず最新の公式ドキュメントや参考情報をご確認ください。

近年、生成AIを業務システムや社内ナレッジ検索へ活用する取り組みが進んでいます。その中でもRAG(Retrieval-Augmented Generation)は、企業が保有する独自データを生成AIの回答に活用できる仕組みとして注目されています。

OCI(Oracle Cloud Infrastructure)では、AIエージェントのサービスとして OCI Generative AI Agents Service が提供されています。このサービスでは、ナレッジベースを参照しながら回答を生成するAIエージェントを構築でき、その機能の一つとしてRAGツールを利用できます。

一方で、すでにOracle Databaseを業務で利用している環境では、「既存のデータベースをそのままナレッジとして活用できないか」と考える方も多いのではないでしょうか。

そこで本稿では、既存の DB(BaseDB上で稼働するOracle AI Database 26ai)をナレッジベースとして活用するための OCI Generative AI Agents Service RAGツール の構築方法を紹介します。

作業時点では Generative AI Agents Service は大阪リージョンのみで提供されていたため、本構成では データベース・ツール接続 機能を利用して東京リージョンのDBを参照 します。これにより、既存の DB 資産を活用しながらRAG環境を構築できます。

本稿を読むことで、以下の内容を理解できます。

  • Oracle AI Database をRAGのナレッジベースとして利用するための準備手順
  • ベクトル検索に必要なモデルやデータの準備方法
  • OCI Generative AI Agents Service と既存DBを連携するための構成イメージ

また、本稿は以下のような方を対象としています。

  • OCIで生成AI活用を検討している方
  • OCI Generative AI Agents Serviceの利用を検討している方
  • Oracle AI Database を活用したRAG環境を構築したい方
  • Oracle AI Database のベクトル検索機能に興味がある方

今回使用した DB の構成は以下の通りです。

項目 設定値
シェイプ VM.Standard3.Flex
CPUコア数 4
DBバージョン 23.26.0.0.0
エディション Enterprise Edition High Performance

▼ 構成イメージ
arc1.png

なお、本テーマは前後編の2部構成となっています。

  • 前編(本稿):環境準備編
  • 後編:RAGツール構築編

本稿では、DB でベクトル検索を実行するための事前準備から RAG 用のデータ登録を行います。

ベクトル検索では、テキストを数値ベクトルへ変換する埋め込みモデルが必要です。本稿では、ONNX形式の埋め込みモデルを DB へ登録し、そのモデルを利用して検索対象データをベクトル化できる環境を構築します。

そのために、以下の作業を実施します。

  1. ONNXモデルをデータベースへ登録するための OML4Py 環境構築
    *OML4Py は Oracle DB を使用したデータ探索、データ準備、機械学習モデリングをサポートする Python API です
  2. ONNX形式の埋め込みモデルのインポート
  3. RAGツールのナレッジ用データの登録

これらの準備を行うことで、後編においてRAGツール構築に必要な ベクトル検索機能 としての、データベース関数を作成します。
それでは、 DB の準備から始めていきます。


1. OML4Py 構築

DB 上で ベクトル機能 を実行するためには、埋め込みモデルをインポートする必要があります。Oracle AI Database 26ai は ONNX Runtime を利用してモデルを実行するため、事前学習済みの埋め込みモデルを ONNX 形式で準備してインポートします。

モデルのインポートには REST API を利用する方法もありますが、今回は Python を利用した方法を採用しました。また、本稿では後続の手順で RAG の検証用データの整形や加工も行うため、同じ環境で作業を完結できるよう Python 環境を構築しています。

ONNX 形式へのテキスト・モデルの変換、およびファイルまたはデータベースへの格納に Python パッケージ oml.utils が使用されます。
マニュアルには以下のように要件が書かれています。

  • オンプレミス・データベース用にLinux X64で実行されているOML4Py 2.1クライアント
  • Python 3.12 (以前のバージョンは互換性がありません)

参考

下記を参考に OML4Py の構築を進めました。

①Python 構築に必要なパッケージをインストール

sudo yum install libffi-devel openssl openssl-devel tk-devel xz-devel \
zlib-devel bzip2-devel readline-devel libuuid-devel \
ncurses-devel libaio tcl-devel openblas

②Python 3.13.5 をソースからビルドしてインストール

当初、 /home 配下に配信する予定でしたが、容量が足りなかったため、 /u01 に配置し作業を進めました。

wget https://www.python.org/ftp/python/3.13.5/Python-3.13.5.tgz
mkdir -p python
tar -xvzf Python-3.13.5.tgz --strip-components=1 -C /u01/python
cd /u01/python
./configure --enable-shared --prefix=/u01/python
make clean; make
make altinstall

③環境変数設定

export PYTHONHOME=/u01/python
export PATH=$PYTHONHOME/bin:$PATH
export LD_LIBRARY_PATH=$PYTHONHOME/lib:$LD_LIBRARY_PATH

④python3とpip3のシンビネームリンクを作成

cd /u01/python/bin
ln -s python3.13 python3
ln -s pip3.13 pip

⑤必要なパッケージのインストール

requirements.txt
--extra-index-url https://download.pytorch.org/whl/cpu
pandas==2.2.3
setuptools==80.8.0
scipy==1.14.1
matplotlib==3.10.0
oracledb==3.3.0
scikit-learn==1.6.1
numpy==2.1.0
pyarrow==19.0.0
onnxruntime==1.20.0
onnxruntime-extensions==0.14.0
onnx==1.18.0torch==2.9.0
transformers==4.56.1
sentencepiece==0.2.1
pip3 install --upgrade pip
pip3 install -r requirements.txt

⑥OML4Pyクライアントのダウンロード

以下に、クライアントの置き場所があります。
▼Oracle Machine Learning for Python Downloads
https://www.oracle.com/database/technologies/oml4py-downloads.html

参考手順の通り、 oml4py-client-linux-x86_64-2.1.1.zip をダウンロードしました。

⑦OML4Pyクライアントをインストール

/u01/oml4py に ⑥でダウンロードしたファイルを転送後インストール

unzip oml4py-client-linux-x86_64-2.1.1.zip
pip3 install client/oml-pi2.1.1-cp313--linux_x86_64.whl

以上で、Python が利用できる状態になりました。

2. ONNX 形式のモデルをDBにインポート

①事前設定モデルのリストのインポート

参考

今回は埋め込みモデルとして multilingual-e5-base を利用しました。利用可能なモデルの概要を確認した結果、日本語を含む多言語データに適していると考えられたため採用しています。なお、他モデルとの比較検証は実施していません。
後続の手順で事前設定モデルを生成します。

▼Python のインタラクティブモードを起動

Python3

▼pipeline.export2file() を実行して ONNX 形式のモデルをエクスポート

from oml.utils import ONNXPipeline, ONNXPipelineConfig
config = ONNXPipelineConfig.from_template("text", max_seq_length=512,distance_metrics=["COSINE"], quantize_model=True)
pipeline = ONNXPipeline(model_name='intfloat/multilingual-e5-base')
pipeline.export2file('multilingual_e5_base',output_dir='.')

config にて、モデルへの最大入力トークン数などを指定することができます。今回使用した multilingual-e5-base は最大 512 トークンまでを入力として扱うため、それを超えるテキストは切り捨てられます。そのため、チャンクサイズや入力トークン数を適切に設定することで、埋め込み生成時の情報欠落を防ぐことができます。

私の環境では、pipeline.export2file() 実行時に容量不足エラーが発生しました。これはデフォルトの出力先である /home/oracle 配下の空き容量が不足していたためです。詳細は本章の末尾の
補足:pipeline.export2file() 実行時のエラー に記載しています。

②VECTORユーザを作成

以降は、 SQL による操作です。

BaseDB との接続のために、 データベース・ツール接続 の設定をする必要があります。
DB ユーザのシークレットを作成すれば、 データベース・ツール接続 を使用して、OCI 上でSQL操作ができます。
▼SQLワークシート
https://docs.oracle.com/ja/cloud/paas/base-database/sql-worksheets/index.html#articletitle

参考

▼2 Create storage, user, and privileges.

ベクトル検索用のテーブル作成やモデル登録を行うため、専用ユーザとして VECTOR ユーザを作成します。

sqlplus / as sysdba
CREATE TABLESPACE tbs1 DATAFILE 'tbs5.dbf' SIZE 20G AUTOEXTEND ON EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;

DROP USER vector CASCADE;

CREATE USER vector IDENTIFIED BY **** DEFAULT TABLESPACE tbs1 quota unlimited on tbs1;

-- ユーザ確認
SELECT username, default_tablespace, temporary_tablespace FROM dba_users WHERE username = 'VECTOR';

-- 権限付与
GRANT DB_DEVELOPER_ROLE TO vector;
GRANT EXECUTE ON DBMS_VECTOR TO vector;

-- 権限確認
SELECT granted_role FROM dba_role_privs WHERE grantee = 'VECTOR';

③DBへモデルをロード

①で生成した ONNX モデルを DB に登録します。
モデルのロードには DBMS_VECTOR.LOAD_ONNX_MODEL を利用するため、事前に DIRECTORY オブジェクトの作成と権限付与を行います。

-- DIRECTORY オブジェクトの作成と権限付与
CREATE DIRECTORY VEC_DUMP AS '/u01/onnx/';

GRANT READ, WRITE ON DIRECTORY VEC_DUMP TO VECTOR;

GRANT CREATE MINING MODEL TO VECTOR;

-- 権限確認
SELECT granted_role FROM dba_role_privs WHERE grantee = 'VECTOR';

-- モデルファイルのロード
EXEC DBMS_VECTOR.DROP_ONNX_MODEL(model_name => 'doc_model', force => true);

EXEC DBMS_VECTOR.LOAD_ONNX_MODEL(
  'VEC_DUMP',
  'multilingual_e5_base.onnx',
  'doc_model'
);

以上で、BaseDB にてベクトル化やベクトル検索が行えるようになります。

ベクトル検索 動作確認用テーブル作成

テスト用に簡易的なデータを入力したテーブル document を用意しました。 CHUNK_DATA には文章、 VEC_DATA には CHUNK_DATA をベクトル化した値を挿入します。

Name   Null?  Type
DOC_ID  NOT NULL  NUMBER
CUS_ID    NUMBER
CHUNK_DATA   CLOB 
VEC_DATA VECTOR(*,*,DENSE)

image.png

chunk_data のベクトル化

DECLARE
    params CLOB;
BEGIN
    params := '
    {
    "provider": "database",
    "model": "doc_model"
    }';
UPDATE document
    SET vec_data =
        DBMS_VECTOR.UTL_TO_EMBEDDING(chunk_data, JSON(params));
    COMMIT;
END;
/

VEC_DATACHUNK_DATAの値をベクトル化した値が入りました。
image.png

ベクトル検索の確認

以下を比較対象をとして、両者のベクトル距離を算出してみます。

  • DOC_ID = 1 :製品仕様とサポート範囲について
  • DOC_ID = 3 :この文書は契約条件について説明しています

VECTOR_DISTANCE() についての詳細は以下をご確認ください。
https://docs.oracle.com/cd/G47991_01/vecse/vector_distance.html

SELECT
    TO_NUMBER(
    VECTOR_DISTANCE
        (
            (SELECT vec_data FROM document WHERE doc_id = 1),
            (SELECT vec_data FROM document WHERE doc_id = 3)
        )
    ) AS distance
FROM dual;

image.png
VECTOR_DISTANCE() の第3引数(距離メトリック)を省略した場合は COSINE距離 が使用されます。 0 に近づくほど両者は意味的に類似していることになります。

無事に、ベクトル検索機能が使えているようです。

3. RAGツールテスト用テーブル準備

①RAGツール用テーブル作成

参考

▼ステップ4.データベース表およびファンクションの設定

Oracle AI Database 26ai をナレッジベースとしたRAGツールを作成するには

  • データベース表
  • データベース関数

が必要になります。
テスト用のデータとして厚生労働省が出版している モデル就業規則 を使用しました。

データベース表(テーブル)には必須のフィールドがあります。最新の情報は参考 URL を確認ください。
今回は、オプションのフィールドを含む全てのフィールドを使用してテーブル modelsyugyokisoku を作成しました。

CREATE TABLE modelsyugyokisoku (
    -- 必須フィールド
    docid         NUMBER         NOT NULL,
    body          CLOB           NOT NULL,
    vector        VECTOR,

    -- オプションのフィールド
    chunkid       NUMBER,
    page_numbers  VARCHAR2(50),
    title         VARCHAR2(255),
    url           VARCHAR2(1000)
);

②データインポート

PDF取得

モデル就業規則のPDFファイルをダウンロードします。

wget -O https://www.mhlw.go.jp/content/001620507.pdf

RAG では文書全体ではなく適切な単位に分割したテキストを検索対象とするため、チャンク分割を実施する必要があります。
modelsyugyokisoku テーブルへインポートする前に今回は検証として、簡易的なテキストの処理を実施していきます。

注意
以下の Python コマンドおよび実装手順は、公式ドキュメントに記載された手順をそのまま転載したものではなく、筆者の検証環境で動作を確認した内容をもとに記載しています。誤りがあった場合でも一切責任を負いかねますので、参考程度にご参照ください。

パッケージ追加

後続の処理に必要なライブラリを使用可能にします。

# PDF からテキストを抽出
pip3 install pdfplumber

# チャンク分割
pip3 install tiktoken

PDFをページごとにテキストファイルへ変換 + 改行・空白の削除

RAG が何ページにある記述から参照したのかを把握できるように、ページごとの分割をします。また、ベクトル化を安定させるために改行・空白の削除も実施しました。

extract_pdf.py
import pdfplumber
import re
from pathlib import Path

BASE_DIR = Path("/u01/python/work")

# 処理前の PDF のパス指定
PDF_DIR = Path("/u01/python/work/PDFs/modelsyugyokisoku.pdf")

# 処理後のテキストのパス指定
TEXT_DIR = Path("/u01/python/work/TEXTs/modelsyugyokisoku")

# 正規化
def normalize_text(text: str) -> str:
    text = text.replace("\u00a0", " ")
    text = re.sub(r"\s+", " ", text)
    return text.strip()


def extract_pdf_pages_to_files(pdf_path: str):
    pdf_path = Path(pdf_path)

    TEXT_DIR.mkdir(parents=True, exist_ok=True)

    with pdfplumber.open(pdf_path) as pdf:
        for i, page in enumerate(pdf.pages):
            raw_text = page.extract_text()

            if not raw_text:
                continue

            text = normalize_text(raw_text)

            page_number = i + 1

            output_file = (
                TEXT_DIR /
                f"{pdf_path.stem}_page_{page_number:03d}.txt"
            )

            with open(output_file, "w", encoding="utf-8") as f:
                f.write(text)

            print(f"Saved: {output_file}")


if __name__ == "__main__":
    extract_pdf_pages_to_files(PDF_DIR)
出力確認

出力されたテキストファイルを確認します。ページごとに分割ができています。
1ページ目はタイトルのみですで、以下の出力になりました。

cat modelsyugyokisoku/modelsyugyokisoku_page_001.txt
モデル就業規則令和7年 12 月版厚生労働省労働基準局監督課

チャンク分割 + テーブル挿入

RAG では文書全体ではなく適切な単位に分割したテキストを検索対象とするため、チャンク分割を実施します。

参考

Python により DB を操作するために以下を使用しました。
▼ python-oracledb

MAX_TOKENS は 500 としました。今回利用した multilingual-e5-base は最大 512 トークンまで処理可能なため、その上限を考慮した値としています。
また、OVERLAP は 50 としました。厳密なチューニング結果に基づくものではなく、チャンク境界で文脈が失われにくいよう一定量の重複を持たせています。

以下により、チャンク分割から DB の modelsyugyokisoku テーブルへの挿入まで一括で実施します。

cbunk_insert.py
from pathlib import Path
import tiktoken
import oracledb

# ORACLE_HOME を自動使用
# oracledb.init_oracle_client()

TEXT_DIR = Path("/u01/python/work/TEXTs/modelsyugyokisoku")

DOCID = 1001
TITLE = "modelsyugyokisoku"

MAX_TOKENS = 500
OVERLAP = 50

DB_DSN = (
    "localhost:1521@DB名"
    "ホスト・ドメイン名"
)
DB_USER = "vector"
DB_PASS = "****"

# Tokenizer
tokenizer = tiktoken.get_encoding("cl100k_base")


# ==================================================
# TEXTs → full_text + page_map
# ==================================================
def load_full_text_with_page_map():
    full_text = ""
    page_map = []  # (start_char, end_char, page_number)

    for path in sorted(TEXT_DIR.glob("*.txt")):
        page_number = int(path.stem.split("_page_")[-1])

        with open(path, "r", encoding="utf-8") as f:
            text = f.read().strip()

        if not text:
            continue

        start = len(full_text)
        full_text += text + "\n"
        end = len(full_text)

        page_map.append((start, end, page_number))

    return full_text, page_map


# ==================================================
# token-based chunking
# ==================================================
def chunk_full_text_with_char_pos(full_text):
    tokens = tokenizer.encode(full_text)

    chunks = []
    start_tok = 0

    while start_tok < len(tokens):
        end_tok = start_tok + MAX_TOKENS

        # token → char 変換
        char_start = len(tokenizer.decode(tokens[:start_tok]))
        char_end = len(tokenizer.decode(tokens[:end_tok]))

        chunk_text = tokenizer.decode(tokens[start_tok:end_tok])

        chunks.append({
            "chunk_text": chunk_text,
            "char_start": char_start,
            "char_end": char_end
        })

        start_tok += MAX_TOKENS - OVERLAP

    return chunks


# ==================================================
# char位置 → page_numbers
# ==================================================
def calc_page_numbers(char_start, char_end, page_map):
    pages = []

    for p_start, p_end, page_num in page_map:
        if p_end > char_start and p_start < char_end:
            pages.append(page_num)

    if not pages:
        return None

    if len(pages) == 1:
        return str(pages[0])

    return f"{pages[0]}-{pages[-1]}"


# ==================================================
# チャンク生成
# ==================================================
def build_chunks():
    full_text, page_map = load_full_text_with_page_map()
    chunk_defs = chunk_full_text_with_char_pos(full_text)

    rows = []

    for idx, c in enumerate(chunk_defs):
        page_numbers = calc_page_numbers(
            c["char_start"],
            c["char_end"],
            page_map
        )

        rows.append({
            "docid": DOCID,
            "chunkid": idx,
            "body": c["chunk_text"],
            "page_numbers": page_numbers,
            "title": TITLE
        })

    return rows


# ==================================================
# Oracle INSERT
# ==================================================
def insert_chunks(rows):
    conn = oracledb.connect(
        user=DB_USER,
        password=DB_PASS,
        dsn=DB_DSN
    )

    sql = """
    INSERT INTO modelsyugyokisoku
        (DOCID, CHUNKID, BODY, PAGE_NUMBERS, TITLE)
    VALUES
        (:docid, :chunkid, :body, :page_numbers, :title)
    """

    with conn.cursor() as cur:
        for r in rows:
            cur.execute(sql, r)

    conn.commit()
    conn.close()


# ==================================================
if __name__ == "__main__":
    rows = build_chunks()

    print(f"生成チャンク数: {len(rows)}")
    print("サンプル:", rows[0])

    insert_chunks(rows)

    print("Oracle INSERT 完了")
確認

データ挿入後に確認すると、以下のような形でPDFの内容が取り込まれています。
PAGE_NUMBERS にページ番号を期待通りに入れることができました。

SELECT * FROM MODELSYUGYOKISOKU ORDER BY CHUNKID;

image.png


ベクトル化

BODY にはこれまでの処理を行ったモデル就業規則のテキストが挿入されています。 VECTORBODY をベクトル化した値を挿入します。

DECLARE
    params CLOB;
BEGIN
    params := '
    {
        "provider": "database",
        "model": "doc_model"
    }';

    UPDATE modelsyugyokisoku
       SET vector =
           DBMS_VECTOR.UTL_TO_EMBEDDING(
               body,
               JSON(params)
           );

    COMMIT;
END;
/
確認

VECTOR の値を確認します。テキスト量が多いためか、ベクトル検索の動作確認で使用したテーブル document より複雑な値となっています。
image.png


⑥データベース関数作成

前述の手順でモデル就業規則のベクトル化は完了しました。
RAGツールではユーザから入力された質問も同じ埋め込みモデルでベクトル化し、テーブルに格納されているVECTOR の値と類似度比較を実施します。

そのため、質問文のベクトル化と類似検索を行うデータベース関数を作成します。

今回は retrieval_func という関数を作成します。

CREATE OR REPLACE FUNCTION retrieval_func (
  p_query IN VARCHAR2,
  top_k IN NUMBER
) RETURN SYS_REFCURSOR IS
  query_vec VECTOR;
  v_results SYS_REFCURSOR;
BEGIN
  query_vec := DBMS_VECTOR.UTL_TO_EMBEDDING(
    p_query,
    JSON('{
      "provider": "database",
      "model": "doc_model"
    }')
  );

  OPEN v_results FOR
    SELECT
      doc_id,
      body,
      vector_distance(vector, query_vec) AS score
    FROM modelsyugyokisoku
    ORDER BY score
    FETCH FIRST top_k ROWS ONLY;

  RETURN v_results;
END;
/

以上で OCI Generative AI Agents Service の RAG ツールを使用する準備が整いました。

後編は RAG ツールの構築、動作確認となっています。

補足:pipeline.export2file() 実行時のエラー

▼ エラー抜粋

UserWarning: Not enough free disk space to download the file.
The expected file size is: 1112.20 MB.
The target location
/home/oracle/.cache/OML/models--intfloat--multilingual-e5-base/blobs
only has 0.00 MB free disk space.

コマンド実行時に出力されるキャッシュファイルがあり、出力先の変更が必要です。

▼キャッシュファイルの出力先を変更

ln -s /u01/.cache /home/oracle/.cache/OML
export HF_HOME=/u01/hf_cache
export HUGGINGFACE_HUB_CACHE=/u01/hf_cache
export TRANSFORMERS_CACHE=/u01/hf_cache
export OML_CACHE=/u01/hf_cache
mount --bind /u01/.cache/OML /home/oracle/.cache/OML

▼Python でも明示

import os
os.environ["HF_HOME"] = "/u01/hf_cache"
os.environ["HUGGINGFACE_HUB_CACHE"] = "/u01/hf_cache"
os.environ["TRANSFORMERS_CACHE"] = "/u01/hf_cache"
os.environ["OML_CACHE"] = "/u01/hf_cache"

from oml.utils import ONNXPipeline, ONNXPipelineConfig
config = ONNXPipelineConfig.from_template("text", max_seq_length=512,distance_metrics=["COSINE"], quantize_model=True)
pipeline = ONNXPipeline(model_name='intfloat/multilingual-e5-base')
pipeline.export2file('multilingual_e5_base',output_dir='.')

以上で、 /u01 配下に ONNXモデルファイル multilingual_e5_base.onnx をエクスポートすることに成功しました。

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?