Skip to content

adm_ai_vector_api

Native Oracle AI Vector Search backend (Oracle 23ai / 26ai+).

Alternative to adm_ai_qdrant_api: stores embeddings in the database using the native VECTOR datatype and searches with VECTOR_DISTANCE, instead of an external Qdrant server. Selected per RAG collection via config_json.vector_store = ‘ORACLE’.

Each ORACLE-backed collection gets its own table adm_ai_rag_vec_<rag_collection_id> with a fixed VECTOR(<dims>, FLOAT32) column and a vector index, so collections with different embedding dimensions can coexist.

All vector SQL is dynamic (object names are per-collection), so this package COMPILES on every Oracle version; it only raises at runtime if invoked on a pre-23ai database. No VECTOR datatype appears in this spec, so it parses everywhere.

The index is configuration, not a constant

Section titled “The index is configuration, not a constant”

How the index is built comes from the collection’s config_json.vector_index section (see the RAG configuration guide). Everything in it is optional; the resolved defaults are IVF at target accuracy 95, unpartitioned, no quantization.

apply_index_config is the single reconcile step: it compares that desired configuration against what adm_ai_rag_vector_stores says was last applied and rebuilds only what differs. It is idempotent, so a release migration that needs to change index settings does nothing more than loop over the collections and call it.

begin
adm_context_api.system_login;
for c in (select rag_collection_id from adm_ai_rag_collections) loop
adm_ai_vector_api.apply_index_config(p_rag_collection_id => c.rag_collection_id);
commit;
end loop;
end;

The commit in that loop is required. None of the DDL here is transactional - it commits on its own - but the row apply_index_config writes to adm_ai_rag_vector_stores afterwards is ordinary DML, and nothing in this package may commit it for you. Roll that transaction back and the store is left recorded as PENDING while its index sits there perfectly healthy, and every later reconcile will dutifully drop and rebuild an index that was never wrong. Commit after calling.

The state of one collection’s vector store, as last applied by apply_index_config. A NULL table_name means no store has been provisioned for the collection.

Definition:

type r_store_status_type is record (
rag_collection_id number,
table_name varchar2(128 char),
dimensions number,
distance_metric varchar2(30 char),
index_type varchar2(20 char),
index_status varchar2(20 char),
partition_count number,
applied_date timestamp with local time zone,
last_error varchar2(4000 char)
);

Fields:

NameTypeDescription
rag_collection_idnumber-
table_namevarchar2(128 char)-
dimensionsnumber-
distance_metricvarchar2(30 char)-
index_typevarchar2(20 char)-
index_statusvarchar2(20 char)-
partition_countnumber-
applied_datetimestamp with local time zone-
last_errorvarchar2(4000 char)-

Create the per-collection vector store table and bring its index in line with the collection configuration. Idempotent: an already-correct store is left untouched.

Signature:

procedure create_collection_store (
p_rag_collection_id in number,
p_dimensions in number,
p_distance in varchar2 default 'Cosine'
);

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection
p_dimensionsinnumberNumber of dimensions of the embedding vectors
p_distanceinvarchar2 default 'Cosine'Distance metric (‘Cosine’, ‘Euclidean’ or ‘Dot’); default ‘Cosine’

Reconcile a collection’s vector store with its stored configuration.

Reads config_json.vector_index, compares it against adm_ai_rag_vector_stores and the data dictionary, and does the least work that makes them agree: nothing at all, a drop-and-recreate of the index, or a rebuild of the store table when the hash partitioning changed. The outcome (including a failed index build, which used to be swallowed) is recorded in adm_ai_rag_vector_stores.

Refuses to run when embedding.dimensions no longer matches the store: the VECTOR column’s dimension count cannot be altered, and dropping the embeddings to rebuild at a new width is not a decision this procedure may take on its own.

Signature:

procedure apply_index_config (
p_rag_collection_id in number,
p_force in boolean default false
);

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection
p_forceinboolean default falseRebuild the index even when the configuration has not changed

What was last applied to a collection’s vector store. Use it to find out whether the index actually exists - a build can fail (an HNSW index needs VECTOR_MEMORY_SIZE > 0) and searches then silently fall back to an exact scan.

Signature:

function get_store_status (
p_rag_collection_id in number
) return r_store_status_type;

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection

Returns: r_store_status_type - The recorded store state; table_name is NULL when there is no store


Drop the per-collection vector store table. Safe to call if it does not exist.

Signature:

procedure drop_collection_store (
p_rag_collection_id in number
);

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection

Upsert (merge) embedding vectors for a batch of chunks into the collection store.

Signature:

procedure upsert_points (
p_rag_collection_id in number,
p_chunk_ids in apex_t_number,
p_file_ids in apex_t_number,
p_vectors in json_array_t
);

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection
p_chunk_idsinapex_t_numberArray of chunk IDs (parallel to p_file_ids and p_vectors)
p_file_idsinapex_t_numberArray of owning rag_collection_file_ids (the hash partition key)
p_vectorsinjson_array_tJSON array of vectors (each element is a JSON array of numbers)

Delete every vector belonging to the given collection files from the collection store, which is what a REMOVE job does when a file leaves a collection.

rag_collection_file_id is the store’s hash partition key, so the delete is partition-local. Safe to call when the store does not exist, or with an empty list: both do nothing rather than deleting everything.

Signature:

procedure delete_points (
p_rag_collection_id in number,
p_file_ids in apex_t_number
);

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection
p_file_idsinapex_t_numberOwning rag_collection_file_ids whose vectors are to be deleted

Search for similar vectors in the collection store. Returns the same JSON shape as adm_ai_qdrant_api.search_similar: a JSON array of objects { “id”: , “score”: }.

The three accuracy parameters are the query-side counterpart of the index’s build-time target accuracy. Pass none of them to search at the accuracy the index was built for. They are mutually exclusive: p_target_accuracy wins over the two internal parameters.

Signature:

function search_similar (
p_rag_collection_id in number,
p_vector in json_array_t,
p_limit in number default 10,
p_score_threshold in number default 0.5,
p_distance in varchar2 default 'Cosine',
p_target_accuracy in number default null,
p_efsearch in number default null,
p_probes in number default null,
p_file_ids in apex_t_number default null
) return json_array_t;

Parameters:

NameDirectionTypeDescription
p_rag_collection_idinnumberID of the RAG collection
p_vectorinjson_array_tQuery vector (JSON array of numbers)
p_limitinnumber default 10Maximum number of results to return
p_score_thresholdinnumber default 0.5Minimum similarity score (results below are dropped)
p_distanceinvarchar2 default 'Cosine'Distance metric (‘Cosine’, ‘Euclidean’ or ‘Dot’); default ‘Cosine’
p_target_accuracyinnumber default nullTarget accuracy percentage (1..100) for this query
p_efsearchinnumber default nullHNSW candidates to consider for this query (1..65535)
p_probesinnumber default nullIVF neighbor partitions to probe for this query
p_file_idsinapex_t_number default nullRestrict the search to the chunks of these rag_collection_file_ids;
null (the default) searches the whole collection. An empty collection returns no
rows rather than searching everything, because "restrict to nothing" must not read
as "restrict to anything". A restricted search is always EXACT: an approximate
index scan filters after it has picked its candidates, so it can return far fewer
rows than asked for - or none - when the predicate is narrow, which is exactly the
case here. One document's chunks are few, so exact costs little. |

Returns: json_array_t - JSON array of { id, score } objects, highest score first