8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

トークン課金を気にせず生成AIを使いたい!OpenAI互換APIで“社内生成AI”を構築できるOracle Private AI Services Containerを試してみた

8
Last updated at Posted at 2026-09-03

生成AIを業務システムに組み込みたい一方で、「機密データを外部AIサービスに送れない」「閉域環境でLLMを動かしたい」「APIのトークン料金を気にせず使いたい」といった課題があります。

Oracle Private AI Services Containerは、こうしたニーズに対応するコンテナ型のプライベートAI推論基盤です。

しかも本製品は無償で利用頂けます。
(もちろんインフラ代は別途掛かります。)

Oracle AI Databaseと連携してEmbedding生成やRAG、Vector Searchを構成できるだけでなく、OpenAI互換APIを提供しているため、Oracle AI Databaseがなくても単独のAI推論サーバーとして利用できます。

image.png

LLMによる文章生成、Vector Embedding、Reranking、Text Classificationなどを、自社管理のオンプレミス環境やプライベートクラウド上で実行できます。

イメージとしては、

「Oracle AI Databaseと連携できる、OpenAI互換API対応のプライベートAIサーバー」

と考えると分かりやすいでしょう。

また、自前のCPUやGPU上でモデルを動かすため、外部の生成AI APIのようなトークン単位の利用料金を気にせず使える点も魅力です。もちろんインフラコストは必要ですが、大量の文書処理や繰り返し実行するRAG、開発・検証用途では、お財布にやさしい構成になり得ます。

image.png

つまり、

「データを外に出さない」
「閉域環境でも動かせる」
「トークン料金を気にせず使える」

という点が大きな特徴です。

本記事では、実際にOracle Private AI Services Containerを構築し、導入のしやすさ、OpenAI互換APIからの利用方法、実際の使い勝手や注意点を検証していきます。

検証対象としてはPrivate AI Services Containerが提供する以下機能を一通り動作確認しましたので、それぞれ使い方をまとめました。

  • テキスト生成モデル
  • VLM(画像から解説文を生成)
  • リランク
  • テキスト分類(センチメント分析など)
  • テキストEmbedding
  • イメージEmbedding(CLIPを使ったマルチモーダルEmbedding)

なお環境構築は以下Oracle公式ブログを参考にしました。

目次

検証環境

OCI Computeで以下サーバを準備しました。

  • OS: Oracle Linux Server release 9.8
  • スペック: 3 OCPU、64 GB RAM
    • 重たい推論モデルなどをメモリにロードできるよう、そこそこのRAM容量を確保
  • Shape: VM.Standard.E6
    • CPU上の推論処理を高速化できるAVX-512命令セットに対応したShapeを選択
  • ブートボリューム: 200GB
    • 推論モデルなどのファイルを配置するために、そこそこの容量を確保

OS設定

ブートボリュームをデフォルトから拡張した場合は以下コマンドを実行

sudo /usr/libexec/oci-growfs -y
df -h

外部からアクセスできるよう firewalld を設定

sudo su -
# http通信を許可
firewall-cmd --add-service=http
# 8080を許可
firewall-cmd --add-port=8080/tcp
# 80ポートへの通信を8080にフォワード
firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080
# 設定保存
firewall-cmd --runtime-to-permanent
# 設定リロード
firewall-cmd --reload
# 内容確認
firewall-cmd --list-all

opc ユーザでディレクトリ作成

mkdir -p /home/opc/privateai
mkdir -p /home/opc/config
mkdir -p /home/opc/models
mkdir -p /home/opc/secrets

opc ユーザで環境変数の設定

vim .bash_profile

export PRIVATE_DIR=/home/opc/privateai
export CONFIG_DIR=/home/opc/config
export SECRETS_DIR=/home/opc/secrets
export HOST=$(hostname -f); echo $HOST

必要パッケージ導入

sudo dnf install -y container-tools

Private AI Services Containerインストール

Oracle Container Registryにログイン

※事前に Oracle Container Registry のAuth Tokenを取得しておく必要あり。
以下サイトにてOracleアカウントでログイン

