Programmatic Tool Calling (Code Mode)
Code mode lets the model write a small program that calls your tools, instead of calling them one at a time.
With normal tool calling, every tool call is a separate round-trip:
- The model asks for a tool.
- UC AI runs the tool.
- The result goes back into the context of the model.
- The model asks for the next tool.
Data-heavy work needs many of these round-trips. An example is âfetch every employee, then their expenses, sum the approved travelâ. Every raw tool result also stays in the context window.
Code mode makes this one call. The model writes a short JavaScript program. UC AI runs the program in the database with the Oracle Multilingual Engine (MLE). The program calls your tools in a loop, filters the data, and aggregates it locally. It returns only its final result to the model. The intermediate tool output never reaches the context of the model.
Requirements
Section titled âRequirementsâCode mode is an opt-in feature and needs Oracle Database 23ai, for MLE JavaScript with PURE execution contexts. The rest of UC AI runs on Oracle 12.2+. Only this feature needs 23ai.
The code of the model is untrusted. Code mode therefore always runs in a dedicated, locked-down sandbox. There is no fallback without a sandbox. Install this sandbox once, as a DBA:
# connect as SYS / a DBA to the same PDB where UC AI is installedsql sys/<pwd>@<pdb> as sysdba
@scripts/install_ptc_sandbox.sqlIf you installed UC AI from a release download, you do not have the repository files. Download install_ptc_sandbox_complete.sql from the same release. Run that file instead. It holds the same steps with the package sources inlined.
sql sys/<pwd>@<pdb> as sysdba
@install_ptc_sandbox_complete.sqlThe script asks for two schema names. The first is the schema that holds UC AI. The second is the new sandbox schema for it. The defaults are UC_AI and UC_AI_MLE_SBX.
The script then creates five objects:
- the schema-only sandbox account (
NO AUTHENTICATION, no login) - a
PUREMLE environment - the MLE runner
- a private synonym in the sandbox schema that points at the UC AI gateway
- a private synonym in your UC AI schema that points at the runner
UC AI finds the sandbox through this synonym at run time. No schema name is compiled into the packages.
Run the script once for each UC AI installation. Two installations must not share one sandbox, because the gateway synonym points at one UC AI schema. The script rejects a sandbox schema that already owns other objects. At the end, the script tests that the runner compiled and that the synonym points at it.
To undo the installation, run scripts/uninstall_ptc_sandbox.sql with the same two answers. Each release also holds this file, as uninstall_ptc_sandbox.sql. If you enable code mode without the sandbox, generate_text raises an error that names the script.
The security model
Section titled âThe security modelâThe JavaScript of the model has no database access.
- UC AI evaluates it in a
PUREMLE execution context. This context removes every database-facing JavaScript API. The program can neitherrequirenorimportmle-js-oracledb, and theoracledb,session,soda, andplsffiglobals do not exist. The program cannot run one SQL statement. - The program therefore cannot
COMMITorROLLBACKeither. Transaction control needs no privilege, so a privilege wall alone does not stop generated code from discarding your pending work. - PL/SQL therefore services
callTool. The program awaits a tool call and control returns to the runner. The runner calls the UC AI gateway. The program then continues with the result. This is the only channel in or out. - As a second barrier, the runner is a definerâs-rights package. The low-privilege sandbox schema owns it. This schema has no privilege that reaches data. It holds only the three privileges that MLE JavaScript needs (
CREATE MLE,EXECUTE DYNAMIC MLE,EXECUTEonsys.dbms_mle) andEXECUTEon the one gateway packageuc_ai_ptc_api. It has no tables, no quota, and no other packages. - Your tools still run with the full UC AI privileges, because the gateway is a UC AI package. They also run in the transaction of the caller, the same as a direct tool call. Each tool therefore keeps control of its own transaction. A tool that commits acts the same in both modes.
UC AI adds two more limits:
- A per-run allow-list. A program can call only the tools of the current run, that is the same enabled and tag-filtered set that the model sees.
- A call budget. This caps the tool calls of one program and bounds a runaway loop.
The optional before_tool_call hook fires for every callTool in a program, not only once for the whole program. Authorization and auditing through this hook therefore also work in code mode. A veto from the hook aborts the whole request.
The run context also reaches a tool that a program calls. UC AI adds it on the PL/SQL side of the gateway, after the allow-list check. The program never sees the context and cannot change it. A _ctx key in the arguments of a callTool is overwritten.
Enabling code mode
Section titled âEnabling code modeâCode mode builds on normal tool calling. Enable tools as usual and add one flag:
begin uc_ai.g_enable_tools := true; -- tools on uc_ai.g_enable_programmatic_tools := true; -- offer code mode uc_ai.g_tool_tags := apex_t_varchar2('reporting');
declare l_result json_object_t; begin l_result := uc_ai.generate_text( p_user_prompt => 'What is the total approved travel spend across all employees?' , p_system_prompt => 'You are a data analyst. Prefer writing a program with the ' || 'code tool over calling tools one by one.' , p_provider => uc_ai.c_provider_anthropic , p_model => uc_ai_anthropic.c_model_claude_5_sonnet ); end;end;/When g_enable_programmatic_tools is true, UC AI adds one extra tool uc_ai__run_code to your normal tools. Its description lists the exact tool codes that the program can call. The model can still call tools directly. Code mode is an additional option, and the model selects it for a task that gains from it.
Choosing which tools are available where
Section titled âChoosing which tools are available whereâBy default, every enabled tool is available both ways: directly and inside a program. The p_code_mode_access parameter of create_tool_from_schema and merge_tool_from_schema narrows this per tool:
uc_ai_tools_api.merge_tool_from_schema( p_tool_code => 'GET_EXPENSES', p_description => 'Fetch expense rows for one employee', p_function_call => 'return my_pkg.get_expenses(:parameters);', p_json_schema => l_schema, p_code_mode_access => 'code' -- direct | code | both);directâ a normal tool only. A program cannot call it. Use this for an action that the model must call deliberately, for example âsend emailâ or âdelete recordâ.codeâ hidden from the direct tool list. OnlycallToolinside a program can call it. Use this for bulk read tools. It keeps them out of the tool list of the model, and their large output out of its context.bothâ available either way.
The two procedures have different defaults. create_tool_from_schema defaults to both. merge_tool_from_schema defaults to null, which keeps the value that an existing tool already has and uses both for a new tool. A merge script therefore never widens a tool that you narrowed.
UC AI enforces this setting. The allow-list rejects a callTool to a direct-only tool, also when the model guesses the tool code. The setting applies only when code mode is on. With code mode off, every tool is a normal tool.
Descriptions in the tool catalog
Section titled âDescriptions in the tool catalogâEach code-callable tool gets one line in the catalog inside the uc_ai__run_code description. This line holds the callTool signature and the full tool description. For a code-only tool, this description is the only documentation that the model gets, because the catalog lists parameter names and not the JSON schema. Write in the description what each parameter expects, for example âemployee_id â numeric id from GET_EMPLOYEESâ. The name alone is not enough.
UC AI replaces each newline and tab in a description with a space, so every entry stays on one line. The whole catalog must fit in about 31 KB. If your descriptions are longer, UC AI leaves the tools that do not fit out of the catalog. A program then cannot call these tools, and UC AI writes a warning to the log. To correct this, shorten the descriptions, or move bulk tools to direct.
How the program calls your tools
Section titled âHow the program calls your toolsâInside the program the model calls:
const data = await callTool("YOUR_TOOL_CODE", { some: "arg" });- The program runs as the body of an async function, so
awaitworks at the top level. await callTool(code, args)returns the result of the tool, already parsed from JSON into an object or an array. The program must await this call, because PL/SQL runs the tool call and not the JavaScript.- If the model forgets the
await, UC AI inserts it. This rewrite leaves string literals, comments, and member calls of the same name unchanged. If the rewritten program does not parse, UC AI uses the original source. For an alias such asconst f = callTool, UC AI cannot insert theawait. The program then fails with the message âcallTool() is asynchronousâ, instead of treating the promise as data. - The program returns its answer in a top-level variable named
result, an object or a string. UC AI sends only this value back to the model. - The program is plain JavaScript: loops,
filter,reduce, and arithmetic. It has no SQL, no network, no file access, and no modules.
A typical program of the model looks like this:
const employees = await callTool("GET_EMPLOYEES", {});let total = 0;for (const e of employees) { const expenses = await callTool("GET_EXPENSES", { employee_id: e.id }); for (const x of expenses) { if (x.category === "travel" && x.status === "approved") total += x.amount; }}const result = { approved_travel_total: total };This is one uc_ai__run_code call. Only { approved_travel_total: ... } goes back to the model. The expense rows of each employee stay in the database.
The program can throw an error, call an unknown or forbidden tool, or pass the call budget. In each case, UC AI returns the error to the model as data and does not abort the request. The model can then correct the program on its next turn.
There is one exception. A veto from the before_tool_call hook is not returned as data. UC AI records it and raises it after the sandbox returns, so a program that catches the rejected callTool still cannot continue the request.
Limitations
Section titled âLimitationsâ- Oracle 23ai only (MLE JavaScript with
PUREcontexts). You must install the sandbox first. - MLE enforces no time limit for one program. Only the Database Resource Manager bounds an infinite JavaScript loop that calls no tool. The per-run call budget bounds the tool calls.
- The program can call only the enabled and tag-filtered tools of the current run. UC AI rejects every other tool, also when the model guesses its code.
- Sub-agents that are registered as tools (orchestrator / handoff) are
direct-only. Delegation to another agent costs LLM calls. It therefore stays a deliberate decision of the calling model, and a program cannot loop over it.