Skip to content

Commit 6b61f2f

Browse files
committed
WMS 12041: Build multimodal AI Vector Search using Oracle Private AI Service Container
WMS 12041: Build multimodal AI Vector Search using Oracle Private AI Service Container
1 parent b5c112a commit 6b61f2f

File tree

16 files changed

+2777
-0
lines changed

16 files changed

+2777
-0
lines changed
48.8 KB
Loading
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Introduction
2+
3+
## About this Workshop
4+
5+
Welcome to **Build multimodal AI Vector Search using Oracle Private AI Service Container**.
6+
7+
Oracle Private AI Services Container exists to give you modern model inference inside your own environment. You get a local model endpoint without sending your text or images to a public AI service.
8+
9+
Its core value is control with flexibility:
10+
- Keep **data inside your network** and security boundary
11+
- Update model services **without changing core database** deployment
12+
- **Reuse one model endpoint** across notebooks, SQL flows, and apps
13+
- **Support multimodal patterns**, such as image and text embeddings in one solution
14+
15+
Use each path for a different job:
16+
- **In-database embeddings (`provider=database`)** fit SQL-first workflows with minimal moving parts.
17+
- **Private AI Services Container (`provider=privateai`)** fits teams that need model agility, multimodal use cases, or shared model serving across tools.
18+
19+
Compared with public embedding APIs, a private container is often the stronger enterprise choice:
20+
- Sensitive data does not leave your environment
21+
- Latency and cost are more predictable on local network paths
22+
- Development is less exposed to external quotas, endpoint drift, and service outages
23+
24+
In the following labs you will work not only with in-database embedding but **specifically** with the Oracle Private AI Services Container to:
25+
- discover available models in the Oracle Private AI Services Container
26+
- generate embeddings using ONXX models stored in the Oracle AI Database and via the API endpoint provided by the Oracle Private AI Services Container.
27+
- store vectors in Oracle AI Database
28+
- run cosine similarity search
29+
- build a simple image search app that used multimodal embedding models
30+
31+
Estimated Workshop Time: 90 minutes
32+
33+
### Architecture at a Glance
34+
35+
- `jupyterlab` runs Python notebooks.
36+
- `privateai` serves embedding models at `http://privateai:8080` on the container network.
37+
- `aidbfree` stores documents and vectors.
38+
39+
![architecture](./images/arch.png)
40+
41+
42+
### Objectives
43+
44+
In this workshop, you will:
45+
- Validate the runtime services required for the lab
46+
- Generate embeddings with both database-stored ONNX models and Oracle Private AI Services Container
47+
- Perform semantic similarity search in Oracle AI Database 26ai
48+
- Build a simple image app that uses multimodal embeddings for similarity search
49+
50+
51+
## Learn More
52+
53+
- [Oracle Private AI Services Container User Guide](https://docs.oracle.com/en/database/oracle/oracle-database/26/prvai/oracle-private-ai-services-container.html)
54+
- [Private AI Services Container API Reference](https://docs.oracle.com/en/database/oracle/oracle-database/26/prvai/private-ai-services-container-api-reference.html)
55+
- [DBMS_VECTOR UTL_TO_EMBEDDING](https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/utl_to_embedding-and-utl_to_embeddings-dbms_vector.html)
56+
57+
## Acknowledgements
58+
- **Author** - Oracle LiveLabs Team
59+
- **Last Updated By/Date** - Oracle LiveLabs Team, March 2026
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Lab 1: Verify the Runtime Environment
2+
3+
## Introduction
4+
5+
In this lab you verify that all the Oracle Private AI Services API endpoint is reachable on the netwokk.
6+
You will also learn how to list all the models available in the container.
7+
All checks are executed from a **JupyterLab Terminal**.
8+
9+
Estimated Time: 10 minutes
10+
11+
### Objectives
12+
13+
In this lab, you will:
14+
- Verify internal container DNS resolution from JupyterLab
15+
- Validate Private AI health and model list
16+
- Confirm Oracle AI Database and ORDS reachability from JupyterLab
17+
- Confirm `/home/.env` is available to the notebook
18+
19+
### Prerequisites
20+
21+
This lab assumes:
22+
- You can open a terminal in JupyterLab (`File` -> `New` -> `Terminal`)
23+
24+
## Task 1: Verify Internal Hostname Resolution
25+
26+
1. In JupyterLab, open a new terminal.
27+
28+
2. Verify that runtime service names resolve:
29+
30+
```bash
31+
<copy>getent hosts privateai aidbfree ords</copy>
32+
```
33+
34+
Expected: one IP entry for each service name.
35+
36+
## Task 2: Validate Private AI REST Endpoints and list available models
37+
38+
1. Health endpoint:
39+
40+
```bash
41+
<copy>curl -sS -i http://privateai:8080/health</copy>
42+
```
43+
44+
Expected: HTTP `200`.
45+
46+
2. List deployed models:
47+
48+
```bash
49+
<copy>curl -sS http://privateai:8080/v1/models | jq .</copy>
50+
```
51+
52+
Expected: JSON payload with a `data` array of model IDs.
53+
54+
55+
## Acknowledgements
56+
- **Author** - Oracle LiveLabs Team
57+
- **Last Updated By/Date** - Oracle LiveLabs Team, March 2026
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Notebook B: Database-Stored Model Embeddings\n",
8+
"\n",
9+
"This notebook demonstrates vector search using embedding models already stored in Oracle AI Database (for example `ALL_MINILM_L12_V2`).\n",
10+
"\n",
11+
"Flow:\n",
12+
"- discover available database models\n",
13+
"- generate embeddings with `provider=database`\n",
14+
"- store vectors and run similarity search\n"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"metadata": {},
21+
"outputs": [],
22+
"source": [
23+
"import os\n",
24+
"import json\n",
25+
"import re\n",
26+
"import oracledb\n",
27+
"from dotenv import dotenv_values\n"
28+
]
29+
},
30+
{
31+
"cell_type": "markdown",
32+
"metadata": {},
33+
"source": [
34+
"## 1) Load DB configuration from `/home/.env`"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": null,
40+
"metadata": {},
41+
"outputs": [],
42+
"source": [
43+
"ENV_PATH = os.getenv('LAB_ENV_FILE', '/home/.env')\n",
44+
"env = dotenv_values(ENV_PATH) if os.path.exists(ENV_PATH) else {}\n",
45+
"\n",
46+
"DB_USER = os.getenv('DB_USER') or env.get('USERNAME') or 'ADMIN'\n",
47+
"DB_PASSWORD = os.getenv('DB_PASSWORD') or env.get('DBPASSWORD')\n",
48+
"DB_HOST = os.getenv('DB_HOST', 'aidbfree')\n",
49+
"DB_PORT = os.getenv('DB_PORT', '1521')\n",
50+
"DB_SERVICE = os.getenv('DB_SERVICE', 'FREEPDB1')\n",
51+
"DB_DSN = os.getenv('DB_DSN', f'{DB_HOST}:{DB_PORT}/{DB_SERVICE}')\n",
52+
"PREFERRED_DB_MODEL = os.getenv('DB_EMBED_MODEL', 'ALL_MINILM_L12_V2').upper()\n",
53+
"\n",
54+
"print('ENV file:', ENV_PATH if os.path.exists(ENV_PATH) else 'not found')\n",
55+
"print('DB user:', DB_USER)\n",
56+
"print('DB dsn :', DB_DSN)\n",
57+
"print('Preferred DB model:', PREFERRED_DB_MODEL)\n",
58+
"\n",
59+
"if not DB_PASSWORD:\n",
60+
" raise ValueError('DB password not found. Set DB_PASSWORD or DBPASSWORD in /home/.env')\n"
61+
]
62+
},
63+
{
64+
"cell_type": "markdown",
65+
"metadata": {},
66+
"source": [
67+
"## 2) Connect and discover stored models"
68+
]
69+
},
70+
{
71+
"cell_type": "code",
72+
"execution_count": null,
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"conn = oracledb.connect(user=DB_USER, password=DB_PASSWORD, dsn=DB_DSN)\n",
77+
"cur = conn.cursor()\n",
78+
"\n",
79+
"cur.execute('select user from dual')\n",
80+
"print('Connected as:', cur.fetchone()[0])\n",
81+
"\n",
82+
"cur.execute('''\n",
83+
" SELECT model_name\n",
84+
" FROM user_mining_models\n",
85+
" ORDER BY model_name\n",
86+
"''')\n",
87+
"models = [row[0] for row in cur.fetchall()]\n",
88+
"print('Models in USER_MINING_MODELS:', models)\n",
89+
"\n",
90+
"if not models:\n",
91+
" raise RuntimeError('No models found in USER_MINING_MODELS. Provision an ONNX model first.')\n",
92+
"\n",
93+
"MODEL_NAME = PREFERRED_DB_MODEL if PREFERRED_DB_MODEL in models else models[0]\n",
94+
"print('Selected DB model:', MODEL_NAME)\n",
95+
"\n",
96+
"if not re.match(r'^[A-Z][A-Z0-9_$#]*$', MODEL_NAME):\n",
97+
" raise ValueError(f'Unsafe model identifier: {MODEL_NAME}')\n"
98+
]
99+
},
100+
{
101+
"cell_type": "markdown",
102+
"metadata": {},
103+
"source": [
104+
"## 3) Determine embedding dimension from the DB model"
105+
]
106+
},
107+
{
108+
"cell_type": "code",
109+
"execution_count": null,
110+
"metadata": {},
111+
"outputs": [],
112+
"source": [
113+
"db_params = json.dumps({\n",
114+
" 'provider': 'database',\n",
115+
" 'model': MODEL_NAME,\n",
116+
"})\n",
117+
"\n",
118+
"cur.execute('''\n",
119+
" SELECT VECTOR_DIMENSION_COUNT(\n",
120+
" DBMS_VECTOR.UTL_TO_EMBEDDING(:txt, JSON(:params))\n",
121+
" )\n",
122+
" FROM dual\n",
123+
"''', {'txt': 'dimension probe', 'params': db_params})\n",
124+
"\n",
125+
"EMBEDDING_DIM = int(cur.fetchone()[0])\n",
126+
"print('Embedding dimension:', EMBEDDING_DIM)\n"
127+
]
128+
},
129+
{
130+
"cell_type": "markdown",
131+
"metadata": {},
132+
"source": [
133+
"## 4) Create table and store in-database embeddings"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": null,
139+
"metadata": {},
140+
"outputs": [],
141+
"source": [
142+
"TABLE_NAME = 'PRIVATEAI_DOCS_DBMODEL'\n",
143+
"\n",
144+
"try:\n",
145+
" cur.execute(f'DROP TABLE {TABLE_NAME} PURGE')\n",
146+
"except oracledb.DatabaseError:\n",
147+
" pass\n",
148+
"\n",
149+
"cur.execute(f'''\n",
150+
" CREATE TABLE {TABLE_NAME} (\n",
151+
" doc_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n",
152+
" title VARCHAR2(200) NOT NULL,\n",
153+
" content CLOB NOT NULL,\n",
154+
" embedding VECTOR({EMBEDDING_DIM}, FLOAT32)\n",
155+
" )\n",
156+
"''')\n",
157+
"\n",
158+
"docs = [\n",
159+
" ('Database Model Path', 'Embeddings can be generated directly in Oracle AI Database with a stored ONNX model.'),\n",
160+
" ('Operational Simplicity', 'Keeping model inference in-database can simplify architecture and governance.'),\n",
161+
" ('Vector Search SQL', 'Use VECTOR_DISTANCE with COSINE to rank semantic similarity results.'),\n",
162+
" ('Model Governance', 'Database-stored models can be versioned and controlled with DB privileges.'),\n",
163+
"]\n",
164+
"\n",
165+
"inserted = 0\n",
166+
"for title, content in docs:\n",
167+
" cur.execute(f'''\n",
168+
" INSERT INTO {TABLE_NAME} (title, content, embedding)\n",
169+
" VALUES (\n",
170+
" :title,\n",
171+
" :content,\n",
172+
" DBMS_VECTOR.UTL_TO_EMBEDDING(:content, JSON(:params))\n",
173+
" )\n",
174+
" ''', {'title': title, 'content': content, 'params': db_params})\n",
175+
" inserted += 1\n",
176+
"\n",
177+
"conn.commit()\n",
178+
"print('Inserted rows:', inserted)\n"
179+
]
180+
},
181+
{
182+
"cell_type": "markdown",
183+
"metadata": {},
184+
"source": [
185+
"## 5) Similarity search with the database model"
186+
]
187+
},
188+
{
189+
"cell_type": "code",
190+
"execution_count": null,
191+
"metadata": {},
192+
"outputs": [],
193+
"source": [
194+
"query_text = 'Which approach keeps embedding generation inside Oracle Database?'\n",
195+
"\n",
196+
"sql = f'''\n",
197+
"SELECT\n",
198+
" title,\n",
199+
" ROUND(1 - VECTOR_DISTANCE(\n",
200+
" embedding,\n",
201+
" DBMS_VECTOR.UTL_TO_EMBEDDING(:query_text, JSON(:params)),\n",
202+
" COSINE\n",
203+
" ), 4) AS similarity,\n",
204+
" SUBSTR(content, 1, 120) AS preview\n",
205+
"FROM {TABLE_NAME}\n",
206+
"ORDER BY VECTOR_DISTANCE(\n",
207+
" embedding,\n",
208+
" DBMS_VECTOR.UTL_TO_EMBEDDING(:query_text, JSON(:params)),\n",
209+
" COSINE\n",
210+
")\n",
211+
"FETCH FIRST 3 ROWS ONLY\n",
212+
"'''\n",
213+
"\n",
214+
"cur.execute(sql, {'query_text': query_text, 'params': db_params})\n",
215+
"rows = cur.fetchall()\n",
216+
"\n",
217+
"print('Query:', query_text)\n",
218+
"for idx, (title, score, preview) in enumerate(rows, 1):\n",
219+
" print(f'{idx}. {title} | score={score}')\n",
220+
" print(f' {preview}')\n"
221+
]
222+
},
223+
{
224+
"cell_type": "code",
225+
"execution_count": null,
226+
"metadata": {},
227+
"outputs": [],
228+
"source": [
229+
"# Optional cleanup\n",
230+
"# cur.execute(f'DROP TABLE {TABLE_NAME} PURGE')\n",
231+
"# conn.commit()\n"
232+
]
233+
},
234+
{
235+
"cell_type": "code",
236+
"execution_count": null,
237+
"metadata": {},
238+
"outputs": [],
239+
"source": [
240+
"cur.close()\n",
241+
"conn.close()\n",
242+
"print('Connection closed.')\n"
243+
]
244+
}
245+
],
246+
"metadata": {
247+
"kernelspec": {
248+
"display_name": "Python 3",
249+
"language": "python",
250+
"name": "python3"
251+
},
252+
"language_info": {
253+
"name": "python",
254+
"version": "3.11"
255+
}
256+
},
257+
"nbformat": 4,
258+
"nbformat_minor": 5
259+
}
Binary file not shown.

0 commit comments

Comments
 (0)