https://container-registry.oracle.com/

ログイン後、右上のユーザ名をクリックして「Auth Token」を選択し、遷移先の画面でTokenを生成。

podman login container-registry.oracle.com

コンテナイメージをOracle Container Registryからダウンロード

※事前にOracle Container Registryの Private AI Services Container 紹介ページでライセンス条項に同意しておく必要あり。

Private AI Services Containerには以下マニュアルの通り、用途に応じた複数のコンテナイメージが用意されています。

今回は推論、Embeddingなど複数用途向け、かつサイズ大きめのLLMを動かすことを想定した large-cpu-avx512-infer-26.2.1.0.0 を使います。

IMAGEID=`podman create container-registry.oracle.com/database/private-ai:large-cpu-avx512-infer-26.2.1.0.0`

podman cp $IMAGEID:/privateai/scripts/privateai-setup-large-cpu-avx512-infer-26.2.1.0.0.zip .

unzip privateai-setup-large-cpu-avx512-infer-26.2.1.0.0.zip

これでPrivate AI Services Containerを稼働させる準備ができました。

モデル追加

Private AI Services Containerはデフォルトで以下モデルが使える状態になっています。

  • Ministral-3-3B-Reasoning-2512-Q8_0(TEXT_GENERATION)
  • clip-vit-base-patch32-img(IMAGE_EMBEDDINGS)
  • clip-vit-base-patch32-txt(TEXT_EMBEDDINGS)
  • multilingual-e5-large(TEXT_EMBEDDINGS)
  • all-minilm-l12-v2(TEXT_EMBEDDINGS)
  • multilingual-e5-base(TEXT_EMBEDDINGS)
  • all-mpnet-base-v2(TEXT_EMBEDDINGS)

ですが、せっかくなので Gemma など精度が良く日本語対応したモデルを追加し、任意にカスタマイズしたいと思います。

OML4Pyインストール

Embeddingなど ONNX フォーマットが必要とされるモデルについては、OML4Py を使った .onnx ファイルの生成が必要です。
そのため以下記事を参考にOML4Pyをインストールしておきます。

テキスト推論モデルの追加(gemma-4-E4B-it-Q4_K_M)

テキスト生成モデルはHugging Faceより既存のGGUFファイルをダウンロードして利用します。
またモデルのファイルは前の手順で作成した /home/opc/models に集約します。

cd ~/models
wget https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-Q4_K_M.gguf

分類モデルの追加(distilbert-base-multilingual-cased-sentiments-student)

以下Pythonコードを実行し、生成された .onnx ファイルを /home/opc/models に配置

from oml.utils import ONNXPipeline, ONNXPipelineConfig, MiningFunction
config = ONNXPipelineConfig.from_template("text", max_seq_length=512, labels=["positive", "neutral", "negative"])
pipeline = ONNXPipeline("lxyuan/distilbert-base-multilingual-cased-sentiments-student", config, function=MiningFunction.CLASSIFICATION)
pipeline.export2file("distilbert-base-multilingual-cased-sentiments-student")

リランクモデルの追加(bge-reranker-v2-m3)

以下Pythonコードを実行し、生成された zip ファイルを /home/opc/models に配置

from oml.utils import ONNXPipeline, ONNXPipelineConfig, MiningFunction
config = ONNXPipelineConfig.from_template("text", max_seq_length=512)
pipeline = ONNXPipeline("BAAI/bge-reranker-v2-m3", config, function=MiningFunction.REGRESSION)
pipeline.export2file("bge-reranker-v2-m3")

テキストEmbeddingモデルの追加(multilingual-e5-large)

multilingual-e5-large についてはコンテナイメージにデフォルトで用意されていますが、ロードするモデルをカスタマイズするとデフォルトのモデルが使えなくなります。
そのため例えば推論モデルとして gemma-4-E4B-it-Q4_K_M を使い、同時にEmbeddingモデルとして multilingual-e5-large を使うといった場合、multilingual-e5-large も明示的にモデルをロードさせる必要があります。
ただし multilingual-e5-large はコンテナイメージにファイルが配置されているため、以下のようにコンテナから取得することが可能です。

