214 lines
6.7 KiB
MySQL
214 lines
6.7 KiB
MySQL
-- ============================================================
|
|
-- 59_kb_select_ai_router_ords.sql
|
|
--
|
|
-- POST /ords/cb-ords/kb-select-ai-agent/run
|
|
-- Authorization: Bearer <VPD token>
|
|
-- {"prompt":"...","conversationId":"optional-safe-id"}
|
|
--
|
|
-- The ORDS runtime validates the bearer and sets the application context
|
|
-- before invoking the POC_2-owned Team API. The context is session scoped,
|
|
-- so VPD still applies to any SQL action selected by the Select AI tool.
|
|
--
|
|
-- Execute as CB_ORDS after 22 and 58.
|
|
-- ============================================================
|
|
WHENEVER SQLERROR EXIT SQL.SQLCODE
|
|
SET ECHO OFF
|
|
SET FEEDBACK ON
|
|
SET DEFINE OFF
|
|
|
|
PROMPT === Creating local ORDS Team API wrapper ===
|
|
CREATE OR REPLACE PACKAGE cb_select_ai_ords_api AUTHID DEFINER AS
|
|
FUNCTION run_team(
|
|
p_prompt IN CLOB,
|
|
p_conversation_id IN VARCHAR2
|
|
) RETURN CLOB;
|
|
END cb_select_ai_ords_api;
|
|
/
|
|
|
|
CREATE OR REPLACE PACKAGE BODY cb_select_ai_ords_api AS
|
|
FUNCTION run_team(
|
|
p_prompt IN CLOB,
|
|
p_conversation_id IN VARCHAR2
|
|
) RETURN CLOB AS
|
|
BEGIN
|
|
RETURN poc_2.kb_select_ai_router_api.run_team(
|
|
p_prompt => p_prompt,
|
|
p_conversation_id => p_conversation_id
|
|
);
|
|
END run_team;
|
|
END cb_select_ai_ords_api;
|
|
/
|
|
|
|
SHOW ERRORS
|
|
|
|
-- ORDS validates named PL/SQL references through its gateway account before
|
|
-- dispatching to the CB_ORDS parsing schema. These are the only two local
|
|
-- wrappers exposed to that account; both remain definer-rights and fixed in
|
|
-- scope (Bearer context setup and one fixed Team invocation).
|
|
GRANT EXECUTE ON cb_ords_handler_pkg TO ords_public_user;
|
|
GRANT EXECUTE ON cb_select_ai_ords_api TO ords_public_user;
|
|
|
|
PROMPT === Resetting only the KB Select AI Agent ORDS module ===
|
|
BEGIN
|
|
ORDS.DELETE_MODULE(p_module_name => 'kb.select.ai.agent');
|
|
EXCEPTION
|
|
WHEN OTHERS THEN NULL;
|
|
END;
|
|
/
|
|
|
|
PROMPT === Creating KB Select AI Agent ORDS endpoint ===
|
|
BEGIN
|
|
ORDS.DEFINE_MODULE(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_base_path => 'kb-select-ai-agent/',
|
|
p_items_per_page => 0,
|
|
p_status => 'PUBLISHED'
|
|
);
|
|
|
|
ORDS.DEFINE_TEMPLATE(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'run'
|
|
);
|
|
|
|
ORDS.DEFINE_TEMPLATE(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'health'
|
|
);
|
|
|
|
ORDS.DEFINE_HANDLER(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'health',
|
|
p_method => 'GET',
|
|
p_source_type => ORDS.source_type_plsql,
|
|
p_source => q'~
|
|
BEGIN
|
|
:status_code := 200;
|
|
OWA_UTIL.MIME_HEADER('application/json', FALSE);
|
|
HTP.P('Cache-Control: no-store');
|
|
OWA_UTIL.HTTP_HEADER_CLOSE;
|
|
HTP.P('{"status":"ok","team":"KB_SELECT_AI_ROUTER_TEAM"}');
|
|
END;
|
|
~',
|
|
p_items_per_page => 0
|
|
);
|
|
|
|
ORDS.DEFINE_HANDLER(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'run',
|
|
p_method => 'POST',
|
|
p_source_type => ORDS.source_type_plsql,
|
|
p_source => q'~
|
|
DECLARE
|
|
v_body_text CLOB;
|
|
v_prompt VARCHAR2(32767);
|
|
v_requested_conversation_id VARCHAR2(128);
|
|
v_internal_conversation_id VARCHAR2(128);
|
|
v_stakeholder_user_id VARCHAR2(4000);
|
|
v_answer CLOB;
|
|
v_response CLOB;
|
|
v_error_code NUMBER;
|
|
v_error_message VARCHAR2(4000);
|
|
BEGIN
|
|
cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id);
|
|
|
|
-- ORDS exposes request JSON as a stream bind. Read it once only.
|
|
v_body_text := :body_text;
|
|
v_prompt := JSON_VALUE(v_body_text, '$.prompt' RETURNING VARCHAR2(32767));
|
|
v_requested_conversation_id := JSON_VALUE(v_body_text, '$.conversationId' RETURNING VARCHAR2(128));
|
|
|
|
IF v_prompt IS NULL OR TRIM(v_prompt) IS NULL THEN
|
|
RAISE_APPLICATION_ERROR(-20801, 'prompt is required');
|
|
END IF;
|
|
IF v_requested_conversation_id IS NULL THEN
|
|
v_requested_conversation_id := 'kb-' || LOWER(RAWTOHEX(SYS_GUID()));
|
|
ELSIF NOT REGEXP_LIKE(v_requested_conversation_id, '^[A-Za-z0-9._:-]{1,128}$') THEN
|
|
RAISE_APPLICATION_ERROR(-20802, 'conversationId contains unsupported characters');
|
|
END IF;
|
|
|
|
-- A connection-pooled ORDS session can be reused. Scope the Team's
|
|
-- conversation memory to the VPD subject as well as the caller's public ID.
|
|
v_stakeholder_user_id := NVL(SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'), 'anonymous');
|
|
v_internal_conversation_id := 'kb-' || LOWER(RAWTOHEX(STANDARD_HASH(
|
|
v_stakeholder_user_id || ':' || v_requested_conversation_id,
|
|
'SHA256'
|
|
)));
|
|
|
|
-- ORDS validates static PL/SQL references before executing a Handler.
|
|
-- Invoke the local, fixed-scope Team wrapper through bound dynamic PL/SQL
|
|
-- so the runtime parser only sees the trusted local context package.
|
|
EXECUTE IMMEDIATE
|
|
'BEGIN :result := cb_select_ai_ords_api.run_team(:prompt, :conversation_id); END;'
|
|
USING OUT v_answer, IN v_prompt, IN v_internal_conversation_id;
|
|
|
|
SELECT JSON_OBJECT(
|
|
'team' VALUE 'KB_SELECT_AI_ROUTER_TEAM',
|
|
'conversationId' VALUE v_requested_conversation_id,
|
|
'answer' VALUE v_answer
|
|
RETURNING CLOB
|
|
)
|
|
INTO v_response
|
|
FROM dual;
|
|
|
|
:status_code := 200;
|
|
OWA_UTIL.MIME_HEADER('application/json', FALSE);
|
|
HTP.P('Cache-Control: no-store');
|
|
OWA_UTIL.HTTP_HEADER_CLOSE;
|
|
HTP.P(v_response);
|
|
|
|
cb_ords_handler_pkg.clear_vpd_context;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
v_error_code := SQLCODE;
|
|
v_error_message := SQLERRM;
|
|
cb_ords_handler_pkg.clear_vpd_context;
|
|
:status_code := CASE
|
|
WHEN v_error_code IN (-20801, -20802) OR v_error_code BETWEEN -40599 AND -40400 THEN 400
|
|
WHEN v_error_code BETWEEN -20199 AND -20100 THEN 403
|
|
ELSE 500
|
|
END;
|
|
SELECT JSON_OBJECT(
|
|
'errorCode' VALUE v_error_code,
|
|
'error' VALUE v_error_message
|
|
RETURNING CLOB
|
|
)
|
|
INTO v_response
|
|
FROM dual;
|
|
OWA_UTIL.MIME_HEADER('application/json', FALSE);
|
|
HTP.P('Cache-Control: no-store');
|
|
OWA_UTIL.HTTP_HEADER_CLOSE;
|
|
HTP.P(v_response);
|
|
END;
|
|
~',
|
|
p_items_per_page => 0
|
|
);
|
|
|
|
ORDS.DEFINE_PARAMETER(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'run',
|
|
p_method => 'POST',
|
|
p_name => 'Authorization',
|
|
p_bind_variable_name => 'auth_header',
|
|
p_source_type => 'HEADER',
|
|
p_param_type => 'STRING',
|
|
p_access_method => 'IN'
|
|
);
|
|
|
|
ORDS.DEFINE_PARAMETER(
|
|
p_module_name => 'kb.select.ai.agent',
|
|
p_pattern => 'run',
|
|
p_method => 'POST',
|
|
p_name => 'X-VPD-Probe-Id',
|
|
p_bind_variable_name => 'probe_id',
|
|
p_source_type => 'HEADER',
|
|
p_param_type => 'STRING',
|
|
p_access_method => 'IN'
|
|
);
|
|
|
|
COMMIT;
|
|
END;
|
|
/
|
|
|
|
PROMPT === KB Select AI Agent ORDS endpoint ready ===
|
|
PROMPT Path: /ords/cb-ords/kb-select-ai-agent/run
|
|
EXIT
|