How to Deploy Oracle AI Applications to Production

Add a kill switch, a daily budget, and monitoring to Oracle AI Database 26ai features, and deploy everything an AI application needs to each environment.

An AI feature that works in development still has to work every day: when the provider is down, when a model is retired, and when the bill must not double. Production needs a few controls that development can skip: a switch that stops every AI call at once, a daily limit on calls, monitoring that shows changes early, and a deployment checklist that covers more than code.

This guide adds a kill switch and a daily budget to the central GENERATE function in Oracle AI Database 26ai, builds a monitoring view over the call log, and lists everything an AI application needs in each environment.

Code for This Guide

The examples are files 04 to 06 in the examples/ch29 folder of the Oracle AI code repository on GitHub, each with its output.

These examples come from AI Applications with Oracle Database 26ai and APEX 26.1, a book of 237 tested examples of AI in Oracle Database and APEX.

They extend the logging GENERATE function from how to make LLM calls reliable in PL/SQL, through which every AI feature of the application calls a model.

Add a Kill Switch and a Daily Limit

Two controls belong in every production AI application: a switch that stops all model calls at once, for a misbehaving provider, runaway costs, or a security problem, and a limit on how many calls a day may make. Both belong in the one place every call goes through.

AI_SETTINGS holds the switch and a daily limit of 2,000 calls, and the production GENERATE checks both before every call, along with the earlier logging, retries, and removal of thinking settings for models that do not think.

Example:

create table ai_settings (
  name   varchar2(30)  constraint ai_settings_pk primary key,
  value  varchar2(100) not null
);
insert into ai_settings values ('AI_ENABLED', 'Yes');        -- the switch for every AI call
insert into ai_settings values ('DAILY_CALL_LIMIT', '2000');  -- calls per day, all models
commit;

-- GENERATE, checking the switch and the limit before every call
create or replace function generate (
  p_prompt   in clob,
  p_model    in varchar2 default 'GEMINI',
  p_options  in json     default null
) return clob
is
  l_params    json;
  l_thinking  varchar2(3);
  l_response  clob;
  l_start     timestamp := systimestamp;
  l_attempt   pls_integer := 0;
  l_error     varchar2(4000);
  l_enabled   varchar2(100);
  l_limit     number;
  l_today     number;

  procedure log_call is
    pragma autonomous_transaction;
    l_elapsed interval day to second := systimestamp - l_start;
  begin
    insert into llm_calls (model, prompt, response, attempts, elapsed_ms, error)
    values (p_model, p_prompt, l_response, l_attempt,
            round((extract(minute from l_elapsed) * 60 + extract(second from l_elapsed))
                  * 1000), l_error);
    commit;
  end;
begin
  select max(case name when 'AI_ENABLED' then value end),
         to_number(max(case name when 'DAILY_CALL_LIMIT' then value end))
  into   l_enabled, l_limit
  from   ai_settings;
  if l_enabled = 'No' then
    raise_application_error(-20100, 'AI features are switched off');
  end if;
  select count(*) into l_today from llm_calls where called_at >= trunc(systimestamp);
  if l_today >= l_limit then
    raise_application_error(-20101,
      'The daily limit of ' || l_limit || ' AI calls is reached');
  end if;

  select case when p_options is null then params
              else json_mergepatch(params, p_options returning json) end,
         thinking
  into   l_params, l_thinking
  from   llm_models
  where  name = p_model;
  if l_thinking = 'No' then
    select json_transform(l_params, remove '$.generationConfig.thinkingConfig')
    into   l_params
    from   dual;
  end if;

  loop
    l_attempt := l_attempt + 1;
    begin
      l_response := dbms_vector_chain.utl_to_generate_text(p_prompt, l_params);
      l_error := null;
      exit;
    exception
      when others then
        l_error := substr(sqlerrm, 1, 4000);
        if l_attempt < 3
           and regexp_like(l_error, 'high demand|unavailable|429|RESOURCE_EXHAUSTED|503',
                           'i') then
          dbms_session.sleep(2 * l_attempt);
        else
          log_call;
          raise;
        end if;
    end;
  end loop;
  log_call;
  return l_response;