podman cp privateai:/privateai/app/oaa_home/linux_x64/models/multilingual-e5-large.zip .

その他のデフォルトのモデルについても一緒に使いたい、という場合は、以下コマンドでコンテナにログインして正確な絶対パスとファイル名をチェックできます。

podman exec -it privateai bash
cd /privateai/app/oaa_home/linux_x64/models/
ls -lh

もしくは以下の通りOML4Pyで新規に .zip ファイルを作成しても良いです。

from oml.utils import ONNXPipeline,ONNXPipelineConfig
config = ONNXPipelineConfig.from_template("text", max_seq_length=512)
pipeline = ONNXPipeline("intfloat/multilingual-e5-large", config)
pipeline.export2db("multilingual-e5-large")

マルチモーダルEmbeddingモデルの追加(clip-vit-base-patch32)

以下Pythonコードを実行し、生成された2つの .onnx ファイルを /home/opc/models に配置

from oml.utils import ONNXPipeline, ONNXPipelineConfig
config = ONNXPipelineConfig.from_template("multimodal_clip")
pipeline = ONNXPipeline("openai/clip-vit-base-patch32", config, settings={ "ignore_safetensors_error": True })
pipeline.export2file("clip-vit-base-patch32")

コンフィグ作成

一旦 gemma-4-E4B-it-GGUF のみをロードして動作確認してみます。
config.json を作成してロードできるようにします。

cd ~/config
vim config.json

config.json の定義は以下の通りです。

{
  "models": [
    {
      "name": "gemma-4-E4B-it-GGUF",
      "path": "gemma-4-E4B-it-Q4_K_M.gguf",
      "runtime": "llamacpp",
      "context_size": 8192,
      "capabilities": [ "TEXT_GENERATION" ]
    }
  ]
}

コンテナ構成の設定

電子署名、APIキーとキーストアを構成

電子署名、APIキーとキーストアを作成

cd /home/opc/setup
./secretsSetup.sh -s $SECRETS_DIR

.bash_profile にAPIキーの自動設定を追記

vim ~/.bash_profile
export API_KEY=$(cat $SECRETS_DIR/api-key)

config.json を反映

コンテナイメージ起動時に config.json の内容を反映させるため、configSetup.sh を実行します。

config.json を編集した際は、毎回 configSetup.sh を実行する必要があります。

./configSetup.sh \
  -d $PRIVATE_DIR \
  -s $SECRETS_DIR \
  -m /home/opc/models \
  -c /home/opc/config/config.json

コンテナ起動

コンテナイメージを起動して推論できるようにします。

./containerSetup.sh -d $PRIVATE_DIR --http

Podの起動状態を確認

podman ps -a

STATUShealthy になるまで待機します。

動作確認(一旦gemma-4-E4B-it-Q4_K_Mのみ)

リモートサーバからの利用はもちろん可能ですが、今回は構成をシンプルにするためローカルから curl コマンドでアクセスします。

RESTエンドポイントの正常性確認

$ curl -i http://localhost:8080/health
HTTP/1.1 200 OK
date: Fri, 28 Aug 2026 07:30:37 GMT
x-server-id: aaf32b69-6a50-4bfc-9b19-dca246b91f18
x-ratelimit-global-limit-requests: 30000
x-ratelimit-global-remaining-requests: 29999
x-ratelimit-global-reset-requests: 1
x-ratelimit-limit-requests: 30000
x-ratelimit-remaining-requests: 29999
x-ratelimit-reset-requests: 1
content-length: 0

ロード済みモデルの確認

$ curl http://localhost:8080/v1/models | jq
{
  "data": [
    {
      "id": "gemma-4-E4B-it-GGUF",
      "modelDeployedTime": "2026-08-28T08:09:36.795508536Z",
      "modelSize": "4.64G",
      "modelCapabilities": [
        "TEXT_GENERATION"
      ]
    }
  ]
}

