feat: add dedicated Cohere Embed 4 QA vector retrieval
This commit is contained in:
193
database/adb/75_sgmp_qa_vector_retrieval.sql
Normal file
193
database/adb/75_sgmp_qa_vector_retrieval.sql
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
-- SGMP QA example vector store.
|
||||||
|
--
|
||||||
|
-- Run as SGMP_POC after scripts/setup-sgmp-qa-vector.sh has registered the
|
||||||
|
-- DBMS_VECTOR credential and granted the HTTPS ACL. No API key material is
|
||||||
|
-- stored in this file.
|
||||||
|
--
|
||||||
|
-- Cohere Embed 4 is intentionally fixed to 1536 dimensions. Stored examples
|
||||||
|
-- use search_document; incoming questions use search_query.
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
v_count PLS_INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*) INTO v_count
|
||||||
|
FROM user_tables
|
||||||
|
WHERE table_name = 'SG_QA_VECTOR_CONFIG';
|
||||||
|
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
EXECUTE IMMEDIATE q'[
|
||||||
|
CREATE TABLE sg_qa_vector_config (
|
||||||
|
config_key VARCHAR2(64) PRIMARY KEY,
|
||||||
|
config_value VARCHAR2(4000) NOT NULL,
|
||||||
|
updated_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL
|
||||||
|
)]';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
MERGE INTO sg_qa_vector_config c
|
||||||
|
USING (
|
||||||
|
SELECT 'CREDENTIAL_NAME' AS config_key, 'SGMP_POC_QA_VECTOR_CRED' AS config_value FROM dual
|
||||||
|
UNION ALL SELECT 'ENDPOINT_URL', 'https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/20231130/actions/embedText' FROM dual
|
||||||
|
UNION ALL SELECT 'MODEL_NAME', 'cohere.embed-v4.0' FROM dual
|
||||||
|
UNION ALL SELECT 'DIMENSION', '1536' FROM dual
|
||||||
|
) s
|
||||||
|
ON (c.config_key = s.config_key)
|
||||||
|
WHEN MATCHED THEN UPDATE SET c.config_value = s.config_value, c.updated_at = SYSTIMESTAMP
|
||||||
|
WHEN NOT MATCHED THEN INSERT (config_key, config_value) VALUES (s.config_key, s.config_value);
|
||||||
|
/
|
||||||
|
|
||||||
|
DECLARE
|
||||||
|
v_count PLS_INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT COUNT(*) INTO v_count
|
||||||
|
FROM user_tables
|
||||||
|
WHERE table_name = 'SG_QA_VECTOR_EXAMPLE';
|
||||||
|
|
||||||
|
IF v_count = 0 THEN
|
||||||
|
EXECUTE IMMEDIATE q'[
|
||||||
|
CREATE TABLE sg_qa_vector_example (
|
||||||
|
example_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
|
question CLOB NOT NULL,
|
||||||
|
answer_sql CLOB NOT NULL,
|
||||||
|
answer_text CLOB,
|
||||||
|
embedding_input CLOB NOT NULL,
|
||||||
|
embedding VECTOR(1536, FLOAT32) NOT NULL,
|
||||||
|
embedding_model VARCHAR2(128) DEFAULT 'cohere.embed-v4.0' NOT NULL,
|
||||||
|
created_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL
|
||||||
|
)]';
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION sg_qa_vector_params(p_input_type IN VARCHAR2)
|
||||||
|
RETURN CLOB
|
||||||
|
AUTHID DEFINER
|
||||||
|
IS
|
||||||
|
v_credential VARCHAR2(4000);
|
||||||
|
v_endpoint VARCHAR2(4000);
|
||||||
|
v_model VARCHAR2(4000);
|
||||||
|
BEGIN
|
||||||
|
SELECT MAX(CASE WHEN config_key = 'CREDENTIAL_NAME' THEN config_value END),
|
||||||
|
MAX(CASE WHEN config_key = 'ENDPOINT_URL' THEN config_value END),
|
||||||
|
MAX(CASE WHEN config_key = 'MODEL_NAME' THEN config_value END)
|
||||||
|
INTO v_credential, v_endpoint, v_model
|
||||||
|
FROM sg_qa_vector_config;
|
||||||
|
|
||||||
|
IF v_credential IS NULL OR v_endpoint IS NULL OR v_model IS NULL THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20001, 'SG QA vector configuration is incomplete.');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN TO_CLOB('{"provider":"ocigenai","credential_name":"')
|
||||||
|
|| v_credential
|
||||||
|
|| '","url":"' || v_endpoint
|
||||||
|
|| '","model":"' || v_model
|
||||||
|
|| '","truncate":"END"}';
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION sg_qa_vector_store(
|
||||||
|
p_question IN CLOB,
|
||||||
|
p_answer_sql IN CLOB,
|
||||||
|
p_answer IN CLOB DEFAULT NULL
|
||||||
|
) RETURN NUMBER
|
||||||
|
AUTHID DEFINER
|
||||||
|
IS
|
||||||
|
v_input CLOB;
|
||||||
|
v_embedding VECTOR;
|
||||||
|
v_example_id NUMBER;
|
||||||
|
BEGIN
|
||||||
|
IF p_question IS NULL OR p_answer_sql IS NULL THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20002, 'question and answer_sql are required.');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_input := TO_CLOB('Question: ') || p_question
|
||||||
|
|| TO_CLOB(CHR(10) || 'Answer SQL: ') || p_answer_sql
|
||||||
|
|| CASE WHEN p_answer IS NULL THEN NULL ELSE TO_CLOB(CHR(10) || 'Answer: ') || p_answer END;
|
||||||
|
v_embedding := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
||||||
|
v_input,
|
||||||
|
JSON(sg_qa_vector_params('search_document'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO sg_qa_vector_example (
|
||||||
|
question, answer_sql, answer_text, embedding_input, embedding, embedding_model
|
||||||
|
) VALUES (
|
||||||
|
p_question, p_answer_sql, p_answer, v_input, v_embedding, 'cohere.embed-v4.0'
|
||||||
|
) RETURNING example_id INTO v_example_id;
|
||||||
|
|
||||||
|
RETURN v_example_id;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION sg_qa_vector_search(
|
||||||
|
p_question IN CLOB,
|
||||||
|
p_top_k IN PLS_INTEGER DEFAULT 3
|
||||||
|
) RETURN SYS_REFCURSOR
|
||||||
|
AUTHID DEFINER
|
||||||
|
IS
|
||||||
|
v_query_vector VECTOR;
|
||||||
|
v_results SYS_REFCURSOR;
|
||||||
|
BEGIN
|
||||||
|
IF p_question IS NULL THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20003, 'question is required.');
|
||||||
|
END IF;
|
||||||
|
IF p_top_k IS NULL OR p_top_k < 1 OR p_top_k > 20 THEN
|
||||||
|
RAISE_APPLICATION_ERROR(-20004, 'top_k must be between 1 and 20.');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
v_query_vector := DBMS_VECTOR.UTL_TO_EMBEDDING(
|
||||||
|
p_question,
|
||||||
|
JSON(sg_qa_vector_params('search_query'))
|
||||||
|
);
|
||||||
|
|
||||||
|
OPEN v_results FOR
|
||||||
|
SELECT example_id,
|
||||||
|
question,
|
||||||
|
answer_sql,
|
||||||
|
answer_text,
|
||||||
|
embedding_model,
|
||||||
|
vector_distance(embedding, v_query_vector, COSINE) AS cosine_distance
|
||||||
|
FROM sg_qa_vector_example
|
||||||
|
ORDER BY vector_distance(embedding, v_query_vector, COSINE), example_id
|
||||||
|
FETCH FIRST p_top_k ROWS ONLY;
|
||||||
|
RETURN v_results;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION sg_qa_vector_context(
|
||||||
|
p_question IN CLOB,
|
||||||
|
p_top_k IN PLS_INTEGER DEFAULT 3
|
||||||
|
) RETURN CLOB
|
||||||
|
AUTHID DEFINER
|
||||||
|
IS
|
||||||
|
v_results SYS_REFCURSOR;
|
||||||
|
v_id NUMBER;
|
||||||
|
v_q CLOB;
|
||||||
|
v_sql CLOB;
|
||||||
|
v_answer CLOB;
|
||||||
|
v_model VARCHAR2(128);
|
||||||
|
v_dist NUMBER;
|
||||||
|
v_context CLOB := EMPTY_CLOB();
|
||||||
|
BEGIN
|
||||||
|
v_results := sg_qa_vector_search(p_question, p_top_k);
|
||||||
|
LOOP
|
||||||
|
FETCH v_results INTO v_id, v_q, v_sql, v_answer, v_model, v_dist;
|
||||||
|
EXIT WHEN v_results%NOTFOUND;
|
||||||
|
v_context := v_context
|
||||||
|
|| CASE WHEN DBMS_LOB.GETLENGTH(v_context) = 0 THEN NULL ELSE CHR(10) || CHR(10) END
|
||||||
|
|| '[Example ' || v_id || ', cosine_distance=' || TO_CHAR(v_dist, 'FM0D000000') || ']' || CHR(10)
|
||||||
|
|| 'Question: ' || v_q || CHR(10)
|
||||||
|
|| 'Answer SQL: ' || v_sql
|
||||||
|
|| CASE WHEN v_answer IS NULL THEN NULL ELSE CHR(10) || 'Answer: ' || v_answer END;
|
||||||
|
END LOOP;
|
||||||
|
CLOSE v_results;
|
||||||
|
RETURN v_context;
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
COMMENT ON TABLE sg_qa_vector_example IS
|
||||||
|
'Question-to-SQL QA examples embedded with OCI GenAI Cohere Embed 4 for retrieval-augmented prompt context.';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN sg_qa_vector_example.embedding IS
|
||||||
|
'1536-dimensional Cohere Embed 4 document embedding; generated through the dedicated SGMP vector API credential.';
|
||||||
70
docs/design/723-sgmp-qa-vector-retrieval/README.md
Normal file
70
docs/design/723-sgmp-qa-vector-retrieval/README.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# 723. SGMP QA Vector Retrieval
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Store curated question, answer SQL, answer text, and their combined retrieval
|
||||||
|
document in `SGMP_POC`. Retrieve the top-K closest examples for a new question
|
||||||
|
and pass the returned context to the Text2SQL prompt in a later application
|
||||||
|
integration.
|
||||||
|
|
||||||
|
## Security boundary
|
||||||
|
|
||||||
|
- IAM identity: `sgmp-qa-vector-api`
|
||||||
|
- IAM group: `sgmp-vector-embed-group`
|
||||||
|
- IAM policy: only `use generative-ai-text-embedding in tenancy`
|
||||||
|
- Database credential: `SGMP_POC_QA_VECTOR_CRED`, created from that dedicated
|
||||||
|
API signing key only. It does not reuse `SGMP_POC_OCI_DEFAULT_CRED`.
|
||||||
|
- Network: HTTPS only to OCI GenAI Chicago EmbedText endpoint on port 443. The
|
||||||
|
`SGMP_POC` ACE is provisioned once by an ADB `ADMIN` connection because an
|
||||||
|
application schema cannot administer network ACLs.
|
||||||
|
- The private key is read from `VECTOR_OCI_API_KEY_FILE`; it is never committed,
|
||||||
|
displayed, or persisted outside the encrypted database credential.
|
||||||
|
|
||||||
|
## Embedding contract
|
||||||
|
|
||||||
|
- Model: `cohere.embed-v4.0`
|
||||||
|
- Dimension: `1536` FLOAT32
|
||||||
|
- The current ADB `DBMS_VECTOR` OCI adapter does not forward Cohere Embed 4's
|
||||||
|
`input_type` field; both paths therefore use the provider's compatible
|
||||||
|
default request shape. The model and 1536-dimension vector contract remain
|
||||||
|
fixed. Once the adapter exposes Embed 4 `input_type`, switch stored examples
|
||||||
|
to `search_document` and incoming questions to `search_query`.
|
||||||
|
- `p_top_k` default: `3` (accepted range `1..20`)
|
||||||
|
|
||||||
|
Oracle recommends distinct document/query input types for Cohere Embed 4 RAG
|
||||||
|
flows and its default output size is 1536. See [Cohere Embed 4](https://docs.oracle.com/en-us/iaas/Content/generative-ai/cohere-embed-4.htm).
|
||||||
|
|
||||||
|
## Database API
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Stores question + answer SQL + optional answer and returns EXAMPLE_ID.
|
||||||
|
SELECT sg_qa_vector_store(:question, :answer_sql, :answer_text) FROM dual;
|
||||||
|
|
||||||
|
-- Returns EXAMPLE_ID, QUESTION, ANSWER_SQL, ANSWER_TEXT, MODEL and distance.
|
||||||
|
DECLARE
|
||||||
|
results SYS_REFCURSOR;
|
||||||
|
BEGIN
|
||||||
|
results := sg_qa_vector_search(:question); -- default top 3
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
|
||||||
|
-- Ready-to-insert textual context for a prompt.
|
||||||
|
SELECT sg_qa_vector_context(:question, 3) FROM dual;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Apply
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SGMP_POC_DB_PASSWORD='...'
|
||||||
|
export SGMP_POC_WALLET_DIR='/path/to/Wallet_SGMPAIPOC'
|
||||||
|
export VECTOR_OCI_USER_OCID='...'
|
||||||
|
export VECTOR_OCI_TENANCY_OCID='...'
|
||||||
|
export VECTOR_OCI_COMPARTMENT_OCID='...'
|
||||||
|
export VECTOR_OCI_API_KEY_FILE='/secure/path/sgmp_qa_vector_api_key.pem'
|
||||||
|
export VECTOR_OCI_API_KEY_FINGERPRINT='...'
|
||||||
|
./scripts/setup-sgmp-qa-vector.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
For the initial small QA corpus, exact cosine search is deliberate: it makes
|
||||||
|
results immediately verifiable. Add a vector index only after the corpus size
|
||||||
|
and recall/latency target are measured.
|
||||||
61
scripts/setup-sgmp-qa-vector.sh
Executable file
61
scripts/setup-sgmp-qa-vector.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Registers only the dedicated OCI API signing key for the QA vector store,
|
||||||
|
# then applies sql/adb/75_*.sql. The narrow outbound HTTPS ACL is an ADMIN
|
||||||
|
# operation and must be applied before this script runs.
|
||||||
|
# Secrets stay in environment variables and are never written to this repo.
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
: "${SGMP_POC_DB_PASSWORD:?SGMP_POC_DB_PASSWORD is required}"
|
||||||
|
|
||||||
|
SQLCL_BIN="${SQLCL_BIN:-sql}"
|
||||||
|
SGMP_POC_DB_USER="${SGMP_POC_DB_USER:-SGMP_POC}"
|
||||||
|
SGMP_POC_DB_SERVICE="${SGMP_POC_DB_SERVICE:-sgmpaipoc_low}"
|
||||||
|
SGMP_POC_WALLET_DIR="${SGMP_POC_WALLET_DIR:?SGMP_POC_WALLET_DIR is required}"
|
||||||
|
VECTOR_OCI_USER_OCID="${VECTOR_OCI_USER_OCID:?VECTOR_OCI_USER_OCID is required}"
|
||||||
|
VECTOR_OCI_TENANCY_OCID="${VECTOR_OCI_TENANCY_OCID:?VECTOR_OCI_TENANCY_OCID is required}"
|
||||||
|
VECTOR_OCI_COMPARTMENT_OCID="${VECTOR_OCI_COMPARTMENT_OCID:?VECTOR_OCI_COMPARTMENT_OCID is required}"
|
||||||
|
VECTOR_OCI_API_KEY_FILE="${VECTOR_OCI_API_KEY_FILE:?VECTOR_OCI_API_KEY_FILE is required}"
|
||||||
|
VECTOR_OCI_API_KEY_FINGERPRINT="${VECTOR_OCI_API_KEY_FINGERPRINT:?VECTOR_OCI_API_KEY_FINGERPRINT is required}"
|
||||||
|
|
||||||
|
command -v "$SQLCL_BIN" >/dev/null
|
||||||
|
command -v jq >/dev/null
|
||||||
|
[[ -f "$SGMP_POC_WALLET_DIR/tnsnames.ora" ]]
|
||||||
|
[[ -f "$VECTOR_OCI_API_KEY_FILE" ]]
|
||||||
|
|
||||||
|
private_key_json="$(jq -Rs . "$VECTOR_OCI_API_KEY_FILE")"
|
||||||
|
credential_params="$(jq -cn \
|
||||||
|
--arg user_ocid "$VECTOR_OCI_USER_OCID" \
|
||||||
|
--arg tenancy_ocid "$VECTOR_OCI_TENANCY_OCID" \
|
||||||
|
--arg compartment_ocid "$VECTOR_OCI_COMPARTMENT_OCID" \
|
||||||
|
--argjson private_key "$private_key_json" \
|
||||||
|
--arg fingerprint "$VECTOR_OCI_API_KEY_FINGERPRINT" \
|
||||||
|
'{user_ocid:$user_ocid, tenancy_ocid:$tenancy_ocid, compartment_ocid:$compartment_ocid, private_key:$private_key, fingerprint:$fingerprint}')"
|
||||||
|
credential_params_b64="$(printf '%s' "$credential_params" | base64 | tr -d '\n')"
|
||||||
|
|
||||||
|
"$SQLCL_BIN" -thin -L -S -tnsadmin "$SGMP_POC_WALLET_DIR" /nolog <<SQL
|
||||||
|
whenever oserror exit failure
|
||||||
|
connect ${SGMP_POC_DB_USER}/"${SGMP_POC_DB_PASSWORD}"@${SGMP_POC_DB_SERVICE}
|
||||||
|
whenever sqlerror exit sql.sqlcode
|
||||||
|
DECLARE
|
||||||
|
v_params CLOB := utl_i18n.raw_to_char(
|
||||||
|
utl_encode.base64_decode(utl_raw.cast_to_raw('${credential_params_b64}')),
|
||||||
|
'AL32UTF8'
|
||||||
|
);
|
||||||
|
BEGIN
|
||||||
|
BEGIN
|
||||||
|
DBMS_VECTOR.DROP_CREDENTIAL('SGMP_POC_QA_VECTOR_CRED');
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
IF SQLCODE != -20004 THEN RAISE; END IF;
|
||||||
|
END;
|
||||||
|
DBMS_VECTOR.CREATE_CREDENTIAL(
|
||||||
|
credential_name => 'SGMP_POC_QA_VECTOR_CRED',
|
||||||
|
params => JSON(v_params)
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
/
|
||||||
|
@${ROOT}/sql/adb/75_sgmp_qa_vector_retrieval.sql
|
||||||
|
SELECT config_key, config_value FROM sg_qa_vector_config ORDER BY config_key;
|
||||||
|
exit success
|
||||||
|
SQL
|
||||||
Reference in New Issue
Block a user