end;
/

Output:

Table AI_SETTINGS created.

1 row inserted.

1 row inserted.

Commit complete.

Function GENERATE compiled

Test the Switch

Example:

-- the switch off: every AI feature stops at once, with a clear error
update ai_settings set value = 'No' where name = 'AI_ENABLED';
commit;

select ask('Can I get my money back for a duplicate charge?') as answer from dual;

update ai_settings set value = 'Yes' where name = 'AI_ENABLED';
commit;

Output:

1 row updated.

Commit complete.

Error starting at line : 5
In command -
select ask('Can I get my money back for a duplicate charge?') as answer from dual
Error at Command Line : 5 Column : 8
Error report -
SQL Error: ORA-20100: AI features are switched off
ORA-06512: at "ATLAS.GENERATE", line 33
ORA-06512: at "ATLAS.ASK", line 40
ORA-06512: at line 1

1 row updated.

Commit complete.

The RAG function failed with the switch's message, and no call reached Gemini. Every AI feature stops the same way, because all of them call GENERATE, and APEX pages show the message instead of an answer. The limit works the same way: after 2,000 calls in a day, GENERATE refuses until midnight. Set the limit from each feature's measured calls, with room for growth, and alert someone well before it is reached.

Monitor with a Daily View

A view over the call log makes the figures available to a dashboard, such as an APEX chart: one row per day and model.

Example:

-- one row per day: what the AI features did, for a dashboard
create or replace view ai_daily_stats as
select trunc(called_at) as day, model, count(*) as calls,
       count(case when error is not null then 1 end) as failed,
       round(avg(elapsed_ms)) as avg_ms,
       round(sum(dbms_lob.getlength(prompt)) / 1024) as prompt_kb
from   llm_calls
group  by trunc(called_at), model;

select * from ai_daily_stats order by day desc, calls desc;

Output:

View AI_DAILY_STATS created.

DAY            MODEL             CALLS    FAILED    AVG_MS    PROMPT_KB
______________ ______________ ________ _________ _________ ____________
02-OCT-2026    GEMINI              139         0      2957          197
02-OCT-2026    GEMINI_LITE          38         0      1553           67
A jump inUsually means
CallsA feature calls more than it should, for example per row of a report
FailuresA provider problem, an expired key, or a retired model
Prompt sizeA retrieval change that sends more sources than before
Average timeA slower model, more thinking, or a busy provider

Watch for changes rather than levels.

Deploy to Another Environment

An AI application consists of more than its code. Each environment, development, test, and production, needs:

ItemHow
Schema objectsTables, views, functions, procedures, triggers, and jobs, deployed from scripts in version control like any other code.
Embedding modelLoaded with DBMS_VECTOR.LOAD_ONNX_MODEL in each database from the same ONNX file; a Data Pump export of the schema also carries it.
EmbeddingsRecomputed in the target with the same model, or moved with the data. Never mix two models' embeddings in one column.
CredentialsCreated with each environment's own key, so a test key cannot spend the production budget and keys can be revoked one at a time. Keys come from a vault or an administrator, never from scripts in version control.
Network ACLsOne per provider host, created by an administrator.
Model configurationThe rows of the model and settings tables for that environment: a cheaper model in test, a stricter limit, the switch off until the environment is ready.
APEX configurationThe workspace's AI services and web credentials, which are workspace settings and are not part of an application export.
JobsCreated disabled, and enabled when the environment is ready.
Vector indexesCreated after the data is loaded: building an index is faster than maintaining it row by row during a load.

Before users see the deployed application, run your evaluation questions against it: a different key, model, or network path shows up there first. Building such a test is shown in how to test AI answers with an LLM grader in Oracle, and the background jobs in how to run AI jobs in the background with DBMS_SCHEDULER.

Conclusion

To run Oracle AI applications in production, put a kill switch and a daily call limit in the one function every model call goes through, watch a daily view of the call log for jumps in calls, failures, prompt size, and time, and deploy more than code: the embedding model and embeddings, per-environment credentials and ACLs, model settings, APEX AI services, disabled jobs, and indexes built after the load. Then test with your evaluation questions before users arrive.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted
00