推論の動作確認

簡単な動作確認

$ curl --noproxy '*' -X POST --header 'Content-Type: application/json' --data '{
        "model": "gemma-4-E4B-it-GGUF",
        "messages":[
                {"role": "user", "content": "こんにちは"}
        ],
        "max_tokens": 256,
        "temperature": 0.7,
        "stream": false
}' http://localhost:8080/v1/chat/completions | jq

{
  "id": "chatcmpl-rOhcsF1nZ4K1ua0AhEj3EvebNRJrpHs4",
  "object": "chat.completion",
  "created": 1787906077,
  "model": "gemma-4-E4B-it-GGUF",
  "choices": [
    {
      "index": 0,
      "message": {
        "content": "こんにちは!😊\n\n何かお手伝いできることはありますか?お話したいこと、質問したいこと、お手伝いしてほしいことがあれば、何でも教えてくださいね!",
        "role": "assistant"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 17,
    "total_tokens": 54,
    "completion_tokens": 37
  }
}

すこし複雑な質問。
コンテキストサイズの設定が小さかったため、回答が途中で途切れていますが、ちゃんと動作しています。
Gemma4 は推論過程も出力してくれている点が分かりますね。

$ curl --noproxy '*' -X POST --header 'Content-Type: application/json' --header "Authorization: Bearer $API_KEY" --cacert $SECRETS_DIR/cert.pem  --data '{
        "model": "gemma-4-E4B-it-GGUF",
        "messages":[
                {"role": "user", "content": "LDLコレステロールとは何ですか?"}
        ],
        "max_tokens": 1000,
        "temperature": 0.7,
        "stream": false
}' http://localhost:8080/v1/chat/completions | jq

{
  "id": "chatcmpl-Z7O1laDNpLt6ObbbNdTIEb8mxEQC7K46",
  "object": "chat.completion",
  "created": 1787906255,
  "model": "gemma-4-E4B-it-GGUF",
  "choices": [
    {
      "index": 0,
      "message": {
        "reasoning_content": "Here's a thinking process that leads to the suggested answer:\n\n1.  **Identify the core question:** The user is asking, \"What is LDL cholesterol?\" (LDLコレステロールとは何ですか?)\n2.  **Determine the target audience/tone:** The answer must be clear, easy to understand (since it's a medical/biological topic), and informative. The tone should be helpful and authoritative but not overly clinical.\n3.  **Define LDL (The \"What\"):**\n    *   LDL stands for Low-Density Lipoprotein.\n    *   It's a type of fat (lipid) carried in the blood.\n    *   Its primary job is to transport cholesterol from the liver to the rest of the body (where cells need it).\n4.  **Explain the function (The \"Why it matters\"):**\n    *   *Good function:* Transporting cholesterol to cells.\n    *   *Bad function (The problem):* When there is too much LDL, it can accumulate in the walls of arteries.\n5.  **Describe the consequence (The danger):**\n    *   Accumulation forms plaque (atherosclerosis).\n    *   Plaque narrows and hardens the arteries.\n    *   This increases the risk of serious cardiovascular diseases (heart attack, stroke).\n6.  **Address the common misconceptions/related concepts (Context):**\n    *   It's helpful to mention the contrast: HDL (High-Density Lipoprotein) is the \"good\" cholesterol that helps *remove* excess cholesterol.\n    *   It's important to state that high LDL is generally bad.\n7.  **Structure the answer (for readability):**\n    *   **Introduction/Simple Definition:** Start with a straightforward, non-technical answer.\n    *   **Detailed Explanation (Mechanism):** Explain its role in the body (transport).\n    *   **The Problem (The Danger):** Explain what happens when levels are too high (plaque, disease).\n    *   **Contrast (HDL):** Briefly introduce HDL for complete understanding.\n    *   **Summary/Actionable Advice:** Briefly mention the importance of diet and lifestyle.\n\n8.  **Drafting the Japanese (Self-Correction/Refinement):**\n    *   *Initial draft thought:* LDLは血液中の脂肪です。\n    *   *Refinement:* Needs more detail. Use terms like「脂質(lipid)」and「コレステロールを運ぶ運び屋(carrier)」.\n    *   *Ensuring clarity:* Use analogies (like \"plaque\" = 血管の詰まり).\n\n9.  **Final Review:** The resulting answer is comprehensive, easy to read, and directly addresses the user's query while providing necessary context. (This leads to the polished Japanese response.)",
        "content": "LDLコレステロールについてですね。これは健康診断などでよく聞く非常に重要な項目です。\n\n専門的になりすぎず、分かりやすくご説明します。\n\n---\n\n### 💡 一言でいうと\n\nLDLコレステロールは、**「血液中のコレステロールを運ぶ運び屋さん」**のような存在です。\n\nしかし、この「運び屋さん」が多すぎると、血管に問題を引き起こしてしまうため、**「悪玉コレステロール」**と呼ばれ、過剰な蓄積は心臓病や脳卒中のリスクを高めます。\n\n---\n\n### 🔍 詳細な解説:LDLコレステロールとは?\n\n#### 1. LDLとは何か?(仕組み)\nLDLは「Low-Density Lipoprotein(低比重リポタンパク質)」の略です。\nコレステロールは、私たちの体が必要とする大切な物質(細胞膜の材料など)ですが、血液中では水に溶けません。そのため、LDLという「乗り物」に包まれて血液中を循環しています。\n\nLDLの主な役割は、**肝臓などで作られたコレステロールを、体内の細胞が必要とする場所まで届けること**です。\n\n#### 2. なぜ「悪玉」と呼ばれるのか?(問題点)\nLDLの量が**「多すぎる」**ことが問題です。\n\n1.  **血管壁への沈着:** LDLが血液中を流れすぎると、その余剰分が血管の壁に付着し始めます。\n2.  **プラークの形成:** 血管の壁にLDLが詰まり、さらに炎症や他の物質が加わることで、固い塊(プラーク)が形成されます。\n3.  **動脈硬化:** このプラークが血管内を狭くし、硬くしていく現象を「動脈硬化」といいます。\n4.  **リスクの",
        "role": "assistant"
      },
      "finish_reason": "length"
    }
  ],
  "usage": {
    "prompt_tokens": 24,
    "total_tokens": 1024,
    "completion_tokens": 1000
  }
}

その他モデルの動作確認

起動済みPod停止

つづいて gemma-4-E4B-it-GGUF 以外のモデルを一気に動作確認してみます。

config.json を編集してモデルを再ロードするため、起動済みPodを一旦停止します。

## コンテナ名を確認
podman ps -a

## 停止
podman stop privateai

## 停止確認
podman ps -a

config.json編集

config.json を編集してその他モデルをロードできるようにします。

ちなみに推論モデルはコンテナイメージ1つにつき1つしかロードできません。
そのため先ほど動作確認した gemma-4-E4B-it-GGUF は定義から削除し、代わりにマルチモーダルな推論に対応した gemma-3-4b-it へ差し替えます。

VLMである gemma-3-4b-it についてはイメージからテキスト生成させる場合、Llama.cppではなくvLLMで起動する必要があります。
vLLMを使う場合はコンテナイメージ起動時にHugging Faceから必要ファイルをダウンロードして起動します。
※VLMでもテキスト生成だけであればGGUF+Llama.cppで起動しても良いです。

また gemma-3-4b-it についてはHugging Faceの google/gemma-3-4b-it へアクセスし、ライセンス条項に同意しないとvLLMとして起動する際のダウンロード処理がエラーになります。

cd ~/config
vim config.json
{
  "models": [
    {
      "name": "gemma-3-4b-it",
      "path": "google/gemma-3-4b-it",
      "runtime": "vllm",
      "capabilities": ["TEXT_GENERATION"],
      "context_size": 8192
    },
    {
      "name": "distilbert-base-multi-sentiments",
      "path": "distilbert-base-multilingual-cased-sentiments-student.onnx",
      "function": "classification",
      "labels": [
        "negative",
        "positive",
        "neutral"
      ]
    },
    {
      "name": "bge-reranker-v2-m3",
      "path": "bge-reranker-v2-m3.zip",
      "function": "reranking"
    },
    {
      "modelname": "multilingual-e5-large",
      "modelfile": "multilingual-e5-large.zip",
      "modelfunction": "EMBEDDING",
      "cache_on_startup": true
    },
    {
      "modelname": "clip-vit-base-patch32_img",
      "modelfile": "clip-vit-base-patch32_img.onnx",
      "modelfunction": "EMBEDDING",
      "cache_on_startup": true
    },
    {
      "modelname": "clip-vit-base-patch32_txt",
      "modelfile": "clip-vit-base-patch32_txt.onnx",
      "modelfunction": "EMBEDDING",
      "cache_on_startup": true
    }
  ]
}

config.json を編集したため configSetup.sh を再実行します。

./configSetup.sh \
  -d $PRIVATE_DIR \
  -s $SECRETS_DIR \
  -m /home/opc/models \
  -c /home/opc/config/config.json

Hugging Faceトークン取得(gemma-3-4b-it用)

このあと containerSetup.sh でコンテナイメージを起動する際、vLLM関連ファイルをHugging Faceからダウンロードしますが、そのためにはHugging FaceのAuth Tokenが必要となります。
トークン取得手順は以下がご参考になります。
https://qiita.com/so9_Monodzukuri_AI/items/388e9b546ecb2ecfc420

Hugging Faceのトークンを記載したファイルを配置します。

printf '%s' '<hugging-face-token>' > /tmp/huggingface-token 
chmod 600 /tmp/huggingface-token

コンテナイメージ起動

vLLMを使う場合は -hf オプションでHugging Faceトークンが記載されたファイルを指定してコンテナを起動します。

./containerSetup.sh -d $PRIVATE_DIR --http -hf /tmp/huggingface-token 

Podの起動状態を確認

podman ps -a

STATUShealthy になるまで待機

初回起動時は gemma-3-4b-it のダウンロードがあるため起動に時間が掛かります。
vLLMはサイズが大きいためメモリもそれなりに消費します。

また今回のようにロードするモデルが多いほど起動に時間が掛かり、メモリも多く消費します。

ロード済みモデルの確認

$ curl http://localhost:8080/v1/models | jq

{
  "data": [
    {
      "id": "clip-vit-base-patch32_txt",
      "modelDeployedTime": "2026-09-01T05:57:55.109409366Z",
      "modelSize": "243.57M",
      "modelCapabilities": [
        "TEXT_EMBEDDINGS"
      ]
    },
    {
      "id": "gemma-3-4b-it",
      "modelDeployedTime": "2026-09-01T05:51:45.664252898Z",
      "modelSize": "8.01G",
      "modelCapabilities": [
        "TEXT_GENERATION"
      ]
    },
    {
      "id": "bge-reranker-v2-m3",
      "modelDeployedTime": "2026-09-01T05:57:26.504648746Z",
      "modelSize": "2.13G",
      "modelCapabilities": [
        "TEXT_RERANK"
      ]
    },
    {
      "id": "multilingual-e5-large",
      "modelDeployedTime": "2026-09-01T05:57:49.238664429Z",
      "modelSize": "2.09G",
      "modelCapabilities": [
        "TEXT_EMBEDDINGS"
      ]
    },
    {
      "id": "clip-vit-base-patch32_img",
      "modelDeployedTime": "2026-09-01T05:57:52.511803727Z",
      "modelSize": "335.38M",
      "modelCapabilities": [
        "IMAGE_EMBEDDINGS"
      ]
    },
    {
      "id": "distilbert-base-multi-sentiments",
      "modelDeployedTime": "2026-09-01T05:57:01.737389378Z",
      "modelSize": "517.30M",
      "modelCapabilities": [
        "TEXT_CLASSIFICATION"
      ]
    }
  ]
}

VLM動作確認 (gemma-3-4b-it)

VLMですが、まずはテキストのクエリを投げて動作確認します。

$ curl --noproxy '*' -X POST --header 'Content-Type: application/json' --data '{
        "model": "gemma-3-4b-it",
        "messages":[
                {"role": "user", "content": "こんにちは"}
        ],
        "max_tokens": 256,
        "temperature": 0.7,
        "stream": false
}' http://localhost:8080/v1/chat/completions | jq

