Building AI-Powered Image Similarity Search in Oracle APEX using Cohere & Oracle 23ai Vector
π§ Introduction
In this blog, we’ll build a real-time Image Similarity Search Application using:
-
Oracle APEX (UI)
-
Oracle 23ai VECTOR (AI storage)
-
Cohere Embed API (Image → Vector)
-
ORDS (Image streaming)
Instead of searching by text, users can upload an image and find visually similar images instantly.
π️ Architecture Overview
The system works in 5 simple steps:
-
Upload image in APEX
-
Convert image → vector using Cohere API
-
Store vector in Oracle DB
-
Compare vectors using cosine similarity
-
Display matching images in classic report.
Step by Step Approaches
π️ Step 1: Create Database Tables
CREATE TABLE "PRODUCT_IMAGES"
( "ID" NUMBER GENERATED ALWAYS AS IDENTITY,
"PRODUCT_NAME" VARCHAR2(200),
"IMAGE_BLOB" BLOB,
"IMAGE_MIME" VARCHAR2(100),
"EMBEDDING" VECTOR (1536, FLOAT32),
PRIMARY KEY ("ID")
USING INDEX ENABLE
);
CREATE TABLE "SEARCH_IMAGE"
( "ID" NUMBER GENERATED ALWAYS AS IDENTITY,
"PRODUCT_NAME" VARCHAR2(200),
"IMAGE_BLOB" BLOB,
"IMAGE_MIME" VARCHAR2(100),
"EMBEDDING" VECTOR (1536, FLOAT32),
PRIMARY KEY ("ID")
USING INDEX ENABLE
);
π VECTOR(1536) matches Cohere embed-v4.0 output.
π Step 2: Get Cohere API Key
-
Go to Cohere Dashboard - https://dashboard.cohere.com/
-
Click API Keys option and Create API Key (Trial is enough)
πΌ️ Step 3: Upload & Store Product Image Embeddings
π This runs when uploading product images
Key Logic:
-
Read image from APEX temp table
-
Convert to Base64
-
Call Cohere API
-
Store embedding in DB
Steps:
Create a new page in your Oracle APEX application and add the following page items:
-
P2_PRODUCT_NAME – Text field to enter the product name
-
P_UPLOAD_IMAGE – File Browse item to upload the product image
-
P_STATUS – Display Only item to show success or error messages
Next, create a button named UPLOAD_IMAGE to trigger the upload process.
Finally, create a page process called Insert Image, which will handle storing the uploaded image along with its embedding into the database table.
DECLARE
l_mime VARCHAR2(100);
l_blob BLOB;
l_blob_req BLOB;
l_base64 CLOB;
l_json_req CLOB;
l_json_resp CLOB;
l_raw_chunk RAW(7500);
l_b64_chunk VARCHAR2(10000);
l_offset INTEGER := 1;
l_chunk_size INTEGER := 7500;
l_amount INTEGER;
l_blob_len INTEGER;
l_dest_off INTEGER := 1;
l_src_off INTEGER := 1;
l_lang_ctx INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
l_warning INTEGER;
l_vector_str CLOB;
l_dim_count INTEGER;
l_val VARCHAR2(50);
BEGIN
-- Step 1: Fetch image
SELECT blob_content, mime_type
INTO l_blob, l_mime
FROM (
SELECT blob_content, mime_type
FROM APEX_APPLICATION_TEMP_FILES
WHERE name = :P_UPLOAD_IMAGE
ORDER BY created_on DESC
)
WHERE ROWNUM = 1;
IF l_mime LIKE 'image/jpg%' OR l_mime = 'image/pjpeg' THEN
l_mime := 'image/jpeg';
END IF;
l_blob_len := DBMS_LOB.GETLENGTH(l_blob);
IF l_blob_len IS NULL OR l_blob_len = 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Image BLOB is empty or NULL');
END IF;
-- Step 2: BLOB → Base64 CLOB (no newlines)
DBMS_LOB.CREATETEMPORARY(l_base64, TRUE);
l_base64 := APEX_WEB_SERVICE.BLOB2CLOBBASE64(l_blob);
l_base64 := REPLACE(REPLACE(l_base64, CHR(10), ''), CHR(13), '');
-- Step 3: Build JSON WITH data URI prefix (required by Cohere v2)
DBMS_LOB.CREATETEMPORARY(l_json_req, TRUE);
DBMS_LOB.APPEND(l_json_req, TO_CLOB('{"model":"embed-v4.0",'));
DBMS_LOB.APPEND(l_json_req, TO_CLOB('"input_type":"image",'));
DBMS_LOB.APPEND(l_json_req, TO_CLOB('"embedding_types":["float"],'));
DBMS_LOB.APPEND(l_json_req, TO_CLOB('"images":["'));
DBMS_LOB.APPEND(l_json_req, TO_CLOB('data:' || l_mime || ';base64,'));
DBMS_LOB.APPEND(l_json_req, l_base64);
DBMS_LOB.APPEND(l_json_req, TO_CLOB('"]}'));
-- Step 4: Convert JSON CLOB → BLOB (avoids 32767 limit)
DBMS_LOB.CREATETEMPORARY(l_blob_req, TRUE);
DBMS_LOB.CONVERTTOBLOB(
dest_lob => l_blob_req,
src_clob => l_json_req,
amount => DBMS_LOB.LOBMAXSIZE,
dest_offset => l_dest_off,
src_offset => l_src_off,
blob_csid => DBMS_LOB.DEFAULT_CSID,
lang_context => l_lang_ctx,
warning => l_warning
);
-- Step 5: Call Cohere API
APEX_WEB_SERVICE.G_REQUEST_HEADERS.DELETE;
APEX_WEB_SERVICE.G_REQUEST_HEADERS(1).NAME := 'Content-Type';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(1).VALUE := 'application/json';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(2).NAME := 'Authorization';
APEX_WEB_SERVICE.G_REQUEST_HEADERS(2).VALUE := 'Bearer YuJE9V6b9FiWzpmyIYtTh7IiGuidOCd2I0m3YBca';
l_json_resp := APEX_WEB_SERVICE.MAKE_REST_REQUEST
(
p_url => 'https://api.cohere.com/v2/embed',
p_http_method => 'POST',
p_body_blob => l_blob_req
);
-- Step 6: Show result
IF APEX_WEB_SERVICE.G_STATUS_CODE = 200 THEN
:P_STATUS := 'success';
APEX_JSON.PARSE(l_json_resp);
-- Cohere v2 response path: embeddings.float[1][n]
l_dim_count := APEX_JSON.GET_COUNT(p_path => 'embeddings.float[1]');
IF l_dim_count IS NULL OR l_dim_count = 0 THEN
RAISE_APPLICATION_ERROR(-20002, 'No embedding dimensions found in response');
END IF;
-- Build vector string: '[v1,v2,...,v1024]'
DBMS_LOB.CREATETEMPORARY(l_vector_str, TRUE);
DBMS_LOB.APPEND(l_vector_str, TO_CLOB('['));
FOR i IN 1 .. l_dim_count LOOP
l_val := TO_CHAR(
APEX_JSON.GET_NUMBER(p_path => 'embeddings.float[1][%d]', p0 => i),
'FM999999990.9999999999'
);
IF i > 1 THEN
DBMS_LOB.APPEND(l_vector_str, TO_CLOB(','));
END IF;
DBMS_LOB.APPEND(l_vector_str, TO_CLOB(l_val));
END LOOP;
DBMS_LOB.APPEND(l_vector_str, TO_CLOB(']'));
-- Insert into PRODUCT_IMAGES
INSERT INTO PRODUCT_IMAGES (
product_name,
image_blob,
image_mime,
embedding
) VALUES (
:P2_PRODUCT_NAME, -- bind your product name item
l_blob,
l_mime,
TO_VECTOR(l_vector_str, 1536, FLOAT32) -- Oracle 23ai VECTOR constructor
);
COMMIT;
--:P2_QUERY_VECTOR := 'SUCCESS — Stored ' || l_dim_count || ' dimensions, ID: ';
DBMS_LOB.FREETEMPORARY(l_vector_str);
else
:P_STATUS := 'HTTP ' || APEX_WEB_SERVICE.G_STATUS_CODE|| ' | ' || SUBSTR(l_json_resp, 1,3000);
END IF;
-- Step 7: Cleanup
DBMS_LOB.FREETEMPORARY(l_base64);
DBMS_LOB.FREETEMPORARY(l_json_req);
DBMS_LOB.FREETEMPORARY(l_blob_req);
EXCEPTION
WHEN OTHERS THEN
:P_STATUS := 'ERROR [' || SQLCODE || ']: ' || SQLERRM
|| ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE();
END;
π Step 4: Store Search Image Embedding
What it does:
-
Reads uploaded image
-
Converts to Base64
-
Calls Cohere API
-
Extracts 1536 vector values
-
Stores in
SEARCH_IMAGE
Steps:
Create a new page in your Oracle APEX application and add the following page items:
-
P4_UPLOAD_IMAGE – File Browse item to upload the search image
-
P4_UPLOADED_IMAGE – Display item to preview the uploaded image
-
P_SEARCH_RESULTS – Hidden item to store the JSON response of similar images
-
P4_STATUS – Display Only item to show success or error messages
Next, create a button named SEARCH_SIMILAR_IMAGE to trigger the search process.
Add a Classic Report region to display the similar images retrieved from the database.
Finally, create two page processes:
-
Insert Image Process – Converts the uploaded image into a vector using the Cohere API and stores it in the
SEARCH_IMAGEtable -
Search Similar Images Process – Compares the uploaded image vector with existing product images and returns the top matching results
π Step 5: Search Similar Images (Vector Comparison)
This is your second process in same page.
DECLARE
l_search_vector VECTOR(1536, FLOAT32);
l_result_json CLOB;
BEGIN
-- Step 1: Get the latest (or specific) search image vector
SELECT embedding
INTO l_search_vector
FROM (
SELECT embedding
FROM SEARCH_IMAGE
ORDER BY id DESC
)
WHERE ROWNUM = 1;
-- Step 2: Build JSON result of top-N matches (for APEX display)
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'product_id' VALUE p.id,
'product_name' VALUE p.product_name,
'mime_type' VALUE p.image_mime,
'similarity' VALUE ROUND(
(1 - VECTOR_DISTANCE(p.embedding, l_search_vector, COSINE)) * 100,
2
)
)
ORDER BY VECTOR_DISTANCE(p.embedding, l_search_vector, COSINE) ASC
)
INTO l_result_json
FROM PRODUCT_IMAGES p
WHERE p.embedding IS NOT NULL
AND ROWNUM <= 20; -- top 5 matches
-- Step 3: Bind to APEX page item (e.g. a Hidden or Display item)
:P_SEARCH_RESULTS := l_result_json;
:P4_STATUS := 'success';
EXCEPTION
WHEN NO_DATA_FOUND THEN
:P4_STATUS := 'No search vector found.';
WHEN OTHERS THEN
:P4_STATUS := 'ERROR [' || SQLCODE || ']: ' || SQLERRM
|| ' | ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE();
END;
Logic:
-
Get latest search vector
-
Compare with all product vectors
-
Return top matches as JSON
π Output stored in:
:P_SEARCH_RESULTS
π Step 6: Display Results (Classic Report)
Use this concept:
-
Parse JSON using
JSON_TABLE -
Show:
-
Image
-
Product Name
-
Similarity %
-
Similarity Formula:
Similarity % = (1 - cosine distance) * 100π Step 7: ORDS Image Rendering (Step-by-Step)
To display images without session issues in Oracle APEX, we use ORDS REST Services to stream
images directly from the database.
π§ Step 7.1: Create REST Module
Go to SQL Workshop → RESTful Services
Click Modules → Create Module
Enter:
Module Name:
product-imagesBase Path:
/product-images/Status: Published
π Click Create Module
π§ Step 7.2: Create Resource Template
Open your module:
product-imagesClick Create Template
Enter:
URI Template:
:idπ Click Create Template
π§ Step 7.3: Create Resource Handler
Inside the template, click Create Handler
Configure:
Method:
GETSource Type:
Media ResourceFormat:
Binary Representationπ§Ύ Step 7.4: Add SQL Query (Handler Source)
Paste the below SQL:
SELECT image_mime AS content_type,
image_blob AS content
FROM PRODUCT_IMAGES
WHERE id =:idπ This tells ORDS:
content_type→ MIME type (image/jpeg, image/png)
content→ actual image BLOBπ This avoids:
Session issues
Broken images
Performance problems
✅ Step 7.5: Apply Changes
Click Apply Changes
Ensure module is Published
π Step 7.6: Test URL
Use this format:
https://oracleapex.com/ords/{workspace}/product-images/{id}
Example:
https://oracleapex.com/ords/sabarinath/product-images/1
π It should directly display the image in browser⚙️ Step 8: APEX Page Configuration
Page 2 (Product Upload)
Product Name
File Upload
Process → Store embedding
Page 4 (Search Image)
File Upload
Button: Search Similar Image
Processes:
Insert Image (Embedding)
Search Similar Images
Refresh Report
π― Step 9: Final Output
User flow:
Upload image
Click Search Similar Image
See results like:
Car → Similar Cars
Bike → Similar Bikes
π Advanced Improvements
You can enhance this project by adding:
π Similarity slider (dynamic threshold)
π Category-based filtering
⚡ Vector Index for performance
π€ Multi-modal search (text + image)
π Conclusion
We successfully built an AI-powered Image Similarity Search system in Oracle APEX using:
Cohere for embeddings
Oracle 23ai VECTOR for storage
APEX for UI
This is a powerful real-world use case of AI inside Oracle applications.
Comments
Post a Comment