diff --git a/autonomous-ai-agents/database_inspect/README.md b/autonomous-ai-agents/database_inspect/README.md index 7990eea..5160925 100644 --- a/autonomous-ai-agents/database_inspect/README.md +++ b/autonomous-ai-agents/database_inspect/README.md @@ -1,4 +1,4 @@ -# Select AI Inspect - Database Inspection Tool Built Using Select AI Agent (26ai) +# Select AI Inspect - Database Inspection Tool Built Using Select AI Agent ## Overview @@ -11,7 +11,7 @@ For definitions of **Tool**, **Task**, **Agent**, and **Agent Team**, see the to Instead of manually reviewing tables, searching through PL/SQL files, or examining function and procedure metadata, users can simply ask the Select AI Inspect agent natural language questions such as: -* How are these tables related? +* Which objects reference this table? * Why am I receiving this error from a function call? * What is this function used for? * What objects will be impacted if I modify this package? @@ -36,6 +36,7 @@ Select AI Inspect supports the following database object types: * Tables * Views * Types +* Type Bodies * Triggers * Functions * Procedures @@ -49,10 +50,12 @@ Users may define the inspection scope either at the individual object level or a ## Prerequisites -- Oracle Database 26ai (Autonomous AI Database supported) +- Oracle Autonomous AI Database (26ai) - Select AI and `DBMS_CLOUD_AI_AGENT` enabled - `ADMIN` or equivalent privileged user for installation - A Select AI profile created with `DBMS_CLOUD_AI.CREATE_PROFILE` +- The Select AI profile used for inspection must include an `embedding_model` attribute +- If the Select AI profile uses an OCI Generative AI model/provider, it must also include `oci_compartment_id` --- @@ -64,7 +67,7 @@ Before running installation commands: 2. Open a terminal and change directory to `autonomous-ai-agents/database_inspect`. 3. Choose one execution mode: - SQL*Plus/SQLcl: run script files directly with `@script_name`. - - SQL Worksheet (Database Actions or other SQL IDE): open the `.sql` file and run/paste its contents. + - SQL Developer or another SQL IDE: run the file in script mode so interactive prompts are shown. 4. Uploading scripts to `DATA_PUMP_DIR` is not required for these methods. Run as `ADMIN` (or another privileged user): @@ -74,43 +77,134 @@ sqlplus admin@ @database_inspect_tool.sql sqlplus admin@ @database_inspect_agent.sql ``` -You can also execute the contents of `database_inspect_tool.sql` and `database_inspect_agent.sql` in SQL Worksheet. +`database_inspect_agent.sql` uses interactive prompts, so run it as a script instead of copy/pasting individual statements into a worksheet. + +### Step 1: Install `DATABASE_INSPECT` Package and Tool Framework + +Run `database_inspect_tool.sql` first. + +The script prompts for: + +- `SCHEMA_NAME`: target schema where `DATABASE_INSPECT` is compiled + +What the tools installer does: + +- Grants required execute privileges to the target schema +- Switches `CURRENT_SCHEMA` to the target schema +- Compiles the `DATABASE_INSPECT` package specification and body inline +- Is safe to rerun + +Notes: + +- The tools installer no longer executes `DATABASE_INSPECT.setup` directly from an `ADMIN` anonymous block. This avoids `ORA-06598` when the package is `AUTHID CURRENT_USER`. +- Internal `DATABASE_INSPECT` tables are initialized from the target-schema execution path when the package is invoked by the agent installer or by direct package usage in the target schema. + +### Step 2: Create or Recreate the Inspect Agent Team + +Run `database_inspect_agent.sql` after the package is installed. + +The script prompts for: + +- `INSTALL_SCHEMA_NAME`: schema where the team is created +- `AI_PROFILE_NAME`: profile used for reasoning and embeddings +- `AGENT_TEAM_NAME`: team name to create +- `RECREATE_EXISTING`: `Y` to drop and recreate an existing team, otherwise `N` +- `SCOPE_MODE`: guided scope mode; supported values are `SCHEMA`, `TABLE`, `PACKAGE`, and `JSON` +- `SCOPE_OWNERS`: use this for `SCHEMA` mode; enter one or more schema owners separated by commas, or leave blank to use `INSTALL_SCHEMA_NAME` +- `OBJECT_OWNER`: use this for `TABLE` or `PACKAGE` mode; enter the owner/schema of the listed objects, or leave blank to use `INSTALL_SCHEMA_NAME` +- `OBJECT_NAMES`: use this for `TABLE` or `PACKAGE` mode; enter one or more object names separated by commas +- `MATCH_LIMIT`: optional +- `TEAM_ATTRIBUTES_JSON`: use this for `JSON` mode + +The script now shows one simple input section for these values. Enter only the fields that apply to the selected `SCOPE_MODE` and leave the others blank. + +Guided input examples: + +- Single schema scope: + `SCOPE_MODE = SCHEMA` + `SCOPE_OWNERS = ` +- Multiple schema owners: + `SCOPE_MODE = SCHEMA` + `SCOPE_OWNERS = SH,HR` +- Multiple tables in one schema: + `SCOPE_MODE = TABLE` + `OBJECT_OWNER = SH` + `OBJECT_NAMES = SALES,CUSTOMERS,PRODUCTS` +- Multiple packages in one schema: + `SCOPE_MODE = PACKAGE` + `OBJECT_OWNER = HR` + `OBJECT_NAMES = EMP_PKG,PAYROLL_PKG` + +Advanced `TEAM_ATTRIBUTES_JSON` example for JSON mode: + +```json +{"object_list":[ + {"owner":"SH","type":"TABLE","name":"SALES"}, + {"owner":"SH","type":"TABLE","name":"CUSTOMERS"}, + {"owner":"SH","type":"TABLE","name":"PRODUCTS"} +]} +``` + +What the agent installer does: + +- Grants required privileges for agent creation and background jobs, including `CREATE JOB`, `CREATE SEQUENCE`, `CREATE PROCEDURE`, and `CREATE VIEW` +- Creates `DATABASE_INSPECT_AGENT_JOB_LOG$` in the target schema +- Creates submitter and worker procedures in the target schema +- Validates that `AI_PROFILE_NAME` has a non-null `embedding_model` +- Requires `AI_PROFILE_NAME` to include `oci_compartment_id` when the profile uses an OCI Generative AI model/provider +- Enforces `AI_PROFILE_NAME` as `profile_name`, even if `TEAM_ATTRIBUTES_JSON` includes a different value +- Converts common installation failures into user-friendly log messages, including missing OCI compartment id, tablespace quota errors, and embedding-dimension mismatch errors +- Verifies that the expected agent tools were recreated successfully + +`RECREATE_EXISTING = Y` behavior: + +- Cleans up any existing Select AI team, agent, and task objects for the requested team name +- Drops the existing team metadata and vectorized object state +- Recreates the team using the resolved attributes + +### Monitoring Background Installation + +Each submission writes one row to `DATABASE_INSPECT_AGENT_JOB_LOG$`. + +Use a query like this to monitor progress: + +```sql +SELECT * +FROM .DATABASE_INSPECT_AGENT_JOB_LOG$ +ORDER BY run_id DESC; +``` + +Typical statuses include `PRECHECK`, `QUEUED`, `RUNNING`, `SUCCEEDED`, `FAILED`, `TEAM_EXISTS`, and `FAILED_TO_QUEUE`. --- ## Architecture Overview -```text -Run database_inspect_tool.sql to install DATABASE_INSPECT package and tools - | - v -Run database_inspect_agent.sql to configure and create the inspect agent team - | - v -Execute DATABASE_INSPECT.create_inspect_agent_team(, ) -to create an inspect agent - | - v +Run `database_inspect_tool.sql` to install the `DATABASE_INSPECT` package and prerequisites + ↓ +Run `database_inspect_agent.sql` to validate inputs and queue background installation + ↓ +`DATABASE_INSPECT_AGENT_WORKER` + ├── Ensures `DATABASE_INSPECT.setup` completed + ├── Validates profile and attributes + ├── Optionally drops and recreates existing team state + └── Creates tools, task, team, and vectorized object metadata + ↓ User query - | - v + ↓ - | - v -Agent reasoning - | - +-- LIST_OBJECTS - +-- LIST_INCOMING_DEPENDENCIES - +-- LIST_OUTGOING_DEPENDENCIES - +-- RETRIEVE_OBJECT_METADATA - +-- RETRIEVE_OBJECT_METADATA_CHUNKS - +-- EXPAND_OBJECT_METADATA_CHUNK - +-- SUMMARIZE_OBJECT - `-- GENERATE_PLDOC - | - v -Final verified answer -``` + ↓ +Agent Reasoning + ├── LIST_OBJECTS + ├── LIST_INCOMING_DEPENDENCIES + ├── LIST_OUTGOING_DEPENDENCIES + ├── RETRIEVE_OBJECT_METADATA + ├── RETRIEVE_OBJECT_METADATA_CHUNKS + ├── EXPAND_OBJECT_METADATA_CHUNK + ├── SUMMARIZE_OBJECT + └── GENERATE_PLDOC + ↓ +Final Verified Answer --- @@ -121,12 +215,14 @@ Final verified answer ├── database_inspect_tool.sql │ ├── Installer script for DATABASE_INSPECT package and tool framework │ ├── Grants required privileges to the target schema -│ └── Compiles package specification and body +│ ├── Compiles package specification and body +│ └── Leaves internal table setup to target-schema execution │ ├── database_inspect_agent.sql │ ├── Installer and configuration script for DATABASE_INSPECT AI team -│ ├── Accepts target schema, AI profile, and optional team attributes -│ └── Recreates the inspect team using DATABASE_INSPECT package APIs +│ ├── Accepts target schema, AI profile, team name, recreate flag, and optional team attributes +│ ├── Logs asynchronous installation progress to DATABASE_INSPECT_AGENT_JOB_LOG$ +│ └── Creates or recreates the inspect team using background job execution │ ├── README.md └── README_nl2sql.md @@ -199,23 +295,21 @@ DATABASE_INSPECT.update_inspect_agent_team( | attribute name | description | Mandatory | attribute value format | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- | -| profile_name | AI profile name for the agent team. If provided, the exisiting profile_name value will be overwritten. If a different AI provider is specified, the `object_list` will be re-vectorized due to embedding dimension mismatches. | N | | +| profile_name | AI profile name for the agent team. If provided, the exisiting profile_name value will be overwritten. Any profile change triggers re-vectorization of the `object_list`, because embedding dimensions or similarity semantics can change even within the same provider. | N | | | object_list | List of database objects the agent team is allowed to inspect.
Support "owner", "type" and optional "name" for the object. if "name" is not provided, we will set all objects in the schema.
If provided, the original object_list will be removed and the new object_list will be vectorized. | N | e.g.
'[{"owner":"DEMO", "type":"schema"}]'
'[{"owner":"DEMO", "type":"package body", "name":"CHECKOUT_PKG"}]' | | match_limit | Specifies the maximum number of results to return in a hybrid/vector search query from RETRIEVE_OBJECT_METADATA agent tool. | N | default value is 10 | - --- ## Agent Setup -An inspection agent is created when you call `DATABASE_INSPECT.create_inspect_agent_team`. +An inspection agent can be created directly by calling `DATABASE_INSPECT.create_inspect_agent_team`, but the recommended installation path is to use `database_inspect_agent.sql` so that setup, validation, logging, and background execution are handled for you. ### Supported Tools * **list_objects**: List all available objects for the agent * **list_incoming_dependencies**: List objects that depend on or reference the given object * **list_outgoing_dependencies**: List objects that the given object itself depends on or references -* **generate_graph**: Generate relationship visualizations as Mermaid graph syntax or HTML/CSS tree markup (dependency, hierarchy, binary-tree style, network) * **retrieve_object_metadata**: Retrieve the full metadata for the given object * **retrieve_object_metadata_chunks**: Retrieve a list of metadata chunks by performing hybrid search (vector search + Oracle Text search) to answer user’s query * **expand_object_metadata_chunk**: Given a selected result from the retrieve_object_metadata_chunks tool, returns an expanded metadata segment around the specified chunk to provide additional context @@ -238,14 +332,15 @@ This schema includes more than 10 tables, such as customers, products, orders, a 4. Explain what the CHECKOUT_PKG.reprice_order procedure is used for, including its purpose, parameters and business rules. 5. Can you write and run a test script for the calc_tax_amount function to verify the results and check for any bugs? 6. When I call the calc_tax_amount function, for state_code = 'CA' (rate 0.0825), calc_tax_amount(10.01, 'CA') returns 0.82, but it should return 0.83. Please debug the function and show me the exact code that needs to be fixed. -7. Please show me a dependency graph for my table relationships. -8. Show the object call hierarchy as a binary-tree style diagram. -9. Render the outgoing dependency hierarchy as HTML + CSS tree markup for my web app. +7. Summarize the CHECKOUT_PKG package body in one paragraph. +8. Generate PLDoc comments for the calc_tax_amount function. +9. Which packages and procedures will be impacted if I modify the ORDERS table? +10.Get me a list of tables available for inspection. --- ## License -Universal Permissive License (UPL) 1.0 +Universal Permissive License (UPL) 1.0 https://oss.oracle.com/licenses/upl/ Copyright (c) 2026 Oracle and/or its affiliates. diff --git a/autonomous-ai-agents/database_inspect/database_inspect_agent.sql b/autonomous-ai-agents/database_inspect/database_inspect_agent.sql index 4f0a5c9..74c9eb8 100644 --- a/autonomous-ai-agents/database_inspect/database_inspect_agent.sql +++ b/autonomous-ai-agents/database_inspect/database_inspect_agent.sql @@ -8,15 +8,22 @@ rem NAME rem database_inspect_agent.sql rem rem DESCRIPTION -rem Installer and configuration script for DATABASE_INSPECT AI team. +rem Installer and configuration script for DATABASE_INSPECT AI teams. rem (Select AI Agent / Oracle AI Database) rem +rem RELEASE VERSION +rem 1.1 +rem +rem RELEASE DATE +rem 5-Feb-2026 +rem rem This script: -rem - Accepts target schema, AI profile name, and optional team attributes -rem - Grants required privileges -rem - Validates AI profile has embedding_model attribute -rem - Recreates DATABASE_INSPECT team using DATABASE_INSPECT package -rem (task, agent, and tools are created internally by package APIs) +rem - Accepts target schema, AI profile name, agent team name, recreate +rem choice, and guided scope inputs +rem - Grants required privileges for agent submission and background jobs +rem - Creates a log table for monitoring asynchronous agent creation +rem - Submits agent creation as a DBMS_SCHEDULER background job +rem - Persists success/failure/progress details in a log table rem rem PARAMETERS rem INSTALL_SCHEMA_NAME (Required) @@ -25,10 +32,35 @@ rem rem AI_PROFILE_NAME (Required) rem AI profile used for reasoning + embeddings. rem -rem TEAM_ATTRIBUTES_JSON (Optional) +rem AGENT_TEAM_NAME (Required) +rem Team name to create or recreate. +rem +rem RECREATE_EXISTING (Optional) +rem Enter Y to drop and recreate an existing team with the same name. +rem Default is N. +rem +rem SCOPE_MODE (Optional) +rem Guided scope input mode for building TEAM_ATTRIBUTES_JSON. +rem Supported values: SCHEMA, TABLE, PACKAGE, JSON. +rem Default is SCHEMA. +rem +rem SCOPE_OWNERS (Optional) +rem For SCHEMA mode, enter one or more schema owners separated by commas. +rem Default is INSTALL_SCHEMA_NAME. +rem +rem OBJECT_OWNER (Optional) +rem For TABLE or PACKAGE mode, enter the owner/schema of the listed +rem objects. Default is INSTALL_SCHEMA_NAME. +rem +rem OBJECT_NAMES (Optional) +rem For TABLE or PACKAGE mode, enter object names separated by commas. +rem +rem MATCH_LIMIT (Optional) +rem Optional match_limit value to include in built team attributes. +rem +rem TEAM_ATTRIBUTES_JSON (Optional, JSON Mode) rem Full attributes JSON passed to DATABASE_INSPECT.create_inspect_agent_team. -rem If not provided (or provided as NULL), default object scope is: -rem {"object_list":[{"owner":"","type":"SCHEMA"}]} +rem This is only prompted when SCOPE_MODE = JSON. rem rem Note: profile_name from AI_PROFILE_NAME is always enforced by this rem installer, even if TEAM_ATTRIBUTES_JSON includes profile_name. @@ -39,23 +71,40 @@ rem (`embedding_model` profile attribute). rem rem INSTALL INSTRUCTIONS rem 1. Connect as ADMIN or a user with required privileges. -rem 2. Run this script using SQL*Plus/SQLcl/SQL Developer. -rem 3. Provide INSTALL_SCHEMA_NAME and AI_PROFILE_NAME when prompted. -rem 4. Optionally provide TEAM_ATTRIBUTES_JSON. +rem 2. Run this script as a SQL script using SQL*Plus/SQLcl/SQL Developer. +rem 3. Provide INSTALL_SCHEMA_NAME, AI_PROFILE_NAME, and AGENT_TEAM_NAME. +rem 4. Provide RECREATE_EXISTING if you want to replace an existing team. +rem 5. Use guided prompts to choose SCHEMA, TABLE, PACKAGE, or JSON scope. +rem 6. If SCOPE_MODE = JSON, provide TEAM_ATTRIBUTES_JSON. +rem 7. Monitor DATABASE_INSPECT_AGENT_JOB_LOG$ for background progress. rem -rem EXAMPLE TEAM_ATTRIBUTES_JSON VALUES +rem GUIDED INPUT EXAMPLES rem 1. Default-equivalent scope (single schema): -rem {"object_list":[{"owner":"AGENT_TEST_USER","type":"SCHEMA"}]} +rem SCOPE_MODE = SCHEMA +rem SCOPE_OWNERS = rem rem 2. Multiple schema owners: -rem {"object_list":[{"owner":"SH","type":"SCHEMA"},{"owner":"HR","type":"SCHEMA"}]} +rem SCOPE_MODE = SCHEMA +rem SCOPE_OWNERS = SH,HR +rem +rem 3. Multiple tables in one schema: +rem SCOPE_MODE = TABLE +rem OBJECT_OWNER = SH +rem OBJECT_NAMES = SALES,CUSTOMERS,PRODUCTS rem -rem 3. Mixed object-level and schema-level scope: +rem 4. Multiple packages in one schema: +rem SCOPE_MODE = PACKAGE +rem OBJECT_OWNER = HR +rem OBJECT_NAMES = EMP_PKG,PAYROLL_PKG +rem +rem JSON MODE TEAM_ATTRIBUTES_JSON EXAMPLE +rem Mixed object-level and schema-level scope: rem {"match_limit":20,"object_list":[{"owner":"SH","type":"TABLE","name":"SALES"},{"owner":"HR","type":"PACKAGE","name":"EMP_PKG"},{"owner":"OE","type":"SCHEMA"}]} rem rem NOTES -rem - Script is safe to re-run; team is dropped/recreated. -rem - Team name is fixed as DATABASE_INSPECT. +rem - Script is safe to re-run. +rem - Existing teams are only dropped when RECREATE_EXISTING = Y. +rem - One row is written to DATABASE_INSPECT_AGENT_JOB_LOG$ per submission. rem ============================================================================ SET SERVEROUTPUT ON @@ -67,68 +116,351 @@ PROMPT ====================================================== -- Target schema VAR v_schema VARCHAR2(128) -EXEC :v_schema := '&INSTALL_SCHEMA_NAME'; +EXEC :v_schema := '&&INSTALL_SCHEMA_NAME'; +VAR v_invoker_schema VARCHAR2(128) +EXEC :v_invoker_schema := SYS_CONTEXT('USERENV', 'SESSION_USER'); -- AI profile name VAR v_ai_profile_name VARCHAR2(128) -EXEC :v_ai_profile_name := '&AI_PROFILE_NAME'; +EXEC :v_ai_profile_name := '&&AI_PROFILE_NAME'; + +-- Agent team name +VAR v_agent_team_name VARCHAR2(128) +EXEC :v_agent_team_name := '&AGENT_TEAM_NAME'; PROMPT -PROMPT Optional TEAM_ATTRIBUTES_JSON (press Enter or type NULL to use default): -PROMPT Default: -PROMPT {"object_list":[{"owner":"","type":"SCHEMA"}]} +PROMPT RECREATE_EXISTING: +PROMPT Do you want to drop the existing team and recreate it if it already exists? +PROMPT Enter Y for Yes or N for No. +PROMPT Default is N. + +-- Recreate flag +VAR v_recreate_existing VARCHAR2(1) +EXEC :v_recreate_existing := NVL(UPPER(TRIM('&RECREATE_EXISTING')), 'N'); + +PROMPT +PROMPT Guided Object Scope Setup +PROMPT Common scope modes: +PROMPT SCHEMA - inspect one or more schemas +PROMPT TABLE - inspect one or more tables from one owner +PROMPT PACKAGE - inspect one or more packages from one owner +PROMPT JSON - advanced mode; enter full TEAM_ATTRIBUTES_JSON +PROMPT Default is SCHEMA. +PROMPT +PROMPT SCOPE_MODE: +PROMPT Enter SCHEMA, TABLE, PACKAGE, or JSON. + +VAR v_scope_mode VARCHAR2(20) +EXEC :v_scope_mode := NVL(UPPER(TRIM('&SCOPE_MODE')), 'SCHEMA'); + +VAR v_scope_owners VARCHAR2(4000) +VAR v_object_owner VARCHAR2(128) +VAR v_object_names VARCHAR2(4000) +VAR v_match_limit VARCHAR2(30) +VAR v_raw_team_attributes_json CLOB + +BEGIN + IF :v_scope_mode NOT IN ('SCHEMA', 'TABLE', 'PACKAGE', 'JSON') THEN + RAISE_APPLICATION_ERROR( + -20000, + 'Invalid SCOPE_MODE. Use SCHEMA, TABLE, PACKAGE, or JSON.' + ); + END IF; +END; +/ + PROMPT -PROMPT Example 1 (multiple owners): -PROMPT {"object_list":[{"owner":"SH","type":"SCHEMA"},{"owner":"HR","type":"SCHEMA"}]} +PROMPT Scope Detail Inputs +PROMPT If SCOPE_MODE = SCHEMA, enter SCOPE_OWNERS and optionally MATCH_LIMIT. +PROMPT If SCOPE_MODE = TABLE or PACKAGE, enter OBJECT_OWNER, OBJECT_NAMES, and optionally MATCH_LIMIT. +PROMPT If SCOPE_MODE = JSON, enter TEAM_ATTRIBUTES_JSON. +PROMPT Leave inputs blank when they do not apply. PROMPT -PROMPT Example 2 (mixed object-level + schema-level scope): -PROMPT {"match_limit":20,"object_list":[{"owner":"SH","type":"TABLE","name":"SALES"},{"owner":"HR","type":"PACKAGE","name":"EMP_PKG"},{"owner":"OE","type":"SCHEMA"}]} + +ACCEPT SCOPE_OWNERS CHAR PROMPT 'SCOPE_OWNERS (optional, comma-separated): ' +ACCEPT OBJECT_OWNER CHAR PROMPT 'OBJECT_OWNER (optional): ' +ACCEPT OBJECT_NAMES CHAR PROMPT 'OBJECT_NAMES (optional, comma-separated): ' +ACCEPT MATCH_LIMIT CHAR PROMPT 'MATCH_LIMIT (optional): ' +ACCEPT TEAM_ATTRIBUTES_JSON CHAR PROMPT 'TEAM_ATTRIBUTES_JSON (optional): ' + +BEGIN + :v_scope_owners := '&&SCOPE_OWNERS'; + :v_object_owner := '&&OBJECT_OWNER'; + :v_object_names := '&&OBJECT_NAMES'; + :v_match_limit := '&&MATCH_LIMIT'; + :v_raw_team_attributes_json := q'~&&TEAM_ATTRIBUTES_JSON~'; +END; +/ + PROMPT PROMPT AI_PROFILE_NAME is always enforced as profile_name by this installer. PROMPT VAR v_team_attributes_json CLOB -EXEC :v_team_attributes_json := '&TEAM_ATTRIBUTES_JSON'; +DECLARE + l_scope_mode VARCHAR2(30); + l_scope_owners VARCHAR2(4000); + l_object_owner VARCHAR2(128); + l_object_names VARCHAR2(4000); + l_match_limit_input VARCHAR2(30); + l_raw_attributes_json CLOB; + l_attributes JSON_OBJECT_T := JSON_OBJECT_T('{}'); + l_object_list JSON_ARRAY_T := JSON_ARRAY_T(); + l_object_item JSON_OBJECT_T; + l_match_limit NUMBER; + l_token VARCHAR2(4000); + l_idx PLS_INTEGER; + + FUNCTION normalize_input( + p_value IN VARCHAR2 + ) RETURN VARCHAR2 + IS + BEGIN + IF p_value IS NULL OR TRIM(p_value) IS NULL OR UPPER(TRIM(p_value)) = 'NULL' + THEN + RETURN NULL; + END IF; + + RETURN TRIM(p_value); + END normalize_input; + + FUNCTION normalize_clob_input( + p_value IN CLOB + ) RETURN CLOB + IS + BEGIN + IF p_value IS NULL OR + DBMS_LOB.GETLENGTH(p_value) = 0 OR + UPPER(TRIM(DBMS_LOB.SUBSTR(p_value, 32767, 1))) = 'NULL' + THEN + RETURN NULL; + END IF; + + RETURN p_value; + END normalize_clob_input; + + PROCEDURE append_schema_owners( + p_owner_csv IN VARCHAR2 + ) + IS + l_owner_csv VARCHAR2(4000); + l_raw_token VARCHAR2(4000); + BEGIN + l_owner_csv := normalize_input(p_owner_csv); + + IF l_owner_csv IS NULL THEN + l_object_item := JSON_OBJECT_T('{}'); + l_object_item.put('owner', :v_schema); + l_object_item.put('type', 'SCHEMA'); + l_object_list.append(l_object_item); + RETURN; + END IF; + + l_idx := 1; + LOOP + l_raw_token := REGEXP_SUBSTR(l_owner_csv, '[^,]+', 1, l_idx); + EXIT WHEN l_raw_token IS NULL; + l_token := normalize_input(l_raw_token); + + IF l_token IS NOT NULL THEN + l_object_item := JSON_OBJECT_T('{}'); + l_object_item.put('owner', l_token); + l_object_item.put('type', 'SCHEMA'); + l_object_list.append(l_object_item); + END IF; + + l_idx := l_idx + 1; + END LOOP; + END append_schema_owners; + + PROCEDURE append_named_objects( + p_owner IN VARCHAR2, + p_type IN VARCHAR2, + p_name_csv IN VARCHAR2 + ) + IS + l_owner VARCHAR2(128); + l_name_csv VARCHAR2(4000); + l_raw_token VARCHAR2(4000); + BEGIN + l_owner := NVL(normalize_input(p_owner), :v_schema); + l_name_csv := normalize_input(p_name_csv); + + IF l_name_csv IS NULL THEN + RAISE_APPLICATION_ERROR( + -20000, + 'OBJECT_NAMES is required when SCOPE_MODE is ' || p_type || '. ' || + 'Enter one or more object names separated by commas.' + ); + END IF; + + l_idx := 1; + LOOP + l_raw_token := REGEXP_SUBSTR(l_name_csv, '[^,]+', 1, l_idx); + EXIT WHEN l_raw_token IS NULL; + l_token := normalize_input(l_raw_token); + + IF l_token IS NOT NULL THEN + l_object_item := JSON_OBJECT_T('{}'); + l_object_item.put('owner', l_owner); + l_object_item.put('type', p_type); + l_object_item.put('name', l_token); + l_object_list.append(l_object_item); + END IF; + + l_idx := l_idx + 1; + END LOOP; + END append_named_objects; +BEGIN + l_scope_mode := NVL(normalize_input(:v_scope_mode), 'SCHEMA'); + l_scope_owners := :v_scope_owners; + l_object_owner := :v_object_owner; + l_object_names := :v_object_names; + l_match_limit_input := normalize_input(:v_match_limit); + l_raw_attributes_json := normalize_clob_input(:v_raw_team_attributes_json); + + IF l_scope_mode = 'JSON' THEN + IF l_raw_attributes_json IS NULL THEN + RAISE_APPLICATION_ERROR( + -20000, + 'TEAM_ATTRIBUTES_JSON is required when SCOPE_MODE is JSON.' + ); + END IF; + + :v_team_attributes_json := l_raw_attributes_json; + ELSE + IF l_scope_mode NOT IN ('SCHEMA', 'TABLE', 'PACKAGE', 'JSON') THEN + RAISE_APPLICATION_ERROR( + -20000, + 'Invalid SCOPE_MODE. Use SCHEMA, TABLE, PACKAGE, or JSON.' + ); + END IF; + + CASE l_scope_mode + WHEN 'SCHEMA' THEN + append_schema_owners(l_scope_owners); + WHEN 'TABLE' THEN + append_named_objects(l_object_owner, 'TABLE', l_object_names); + WHEN 'PACKAGE' THEN + append_named_objects(l_object_owner, 'PACKAGE', l_object_names); + END CASE; + + l_attributes.put('object_list', l_object_list); + + IF l_match_limit_input IS NOT NULL THEN + BEGIN + l_match_limit := TO_NUMBER(l_match_limit_input); + EXCEPTION + WHEN OTHERS THEN + RAISE_APPLICATION_ERROR( + -20000, + 'MATCH_LIMIT must be an integer between 1 and 100.' + ); + END; + + IF l_match_limit <= 0 OR l_match_limit > 100 OR MOD(l_match_limit, 1) != 0 + THEN + RAISE_APPLICATION_ERROR( + -20000, + 'MATCH_LIMIT must be an integer between 1 and 100.' + ); + END IF; + + l_attributes.put('match_limit', l_match_limit); + END IF; + + :v_team_attributes_json := l_attributes.to_clob; + END IF; + +END; +/ ---------------------------------------------------------------- -- 1. Grants (safe to re-run) ---------------------------------------------------------------- DECLARE - l_sql VARCHAR2(500); - l_schema VARCHAR2(128); - l_session_user VARCHAR2(128); + l_sql VARCHAR2(500); + l_session_user VARCHAR2(128) := SYS_CONTEXT('USERENV', 'SESSION_USER'); BEGIN - l_schema := DBMS_ASSERT.SIMPLE_SQL_NAME(:v_schema); - l_session_user := SYS_CONTEXT('USERENV', 'SESSION_USER'); - - -- Avoid self-grant errors (ORA-01749) when target schema == connected user. - IF UPPER(l_schema) <> UPPER(l_session_user) THEN - l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_AI_AGENT TO ' || l_schema; + -- Granting privileges to the current session user is a no-op and raises + -- ORA-01749. Skip that case to keep reruns clean. + IF UPPER(:v_schema) = UPPER(l_session_user) THEN + DBMS_OUTPUT.PUT_LINE( + 'Skipping grants because target schema ' || :v_schema || + ' is the current session user.' + ); + ELSE + l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_AI_AGENT TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; - l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_AI TO ' || l_schema; + l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_AI TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; - l_sql := 'GRANT EXECUTE ON DBMS_CLOUD TO ' || l_schema; + l_sql := 'GRANT EXECUTE ON DBMS_CLOUD TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; - l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_REPO TO ' || l_schema; + l_sql := 'GRANT EXECUTE ON DBMS_CLOUD_REPO TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; - l_sql := 'GRANT EXECUTE ON DBMS_VECTOR_CHAIN TO ' || l_schema; + l_sql := 'GRANT EXECUTE ON DBMS_VECTOR_CHAIN TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; BEGIN - l_sql := 'GRANT EXECUTE ON CTXSYS.CTX_DDL TO ' || l_schema; + l_sql := 'GRANT EXECUTE ON CTXSYS.CTX_DDL TO ' || :v_schema; EXECUTE IMMEDIATE l_sql; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Warning: failed to grant CTXSYS.CTX_DDL - ' || SQLERRM); END; - ELSE - DBMS_OUTPUT.PUT_LINE('Skipping grants for schema ' || l_schema || - ' (same as session user).'); + + BEGIN + l_sql := 'GRANT CREATE JOB TO ' || :v_schema; + EXECUTE IMMEDIATE l_sql; + EXCEPTION + WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE( + 'Warning: failed to grant CREATE JOB - ' || SQLERRM || + '. Ensure schema ' || :v_schema || + ' already has CREATE JOB before running the background installer.' + ); + END; + + BEGIN + l_sql := 'GRANT CREATE SEQUENCE TO ' || :v_schema; + EXECUTE IMMEDIATE l_sql; + EXCEPTION + WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE( + 'Warning: failed to grant CREATE SEQUENCE - ' || SQLERRM || + '. Ensure schema ' || :v_schema || + ' already has CREATE SEQUENCE before running the installer.' + ); + END; + + BEGIN + l_sql := 'GRANT CREATE PROCEDURE TO ' || :v_schema; + EXECUTE IMMEDIATE l_sql; + EXCEPTION + WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE( + 'Warning: failed to grant CREATE PROCEDURE - ' || SQLERRM || + '. Ensure schema ' || :v_schema || + ' already has CREATE PROCEDURE before running the installer.' + ); + END; + + BEGIN + l_sql := 'GRANT CREATE VIEW TO ' || :v_schema; + EXECUTE IMMEDIATE l_sql; + EXCEPTION + WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE( + 'Warning: failed to grant CREATE VIEW - ' || SQLERRM || + '. Ensure schema ' || :v_schema || + ' already has CREATE VIEW before running the installer.' + ); + END; END IF; DBMS_OUTPUT.PUT_LINE('Grants completed.'); @@ -147,42 +479,411 @@ END; ---------------------------------------------------------------- --- 3. Create installer procedure in target schema +-- 3. Create monitoring log table in target schema ---------------------------------------------------------------- -CREATE OR REPLACE PROCEDURE database_inspect_agent ( - p_install_schema IN VARCHAR2, - p_profile_name IN VARCHAR2, - p_team_attributes_json IN CLOB DEFAULT NULL +BEGIN + EXECUTE IMMEDIATE q'[ + CREATE TABLE DATABASE_INSPECT_AGENT_JOB_LOG$ + ( + run_id NUMBER GENERATED ALWAYS AS IDENTITY, + team_name VARCHAR2(128) NOT NULL, + install_schema VARCHAR2(128) NOT NULL, + profile_name VARCHAR2(128) NOT NULL, + recreate_existing VARCHAR2(1) NOT NULL, + requested_attributes_json CLOB, + effective_attributes_json CLOB, + existing_attributes_snapshot CLOB, + object_list_count NUMBER, + job_name VARCHAR2(128), + status VARCHAR2(30) NOT NULL, + current_step VARCHAR2(100), + status_message CLOB, + error_code NUMBER, + error_message CLOB, + error_stack CLOB, + created_by VARCHAR2(128) DEFAULT SYS_CONTEXT('USERENV', 'SESSION_USER') NOT NULL, + created_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL, + queued_at TIMESTAMP(6), + started_at TIMESTAMP(6), + completed_at TIMESTAMP(6), + updated_at TIMESTAMP(6) DEFAULT SYSTIMESTAMP NOT NULL, + CONSTRAINT DATABASE_INSPECT_AGENT_JOB_LOG_PK PRIMARY KEY (run_id) + ) + ]'; + DBMS_OUTPUT.PUT_LINE('Created log table DATABASE_INSPECT_AGENT_JOB_LOG$.'); +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -955 THEN + DBMS_OUTPUT.PUT_LINE('Log table DATABASE_INSPECT_AGENT_JOB_LOG$ already exists.'); + ELSE + RAISE; + END IF; +END; +/ + + +---------------------------------------------------------------- +-- 4. Create background worker procedure in target schema +---------------------------------------------------------------- +CREATE OR REPLACE PROCEDURE &&INSTALL_SCHEMA_NAME..database_inspect_agent_worker ( + p_run_id IN NUMBER ) AUTHID DEFINER AS - l_install_schema VARCHAR2(128); - l_profile_name VARCHAR2(128); - l_has_embedding_model NUMBER := 0; - l_team_name CONSTANT VARCHAR2(128) := - DATABASE_INSPECT.DATABASE_INSPECT_PACKAGE; - l_object_list_count NUMBER := 0; - l_team_attributes_json CLOB := p_team_attributes_json; + l_inspect_agent_teams CONSTANT VARCHAR2(128) := + 'DATABASE_INSPECT_AGENT_TEAMS$'; + l_inspect_agent_team_attributes CONSTANT VARCHAR2(128) := + 'DATABASE_INSPECT_AGENT_TEAM_ATTRIBUTES$'; + l_inspect_agent_prefix CONSTANT VARCHAR2(128) := 'INSPECT_AGENT'; + l_inspect_task_prefix CONSTANT VARCHAR2(128) := 'INSPECT_TASK'; + + l_install_schema VARCHAR2(128); + l_profile_name VARCHAR2(128); + l_team_name VARCHAR2(128); + l_recreate_existing VARCHAR2(1); + l_requested_attributes_json CLOB; + l_effective_attributes_json CLOB; + l_existing_attributes_snapshot CLOB; + l_has_embedding_model NUMBER := 0; + l_profile_provider VARCHAR2(128); + l_has_oci_compartment_id NUMBER := 0; + l_object_list_count NUMBER := 0; + l_current_step VARCHAR2(100) := 'INITIALIZING'; + l_verification_message CLOB; + l_error_code NUMBER; + l_error_message VARCHAR2(32767); + l_error_stack VARCHAR2(32767); + l_friendly_status_message VARCHAR2(32767); + l_friendly_error_message VARCHAR2(32767); l_attributes JSON_OBJECT_T := JSON_OBJECT_T('{}'); l_default_object_list JSON_ARRAY_T := JSON_ARRAY_T(); l_default_object_item JSON_OBJECT_T := JSON_OBJECT_T('{}'); + + FUNCTION team_exists( + p_team_name IN VARCHAR2 + ) RETURN BOOLEAN + IS + l_sql VARCHAR2(4000); + l_count NUMBER; + BEGIN + l_sql := 'SELECT COUNT(*) FROM ' || l_inspect_agent_teams || + ' WHERE agent_team_name = :1'; + EXECUTE IMMEDIATE l_sql INTO l_count USING p_team_name; + RETURN l_count > 0; + EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -942 THEN + RAISE_APPLICATION_ERROR( + -20000, + 'DATABASE_INSPECT internal tables are not available. ' || + 'Run database_inspect_tool.sql before running this installer.' + ); + ELSE + RAISE; + END IF; + END team_exists; + + FUNCTION get_existing_attributes_snapshot( + p_team_name IN VARCHAR2 + ) RETURN CLOB + IS + l_snapshot CLOB; + l_sql VARCHAR2(4000); + l_cursor SYS_REFCURSOR; + l_attr_name VARCHAR2(128); + l_attr_value CLOB; + l_line VARCHAR2(32767); + BEGIN + DBMS_LOB.CREATETEMPORARY(l_snapshot, TRUE); + + l_sql := 'SELECT attribute_name, attribute_value FROM ' || + l_inspect_agent_team_attributes || + ' WHERE agent_team_name = :1 ORDER BY attribute_name'; + + OPEN l_cursor FOR l_sql USING p_team_name; + LOOP + FETCH l_cursor INTO l_attr_name, l_attr_value; + EXIT WHEN l_cursor%NOTFOUND; + + l_line := l_attr_name || ' = ' || + CASE + WHEN l_attr_value IS NULL THEN + 'NULL' + WHEN DBMS_LOB.GETLENGTH(l_attr_value) <= 3000 THEN + DBMS_LOB.SUBSTR(l_attr_value, 3000, 1) + ELSE + DBMS_LOB.SUBSTR(l_attr_value, 3000, 1) || + '... [truncated]' + END || CHR(10); + + DBMS_LOB.WRITEAPPEND(l_snapshot, LENGTH(l_line), l_line); + END LOOP; + CLOSE l_cursor; + + IF DBMS_LOB.GETLENGTH(l_snapshot) = 0 THEN + DBMS_LOB.FREETEMPORARY(l_snapshot); + RETURN NULL; + END IF; + + RETURN l_snapshot; + EXCEPTION + WHEN OTHERS THEN + IF l_cursor%ISOPEN THEN + CLOSE l_cursor; + END IF; + + IF DBMS_LOB.ISTEMPORARY(l_snapshot) = 1 THEN + DBMS_LOB.FREETEMPORARY(l_snapshot); + END IF; + + RETURN 'Unable to retrieve existing team attributes: ' || SQLERRM; + END get_existing_attributes_snapshot; + + PROCEDURE cleanup_external_agent_objects( + p_team_name IN VARCHAR2 + ) + IS + l_agent_name VARCHAR2(261); + l_task_name VARCHAR2(261); + BEGIN + l_agent_name := l_inspect_agent_prefix || '_' || p_team_name; + l_task_name := l_inspect_task_prefix || '_' || p_team_name; + + BEGIN + DBMS_CLOUD_AI_AGENT.drop_team(p_team_name, TRUE); + EXCEPTION + WHEN OTHERS THEN + NULL; + END; + + BEGIN + DBMS_CLOUD_AI_AGENT.drop_agent(l_agent_name, TRUE); + EXCEPTION + WHEN OTHERS THEN + NULL; + END; + + BEGIN + DBMS_CLOUD_AI_AGENT.drop_task(l_task_name, TRUE); + EXCEPTION + WHEN OTHERS THEN + NULL; + END; + END cleanup_external_agent_objects; + + FUNCTION build_friendly_install_error( + p_error_code IN NUMBER, + p_error_message IN VARCHAR2, + p_error_stack IN VARCHAR2 + ) RETURN VARCHAR2 + IS + l_error_text VARCHAR2(32767); + l_tablespace_name VARCHAR2(128); + l_friendly_text VARCHAR2(32767); + BEGIN + l_error_text := UPPER(NVL(p_error_message, '')); + + IF p_error_stack IS NOT NULL THEN + l_error_text := l_error_text || CHR(10) || UPPER(p_error_stack); + END IF; + + l_tablespace_name := REGEXP_SUBSTR( + l_error_text, + 'TABLESPACE ''([^'']+)''', + 1, + 1, + NULL, + 1 + ); + + IF INSTR(l_error_text, 'COMPARTMENT ID MUST BE PROVIDED') > 0 THEN + l_friendly_text := + 'The selected AI profile uses OCI Generative AI embeddings but does not include oci_compartment_id. ' || + 'Update the AI profile to set oci_compartment_id and rerun the installer.'; + ELSIF INSTR(l_error_text, 'ORA-01950') > 0 OR + INSTR(l_error_text, 'NO PRIVILEGES ON TABLESPACE') > 0 + THEN + l_friendly_text := + 'Schema ' || l_install_schema || ' does not have quota on tablespace ' || + NVL(l_tablespace_name, 'required by the install') || '. ' || + 'Grant quota on that tablespace and rerun the installer.'; + ELSIF INSTR(l_error_text, 'ORA-01536') > 0 OR + INSTR(l_error_text, 'SPACE QUOTA EXCEEDED FOR TABLESPACE') > 0 + THEN + l_friendly_text := + 'Schema ' || l_install_schema || ' has exceeded its quota on tablespace ' || + NVL(l_tablespace_name, 'required by the install') || '. ' || + 'Increase the quota or free space, then rerun the installer.'; + ELSIF INSTR(l_error_text, 'ORA-51808') > 0 OR + INSTR(l_error_text, 'SAME DIMENSION COUNT') > 0 OR + INSTR(l_error_text, 'DIMENSION COUNT') > 0 + THEN + l_friendly_text := + 'The embedding dimension does not match the existing vector objects. ' || + 'This usually means the AI profile embedding model changed or stale inspect vector data already exists. ' || + 'Recreate the team with RECREATE_EXISTING = Y and rerun the installer. ' || + 'If the problem continues, drop the existing inspect team objects and retry with one consistent embedding model.'; + ELSE + RETURN p_error_message; + END IF; + + RETURN l_friendly_text || CHR(10) || CHR(10) || + 'Original error:' || CHR(10) || p_error_message; + END build_friendly_install_error; + + FUNCTION build_friendly_status_message( + p_error_code IN NUMBER, + p_error_message IN VARCHAR2, + p_error_stack IN VARCHAR2 + ) RETURN VARCHAR2 + IS + l_error_text VARCHAR2(32767); + l_tablespace_name VARCHAR2(128); + BEGIN + l_error_text := UPPER(NVL(p_error_message, '')); + + IF p_error_stack IS NOT NULL THEN + l_error_text := l_error_text || CHR(10) || UPPER(p_error_stack); + END IF; + + l_tablespace_name := REGEXP_SUBSTR( + l_error_text, + 'TABLESPACE ''([^'']+)''', + 1, + 1, + NULL, + 1 + ); + + IF INSTR(l_error_text, 'COMPARTMENT ID MUST BE PROVIDED') > 0 THEN + RETURN 'Agent installation failed because the OCI AI profile is missing oci_compartment_id.'; + ELSIF INSTR(l_error_text, 'ORA-01950') > 0 OR + INSTR(l_error_text, 'NO PRIVILEGES ON TABLESPACE') > 0 + THEN + RETURN 'Agent installation failed because schema ' || l_install_schema || + ' does not have quota on tablespace ' || + NVL(l_tablespace_name, 'required by the install') || '.'; + ELSIF INSTR(l_error_text, 'ORA-01536') > 0 OR + INSTR(l_error_text, 'SPACE QUOTA EXCEEDED FOR TABLESPACE') > 0 + THEN + RETURN 'Agent installation failed because schema ' || l_install_schema || + ' exceeded its quota on tablespace ' || + NVL(l_tablespace_name, 'required by the install') || '.'; + ELSIF INSTR(l_error_text, 'ORA-51808') > 0 OR + INSTR(l_error_text, 'SAME DIMENSION COUNT') > 0 OR + INSTR(l_error_text, 'DIMENSION COUNT') > 0 + THEN + RETURN 'Agent installation failed because the embedding dimension does not match the existing inspect vector objects.'; + ELSE + RETURN 'Agent installation failed.'; + END IF; + END build_friendly_status_message; + + PROCEDURE update_log( + p_status IN VARCHAR2, + p_current_step IN VARCHAR2, + p_status_message IN CLOB, + p_effective_attributes_json IN CLOB DEFAULT NULL, + p_existing_attributes_snapshot IN CLOB DEFAULT NULL, + p_object_list_count IN NUMBER DEFAULT NULL, + p_error_code IN NUMBER DEFAULT NULL, + p_error_message IN CLOB DEFAULT NULL, + p_error_stack IN CLOB DEFAULT NULL, + p_mark_started IN VARCHAR2 DEFAULT 'N', + p_mark_completed IN VARCHAR2 DEFAULT 'N' + ) + IS + BEGIN + UPDATE DATABASE_INSPECT_AGENT_JOB_LOG$ + SET status = p_status, + current_step = p_current_step, + status_message = p_status_message, + effective_attributes_json = + CASE + WHEN p_effective_attributes_json IS NOT NULL THEN + p_effective_attributes_json + ELSE + effective_attributes_json + END, + existing_attributes_snapshot = + CASE + WHEN p_existing_attributes_snapshot IS NOT NULL THEN + p_existing_attributes_snapshot + ELSE + existing_attributes_snapshot + END, + object_list_count = + CASE + WHEN p_object_list_count IS NOT NULL THEN + p_object_list_count + ELSE + object_list_count + END, + error_code = p_error_code, + error_message = p_error_message, + error_stack = p_error_stack, + started_at = + CASE + WHEN p_mark_started = 'Y' AND started_at IS NULL THEN + SYSTIMESTAMP + ELSE + started_at + END, + completed_at = + CASE + WHEN p_mark_completed = 'Y' THEN + SYSTIMESTAMP + ELSE + completed_at + END, + updated_at = SYSTIMESTAMP + WHERE run_id = p_run_id; + + COMMIT; + END update_log; BEGIN - DBMS_UTILITY.canonicalize( - DBMS_ASSERT.SIMPLE_SQL_NAME(p_install_schema), - l_install_schema, - LENGTHB(p_install_schema) + SELECT install_schema, + profile_name, + team_name, + recreate_existing, + requested_attributes_json + INTO l_install_schema, + l_profile_name, + l_team_name, + l_recreate_existing, + l_requested_attributes_json + FROM DATABASE_INSPECT_AGENT_JOB_LOG$ + WHERE run_id = p_run_id; + + l_current_step := 'VALIDATING_PROFILE'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Background job started. Validating AI profile.', + p_mark_started => 'Y' ); - DBMS_UTILITY.canonicalize( - DBMS_ASSERT.SIMPLE_SQL_NAME(p_profile_name), - l_profile_name, - LENGTHB(p_profile_name) + l_current_step := 'ENSURING_SETUP'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Ensuring DATABASE_INSPECT internal tables exist.' ); - ---------------------------------------------------------------- - -- Validate that profile has embedding_model configured. - ---------------------------------------------------------------- + BEGIN + -- Do not rely on package initialization side effects. Explicit setup + -- guarantees the internal tables exist before we query them. + DATABASE_INSPECT.setup; + EXCEPTION + WHEN OTHERS THEN + RAISE_APPLICATION_ERROR( + -20000, + 'DATABASE_INSPECT package setup failed. Run database_inspect_tool.sql before running this installer. ' || + SQLERRM + ); + END; + BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM user_cloud_ai_profile_attributes ' || @@ -208,37 +909,64 @@ BEGIN RAISE_APPLICATION_ERROR( -20000, 'AI profile ' || l_profile_name || - ' must include attribute embedding_model. ' - || 'Please configure an embedding model and re-run installer.' + ' must include attribute embedding_model. ' || + 'Please configure an embedding model and re-run installer.' + ); + END IF; + + BEGIN + EXECUTE IMMEDIATE + 'SELECT MAX(CASE WHEN LOWER(attribute_name) = ''provider'' ' || + ' THEN DBMS_LOB.SUBSTR(attribute_value, 4000, 1) END), ' || + ' MAX(CASE WHEN LOWER(attribute_name) = ''oci_compartment_id'' ' || + ' AND attribute_value IS NOT NULL THEN 1 ELSE 0 END) ' || + ' FROM user_cloud_ai_profile_attributes ' || + ' WHERE profile_name = :1' + INTO l_profile_provider, + l_has_oci_compartment_id + USING l_profile_name; + EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -942 THEN + RAISE_APPLICATION_ERROR( + -20000, + 'USER_CLOUD_AI_PROFILE_ATTRIBUTES is not available in this schema context. ' || + 'Validate DBMS_CLOUD_AI profile metadata visibility and retry.' + ); + ELSE + RAISE; + END IF; + END; + + l_profile_provider := LOWER(TRIM(l_profile_provider)); + + IF l_profile_provider = 'oci' AND l_has_oci_compartment_id = 0 THEN + RAISE_APPLICATION_ERROR( + -20000, + 'AI profile ' || l_profile_name || + ' uses OCI Generative AI and must include attribute oci_compartment_id. ' || + 'Please configure oci_compartment_id and re-run installer.' ); END IF; - DBMS_OUTPUT.PUT_LINE('--------------------------------------------'); - DBMS_OUTPUT.PUT_LINE('Starting DATABASE_INSPECT team installation'); - DBMS_OUTPUT.PUT_LINE('--------------------------------------------'); - DBMS_OUTPUT.PUT_LINE('AI profile: ' || l_profile_name); - DBMS_OUTPUT.PUT_LINE('Embedding model attribute is configured.'); - - ---------------------------------------------------------------- - -- Resolve team attributes. - ---------------------------------------------------------------- - IF l_team_attributes_json IS NULL OR - NVL(DBMS_LOB.GETLENGTH(l_team_attributes_json), 0) = 0 OR - UPPER(TRIM(DBMS_LOB.SUBSTR(l_team_attributes_json, 4, 1))) = 'NULL' + l_current_step := 'RESOLVING_ATTRIBUTES'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Resolving team attributes and object scope.' + ); + + IF l_requested_attributes_json IS NULL OR + NVL(DBMS_LOB.GETLENGTH(l_requested_attributes_json), 0) = 0 OR + UPPER(TRIM(DBMS_LOB.SUBSTR(l_requested_attributes_json, 4, 1))) = 'NULL' THEN l_default_object_item.put('owner', l_install_schema); l_default_object_item.put('type', 'SCHEMA'); l_default_object_list.append(l_default_object_item); l_attributes.put('object_list', l_default_object_list); - - DBMS_OUTPUT.PUT_LINE( - 'TEAM_ATTRIBUTES_JSON not provided. Using default object_list for schema ' || - l_install_schema || '.' - ); ELSE BEGIN - l_attributes := JSON_OBJECT_T.parse(l_team_attributes_json); - DBMS_OUTPUT.PUT_LINE('Using custom TEAM_ATTRIBUTES_JSON.'); + l_attributes := JSON_OBJECT_T.parse(l_requested_attributes_json); EXCEPTION WHEN OTHERS THEN RAISE_APPLICATION_ERROR( @@ -249,7 +977,6 @@ BEGIN END; END IF; - -- Always enforce the AI profile parameter. l_attributes.put('profile_name', l_profile_name); IF l_attributes.get('object_list') IS NULL THEN @@ -275,48 +1002,100 @@ BEGIN 'TEAM_ATTRIBUTES_JSON object_list must not be empty.' ); END IF; - DBMS_OUTPUT.PUT_LINE('object_list entries: ' || l_object_list_count); - ---------------------------------------------------------------- - -- Recreate team to ensure task/agent/tools are recreated. - ---------------------------------------------------------------- - DATABASE_INSPECT.drop_inspect_agent_team( - agent_team_name => l_team_name, - force => TRUE + l_effective_attributes_json := l_attributes.to_clob; + + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Effective team attributes resolved.', + p_effective_attributes_json => l_effective_attributes_json, + p_object_list_count => l_object_list_count + ); + + IF l_recreate_existing = 'Y' THEN + l_current_step := 'CLEANING_EXTERNAL_OBJECTS'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'RECREATE_EXISTING = Y. Cleaning up any existing external AI team, agent, and task objects before creation.', + p_effective_attributes_json => l_effective_attributes_json, + p_object_list_count => l_object_list_count + ); + + cleanup_external_agent_objects(l_team_name); + END IF; + + IF team_exists(l_team_name) THEN + l_existing_attributes_snapshot := + get_existing_attributes_snapshot(l_team_name); + + IF l_recreate_existing = 'Y' THEN + l_current_step := 'DROPPING_EXISTING_TEAM'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Existing team found. Dropping team before recreation.', + p_effective_attributes_json => l_effective_attributes_json, + p_existing_attributes_snapshot => l_existing_attributes_snapshot, + p_object_list_count => l_object_list_count + ); + + DATABASE_INSPECT.drop_inspect_agent_team( + agent_team_name => l_team_name, + force => TRUE + ); + ELSE + RAISE_APPLICATION_ERROR( + -20000, + 'Agent team ' || l_team_name || + ' already exists. Re-run installer with RECREATE_EXISTING = Y ' || + 'to drop and recreate it.' + ); + END IF; + END IF; + + l_current_step := 'CREATING_TEAM'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Creating team, task, tools, and vectorized object metadata. This step can take time for large object lists.', + p_effective_attributes_json => l_effective_attributes_json, + p_object_list_count => l_object_list_count ); DATABASE_INSPECT.create_inspect_agent_team( agent_team_name => l_team_name, - attributes => l_attributes.to_clob + attributes => l_effective_attributes_json ); - DBMS_OUTPUT.PUT_LINE('Created team ' || l_team_name || - ' (agent/task/tools created internally by DATABASE_INSPECT package).'); + l_current_step := 'VERIFYING_TOOLS'; + update_log( + p_status => 'RUNNING', + p_current_step => l_current_step, + p_status_message => 'Team created. Verifying tool recreation.', + p_effective_attributes_json => l_effective_attributes_json, + p_object_list_count => l_object_list_count + ); - ---------------------------------------------------------------- - -- Verification for recreated tools with expected team-id suffix. - ---------------------------------------------------------------- DECLARE - l_count NUMBER := 0; - l_team_id NUMBER; - l_tool_suffix VARCHAR2(100); - l_sql CLOB; - l_cursor SYS_REFCURSOR; - l_tool_name VARCHAR2(4000); + l_count NUMBER := 0; + l_team_id NUMBER; + l_tool_suffix VARCHAR2(100); + l_sql CLOB; l_expected_tools SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST( - DATABASE_INSPECT.TOOL_LIST_OBJECTS, - DATABASE_INSPECT.TOOL_LIST_INCOMING_DEPENDENCIES, - DATABASE_INSPECT.TOOL_LIST_OUTGOING_DEPENDENCIES, - DATABASE_INSPECT.TOOL_GENERATE_GRAPH, - DATABASE_INSPECT.TOOL_RETRIEVE_OBJECT_METADATA, - DATABASE_INSPECT.TOOL_RETRIEVE_OBJECT_METADATA_CHUNKS, - DATABASE_INSPECT.TOOL_EXPAND_OBJECT_METADATA_CHUNK, - DATABASE_INSPECT.TOOL_SUMMARIZE_OBJECT, - DATABASE_INSPECT.TOOL_GENERATE_PLDOC + 'list_objects', + 'list_incoming_dependencies', + 'list_outgoing_dependencies', + 'retrieve_object_metadata', + 'retrieve_object_metadata_chunks', + 'expand_object_metadata_chunk', + 'summarize_object', + 'generate_pldoc' ); BEGIN - l_sql := 'SELECT id# FROM ' || DATABASE_INSPECT.INSPECT_AGENT_TEAMS || ' ' || - 'WHERE agent_team_name = :1'; + l_sql := 'SELECT id# FROM ' || l_inspect_agent_teams || + ' WHERE agent_team_name = :1'; EXECUTE IMMEDIATE l_sql INTO l_team_id USING l_team_name; l_tool_suffix := '_' || TO_CHAR(l_team_id); @@ -329,63 +1108,450 @@ BEGIN FROM TABLE(l_expected_tools) ); - DBMS_OUTPUT.PUT_LINE('Tool suffix for team ' || l_team_name || - ': ' || l_tool_suffix); - DBMS_OUTPUT.PUT_LINE('Expected tools found: ' || l_count || ' / ' || - l_expected_tools.COUNT); - IF l_count != l_expected_tools.COUNT THEN RAISE_APPLICATION_ERROR( -20000, 'Tool recreation check failed. Expected ' || l_expected_tools.COUNT || - ' tools with suffix ' || - l_tool_suffix || ', found ' || l_count || '.' + ' tools with suffix ' || l_tool_suffix || ', found ' || l_count || '.' ); END IF; - l_sql := 'SELECT tool_name FROM user_ai_agent_tools ' || - 'WHERE tool_name LIKE :1 ORDER BY tool_name'; - OPEN l_cursor FOR l_sql USING '%' || l_tool_suffix; - LOOP - FETCH l_cursor INTO l_tool_name; - EXIT WHEN l_cursor%NOTFOUND; - DBMS_OUTPUT.PUT_LINE(' - ' || l_tool_name); - END LOOP; - CLOSE l_cursor; + l_verification_message := + 'Team created successfully. Verified ' || l_count || + ' expected tools using suffix ' || l_tool_suffix || '.'; EXCEPTION WHEN OTHERS THEN IF SQLCODE = -942 THEN - DBMS_OUTPUT.PUT_LINE( - 'Warning: tool verification skipped because required table/view is not visible: ' || - SQLERRM + l_verification_message := + 'Team created successfully. Tool verification was skipped because a required view/table is not visible: ' || + SQLERRM; + ELSE + RAISE; + END IF; + END; + + l_current_step := 'COMPLETED'; + update_log( + p_status => 'SUCCEEDED', + p_current_step => l_current_step, + p_status_message => l_verification_message, + p_effective_attributes_json => l_effective_attributes_json, + p_object_list_count => l_object_list_count, + p_mark_completed => 'Y' + ); +EXCEPTION + WHEN OTHERS THEN + l_error_code := SQLCODE; + l_error_message := SQLERRM; + l_error_stack := DBMS_UTILITY.format_error_stack || CHR(10) || + DBMS_UTILITY.format_error_backtrace; + l_friendly_status_message := build_friendly_status_message( + p_error_code => l_error_code, + p_error_message => l_error_message, + p_error_stack => l_error_stack + ); + l_friendly_error_message := build_friendly_install_error( + p_error_code => l_error_code, + p_error_message => l_error_message, + p_error_stack => l_error_stack + ); + + UPDATE DATABASE_INSPECT_AGENT_JOB_LOG$ + SET status = 'FAILED', + current_step = l_current_step, + status_message = l_friendly_status_message, + effective_attributes_json = + CASE + WHEN l_effective_attributes_json IS NOT NULL THEN + l_effective_attributes_json + ELSE + effective_attributes_json + END, + existing_attributes_snapshot = + CASE + WHEN l_existing_attributes_snapshot IS NOT NULL THEN + l_existing_attributes_snapshot + ELSE + existing_attributes_snapshot + END, + object_list_count = + CASE + WHEN l_object_list_count IS NOT NULL THEN + l_object_list_count + ELSE + object_list_count + END, + error_code = l_error_code, + error_message = l_friendly_error_message, + error_stack = l_error_stack, + completed_at = SYSTIMESTAMP, + updated_at = SYSTIMESTAMP + WHERE run_id = p_run_id; + + COMMIT; + RAISE; +END database_inspect_agent_worker; +/ + +show errors procedure database_inspect_agent_worker; + + +---------------------------------------------------------------- +-- 5. Create installer submitter procedure in target schema +---------------------------------------------------------------- +CREATE OR REPLACE PROCEDURE &&INSTALL_SCHEMA_NAME..database_inspect_agent ( + p_install_schema IN VARCHAR2, + p_profile_name IN VARCHAR2, + p_team_name IN VARCHAR2, + p_team_attributes_json IN CLOB DEFAULT NULL, + p_recreate_existing IN VARCHAR2 DEFAULT 'N' +) +AUTHID DEFINER +AS + l_inspect_agent_teams CONSTANT VARCHAR2(128) := + 'DATABASE_INSPECT_AGENT_TEAMS$'; + l_inspect_agent_team_attributes CONSTANT VARCHAR2(128) := + 'DATABASE_INSPECT_AGENT_TEAM_ATTRIBUTES$'; + + l_install_schema VARCHAR2(128); + l_profile_name VARCHAR2(128); + l_team_name VARCHAR2(128); + l_recreate_existing VARCHAR2(1); + l_existing_attributes_snapshot CLOB; + l_job_name VARCHAR2(128); + l_run_id NUMBER; + l_error_code NUMBER; + l_error_message CLOB; + l_error_stack CLOB; + + FUNCTION team_exists( + p_team_name IN VARCHAR2 + ) RETURN BOOLEAN + IS + l_sql VARCHAR2(4000); + l_count NUMBER; + BEGIN + l_sql := 'SELECT COUNT(*) FROM ' || l_inspect_agent_teams || + ' WHERE agent_team_name = :1'; + EXECUTE IMMEDIATE l_sql INTO l_count USING p_team_name; + RETURN l_count > 0; + EXCEPTION + WHEN OTHERS THEN + IF SQLCODE = -942 THEN + RAISE_APPLICATION_ERROR( + -20000, + 'DATABASE_INSPECT internal tables are not available. ' || + 'Run database_inspect_tool.sql before running this installer.' ); ELSE RAISE; END IF; + END team_exists; + + FUNCTION get_existing_attributes_snapshot( + p_team_name IN VARCHAR2 + ) RETURN CLOB + IS + l_snapshot CLOB; + l_sql VARCHAR2(4000); + l_cursor SYS_REFCURSOR; + l_attr_name VARCHAR2(128); + l_attr_value CLOB; + l_line VARCHAR2(32767); + BEGIN + DBMS_LOB.CREATETEMPORARY(l_snapshot, TRUE); + + l_sql := 'SELECT attribute_name, attribute_value FROM ' || + l_inspect_agent_team_attributes || + ' WHERE agent_team_name = :1 ORDER BY attribute_name'; + + OPEN l_cursor FOR l_sql USING p_team_name; + LOOP + FETCH l_cursor INTO l_attr_name, l_attr_value; + EXIT WHEN l_cursor%NOTFOUND; + + l_line := l_attr_name || ' = ' || + CASE + WHEN l_attr_value IS NULL THEN + 'NULL' + WHEN DBMS_LOB.GETLENGTH(l_attr_value) <= 3000 THEN + DBMS_LOB.SUBSTR(l_attr_value, 3000, 1) + ELSE + DBMS_LOB.SUBSTR(l_attr_value, 3000, 1) || + '... [truncated]' + END || CHR(10); + + DBMS_LOB.WRITEAPPEND(l_snapshot, LENGTH(l_line), l_line); + END LOOP; + CLOSE l_cursor; + + IF DBMS_LOB.GETLENGTH(l_snapshot) = 0 THEN + DBMS_LOB.FREETEMPORARY(l_snapshot); + RETURN NULL; + END IF; + + RETURN l_snapshot; + EXCEPTION + WHEN OTHERS THEN + IF l_cursor%ISOPEN THEN + CLOSE l_cursor; + END IF; + + IF DBMS_LOB.ISTEMPORARY(l_snapshot) = 1 THEN + DBMS_LOB.FREETEMPORARY(l_snapshot); + END IF; + + RETURN 'Unable to retrieve existing team attributes: ' || SQLERRM; + END get_existing_attributes_snapshot; + + PROCEDURE print_clob( + p_text IN CLOB + ) + IS + l_pos NUMBER := 1; + BEGIN + IF p_text IS NULL THEN + RETURN; + END IF; + + WHILE l_pos <= DBMS_LOB.GETLENGTH(p_text) LOOP + DBMS_OUTPUT.PUT_LINE(DBMS_LOB.SUBSTR(p_text, 3000, l_pos)); + l_pos := l_pos + 3000; + END LOOP; + END print_clob; + + PROCEDURE print_monitor_query( + p_run_id IN NUMBER + ) + IS + BEGIN + DBMS_OUTPUT.PUT_LINE('### Monitor Progress'); + DBMS_OUTPUT.PUT_LINE( + 'Use the query below to track this request in ' || + l_install_schema || '.DATABASE_INSPECT_AGENT_JOB_LOG$:' + ); + DBMS_OUTPUT.PUT_LINE('SELECT'); + DBMS_OUTPUT.PUT_LINE(' run_id,'); + DBMS_OUTPUT.PUT_LINE(' team_name,'); + DBMS_OUTPUT.PUT_LINE(' status,'); + DBMS_OUTPUT.PUT_LINE(' current_step,'); + DBMS_OUTPUT.PUT_LINE(' job_name,'); + DBMS_OUTPUT.PUT_LINE(' created_at,'); + DBMS_OUTPUT.PUT_LINE(' started_at,'); + DBMS_OUTPUT.PUT_LINE(' completed_at,'); + DBMS_OUTPUT.PUT_LINE(' error_code,'); + DBMS_OUTPUT.PUT_LINE( + ' DBMS_LOB.SUBSTR(status_message, 4000, 1) AS status_message' + ); + DBMS_OUTPUT.PUT_LINE( + 'FROM ' || l_install_schema || '.DATABASE_INSPECT_AGENT_JOB_LOG$' + ); + DBMS_OUTPUT.PUT_LINE('WHERE run_id = ' || p_run_id || ';'); + END print_monitor_query; +BEGIN + DBMS_UTILITY.canonicalize( + DBMS_ASSERT.SIMPLE_SQL_NAME(p_install_schema), + l_install_schema, + LENGTHB(p_install_schema) + ); + + DBMS_UTILITY.canonicalize( + DBMS_ASSERT.SIMPLE_SQL_NAME(p_profile_name), + l_profile_name, + LENGTHB(p_profile_name) + ); + + DBMS_UTILITY.canonicalize( + DBMS_ASSERT.SIMPLE_SQL_NAME(p_team_name), + l_team_name, + LENGTHB(p_team_name) + ); + + l_recreate_existing := NVL(UPPER(TRIM(p_recreate_existing)), 'N'); + + IF l_recreate_existing NOT IN ('Y', 'N') THEN + RAISE_APPLICATION_ERROR( + -20000, + 'Invalid RECREATE_EXISTING value. Use Y or N.' + ); + END IF; + + INSERT INTO DATABASE_INSPECT_AGENT_JOB_LOG$ + ( + team_name, + install_schema, + profile_name, + recreate_existing, + requested_attributes_json, + status, + current_step, + status_message, + updated_at + ) + VALUES + ( + l_team_name, + l_install_schema, + l_profile_name, + l_recreate_existing, + p_team_attributes_json, + 'PRECHECK', + 'PRECHECK', + 'Validating team request before scheduling.', + SYSTIMESTAMP + ) + RETURNING run_id INTO l_run_id; + + COMMIT; + + BEGIN + -- Make sure internal team tables exist before precheck queries. + DATABASE_INSPECT.setup; + EXCEPTION + WHEN OTHERS THEN + RAISE_APPLICATION_ERROR( + -20000, + 'DATABASE_INSPECT package setup failed. Run database_inspect_tool.sql before running this installer. ' || + SQLERRM + ); END; - DBMS_OUTPUT.PUT_LINE('------------------------------------------------'); - DBMS_OUTPUT.PUT_LINE('DATABASE_INSPECT Team installation COMPLETE'); - DBMS_OUTPUT.PUT_LINE('------------------------------------------------'); + IF team_exists(l_team_name) THEN + l_existing_attributes_snapshot := + get_existing_attributes_snapshot(l_team_name); + + IF l_recreate_existing = 'N' THEN + UPDATE DATABASE_INSPECT_AGENT_JOB_LOG$ + SET status = 'TEAM_EXISTS', + current_step = 'PRECHECK_TEAM_EXISTS', + status_message = + 'Agent team already exists. Re-run with RECREATE_EXISTING = Y to drop and recreate it.', + existing_attributes_snapshot = l_existing_attributes_snapshot, + completed_at = SYSTIMESTAMP, + updated_at = SYSTIMESTAMP + WHERE run_id = l_run_id; + + COMMIT; + + DBMS_OUTPUT.PUT_LINE('### Team Already Exists'); + DBMS_OUTPUT.PUT_LINE( + 'Agent team ' || l_team_name || ' already exists in schema ' || + l_install_schema || '.' + ); + DBMS_OUTPUT.PUT_LINE( + 'Current attributes from ' || + l_install_schema || '.DATABASE_INSPECT_AGENT_TEAM_ATTRIBUTES$:' + ); + print_clob(l_existing_attributes_snapshot); + DBMS_OUTPUT.PUT_LINE('Run id: ' || l_run_id); + DBMS_OUTPUT.PUT_LINE( + 'No background job was submitted. Re-run with RECREATE_EXISTING = Y to drop and recreate this team.' + ); + print_monitor_query(l_run_id); + RETURN; + END IF; + END IF; + + l_job_name := 'DBIA_JOB_' || TO_CHAR(l_run_id); + + UPDATE DATABASE_INSPECT_AGENT_JOB_LOG$ + SET status = 'QUEUED', + current_step = 'JOB_SUBMITTED', + status_message = + CASE + WHEN l_existing_attributes_snapshot IS NOT NULL THEN + 'Existing team found. Background job submitted and will drop/recreate the team.' + ELSE + 'Background job submitted. Waiting for scheduler to start.' + END, + existing_attributes_snapshot = + CASE + WHEN l_existing_attributes_snapshot IS NOT NULL THEN + l_existing_attributes_snapshot + ELSE + existing_attributes_snapshot + END, + job_name = l_job_name, + queued_at = SYSTIMESTAMP, + updated_at = SYSTIMESTAMP + WHERE run_id = l_run_id; + + COMMIT; + + DBMS_SCHEDULER.create_job( + job_name => l_job_name, + job_type => 'STORED_PROCEDURE', + job_action => l_install_schema || '.DATABASE_INSPECT_AGENT_WORKER', + number_of_arguments => 1, + start_date => SYSTIMESTAMP, + enabled => FALSE, + auto_drop => TRUE + ); + + DBMS_SCHEDULER.set_job_argument_value( + job_name => l_job_name, + argument_position => 1, + argument_value => TO_CHAR(l_run_id) + ); + + DBMS_SCHEDULER.enable(l_job_name); + + DBMS_OUTPUT.PUT_LINE('### Background Job Submitted'); + DBMS_OUTPUT.PUT_LINE('Submitted background job ' || l_job_name || + ' for team ' || l_team_name || '.'); + DBMS_OUTPUT.PUT_LINE('Run id: ' || l_run_id); + print_monitor_query(l_run_id); +EXCEPTION + WHEN OTHERS THEN + l_error_code := SQLCODE; + l_error_message := SQLERRM; + l_error_stack := DBMS_UTILITY.format_error_stack || CHR(10) || + DBMS_UTILITY.format_error_backtrace; + + IF l_run_id IS NOT NULL THEN + UPDATE DATABASE_INSPECT_AGENT_JOB_LOG$ + SET status = 'FAILED_TO_QUEUE', + current_step = 'JOB_SUBMISSION_FAILED', + status_message = 'Failed to submit background job.', + error_code = l_error_code, + error_message = l_error_message, + error_stack = l_error_stack, + completed_at = SYSTIMESTAMP, + updated_at = SYSTIMESTAMP + WHERE run_id = l_run_id; + + COMMIT; + END IF; + + RAISE; END database_inspect_agent; / +show errors procedure database_inspect_agent; + ---------------------------------------------------------------- --- 4. Execute installer in target schema +-- 6. Execute installer submitter in target schema ---------------------------------------------------------------- PROMPT Executing installer procedure ... BEGIN - database_inspect_agent( + &&INSTALL_SCHEMA_NAME..database_inspect_agent( p_install_schema => :v_schema, p_profile_name => :v_ai_profile_name, - p_team_attributes_json => :v_team_attributes_json + p_team_name => :v_agent_team_name, + p_team_attributes_json => :v_team_attributes_json, + p_recreate_existing => :v_recreate_existing ); END; / PROMPT ====================================================== -PROMPT Installation finished successfully +PROMPT Installer request completed +PROMPT Monitor DATABASE_INSPECT_AGENT_JOB_LOG$ for progress PROMPT ====================================================== -alter session set current_schema = ADMIN; +BEGIN + EXECUTE IMMEDIATE + 'ALTER SESSION SET CURRENT_SCHEMA = ' || :v_invoker_schema; +END; +/ diff --git a/autonomous-ai-agents/database_inspect/database_inspect_tool.sql b/autonomous-ai-agents/database_inspect/database_inspect_tool.sql index de41711..1e33058 100644 --- a/autonomous-ai-agents/database_inspect/database_inspect_tool.sql +++ b/autonomous-ai-agents/database_inspect/database_inspect_tool.sql @@ -11,6 +11,12 @@ rem DESCRIPTION rem Installer script for DATABASE_INSPECT package and tool framework. rem (Select AI Agent / Oracle AI Database) rem +rem RELEASE VERSION +rem 1.1 +rem +rem RELEASE DATE +rem 5-Feb-2026 +rem rem This script: rem - Grants required privileges to the target schema rem - Switches session to the target schema @@ -42,6 +48,8 @@ PROMPT ====================================================== -- Target schema VAR v_schema VARCHAR2(128) EXEC :v_schema := '&SCHEMA_NAME'; +VAR v_invoker_schema VARCHAR2(128) +EXEC :v_invoker_schema := SYS_CONTEXT('USERENV', 'SESSION_USER'); CREATE OR REPLACE PROCEDURE initialize_database_inspect_tools( @@ -49,6 +57,7 @@ CREATE OR REPLACE PROCEDURE initialize_database_inspect_tools( ) IS l_schema_name VARCHAR2(128); + l_session_user VARCHAR2(128) := SYS_CONTEXT('USERENV', 'SESSION_USER'); TYPE priv_list_t IS VARRAY(50) OF VARCHAR2(4000); l_priv_list CONSTANT priv_list_t := priv_list_t( @@ -65,14 +74,14 @@ IS p_objects IN priv_list_t ) IS - l_session_user VARCHAR2(128); BEGIN - l_session_user := SYS_CONTEXT('USERENV', 'SESSION_USER'); - - -- Avoid self-grant errors (ORA-01749) when installer schema == connected user. + -- Granting privileges to the current session user is a no-op and raises + -- ORA-01749. Skip that case to keep reruns clean. IF UPPER(p_schema) = UPPER(l_session_user) THEN - DBMS_OUTPUT.PUT_LINE('Skipping grants for schema ' || p_schema || - ' (same as session user).'); + DBMS_OUTPUT.PUT_LINE( + 'Skipping EXECUTE grants because target schema ' || p_schema || + ' is the current session user.' + ); RETURN; END IF; @@ -81,8 +90,12 @@ IS EXECUTE IMMEDIATE 'GRANT EXECUTE ON ' || p_objects(i) || ' TO ' || p_schema; EXCEPTION WHEN OTHERS THEN - DBMS_OUTPUT.PUT_LINE('Warning: failed to grant ' || p_objects(i) || - ' to ' || p_schema || ' - ' || SQLERRM); + IF SQLCODE = -1749 THEN + NULL; + ELSE + DBMS_OUTPUT.PUT_LINE('Warning: failed to grant ' || p_objects(i) || + ' to ' || p_schema || ' - ' || SQLERRM); + END IF; END; END LOOP; END execute_grants; @@ -149,7 +162,9 @@ CREATE OR REPLACE PACKAGE database_inspect AUTHID CURRENT_USER AS OBJECT_FUNCTION CONSTANT DBMS_ID := 'FUNCTION'; OBJECT_TRIGGER CONSTANT DBMS_ID := 'TRIGGER'; OBJECT_VIEW CONSTANT DBMS_ID := 'VIEW'; - OBJECT_TYPE CONSTANT DBMS_ID := 'TYPE'; + -- Avoid confusion with object_type + OBJECT_TYPE_TYPE CONSTANT DBMS_ID := 'TYPE'; + OBJECT_TYPE_BODY CONSTANT DBMS_ID := 'TYPE BODY'; -- Reserved name for internal tables INSPECT_AGENT_TEAMS CONSTANT DBMS_ID := 'DATABASE_INSPECT_AGENT_TEAMS$'; @@ -174,7 +189,6 @@ CREATE OR REPLACE PACKAGE database_inspect AUTHID CURRENT_USER AS TOOL_EXPAND_OBJECT_METADATA_CHUNK CONSTANT DBMS_ID := 'expand_object_metadata_chunk'; TOOL_SUMMARIZE_OBJECT CONSTANT DBMS_ID := 'summarize_object'; TOOL_GENERATE_PLDOC CONSTANT DBMS_ID := 'generate_pldoc'; - TOOL_GENERATE_GRAPH CONSTANT DBMS_ID := 'generate_graph'; -- Vector distance types VEC_DIST_COSINE CONSTANT DBMS_ID := 'cosine'; @@ -269,14 +283,22 @@ CREATE OR REPLACE PACKAGE database_inspect AUTHID CURRENT_USER AS ----------------------------------------------------------------------------- - -- expand_object_metadata_chunk: Returns a concatenated CLOB of - -- vectorized code snippets surrounding a specified content index for a given - -- object. + -- expand_object_metadata_chunk: Returns a JSON CLOB containing expanded + -- source code and its corresponding line range for a specified content index + -- within a given object. -- - -- The function retrieves snippets whose content_index falls within - -- ± search_range of the provided content_index, ordered by content_index - -- to preserve the original code sequence. - -- Tool name will contain the id for the agent team. + -- The function retrieves vectorized code snippets whose content_index falls + -- within ± search_range of the provided content_index. The snippets are + -- ordered by content_index to reconstruct the original code sequence. + -- + -- The returned JSON object contains: + -- - code: the concatenated expanded code snippet + -- - start_line: the starting line number of the expanded code in the + -- original source (minimum start_line across the snippets) + -- - end_line: the ending line number of the expanded code in the original + -- source (maximum end_line across the snippets) + -- + -- Tool name contains the identifier of the agent team. ----------------------------------------------------------------------------- FUNCTION expand_object_metadata_chunk( tool_name IN VARCHAR2, @@ -316,20 +338,6 @@ CREATE OR REPLACE PACKAGE database_inspect AUTHID CURRENT_USER AS ) RETURN CLOB; - ----------------------------------------------------------------------------- - -- generate_chart: Generates graph content from a natural language prompt and - -- relationship context (Mermaid or HTML tree). - -- Tool name will contain the id for the agent team. - ----------------------------------------------------------------------------- - FUNCTION generate_chart( - tool_name IN VARCHAR2, - chart_prompt IN CLOB, - output_format IN VARCHAR2 DEFAULT 'mermaid', - graph_style IN VARCHAR2 DEFAULT 'auto', - graph_direction IN VARCHAR2 DEFAULT 'TB' - ) RETURN CLOB; - - ----------------------------------------------------------------------------- -- setup: create basic internal tables ----------------------------------------------------------------------------- @@ -624,9 +632,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS TOOL_RETRIEVE_OBJECT_METADATA, TOOL_RETRIEVE_OBJECT_METADATA_CHUNKS, TOOL_EXPAND_OBJECT_METADATA_CHUNK, - TOOL_SUMMARIZE_OBJECT, TOOL_GENERATE_PLDOC, - TOOL_GENERATE_GRAPH, - 'generate_dependency_chart') + TOOL_SUMMARIZE_OBJECT, TOOL_GENERATE_PLDOC) THEN raise_application_error(-20000, 'Invalid tool type - ' || tool_type); END IF; @@ -1312,7 +1318,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IS l_metdata CLOB := ''; l_chunk_size NUMBER; - l_start_position NUMBER; + l_start_position NUMBER := 1; l_chunk CLOB; l_new_line_index NUMBER; l_metadata_js JSON_OBJECT_T; @@ -1326,6 +1332,8 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_provider DBMS_ID; l_vector_table_name VARCHAR2(200) := VECTOR_TABLE_PREFIX || agent_team_name; + l_start_line_number NUMBER := 1; + l_end_line_number NUMBER; PROCEDURE store_vector IS @@ -1366,7 +1374,9 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS PROCEDURE store_chunk( content IN CLOB, - content_index IN NUMBER + content_index IN NUMBER, + start_line IN NUMBER, + end_line IN NUMBER ) IS BEGIN @@ -1376,6 +1386,8 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_metadata_js.put('object_type', obj_type); l_metadata_js.put('content_index', content_index); l_metadata_js.put('content', content); + l_metadata_js.put('start_line', start_line); + l_metadata_js.put('end_line', end_line); l_point := JSON_OBJECT_T('{}'); l_point.put('payload', l_metadata_js); @@ -1405,7 +1417,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_chunk_size := DEFAULT_CHUNK_SIZE; END IF; - l_start_position := 1; WHILE l_start_position < LENGTH(l_metdata) LOOP l_chunk := SUBSTR(l_metdata, l_start_position, l_chunk_size); l_new_line_index := INSTR(l_chunk, CHR(10), -1, 1); @@ -1414,12 +1425,19 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS THEN l_chunk := SUBSTR(l_chunk, 1, l_new_line_index); l_start_position := l_start_position + l_new_line_index; + l_end_line_number := l_start_line_number + + REGEXP_COUNT(l_chunk, CHR(10)) - 1; ELSE l_start_position := l_start_position + l_chunk_size; + l_end_line_number := l_start_line_number + + REGEXP_COUNT(l_chunk, CHR(10)); END IF; l_content_index := l_content_index + 1; - store_chunk(l_chunk, l_content_index); + + store_chunk(l_chunk, l_content_index, l_start_line_number, + l_end_line_number); + l_start_line_number := l_end_line_number + 1; END LOOP; IF l_points_arr.get_size > 0 THEN @@ -1511,7 +1529,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS profile_name IN VARCHAR2 ) IS - l_embedding JSON_ARRAY_T; l_chunk_size NUMBER; l_dummy_prompt CLOB; l_vector_dimension NUMBER; @@ -1522,6 +1539,10 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_text_index_name VARCHAR2(200); l_count NUMBER; l_distance_type DBMS_ID := VEC_DIST_COSINE; + l_dummy_payload JSON_OBJECT_T; + l_dummy_point JSON_OBJECT_T; + l_points_arr JSON_ARRAY_T := JSON_ARRAY_T('[]'); + l_embed_arr JSON_ARRAY_T; BEGIN l_vector_table_name := VECTOR_TABLE_PREFIX || agent_team_name; l_vector_index_name := VECTOR_INDEX_PREFIX || agent_team_name; @@ -1542,16 +1563,22 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS INITCAP(l_provider) || ' is not supported for vector embeddings.'); END IF; - -- Validate the dimension using a dummy vector embedding request + -- Use the same embedding path as object registration so the vector table + -- dimension always matches the vectors we later insert. l_dummy_prompt := DBMS_RANDOM.string('a', l_chunk_size); - l_embedding := JSON_ARRAY_T( - DBMS_CLOUD_AI.generate( - prompt => l_dummy_prompt, - profile_name => profile_name, - action => 'embedding')); + l_dummy_payload := JSON_OBJECT_T('{}'); + l_dummy_payload.put('content', l_dummy_prompt); + l_dummy_point := JSON_OBJECT_T('{}'); + l_dummy_point.put('payload', l_dummy_payload); + l_points_arr.append(l_dummy_point); + + get_embedding_batch(agent_team_name => agent_team_name, + points_arr => l_points_arr); + l_embed_arr := TREAT(l_points_arr.get(0) AS JSON_OBJECT_T) + .get_array('vector'); -- Get value for dimension - l_vector_dimension := l_embedding.get_size(); + l_vector_dimension := l_embed_arr.get_size(); drop_vector_table(agent_team_name); @@ -1702,11 +1729,13 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS SELECT object_name, object_type FROM all_objects WHERE owner = owner_name AND object_type IN (OBJECT_TABLE, OBJECT_VIEW, OBJECT_TRIGGER, - OBJECT_TYPE, OBJECT_FUNCTION, OBJECT_PROCEDURE, + OBJECT_TYPE_TYPE, OBJECT_FUNCTION, + OBJECT_PROCEDURE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY) ) LOOP IF m.object_type IN (OBJECT_TABLE, OBJECT_VIEW, OBJECT_TRIGGER, - OBJECT_TYPE, OBJECT_FUNCTION, OBJECT_PROCEDURE, + OBJECT_TYPE_TYPE, OBJECT_FUNCTION, + OBJECT_PROCEDURE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY) AND -- Avoid set the database_inspect package (m.object_type NOT IN (OBJECT_PACKAGE, OBJECT_PACKAGE_BODY) OR @@ -1718,10 +1747,15 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS (m.object_name NOT IN (INSPECT_AGENT_TEAMS, INSPECT_AGENT_TEAM_ATTRIBUTES, INSPECT_OBJECTS) AND - m.object_name NOT LIKE VECTOR_TABLE_PREFIX || '%' AND + m.object_name NOT LIKE VECTOR_TABLE_PREFIX || '%' AND + m.object_name NOT LIKE 'COPY$%' AND + m.object_name NOT LIKE 'SYNTHETIC_DATA$%' AND + m.object_name NOT LIKE 'DM$%' AND + m.object_name NOT LIKE 'PIPELINE$%' AND -- Internal tables for Select AI Agent m.object_name NOT IN ('ADB_CHAT_PROMPTS', - 'DBTOOLS$EXECUTION_HISTORY', + 'DBTOOLS$EXECUTION_HISTORY', + 'ASK_ORACLE_AUTO_VISUAL_LOG', 'PIN_CONVERSATIONS', 'CONVERSATION_TIME') AND m.object_name NOT LIKE 'SYS_%')) @@ -1729,8 +1763,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS AND (m.object_type NOT IN (OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE) OR m.object_name NOT IN ('ADB_CHAT', 'PROCESS_PROMPT_REQUEST', - 'SET_THEME_STYLE_FOR_APP', - 'SET_THEME_STYLE_FOR_APP') + 'SET_THEME_STYLE_FOR_APP','ASK_ORACLE_GET_AGENT_AUTO_VISUAL') ) -- You can add more tables here that you want to skip during -- schema registration. @@ -1829,12 +1862,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS tool_name_suffix; l_tool_generate_pldoc VARCHAR2(200) := TOOL_GENERATE_PLDOC || tool_name_suffix; - l_tool_generate_graph VARCHAR2(200) := - TOOL_GENERATE_GRAPH || - tool_name_suffix; - l_tool_generate_graph_legacy VARCHAR2(200) := - 'generate_dependency_chart' || - tool_name_suffix; BEGIN -- TOOL: list_objects BEGIN @@ -1956,60 +1983,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS }' ); - -- TOOL: generate_graph - BEGIN - DBMS_CLOUD_AI_AGENT.drop_tool(l_tool_generate_graph); - EXCEPTION - WHEN OTHERS THEN - NULL; - END; - -- Backward compatibility cleanup for prior tool name. - BEGIN - DBMS_CLOUD_AI_AGENT.drop_tool(l_tool_generate_graph_legacy); - EXCEPTION - WHEN OTHERS THEN - NULL; - END; - DBMS_CLOUD_AI_AGENT.create_tool( - tool_name => l_tool_generate_graph, - attributes => '{ - "instruction": "Use this tool to generate relationship visualization output as Mermaid syntax or HTML tree markup. ' || - 'Supported scenarios include dependency maps, hierarchy trees, binary-tree style layouts, and general relationship networks. ' || - 'Before calling this tool, collect/validate relationship data with discovery tools such as ' || l_tool_list_objects || ', ' || l_tool_list_incoming_deps || ', and ' || l_tool_list_outgoing_deps || ' whenever possible. ' || - 'Provide a complete chart_prompt that includes the nodes and directed edges to visualize. ' || - 'Use output_format to choose the rendering format, then use graph_style and graph_direction to guide layout. ' || - 'The output must be raw generated content only (no markdown fences and no explanation text).", - "tool_inputs": [ - { - "name": "tool_name", - "mandatory": true, - "description": "The name of the current tool to call." - }, - { - "name": "chart_prompt", - "mandatory": true, - "description": "A detailed prompt describing relationships to render, including nodes and directed edges." - }, - { - "name": "output_format", - "mandatory": false, - "description": "Optional output format. Allowed values: mermaid, html_tree. Default is mermaid." - }, - { - "name": "graph_style", - "mandatory": false, - "description": "Optional layout preference. Allowed values: auto, binary_tree, hierarchical, network, dependency." - }, - { - "name": "graph_direction", - "mandatory": false, - "description": "Optional Mermaid flow direction: TB, LR, BT, or RL. Default is TB." - } - ], - "function": "' || DATABASE_INSPECT_PACKAGE || '.generate_chart" - }' - ); - -- TOOL: retrieve_object_metadata BEGIN DBMS_CLOUD_AI_AGENT.drop_tool(l_tool_retrieve_object_metadata); @@ -2023,7 +1996,8 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS "instruction": "Use this tool to retrieve the full metadata (DDL) of a specified database object. ' || 'This is useful when you need to inspect the complete definition of an object (for example, table structure, trigger definition, or a standalone procedure/function) as part of a code analysis or impact assessment. ' || 'It is recommended to use this tool directly for relatively small object types such as: [TABLE, VIEW, TRIGGER, PROCEDURE, FUNCTION, TYPE], because their metadata is usually compact and unlikely to exceed the LLM token limit. ' || - 'However, for large objects such as PACKAGE, PACKAGE BODY, or SCHEMA-level metadata, the result can be very large and may exceed the LLM''s token budget. For these cases, only call retrieve_object_metadata when you truly need the full DDL; otherwise, prefer using retrieve_object_metadata_chunks (optionally scoped by object_list) and expand_object_metadata_chunk to retrieve just the relevant code sections. ' || + 'However, for large objects such as PACKAGE, PACKAGE BODY, the result can be very large and may exceed the LLM''s token budget. For these cases, only call retrieve_object_metadata when you truly need the full DDL; otherwise, prefer using retrieve_object_metadata_chunks (optionally scoped by object_list) and expand_object_metadata_chunk to retrieve just the relevant code sections. ' || + 'Do not use this tool for to get the full SCHEMA-level metadata. ' || 'Before calling this tool, you should always identify and validate the correct object_name, object_type, and object_owner using ' || l_tool_list_objects || ' (or ' || l_tool_list_incoming_deps || ' or ' || l_tool_list_outgoing_deps || ' when doing impact analysis).", "tool_inputs": [ { @@ -2065,7 +2039,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS 'The keyword MUST follow Oracle Text syntax and be wrapped in braces, for example: ''{keyword_1}''. ' || 'To specify multiple keywords, you may use logical expressions such as ''{keyword_1} OR {keyword_2}'' or ''{keyword_1} AND {keyword_2}''. ' || 'If object_list is provided, search results are further restricted to the specified objects. ' || - 'Each returned JSON item contains: the code snippet (data), metadata including object_owner, object_name, object_type, and content_index, as well as scoring information used to rank the results. ' || + 'Each returned JSON item contains: the code snippet (data), metadata including object_owner, object_name, object_type, line range (start_line, end_line) of the snippet in the source code, and content_index identifying the snippet within the object. It also contains scoring information used to rank the results. ' || 'Do not rely solely on the score when selecting relevant snippets; always inspect the actual content in the data field.", "tool_inputs": [ { @@ -2104,9 +2078,12 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS tool_name => l_tool_expand_object_metadata_chunk, attributes => '{ "instruction": "Use this tool to retrieve the surrounding source-code section for a snippet selected from the ' || l_tool_retrieve_object_metadata_chunks || ' tool''s response. ' || - 'Note: For the input value, you must use the exact values of object_name, object_owner, object_type in the selected json object response from ' || l_tool_retrieve_object_metadata_chunks || ' tool. ' || + 'For the input value, you must use the exact values of object_name, object_owner, object_type in the selected json object response from ' || l_tool_retrieve_object_metadata_chunks || ' tool. ' || 'If multiple code snippets are selected from the ' || l_tool_retrieve_object_metadata_chunks || ' response, call this tool separately for each one. ' || - 'Behavior: the tool returns the code block centered around the specified content_index and may include surrounding lines not strictly limited to the target function or procedure. ' || + 'Behavior: the tool returns a JSON object containing the expanded code block and its line range in the original source code. ' || + 'The JSON output contains: code (the expanded source code), start_line (the first line number of the returned code), and end_line (the last line number of the returned code). ' || + 'Use the start_line and end_line values when explaining where the code appears in the source file, identifying bugs, or referencing the location of specific logic. ' || + 'The returned code block is centered around the specified content_index and may include surrounding lines not strictly limited to the target function or procedure. ' || 'If the returned section does not include the full function or procedure, retry with a larger search_range value (up to a maximum of 100). ' || 'If multiple nearby snippets are relevant (adjacent content_index values), increase search_range so that the combined region is captured. ", "tool_inputs": [ @@ -2118,7 +2095,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS { "name": "object_name", "mandatory": true, - "description": "The name of the database object that contains the source code. Value must exactly match the one from the ''object_name'' field in the selected JSON item of the ' || l_tool_retrieve_object_metadata_chunks || ' response." + "description": "The name of the database object that contains the source code. Value must exactly match the one from the ''object_name'' field in the selected JSON item of the ' || l_tool_retrieve_object_metadata_chunks || ' response." }, { "name": "object_type", @@ -2292,7 +2269,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS EXCEPTION WHEN OTHERS THEN NULL; END; - l_task_instruction := + l_task_instruction := 'The user''s request is: {query}. Analyze the request to determine the specific Oracle database and PL/SQL codebase task being asked (for example, ' || 'finding all programs that reference a column, assessing the impact of a logic change, or explaining how a program uses a particular field). ' || 'Use the available tools to gather the necessary context from the codebase before forming your answer. ' || @@ -2320,10 +2297,10 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS ' Use these validated dependency lists together with {list_objects} to build the object_list argument for {retrieve_object_metadata_chunks} when you need to ' || 'constrain vector search to a concrete set of related objects. ' || - ' - Graph visualization: If the user asks for a graph, diagram, binary tree, hierarchy, or relationship map, gather the relevant relationships first (for dependency-style requests use {list_incoming_dependencies} and/or {list_outgoing_dependencies}). ' || - 'Then call {generate_graph} with a detailed prompt containing validated nodes and directed edges. ' || - 'If the user explicitly asks for a binary tree, call {generate_graph} with graph_style = ''binary_tree''. ' || - 'If the user explicitly asks for HTML/CSS output (instead of Mermaid), call {generate_graph} with output_format = ''html_tree''. ' || + ' - Full object metadata: When you need the full metadata of a relatively small object (TABLE, VIEW, TRIGGER, PROCEDURE, FUNCTION, TYPE), you can call ' || + '{retrieve_object_metadata} directly, since these are usually small enough not to exceed the LLM''s token limit. For large objects such as PACKAGE and PACKAGE BODY, ' || + 'avoid calling {retrieve_object_metadata} unless you truly need the full metadata. Prefer using {retrieve_object_metadata_chunks} (optionally with a validated ' || + 'object_list) plus {expand_object_metadata_chunk} to stay within token limits while focusing on relevant regions of code. ' || ' - Keyword usage for {retrieve_object_metadata_chunks}: When searching code with {retrieve_object_metadata_chunks}, provide a keyword whenever possible to improve search accuracy. ' || 'The keyword must follow Oracle Text syntax and must be wrapped in braces, for example: ''{}''. To specify multiple keywords, use expressions ' || @@ -2337,17 +2314,38 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS 'objects, call {retrieve_object_metadata_chunks} without object_list instead of guessing. ' || ' - Code search and context expansion: Use {retrieve_object_metadata_chunks} to identify relevant PL/SQL snippets or metadata blocks, guided by user_question, keyword, and ' || - 'optionally object_list. Once you identify promising snippets, call {expand_object_metadata_chunk} to retrieve the broader source-code context around those snippets ' || + 'optionally object_list. ' || + 'Each JSON item returned by {retrieve_object_metadata_chunks} includes metadata fields such as object_owner, object_name, object_type, content_index, start_line, and end_line. ' || + 'The start_line and end_line values indicate where the snippet appears in the original source code. Use these line numbers to understand the exact location of the logic within the ' || + 'source file. ' || + 'Once you identify promising snippets, call {expand_object_metadata_chunk} to retrieve the broader source-code context around those snippets ' || '(for example, entire procedures or functions) before drawing conclusions. If you decide to use multiple snippets from the {retrieve_object_metadata_chunks} response, ' || 'invoke {expand_object_metadata_chunk} individually for each snippet. ' || - ' When calling {expand_object_metadata_chunk}, the values for object_name, object_owner, object_type, and content_index must exactly match the corresponding fields in the ' || + 'The {expand_object_metadata_chunk} tool returns a JSON string object containing "code"(the expanded code block), "start_line" and "end_line". ' || + 'When calling {expand_object_metadata_chunk}, the values for object_name, object_owner, object_type, and content_index must exactly match the corresponding fields in the ' || 'selected JSON item from the {retrieve_object_metadata_chunks} response. Do not derive or guess these values from the code text in the data field; always copy them directly from ' || 'the structured attributes. ' || - ' - Full object metadata: When you need the full definition of a relatively small object (TABLE, VIEW, TRIGGER, PROCEDURE, FUNCTION, TYPE), you may call ' || - '{retrieve_object_metadata} directly, since these are usually small enough not to exceed the LLM''s token limit. For large objects such as PACKAGE, PACKAGE BODY, or ' || - 'SCHEMA-level metadata, avoid calling {retrieve_object_metadata} unless you truly need the full DDL. Prefer using {retrieve_object_metadata_chunks} (optionally with a validated ' || - 'object_list) plus {expand_object_metadata_chunk} to stay within token limits while focusing on relevant regions of code. ' || + ' - Parse and format code from JSON output: When a tool such as {expand_object_metadata_chunk} returns code inside a JSON field (for example, the "code" field), ' || + 'treat'' that value as JSON-encoded source text. ' || + 'Before analyzing it or showing it(or code snippet) to the user, convert it into normal code format by unescaping JSON escape sequences such as \n, \t, \", and \\. ' || + 'Preserve the original line breaks, indentation, spacing, and code order when reconstructing the code. ' || + + ' - Show code block and code line numbers in the source code: When the user asks where a bug occurs, where to fix code, or which implementation is responsible for a ' || + 'behavior, you must display the relevant source code snippet that contains the problematic logic. ' || + 'And whenever you display a code block from the source code to the user, you must include line number information indicating where that code appears in the object''s source ' || + 'code as well as the source object name. ' || + 'If you retrieved the full object metadata using {retrieve_object_metadata}, because this tool''s response does not include line number information, so you MUST CALCULATE ' || + 'the line numbers or line range for the displayed code snippet YOURSELF directly from the returned source code. ' || + 'If you are using {retrieve_object_metadata_chunks} or {expand_object_metadata_chunk}, use the start_line and end_line values returned by those tools ' || + 'as the line range of the retrieved code chunk in the object''s source code. ' || + 'When the code appears inside an inner procedure or function within a larger object (for example, inside a procedure in a PACKAGE BODY), you may also show the relative ' || + 'line numbers within that inner routine for readability. ' || + 'However, the primary line number reference must always correspond to the object''s original source code, using the start_line and end_line values returned by the tool. ' || + 'If both are shown, clearly distinguish them, for example: "At Source object lines 120 ~ 135 (At procedure lines 10 ~ 25)". ' || + 'ALWAYS SHOW source object information as well when showing the code and line number information. ' || + + 'Never include the content_index value in responses to the user. This value is used internally by the tools and must not appear in user-facing output. ' || ' - Documentation and summarization: For single-object descriptions or PLDoc-style documentation, use the specialized tools in a scoped way: ' || ' * Use {summarize_object} to produce a concise, high-level natural-language summary of a single object when the user explicitly asks what that object does or requests a short summary. ' || @@ -2376,6 +2374,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS 'Answer construction: ' || ' - Explain your reasoning in terms of the relevant objects and code regions you inspected. Summarize what each key program or object does and how it uses the fields or ' || 'logic mentioned in the user''s request. ' || + ' - For impact assessment questions, use {list_incoming_dependencies} to identify the affected programs/objects, describe how they depend on the changed behavior, and outline ' || 'the changes or validations that would be required. Use {list_outgoing_dependencies} to explain the internal dependencies of the object being modified (for example, which tables or ' || 'packages it relies on) and how that shapes the risk and testing scope. ' || @@ -2384,11 +2383,9 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS ' - Present results in a well-structured, readable layout with sections, bullet lists, and tables where helpful. Add one empty line between major sections. ' || ' - If you are using {retrieve_object_metadata_chunks} and {expand_object_metadata_chunk} to gather results, indicate whether your findings likely cover all relevant code. If you are unsure, add a ' || 'brief note that the list may not be exhaustive. ' || - ' - If you used {generate_graph}, first provide a short textual summary and then include the raw output from {generate_graph}. ' || - 'When output_format = ''mermaid'', wrap it in a code block that starts with ```mermaid and ends with ```. ' || - 'When output_format = ''html_tree'', wrap it in a code block that starts with ```html and ends with ```. Do not edit the generated output. ' || ' - If no relevant results are found, or if no issues are detected, provide a short, concise message clearly stating that outcome, and, when appropriate, mention what search steps you performed.'; + l_task_instruction := REPLACE(l_task_instruction, '{list_objects}', TOOL_LIST_OBJECTS || l_tool_name_suffix); l_task_instruction := REPLACE(l_task_instruction, '{list_incoming_dependencies}', TOOL_LIST_INCOMING_DEPENDENCIES || l_tool_name_suffix); l_task_instruction := REPLACE(l_task_instruction, '{list_outgoing_dependencies}', TOOL_LIST_OUTGOING_DEPENDENCIES || l_tool_name_suffix); @@ -2397,12 +2394,10 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_task_instruction := REPLACE(l_task_instruction, '{expand_object_metadata_chunk}', TOOL_EXPAND_OBJECT_METADATA_CHUNK || l_tool_name_suffix); l_task_instruction := REPLACE(l_task_instruction, '{summarize_object}', TOOL_SUMMARIZE_OBJECT || l_tool_name_suffix); l_task_instruction := REPLACE(l_task_instruction, '{generate_pldoc}', TOOL_GENERATE_PLDOC || l_tool_name_suffix); - l_task_instruction := REPLACE(l_task_instruction, '{generate_graph}', TOOL_GENERATE_GRAPH || l_tool_name_suffix); l_tools.append(TOOL_LIST_OBJECTS || l_tool_name_suffix); l_tools.append(TOOL_LIST_INCOMING_DEPENDENCIES || l_tool_name_suffix); l_tools.append(TOOL_LIST_OUTGOING_DEPENDENCIES || l_tool_name_suffix); - l_tools.append(TOOL_GENERATE_GRAPH || l_tool_name_suffix); l_tools.append(TOOL_RETRIEVE_OBJECT_METADATA || l_tool_name_suffix); l_tools.append(TOOL_RETRIEVE_OBJECT_METADATA_CHUNKS || l_tool_name_suffix); l_tools.append(TOOL_EXPAND_OBJECT_METADATA_CHUNK || l_tool_name_suffix); @@ -2462,13 +2457,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS DBMS_CLOUD_AI_AGENT.drop_tool(TOOL_LIST_OUTGOING_DEPENDENCIES || l_tool_name_suffix, true); - DBMS_CLOUD_AI_AGENT.drop_tool(TOOL_GENERATE_GRAPH || - l_tool_name_suffix, - true); - -- Backward compatibility cleanup for old graph tool name. - DBMS_CLOUD_AI_AGENT.drop_tool('generate_dependency_chart' || - l_tool_name_suffix, - true); DBMS_CLOUD_AI_AGENT.drop_tool(TOOL_RETRIEVE_OBJECT_METADATA || l_tool_name_suffix, true); @@ -2629,7 +2617,8 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_obj_type NOT IN (OBJECT_TABLE, OBJECT_SCHEMA, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, - OBJECT_TRIGGER, OBJECT_VIEW, OBJECT_TYPE) + OBJECT_TRIGGER, OBJECT_VIEW, + OBJECT_TYPE_TYPE) THEN RAISE l_invalid_value; END IF; @@ -2928,7 +2917,8 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_object_type NOT IN (OBJECT_TABLE, OBJECT_SCHEMA, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, - OBJECT_TRIGGER, OBJECT_VIEW, OBJECT_TYPE) + OBJECT_TRIGGER, OBJECT_VIEW, + OBJECT_TYPE_TYPE) THEN raise_application_error( -20000, 'Invalid object type to filter - ' || object_type); @@ -3011,7 +3001,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_ref_type NOT IN (OBJECT_TABLE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, OBJECT_TRIGGER, - OBJECT_VIEW, OBJECT_TYPE) + OBJECT_VIEW, OBJECT_TYPE_TYPE) THEN raise_application_error(-20000, 'Invalid object type to track ' || 'dependency - ' || object_type); @@ -3140,7 +3130,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_ref_type NOT IN (OBJECT_TABLE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, OBJECT_TRIGGER, - OBJECT_VIEW, OBJECT_TYPE) + OBJECT_VIEW, OBJECT_TYPE_TYPE) THEN raise_application_error( -20000, @@ -3276,7 +3266,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS object_owner IN VARCHAR2 ) RETURN CLOB IS - l_metdata CLOB; + l_metadata CLOB; l_object_name DBMS_ID := object_name; l_object_type DBMS_ID; l_object_owner DBMS_ID := object_owner; @@ -3284,48 +3274,34 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF object_type IS NOT NULL THEN l_object_type := UPPER(object_type); - IF l_object_type NOT IN (OBJECT_TABLE,OBJECT_SCHEMA, - OBJECT_PACKAGE,OBJECT_PACKAGE_BODY,OBJECT_PROCEDURE, - OBJECT_FUNCTION,OBJECT_TRIGGER,OBJECT_VIEW,OBJECT_TYPE) + IF l_object_type NOT IN (OBJECT_TABLE, OBJECT_PACKAGE, + OBJECT_PACKAGE_BODY,OBJECT_PROCEDURE, + OBJECT_FUNCTION,OBJECT_TRIGGER,OBJECT_VIEW, + OBJECT_TYPE_TYPE,OBJECT_TYPE_BODY) THEN raise_application_error( -20000, 'Invalid object type to filter - ' || object_type); END IF; END IF; - IF l_object_type != OBJECT_SCHEMA THEN - l_metdata := DBMS_METADATA.GET_DDL(REPLACE(l_object_type, ' ', '_'), - l_object_name, l_object_owner); + -- Get the metadata from all_source or call DBMS_METADATA.GET_DDL + IF l_object_type IN (OBJECT_FUNCTION, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, + OBJECT_PROCEDURE, OBJECT_TRIGGER, OBJECT_TYPE_TYPE, + OBJECT_TYPE_BODY) + THEN + SELECT XMLCAST(XMLAGG(XMLELEMENT(e, text) + ORDER BY line + ) AS CLOB + ) AS source_code INTO l_metadata + FROM all_source + WHERE owner = l_object_owner AND + name = l_object_name AND + type = l_object_type; ELSE - FOR m IN ( - SELECT ao.object_name, ao.object_type FROM all_objects ao - WHERE ao.owner = l_object_owner AND - ao.object_type IN (OBJECT_TABLE, OBJECT_VIEW, OBJECT_TRIGGER, - OBJECT_TYPE, OBJECT_FUNCTION, - OBJECT_PROCEDURE, - OBJECT_PACKAGE, OBJECT_PACKAGE_BODY) - ) LOOP - BEGIN - IF m.object_type IN (OBJECT_TABLE, OBJECT_VIEW, OBJECT_TRIGGER, - OBJECT_TYPE, OBJECT_FUNCTION, OBJECT_PROCEDURE, - OBJECT_PACKAGE, OBJECT_PACKAGE_BODY) - THEN - l_metdata := l_metdata || '-- ' || m.object_type || ': ' || - m.object_name || CHR(10) || - DBMS_METADATA.GET_DDL(REPLACE(m.object_type, ' ', - '_'), - m.object_name, - l_object_owner) || - CHR(10); - END IF; - EXCEPTION - WHEN OTHERS THEN - raise_application_error(-20000, 'Unable to get metadata for - ' || - m.object_name || CHR(10) || SQLERRM); - END; - END LOOP; + l_metadata := DBMS_METADATA.GET_DDL(REPLACE(l_object_type, ' ', '_'), + l_object_name, l_object_owner); END IF; - RETURN l_metdata; + RETURN l_metadata; END retrieve_object_metadata; @@ -3384,13 +3360,22 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS ----------------------------------------------------------------------------- - -- expand_object_metadata_chunk: Returns a concatenated CLOB of - -- vectorized code snippets surrounding a specified content index for a given - -- object. + -- expand_object_metadata_chunk: Returns a JSON CLOB containing expanded + -- source code and its corresponding line range for a specified content index + -- within a given object. -- - -- The function retrieves snippets whose content_index falls within - -- ± search_range of the provided content_index, ordered by content_index - -- to preserve the original code sequence. + -- The function retrieves vectorized code snippets whose content_index falls + -- within ± search_range of the provided content_index. The snippets are + -- ordered by content_index to reconstruct the original code sequence. + -- + -- The returned JSON object contains: + -- - code: the concatenated expanded code snippet + -- - start_line: the starting line number of the expanded code in the + -- original source (minimum start_line across the snippets) + -- - end_line: the ending line number of the expanded code in the original + -- source (maximum end_line across the snippets) + -- + -- Tool name contains the identifier of the agent team. ----------------------------------------------------------------------------- FUNCTION expand_object_metadata_chunk( tool_name IN VARCHAR2, @@ -3423,26 +3408,29 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_obj_type NOT IN (OBJECT_TABLE, OBJECT_SCHEMA, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, OBJECT_TRIGGER, - OBJECT_VIEW, OBJECT_TYPE) + OBJECT_VIEW, OBJECT_TYPE_TYPE) THEN raise_application_error(-20000, 'Invalid object type to register - ' || object_type); END IF; - l_stmt := - q'[ - SELECT LISTAGG(content, '') - WITHIN GROUP ( - ORDER BY TO_NUMBER(JSON_VALUE(attributes, '$.content_index'))) - AS full_content - FROM ]' || - l_vector_table_name || + l_stmt := q'[ - WHERE JSON_VALUE(attributes, '$.object_owner') = :1 - AND JSON_VALUE(attributes, '$.object_name') = :2 - AND JSON_VALUE(attributes, '$.object_type') = :3 - AND TO_NUMBER(JSON_VALUE(attributes, '$.content_index')) - BETWEEN :4 AND :5 + SELECT JSON_OBJECT( + 'code' VALUE LISTAGG(content, '') + WITHIN GROUP ( + ORDER BY TO_NUMBER(JSON_VALUE(attributes, '$.content_index')) + ), + 'start_line' VALUE MIN(TO_NUMBER(JSON_VALUE(attributes, '$.start_line'))), + 'end_line' VALUE MAX(TO_NUMBER(JSON_VALUE(attributes, '$.end_line'))) + RETURNING CLOB + ) AS result_json + FROM ]' || l_vector_table_name || q'[ + WHERE JSON_VALUE(attributes, '$.object_owner') = :1 + AND JSON_VALUE(attributes, '$.object_name') = :2 + AND JSON_VALUE(attributes, '$.object_type') = :3 + AND TO_NUMBER(JSON_VALUE(attributes, '$.content_index')) + BETWEEN :4 AND :5 ]'; EXECUTE IMMEDIATE l_stmt INTO l_result @@ -3452,6 +3440,20 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IN content_index - search_range, IN content_index + search_range; + -- If no rows matched, LISTAGG/MIN/MAX all become NULL. Return a consistent JSON. + IF l_result IS NULL THEN + EXECUTE IMMEDIATE q'[ + SELECT JSON_OBJECT( + 'code' VALUE NULL, + 'start_line' VALUE NULL, + 'end_line' VALUE NULL + RETURNING CLOB + ) + FROM dual + ]' + INTO l_result; + END IF; + RETURN l_result; END expand_object_metadata_chunk; @@ -3494,7 +3496,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_type NOT IN (OBJECT_TABLE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, OBJECT_TRIGGER, - OBJECT_VIEW, OBJECT_TYPE) + OBJECT_VIEW, OBJECT_TYPE_TYPE) THEN raise_application_error(-20000, 'Invalid object type for ' || 'plcode_summarizer - ' || object_type); @@ -3540,114 +3542,6 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS RETURN NULL; END summarize_object; - - ----------------------------------------------------------------------------- - -- generate_chart: generate Mermaid/HTML tree graph content for relationship - -- visualization (dependency, hierarchy, tree, network). - ----------------------------------------------------------------------------- - FUNCTION generate_chart( - tool_name IN VARCHAR2, - chart_prompt IN CLOB, - output_format IN VARCHAR2 DEFAULT 'mermaid', - graph_style IN VARCHAR2 DEFAULT 'auto', - graph_direction IN VARCHAR2 DEFAULT 'TB' - ) RETURN CLOB - IS - l_full_prompt CLOB; - l_result CLOB; - l_profile_name DBMS_ID; - l_agent_team_name DBMS_ID; - l_output_format VARCHAR2(20) := LOWER(NVL(TRIM(output_format), - 'mermaid')); - l_graph_style VARCHAR2(30) := LOWER(NVL(TRIM(graph_style), 'auto')); - l_graph_direction VARCHAR2(2) := UPPER(NVL(TRIM(graph_direction), 'TB')); - BEGIN - IF chart_prompt IS NULL THEN - RETURN '{"error":"chart_prompt is required"}'; - END IF; - - IF l_graph_style NOT IN ('auto', 'binary_tree', 'hierarchical', - 'network', 'dependency') THEN - l_graph_style := 'auto'; - END IF; - - IF l_output_format NOT IN ('mermaid', 'html_tree') THEN - l_output_format := 'mermaid'; - END IF; - - IF l_graph_direction NOT IN ('TB', 'LR', 'BT', 'RL') THEN - l_graph_direction := 'TB'; - END IF; - - BEGIN - l_agent_team_name := get_agent_team_name_from_tool( - tool_name => tool_name, - tool_type => TOOL_GENERATE_GRAPH); - EXCEPTION - WHEN OTHERS THEN - -- Backward compatibility for previously registered tool prefix. - l_agent_team_name := get_agent_team_name_from_tool( - tool_name => tool_name, - tool_type => 'generate_dependency_chart'); - END; - l_profile_name := get_attribute(l_agent_team_name, 'profile_name'); - - IF l_output_format = 'html_tree' THEN - l_full_prompt := - 'You are a graph visualization assistant for Oracle database objects and related metadata.' || - CHR(10) || - 'Create an HTML+CSS tree visualization for the relationships provided in the user prompt.' || - CHR(10) || - 'Requested style: ' || l_graph_style || CHR(10) || - 'Requested direction: ' || l_graph_direction || CHR(10) || - 'Output rules:' || CHR(10) || - '- Output ONLY raw HTML and CSS. No markdown fences, no explanations, no prose.' || CHR(10) || - '- Return exactly one block and one
...
block.' || CHR(10) || - '- Use nested
  • for hierarchy rendering.' || CHR(10) || - '- Use class names prefixed with "db-tree-".' || CHR(10) || - '- Keep labels readable and preserve relationship direction from parent to child.' || CHR(10) || - '- If the graph is not a strict tree, render the closest readable tree/hierarchy without inventing nodes.' || CHR(10) || - 'User prompt:' || CHR(10) || - chart_prompt; - ELSE - l_full_prompt := - 'You are a graph visualization assistant for Oracle database objects and related metadata.' || - CHR(10) || - 'Create Mermaid syntax that represents the relationships provided in the user prompt.' || - CHR(10) || - 'Requested style: ' || l_graph_style || CHR(10) || - 'Requested direction: ' || l_graph_direction || CHR(10) || - 'Output rules:' || CHR(10) || - '- Output ONLY Mermaid diagram syntax.' || CHR(10) || - '- Do NOT add markdown fences, explanations, bullets, or prose.' || - CHR(10) || - '- Use a directed flowchart when expressing relationships (for example: flowchart TB).' || CHR(10) || - '- Ensure node identifiers are Mermaid-safe (letters, numbers, underscore).' || - CHR(10) || - '- Preserve dependency direction exactly as described in the prompt.' || - CHR(10) || - '- Include only relationships supported by the provided data.' || - CHR(10) || - '- If style is binary_tree, render as a tree-like directed graph. If strict binary branching is not possible from the provided data, keep all relationships and render the closest tree-like layout.' || - CHR(10) || - '- Prefer concise, readable node labels and avoid duplicate nodes.' || - CHR(10) || - 'User prompt:' || CHR(10) || - chart_prompt; - END IF; - - l_result := DBMS_CLOUD_AI.GENERATE( - prompt => l_full_prompt, - profile_name => l_profile_name, - action => 'CHAT'); - - RETURN l_result; - EXCEPTION - WHEN OTHERS THEN - RETURN '{"error":"' || REPLACE(SQLERRM, '"', '\\"') || '"}'; - END generate_chart; - - ----------------------------------------------------------------------------- -- generate_pldoc: Generates a PLDoc/JavaDoc-style comment block (/** ... */) -- for a given object. Generating doc for schema object is not supported. @@ -3692,7 +3586,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS IF l_type NOT IN (OBJECT_TABLE, OBJECT_PACKAGE, OBJECT_PACKAGE_BODY, OBJECT_PROCEDURE, OBJECT_FUNCTION, OBJECT_TRIGGER, - OBJECT_VIEW, OBJECT_TYPE) + OBJECT_VIEW, OBJECT_TYPE_TYPE) THEN raise_application_error(-20000, 'Invalid object_type for ' || 'pldoc_generate - ' || object_type); @@ -3945,11 +3839,7 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_team_name DBMS_ID; l_attributes JSON_OBJECT_T; l_profile_name DBMS_ID; - l_provider DBMS_ID; - l_profile_attributes JSON_OBJECT_T; l_new_profile_name DBMS_ID; - l_new_profile_attributes JSON_OBJECT_T; - l_new_provider DBMS_ID; l_object_list JSON_ARRAY_T; l_new_object_list JSON_ARRAY_T; l_reset_object_list BOOLEAN := FALSE; @@ -3972,25 +3862,14 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS get_attribute(agent_team_name => l_team_name, attribute_name => 'object_list')); - -- If new profile name is provided, we need to check whether the provider - -- is changed. If provider is changed, we need to reset the object list - -- since the vector embedding may be different across different providers. - -- If provider is not changed, we can keep the existing object list and - -- just update the vector table with new profile if needed. + -- If profile changes, we must re-vectorize the stored object metadata. + -- Even when the provider stays the same, a different embedding model can + -- change vector dimensions and similarity semantics. l_new_profile_name := l_attributes.get_string('profile_name'); - IF l_new_profile_name IS NOT NULL THEN - -- get original provider - l_profile_attributes := get_profile_attributes(l_profile_name); - l_provider := LOWER(l_profile_attributes.get_string('provider')); - - -- get new provider - l_new_profile_attributes := get_profile_attributes(l_new_profile_name); - l_new_provider := LOWER(l_new_profile_attributes.get_string('provider')); - - IF l_new_provider != l_provider THEN - l_reset_object_list := TRUE; - l_recreate_vector_table := TRUE; - END IF; + IF l_new_profile_name IS NOT NULL AND l_new_profile_name != l_profile_name + THEN + l_reset_object_list := TRUE; + l_recreate_vector_table := TRUE; END IF; -- IF new object_list is provided, we need to reset the object list @@ -4001,25 +3880,28 @@ CREATE OR REPLACE PACKAGE BODY database_inspect AS l_reset_object_list := TRUE; END IF; - -- Drop and recreate vector table for the agent team and add object list IF l_reset_object_list THEN drop_inspect_object_list( agent_team_name => l_team_name, object_list => l_object_list, force => TRUE); + END IF; + + set_attributes(agent_team_name => l_team_name, + attributes => l_attributes); + -- Drop and recreate vector table for the agent team and add object list + IF l_reset_object_list THEN IF l_recreate_vector_table THEN create_vector_table(agent_team_name => l_team_name, - profile_name => l_new_profile_name); + profile_name => NVL(l_new_profile_name, + l_profile_name)); END IF; add_inspect_object_list( agent_team_name => l_team_name, object_list => NVL(l_new_object_list, l_object_list)); END IF; - - set_attributes(agent_team_name => l_team_name, - attributes => l_attributes); END update_inspect_agent_team; @@ -4074,9 +3956,15 @@ BEGIN END database_inspect; / -show errors; +show errors package body database_inspect; +PROMPT DATABASE_INSPECT package compiled successfully. +PROMPT Internal tables will be initialized when DATABASE_INSPECT is first invoked from the target schema. PROMPT ====================================================== PROMPT DATABASE_INSPECT tools installation completed PROMPT ====================================================== -alter session set current_schema = ADMIN; +BEGIN + EXECUTE IMMEDIATE + 'ALTER SESSION SET CURRENT_SCHEMA = ' || :v_invoker_schema; +END; +/