{
  "id": "chatcmpl-af678ab2ea54eb3d",
  "object": "chat.completion",
  "created": 1788243562,
  "model": "gemma-3-4b-it",
  "choices": [
    {
      "index": 0,
      "message": {
        "tool_calls": [],
        "content": "こんにちは!何かお手伝いできることはありますか?😊\n",
        "role": "assistant"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 10,
    "total_tokens": 24,
    "completion_tokens": 14
  }
}

次にイメージを渡して解説文を生成してもらいます。
今回は以下イメージファイルを使いました。
sample.jpg

$ BASE64_IMG=$(base64 -w 0 sample.png)

$ curl --noproxy '*' \
  -X POST \
  -H 'Content-Type: application/json' \
  --data-binary @- \
  http://localhost:8080/v1/chat/completions | jq <<EOF
{
  "model": "gemma-3-4b-it",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "添付画像を説明してください。"
        },
        {
          "type": "image_url",
          "image_url": {
            "url": "data:image/png;base64,${BASE64_IMG}"
          }
        }
      ]
    }
  ],
  "max_tokens": 1024,
  "temperature": 0.7
}
EOF

{
  "id": "chatcmpl-b1abc14bf260322b",
  "object": "chat.completion",
  "created": 1788243613,
  "model": "gemma-3-4b-it",
  "choices": [
    {
      "index": 0,
      "message": {
        "tool_calls": [],
        "content": "添付画像は、イタリアのトスカーナ地方の風景写真です。以下に詳細な説明をします。\n\n*   **地形:**  広大な丘陵地帯で、緑豊かな草地が波状に広がる様子が特徴です。丘陵の表面は、耕作のために区切られた畑の区切りや溝が刻ま れており、複雑な地形を形成しています。\n*   **樹木:** 丘陵の頂上や中央に、細長く、優雅なシルエットのシダー(シダー) の木々が並んでいます。これらの木々は、トスカーナの風景を象徴する存在です。\n*   **遠景:** 遠くには、薄い雲に覆われた 空と、より遠くの山々が見えます。\n*   **光と雰囲気:** 写真全体が、夕暮れ時や日の出時に撮影されたかのような、暖かく柔 らかな光に包まれています。特に、水平線付近が黄色みを帯びており、穏やかで平和な雰囲気を醸し出しています。\n*   **全体 的な印象:** この写真は、トスカーナ地方の美しい自然景観を捉えたものであり、その静寂と優雅さ、そして豊穣な大地が感じら れます。\n\nこの写真は、イタリアの風景写真の代表的な例として知られており、トスカーナ地方の魅力を伝えるとともに、見る 人に安らぎと感動を与えます。",
        "role": "assistant"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 274,
    "total_tokens": 586,
    "completion_tokens": 312
  }
}

ちゃんとイメージの解説文が生成されました。

分類モデルの動作確認(distilbert-base-multilingual-cased-sentiments-student)

$ curl -sS -X POST \
  --header "Content-Type: application/json" \
  --data '{
    "model": "distilbert-base-multi-sentiments",
    "input": [
      "今日は雨が止んだあとに虹が見えて素敵でした。"
    ]
  }' \
  "http://localhost:8080/v1/classify" | jq

{
  "id": "classify-a54179910fd641248f2d03d18ec3ebc5",
  "created": 1788244912,
  "model": "distilbert-base-multi-sentiments",
  "data": [
    {
      "index": 0,
      "label": "neutral",
      "probs": [
        0.16572992503643036,
        0.16398286819458008,
        0.6702871918678284
      ],
      "num_classes": 3
    }
  ]
}

リランクモデルの動作確認(bge-reranker-v2-m3)

$ curl -sS -X POST \
  --header "Content-Type: application/json" \
  --data '{
    "model": "bge-reranker-v2-m3",
    "query": "Oracle Databaseでベクトル検索を行う方法",
    "documents": [
      "Oracle Database 23aiではAI Vector Searchを使用できます。",
      "Oracle Cloud InfrastructureではCompute Instanceを作成できます。",
      "VECTOR_DISTANCE関数を利用してベクトル間の距離を計算できます。"
    ],
    "top_n": 3
  }' \
  "http://localhost:8080/v1/rerank" | jq

