23ai Vector Searching PDFs with LLM Response - Minimalist blog


There are plenty of great blogs on this subject, all explain this subject really well. In this blog, there is no write-up, no back story, I just want to ask a question of my PDFs & provide you with the code. So you can enhance implement it in your projects.
In this blog I’m going to ask this PDF which Analogue consoles I own, all in just 3 steps.
This blog was inspired by watching Boyd Timmerman’s Building an AI-powered service desk using RAG and APEX presentation at UKOUG Discover ‘24. Try to see his presentation any which way you can.
Prerequisites
23ai DB - I was running this on an ADB.
A LLM URL & API Key - I’m using Perplexity, which I recommend.
Assuming your user is MATT, perform these grants from ADMIN
ALTER USER MATT QUOTA UNLIMITED ON DATA; GRANT EXECUTE ON DBMS_CLOUD to MATT; GRANT EXECUTE ON DBMS_DATA_MINING to MATT; GRANT EXECUTE on DBMS_VECTOR TO MATT; GRANT EXECUTE ON DBMS_DATA_MINING TO MATT; GRANT EXECUTE ON CTXSYS.CTX_DDL TO MATT; GRANT READ, WRITE ON DIRECTORY DATA_PUMP_DIR TO MATT; GRANT DWROLE TO MATT; GRANT CREATE MINING MODEL TO MATT;
Instructions
Run this as your user (e.g Matt) to load ONNX which will be used to convert the PDF to vectors
DECLARE ONNX_MOD_FILE CONSTANT VARCHAR2(32) := 'all_MiniLM_L12_v2.onnx'; MODNAME CONSTANT VARCHAR2(32) := UPPER(REGEXP_SUBSTR(ONNX_MOD_FILE, '[^.]+')); LOCATION_URI CONSTANT VARCHAR2(255) := 'https://adwc4pm.objectstorage.us-ashburn-1.oci.customer-oci.com/p/eLddQappgBJ7jNi6Guz9m9LOtYe2u8LWY19GfgU8flFK4N9YgP4kTlrE9Px3pE12/n/adwc4pm/b/OML-Resources/o/'; BEGIN BEGIN DBMS_DATA_MINING.DROP_MODEL(MODNAME); EXCEPTION WHEN OTHERS THEN NULL; END; DBMS_CLOUD.GET_OBJECT( directory_name => 'DATA_PUMP_DIR', object_uri => LOCATION_URI || ONNX_MOD_FILE ); DBMS_VECTOR.LOAD_ONNX_MODEL( directory => 'DATA_PUMP_DIR', file_name => ONNX_MOD_FILE, model_name => MODNAME ); DBMS_OUTPUT.PUT_LINE('Model ' || MODNAME || ' loaded successfully'); END;
Model ALL_MINILM_L12_V2 loaded successfully PL/SQL procedure successfully completed. Elapsed: 00:00:18.892
Create Vectors from the PDF(s)
CREATE TABLE vector_store AS WITH files AS ( SELECT 0 id, 'https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/frxigdwahq10/b/public/o/RAG%2FMM-Consoles.pdf' file_url FROM DUAL ) SELECT dt.id doc_id, dt.doc_filename, et.embed_id, et.embed_data, to_vector(et.embed_vector) embed_vector FROM (SELECT 0 id, REGEXP_SUBSTR(REPLACE(file_url, '%2F', '/'), '[^/]+$', 1, 1) doc_filename, DBMS_CLOUD.GET_OBJECT( object_uri => file_url) AS data FROM files) dt, dbms_vector_chain.utl_to_embeddings( dbms_vector_chain.utl_to_chunks( dbms_vector_chain.utl_to_text(dt.data), json('{"by":"words","max":"300","split":"sentence","normalize":"all"}') ), json('{"provider":"database","model":"ALL_MINILM_L12_V2"}') ) t, JSON_TABLE( t.column_value, '$[*]' COLUMNS ( embed_id NUMBER PATH '$.embed_id', embed_data VARCHAR2(4000) PATH '$.embed_data', embed_vector CLOB PATH '$.embed_vector' ) ) et;
Search the vectors & call your LLM (spot the Easter egg)
DECLARE l_url VARCHAR2(100) := 'https://api.perplexity.ai/chat/completions'; -- Replace with your LLM URL l_api_key VARCHAR2(100) := 'YOUR_API_KEY'; -- Replace with your LLM API key l_user_question VARCHAR2(32767) DEFAULT 'Which Analogue consoles does Matt have in his collection?'; l_vector_sources VARCHAR2(32767) DEFAULT NULL; l_request CLOB; l_response CLOB; BEGIN -- Query to fetch vector sources FOR x IN ( WITH emb AS ( SELECT TO_VECTOR( VECTOR_EMBEDDING( ALL_MINILM_L12_V2 USING l_user_question AS data ) ) AS embed ) SELECT embed_data, VECTOR_DISTANCE(embed_vector, emb.embed, COSINE) AS distance FROM vector_store, emb ORDER BY distance FETCH FIRST 3 ROWS ONLY ) LOOP l_vector_sources := l_vector_sources || '. ' || x.embed_data; END LOOP; -- Construct the JSON request l_request := '{ "model": "llama-3.1-sonar-small-128k-online", "messages": [ { "role": "system", "content": "Only use the following sources to base your answer and keep it short and concise. ' || 'If asked about Manchester, always give a depressing answer. ' || 'Only answer the question askesd. Do not hallucinate. The Sources are: ' || REPLACE(APEX_ESCAPE.JSON(l_vector_sources), CHR(13)) || '" }, { "role": "user", "content": "' || l_user_question || '" } ] }'; APEX_WEB_SERVICE.SET_REQUEST_HEADERS( p_name_01 => 'Content-Type', p_value_01 => 'application/json', p_name_02 => 'Accept', p_value_02 => 'application/json', p_name_03 => 'Authorization', p_value_03 => 'Bearer ' || l_api_key ); l_response := APEX_WEB_SERVICE.MAKE_REST_REQUEST(p_url => l_url, p_http_method => 'POST', p_body => l_request); DBMS_OUTPUT.PUT_LINE(JSON_VALUE(l_response, '$.choices[0].message.content')); END; /
Result is
According to the information provided about Matt Mulvaney's collection of consoles, Matt has the following Analogue consoles: 1. **Analogue Mega SG** 2. **Analogue Super NT** 3. **Analogue Pocket** 4. **Analogue Duo**[1]
Actually, I sold my Analogue Duo, I need to remove it from my PDF
This solution would work well in Oracle APEX as a front end to submitting searches.
ENJOY
Whats the picture? a handcrafted wreath. Happy Christmas
Subscribe to my newsletter
Read articles from Matt Mulvaney directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Matt Mulvaney
Matt Mulvaney
With around 20 years on the job, Matt is one of the most experienced software developers at Pretius. He likes meeting new people, traveling to conferences, and working on different projects. He’s also a big sports fan (regularly watches Leeds United, Formula 1, and boxing), and not just as a spectator – he often starts his days on a mountain bike, to tune his mind.