はじめに
ADB(Autonomous AI Database)等で利用できるSelect AI Agentは
「自然言語からSELECT文を生成して実行するエージェント」
というイメージを持たれがち?かもしれませんが、それだけでなく、実際にはPL/SQLをツールとして登録し、自然言語から適切なツール(PL/SQL)をエージェントが自動的に選んで実行することも可能です。
これにより、例えば既存に多くのPL/SQLを保持しているシステムでは、Select AI Agentを導入することで、自然言語からPL/SQLを容易に実行できるようになります。
本記事では、PL/SQL関数をToolとして扱います。
この構成により、ユーザのプロンプトを最終的に登録済みPL/SQLへマッピングし、LLMの推論を「どのツールをどのパラメータで実行するか」に限定しやすくなります。主な利点は以下です。
- 自然言語から複数のPL/SQLを問い合わせ内容に応じて実行可能(PL/SQLの価値再発見)
- 高速に動作
- ハルシネーションが少ない(アウトプットが既存PL/SQL実行のため)
本記事では、Select AI Agentが業務ロジックを持つPL/SQLを自然言語で実行できるという観点で、次の内容を検証します。
- ユーザーの自然言語問い合わせを Select AI Agent が解釈
- 複数のPL/SQLをツールとして登録
- 問い合わせ内容に応じて適切なツールを選択して実行
Select AI Agentについて
Select AI Agentは、ざっくり言うと次の4つで構成されます。
- Team: 実行単位(どのAgentをどう実行するか)
- Agent: 役割(問い合わせをどう扱うか)
- Task: 指示(どのツールを使い、どんなルールで応答するか)
- Tool: 実行可能な機能(SQL / RAG / WebSearch / Notification / カスタムPL/SQLなど)
今回は、レストランにおけるユーザ予約、従業員登録のシーンをデモケースとし、簡易なテーブルを用意して検証しています。
Select AI Agentの構成ですが
RESTAURANT_TEAM の配下に RESTAURANT_AGENT、RESTAURANT_TASK を配置し、以下のツールを登録しています。
- 従業員登録(INSERT)
- 従業員参照(SELECT)
- 予約登録(INSERT)
- 予約参照(SELECT)
Select AI Agentの操作はDBMS_CLOUD_AI_AGENTパッケージを使っていきます。
つまり、自然言語問い合わせを入口にして、マルチツール(PL/SQL)実行を行う構成です。
Select AI Agentを作っていく
上述した4ツールを含むSelect AI AgentのSQLは下記です:
サンプルSQL(長いため省略)
-- =========================================================
-- Select AI Agent: 予約登録(可変パラメータ)+ 予約参照 デモ
-- =========================================================
-- 0) 前提権限(必要に応じて管理者で実施)
-- GRANT EXECUTE ON DBMS_CLOUD_AI_AGENT TO select_ai_user;
-- GRANT EXECUTE ON DBMS_CLOUD_AI TO select_ai_user;
-- 1) 既存オブジェクト掃除(再実行向け)
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TEAM (team_name => 'RESTAURANT_TEAM', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TASK (task_name => 'RESTAURANT_TASK', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TOOL (tool_name => 'ADD_RESERVATION_TOOL', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TOOL (tool_name => 'LIST_RESERVATION_TOOL', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TOOL (tool_name => 'ADD_EMPLOYEE_TOOL', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_TOOL (tool_name => 'LIST_EMPLOYEE_TOOL', force => TRUE); END;
/
BEGIN DBMS_CLOUD_AI_AGENT.DROP_AGENT(agent_name => 'RESTAURANT_AGENT', force => TRUE); END;
/
-- 2) 既存テーブル利用
-- 既存: SELECT_AI_USER.RESERVATIONS
-- (ID, RESERVATION_TIME, PARTY_SIZE, CUSTOMER_NAME, PHONE_NUMBER,
-- CREATED_AT, DINING_OPTION, CUSTOMER_ID)
-- 2-2) 従業員テーブル(新規)
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE restaurant_employees PURGE';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942 THEN RAISE; END IF;
END;
/
CREATE TABLE restaurant_employees (
employee_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
employee_name VARCHAR2(100) NOT NULL,
role_name VARCHAR2(50) NOT NULL,
hire_date DATE NOT NULL,
phone_number VARCHAR2(50),
status VARCHAR2(20) DEFAULT 'ACTIVE' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
/
COMMENT ON TABLE restaurant_employees IS 'レストラン従業員マスタ。自然言語経由で追加・参照するデモ用テーブル';
COMMENT ON COLUMN restaurant_employees.employee_name IS '従業員氏名';
COMMENT ON COLUMN restaurant_employees.role_name IS '役割(例: ホール/キッチン/店長)';
COMMENT ON COLUMN restaurant_employees.hire_date IS '入社日';
COMMENT ON COLUMN restaurant_employees.phone_number IS '連絡先電話番号';
COMMENT ON COLUMN restaurant_employees.status IS '在籍状態(ACTIVE/INACTIVE)';
-- 3) ツール本体PL/SQL
-- ここからが「自然言語→PL/SQL実行」の中核です。
-- Agentは以下4つの関数をツールとして呼び分けます。
CREATE OR REPLACE PACKAGE reservation_tool_pkg AS
-- [1] 予約登録(INSERT)
FUNCTION add_reservation(
p_customer_name IN VARCHAR2,
p_people_count IN NUMBER,
p_visit_at IN VARCHAR2,
p_plan_type IN VARCHAR2,
p_phone_number IN VARCHAR2 DEFAULT NULL
) RETURN CLOB;
-- [2] 予約参照(SELECT)
FUNCTION list_reservations(p_customer_name IN VARCHAR2 DEFAULT NULL) RETURN CLOB;
END reservation_tool_pkg;
/
CREATE OR REPLACE PACKAGE employee_tool_pkg AS
-- [3] 従業員登録(INSERT)
FUNCTION add_employee(
p_employee_name IN VARCHAR2,
p_role_name IN VARCHAR2,
p_hire_date IN VARCHAR2,
p_phone_number IN VARCHAR2 DEFAULT NULL,
p_status IN VARCHAR2 DEFAULT 'ACTIVE'
) RETURN CLOB;
-- [4] 従業員参照(SELECT)
FUNCTION list_employees(
p_role_name IN VARCHAR2 DEFAULT NULL,
p_status IN VARCHAR2 DEFAULT NULL
) RETURN CLOB;
END employee_tool_pkg;
/
CREATE OR REPLACE PACKAGE BODY employee_tool_pkg AS
-- [3] 従業員登録(INSERT)
-- 必須項目(氏名・役割・入社日)で従業員を1件追加
FUNCTION add_employee(
p_employee_name IN VARCHAR2,
p_role_name IN VARCHAR2,
p_hire_date IN VARCHAR2,
p_phone_number IN VARCHAR2 DEFAULT NULL,
p_status IN VARCHAR2 DEFAULT 'ACTIVE'
) RETURN CLOB IS
l_hire_date DATE;
l_status VARCHAR2(20);
l_id NUMBER;
BEGIN
l_hire_date := TO_DATE(p_hire_date, 'YYYY-MM-DD');
l_status := UPPER(TRIM(NVL(p_status, 'ACTIVE')));
IF l_status NOT IN ('ACTIVE', 'INACTIVE') THEN
RETURN '{"status":"ERROR","message":"status は ACTIVE または INACTIVE を指定してください"}';
END IF;
INSERT INTO restaurant_employees(
employee_name, role_name, hire_date, phone_number, status
) VALUES (
p_employee_name, p_role_name, l_hire_date, p_phone_number, l_status
) RETURNING employee_id INTO l_id;
RETURN '{"status":"OK","employee_id":' || l_id || ',"message":"従業員を登録しました"}';
EXCEPTION
WHEN OTHERS THEN
RETURN '{"status":"ERROR","message":"' || REPLACE(SQLERRM, '"', '\"') || '"}';
END add_employee;
-- [4] 従業員参照(SELECT)
-- 役割・在籍状態で絞り込み可能な一覧取得
FUNCTION list_employees(
p_role_name IN VARCHAR2 DEFAULT NULL,
p_status IN VARCHAR2 DEFAULT NULL
) RETURN CLOB IS
l_json CLOB;
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'employee_id' VALUE employee_id,
'employee_name' VALUE employee_name,
'role_name' VALUE role_name,
'hire_date' VALUE TO_CHAR(hire_date, 'YYYY-MM-DD'),
'phone_number' VALUE phone_number,
'status' VALUE status
) RETURNING CLOB
)
INTO l_json
FROM restaurant_employees
WHERE (p_role_name IS NULL OR role_name = p_role_name)
AND (p_status IS NULL OR status = UPPER(p_status));
RETURN COALESCE(l_json, '[]');
END list_employees;
END employee_tool_pkg;
/
CREATE OR REPLACE PACKAGE BODY reservation_tool_pkg AS
-- [1] 予約登録(INSERT)
-- 予約情報を1件登録(日時は YYYY-MM-DD HH24:MI を想定)
FUNCTION add_reservation(
p_customer_name IN VARCHAR2,
p_people_count IN NUMBER,
p_visit_at IN VARCHAR2,
p_plan_type IN VARCHAR2,
p_phone_number IN VARCHAR2 DEFAULT NULL
) RETURN CLOB IS
l_visit_at TIMESTAMP;
l_plan_type VARCHAR2(100);
l_id NUMBER;
BEGIN
-- 例: 2026-03-30 19:00
l_visit_at := TO_TIMESTAMP(p_visit_at, 'YYYY-MM-DD HH24:MI');
l_plan_type := UPPER(TRIM(p_plan_type));
IF l_plan_type IN ('COURSE', 'コース') THEN
l_plan_type := 'コース';
ELSIF l_plan_type IN ('A_LA_CARTE', 'ALACARTE', 'アラカルト') THEN
l_plan_type := 'アラカルト';
ELSE
-- 既存テーブルの DINING_OPTION には具体的なコース名も入るためそのまま許可
l_plan_type := TRIM(p_plan_type);
END IF;
INSERT INTO reservations(
reservation_time,
party_size,
customer_name,
phone_number,
dining_option
)
VALUES (
l_visit_at,
p_people_count,
p_customer_name,
p_phone_number,
l_plan_type
)
RETURNING id INTO l_id;
RETURN '{"status":"OK","reservation_id":' || l_id || ',"message":"予約を登録しました"}';
EXCEPTION
WHEN OTHERS THEN
RETURN '{"status":"ERROR","message":"' || REPLACE(SQLERRM, '"', '\"') || '"}';
END add_reservation;
-- [2] 予約参照(SELECT)
-- 氏名指定あり/なしの両方に対応した予約一覧取得
FUNCTION list_reservations(p_customer_name IN VARCHAR2 DEFAULT NULL) RETURN CLOB IS
l_json CLOB;
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id' VALUE id,
'customer_name' VALUE customer_name,
'party_size' VALUE party_size,
'reservation_time' VALUE TO_CHAR(reservation_time, 'YYYY-MM-DD HH24:MI'),
'phone_number' VALUE phone_number,
'dining_option' VALUE dining_option
) RETURNING CLOB
)
INTO l_json
FROM reservations
WHERE p_customer_name IS NULL
OR customer_name = p_customer_name;
RETURN COALESCE(l_json, '[]');
END list_reservations;
END reservation_tool_pkg;
/
-- 4) Agent
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_AGENT(
agent_name => 'RESTAURANT_AGENT',
attributes => '{
"profile_name": "GENAI_YOUR_PROFILE",
"role": "あなたはレストラン運用エージェントです。対応範囲は予約登録/確認と従業員登録/一覧確認です。回答根拠は登録ツール実行結果のみです。必要項目が不足していれば追加質問してください。対応範囲外の質問には丁重にお断りしてください。"
}'
);
END;
/
-- 5) Tool: 予約登録(可変パラメータ)
-- function にPL/SQL関数を指定することで、SQL生成ではなく
-- 「登録済み関数そのもの」をツール実行できます。
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'ADD_RESERVATION_TOOL',
attributes => '{
"instruction": "レストラン予約を登録します。氏名、人数、日時、プランを受け取り登録してください。",
"function": "RESERVATION_TOOL_PKG.ADD_RESERVATION",
"tool_inputs": [
{"name":"p_customer_name","description":"予約者氏名"},
{"name":"p_people_count","description":"人数(整数)"},
{"name":"p_visit_at","description":"来店日時。形式: YYYY-MM-DD HH24:MI"},
{"name":"p_plan_type","description":"コース/アラカルト/席のみ予約/具体的なコース名"},
{"name":"p_phone_number","description":"電話番号(任意)。例: 090-1111-2222"}
]
}'
);
END;
/
-- 6-2) Tool: 従業員登録
-- 従業員の追加用ツール(INSERT)
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'ADD_EMPLOYEE_TOOL',
attributes => '{
"instruction": "従業員を登録します。氏名、役割、入社日を受け取り登録してください。",
"function": "EMPLOYEE_TOOL_PKG.ADD_EMPLOYEE",
"tool_inputs": [
{"name":"p_employee_name","description":"従業員氏名"},
{"name":"p_role_name","description":"役割。例: ホール/キッチン/店長"},
{"name":"p_hire_date","description":"入社日。形式: YYYY-MM-DD"},
{"name":"p_phone_number","description":"電話番号(任意)"},
{"name":"p_status","description":"在籍状態(省略時ACTIVE)。ACTIVE/INACTIVE"}
]
}'
);
END;
/
-- 6-3) Tool: 従業員一覧
-- 従業員の参照用ツール(SELECT)
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'LIST_EMPLOYEE_TOOL',
attributes => '{
"instruction": "従業員一覧を取得します。必要に応じて役割・在籍状態で絞り込みます。",
"function": "EMPLOYEE_TOOL_PKG.LIST_EMPLOYEES",
"tool_inputs": [
{"name":"p_role_name","description":"役割(任意)"},
{"name":"p_status","description":"在籍状態(任意)。ACTIVE/INACTIVE"}
]
}'
);
END;
/
-- 6) Tool: 予約参照
-- 予約の参照用ツール(SELECT)
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'LIST_RESERVATION_TOOL',
attributes => '{
"instruction": "予約一覧を取得します。必要なら氏名で絞り込みます。",
"function": "RESERVATION_TOOL_PKG.LIST_RESERVATIONS",
"tool_inputs": [
{"name":"p_customer_name","description":"省略可。指定時はその氏名のみ"}
]
}'
);
END;
/
-- 7) Task(1つで運用)
-- ポイント:
-- - 1つのTaskに複数Toolを登録
-- - 問い合わせ内容から適切なToolをAgentが選択
-- - 入力不足時は enable_human_tool=true で追加質問
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TASK(
task_name => 'RESTAURANT_TASK',
attributes => '{
"instruction": "ユーザー質問({query})を解析して、予約登録はADD_RESERVATION_TOOL、予約確認はLIST_RESERVATION_TOOL、従業員登録はADD_EMPLOYEE_TOOL、従業員一覧確認はLIST_EMPLOYEE_TOOLを使用する。予約登録で必要な4項目(氏名・人数・日時・プラン)や、従業員登録で必要な3項目(氏名・役割・入社日)が不足している場合は不足分だけ追加質問し、揃ってから実行する。対応範囲外の質問には必ず次の定型文で丁重に回答する: 『申し訳ありません。このデモで対応できるのは予約管理と従業員管理のみです。ご希望内容をお知らせください。』 ツール結果にない一般知識は回答しない。",
"tools": ["ADD_RESERVATION_TOOL", "LIST_RESERVATION_TOOL", "ADD_EMPLOYEE_TOOL", "LIST_EMPLOYEE_TOOL"],
"enable_human_tool": true
}'
);
END;
/
-- 8) Team
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TEAM(
team_name => 'RESTAURANT_TEAM',
attributes => '{
"process": "sequential",
"agents": [
{"name": "RESTAURANT_AGENT", "task": "RESTAURANT_TASK"}
]
}'
);
END;
/
-- 9) 実行確認
EXEC DBMS_CLOUD_AI_AGENT.SET_TEAM('RESTAURANT_TEAM');
SELECT DBMS_CLOUD_AI_AGENT.GET_TEAM FROM DUAL;
PL/SQLをマルチツールとして登録する部分について、
今回の実装の肝は、CREATE_TOOL で tool_type: "SQL" を使うのではなく、function でPL/SQLを直接登録する点です。
下に簡単なフローチャートを書いていますが、イメージとしては以下の流れです。
-
RESERVATION_TOOL_PKG/EMPLOYEE_TOOL_PKGに関数を用意 -
DBMS_CLOUD_AI_AGENT.CREATE_TOOLで各関数をツール化 -
CREATE_TASKで利用可能ツール群を定義 - ユーザー質問に応じてAgentがツールを選択・実行
AgentとTaskは1つとし、そのTaskに対して4つのToolを登録して問い合わせ内容に応じて使い分けます。
BEGIN
DBMS_CLOUD_AI_AGENT.CREATE_TOOL(
tool_name => 'ADD_RESERVATION_TOOL',
attributes => '{
"instruction": "レストラン予約を登録します。氏名、人数、日時、プランを受け取り登録してください。",
"function": "RESERVATION_TOOL_PKG.ADD_RESERVATION",
"tool_inputs": [
{"name":"p_customer_name","description":"予約者氏名"},
{"name":"p_people_count","description":"人数(整数)"},
{"name":"p_visit_at","description":"来店日時。形式: YYYY-MM-DD HH24:MI"},
{"name":"p_plan_type","description":"コース/アラカルト/席のみ予約/具体的なコース名"},
{"name":"p_phone_number","description":"電話番号(任意)。例: 090-1111-2222"}
]
}'
);
END;
/
今回のポイント
- 複数PL/SQLを同時登録 して用途ごとに使い分け
- Taskの指示により、必要項目が足りない場合は追加質問
- ドメイン外の質問は、Agent/Taskの設定により丁重にお断り
これにより、単なる自然言語SQL実行を超えて、業務処理そのものを自然言語で呼び出す 形にできます。
作ったエージェントを触ってみる
実行にteam,agent,task,toolおよびプロシージャを作成したらまずはteamをセットします。
EXEC DBMS_CLOUD_AI_AGENT.SET_TEAM('RESTAURANT_TEAM');
-- SELECT DBMS_CLOUD_AI_AGENT.GET_TEAM FROM DUAL;
Select AI Agentの実行には、
-
SELECT AI AGENTコマンドか、あるいは -
DBMS_CLOUD_AI.GENERATEを使ってプロンプト等を渡します
SELECT AI AGENT '山田太郎で2名、2026-03-30 19:00、コースで予約して';
SELECT DBMS_CLOUD_AI.GENERATE(
prompt => '山田太郎で2名、2026-03-30 19:00、コースで予約して',
action => 'narrate'
) AS result
FROM dual;
例えば以下のような依頼を行うと、Taskの指示に従って適切なToolが選択されます。
- 「田中花子さんの予約を確認して」→
LIST_RESERVATION_TOOL - 「キッチン担当の従業員を追加して」→
ADD_EMPLOYEE_TOOL - 「ACTIVEの従業員一覧を見せて」→
LIST_EMPLOYEE_TOOL
また、必須項目が不足している場合は enable_human_tool: true により追加質問が行われるため、入力漏れがあっても対話的に処理を完了できます。
実行履歴ビューを確認する
こちらに保存されます。
select * from USER_AI_AGENT_TASK_HISTORY order by START_DATE desc;
select * from USER_AI_AGENT_TOOL_HISTORY order by START_DATE desc;
select * from USER_AI_AGENT_TEAM_HISTORY order by START_DATE desc;
例えば、下記を実行するとtask,tool historyビューに実行ログが記載されます。
SELECT AI AGENT '予約一覧を教えてください。';
それぞれのビューは下記カラムを持っているため、例えば
- どのタスクやどのツールが呼ばれたか
- 開始時刻と終了時刻
- インプットとアウトプット
が詳細に記述されています。
名前 Nullかどうか タイプ
------------------- -------- ---------------------------
TEAM_EXEC_ID VARCHAR2(36)
TEAM_NAME NOT NULL VARCHAR2(128)
TASK_ORDER NUMBER
AGENT_NAME NOT NULL VARCHAR2(128)
TASK_NAME NOT NULL VARCHAR2(128)
CONVERSATION_PARAMS CLOB
INPUT CLOB
RESULT CLOB
STATE VARCHAR2(128)
START_DATE TIMESTAMP(6) WITH TIME ZONE
END_DATE TIMESTAMP(6) WITH TIME ZONE
名前 Nullかどうか タイプ
------------- -------- ---------------------------
INVOCATION_ID NOT NULL NUMBER
TEAM_EXEC_ID VARCHAR2(36)
TASK_ORDER NUMBER
TOOL_NAME NOT NULL VARCHAR2(128)
AGENT_NAME NOT NULL VARCHAR2(128)
TASK_NAME NOT NULL VARCHAR2(128)
START_DATE TIMESTAMP(6) WITH TIME ZONE
END_DATE TIMESTAMP(6) WITH TIME ZONE
INPUT CLOB
OUTPUT CLOB
TOOL_OUTPUT CLOB
おわりに
本記事では、Select AI AgentでPL/SQL関数をToolとして登録し、自然言語から業務処理(予約管理・従業員管理)を実行する構成を確認しました。
tool_type: "SQL" によるSQL生成中心の使い方だけでなく、function で既存PL/SQLを直接呼び出すことで、既存資産を活かしながら安全にAI連携しやすくなる点が大きなメリットです。特に、処理をツール実行結果に寄せることで、ハルシネーションの抑制や意図しない操作の抑制にもつながります。
今後は、以下のような拡張を行うと実運用に近づけられます。
- 監査ログ・実行履歴の可視化
- 権限分離(参照系/更新系のTask・Tool分離)
- エラーハンドリングとリトライ方針の整備
既存のPL/SQL資産が豊富な環境では、Select AI Agentは「自然言語UIを後付けする」有力な選択肢になります。まずは小さな業務領域から適用し、段階的に対象業務を広げていくことで本領を発揮できるでしょう。