{
  "results": [
    {
      "index": 0,
      "relevance_score": 1.360196590423584
    },
    {
      "index": 2,
      "relevance_score": -5.635956764221191
    },
    {
      "index": 1,
      "relevance_score": -10.371610641479492
    }
  ]
}

テキストEmbeddingの動作確認(multilingual-e5-large、clip-vit-base-patch32_txt)

まずは multilingual-e5-large で動作確認します。

$ curl -X POST -H "Content-Type: application/json" -d '{"model": "multilingual-e5-large", "input":["こんにちは"]}' http://localhost:8080/v1/embeddings

{"data":[{"embedding":[0.024386024,0.0067647337,-0.02087328,-0.05885435, ...()... ,-0.024920529,0.035158876],"index":0,"object":"embedding"}],"model":"MULTILINGUAL-E5-LARGE"}

マルチモーダルEmbeddingモデルの clip-vit-base-patch32_txt でも動作確認します。

$ curl -X POST -H "Content-Type: application/json" -d '{"model": "clip-vit-base-patch32_txt", "input":["こんにちは"]}' http://localhost:8080/v1/embeddings

{"data":[{"embedding":[-0.013562894,-0.006810102,-0.0011304714, ...()... ,-5.134315E-4,0.012722411],"index":0,"object":"embedding"}],"model":"CLIP-VIT-BASE-PATCH32_TXT"}

イメージEmbeddingの動作確認(clip-vit-base-patch32_img)

VLMの動作確認で利用したものと同じイメージファイルを投げてみました。

$ IMAGE_BASE64=$(base64 -w 0 sample.jpg)

$ curl -X POST \
  -H "Content-Type: application/json" \
  --data-binary @- \
  http://localhost:8080/v1/embeddings <<EOF
{
  "model": "clip-vit-base-patch32_img",
  "input": [
    "${IMAGE_BASE64}"
  ]
}
EOF

{"data":[{"embedding":[0.0024235863,-0.004431251,0.012965481, ...()... ,-0.03196023,0.011150674],"index":0,"object":"embedding"}],"model":"CLIP-VIT-BASE-PATCH32_IMG"}

まとめ

今回、Oracle Private AI Services Containerを実際に構築し、テキスト生成、VLM、Rerank、Text Classification、Embeddingまで一通り動作確認しました。

実際に触ってみて感じた最大の魅力は、「データを外部に出さず、自分たちの環境で生成AIを動かせる」ことに加えて、OpenAI互換APIで比較的シンプルにAI機能を利用できることです。

特に、

  • 機密データを外部AIサービスへ送信できない
  • 閉域環境で生成AI・RAGを構築したい
  • 大量処理でAPIのトークン料金を気にしたくない
  • 既存のOpenAI API向けアプリケーションやツールを活用したい

といった要件がある場合、Private AI Services Containerは有力な選択肢になると思います。

また、LLMによる文章生成だけではなく、Embedding、Reranking、画像認識、分類モデルなども同じ基盤上で利用できるため、「多機能な社内版生成AI API基盤」を作りたいケースとも相性が良さそうです。

生成AIというとクラウド上のAPIを呼び出す構成がまず思い浮かびますが、「AIを自分たちの環境へ持ってくる」 という選択肢も、これからますます重要になってくるのではないでしょうか。

“社内ChatGPTを閉域環境で作りたい”と考えている方には、ぜひ一度試してほしい製品です。

8
4
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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?