Skip to main content

Command Palette

Search for a command to run...

APEX AI Agent Handlers: Intercepting & Updating Requests and Responses

Updated
10 min readView as Markdown
APEX AI Agent Handlers: Intercepting & Updating Requests and Responses
J
Hi, thanks for stopping by! I am focused on designing and building innovative solutions using AI, the Oracle Database, Oracle APEX, and Oracle REST Data Services (ORDS). I hope you enjoy my blog.

Introduction

In my previous post, I looked at logging APEX AI Agent requests and responses with Request and Response handlers. Logging is the first step because it shows you what is actually moving through the agent loop: prompts, messages, tool definitions, tool calls, tool results, token counts, and final responses.

Once you can see those values, the next question is obvious:

Can I change them?

Yes. That is where handlers become more than instrumentation. They let you inspect and update selected values before and after the AI service call.

In this post, I will show how you can use APEX AI Agent handlers to:

  • Add runtime application context to the request.

  • Redact or normalize user input before sending it to the model.

  • Inspect pending tool calls before execution.

  • Reject unsafe arguments to tool calls before the tool runs.

  • Modify the final assistant response before the user sees it.

The Mental Model

The most important handler pattern is simple:

  • p_param tells you what APEX passes to the handler.

  • p_result is where you make changes.

The handler procedures use specific APEX signatures:

PROCEDURE agent_request_handler
 (p_param  in apex_ai.t_chat_request_handler_param,
  p_result in out nocopy apex_ai.t_chat_request_handler_result);

PROCEDURE agent_response_handler
 (p_param  in apex_ai.t_chat_response_handler_param,
  p_result in out nocopy apex_ai.t_chat_response_handler_result);

The request handler runs before the request is sent to the AI service. This is where you shape the outgoing request.

The response handler runs after the AI service responds. This is where you inspect the model response, validate pending tool calls, stop the loop early, or modify the assistant's final response.

Oracle's APEX 26.1 APEX_AI docs describe the normalized chat request, chat response, response handler parameter, response handler result, chat message, tool call, and tool result types in the APEX_AI data types documentation. The response handler can also return a tool result directly with APEX_AI.SET_TOOL_RESULT signature 2, but that behaves differently from returning a final assistant response, as shown below.

Request Handler Uses

Use the request handler when you want to change what the model sees.

Common examples:

  • Add the current APEX application context to the system prompt.

  • Add user, datetime, timezone, project, or customer context.

  • Redact sensitive values from user messages.

  • Change temperature or other request-level values when exposed through the request record.

The request handler is especially useful because it operates before the model has a chance to make a decision.

Example 1: Add Runtime Context to the System Prompt

The system prompt defines the agent's operating rules, but some context is only known at runtime. For example, the current app user, customer, project, security role, or selected record may depend on APEX session state.

For stable application context, I would normally use the agent's Augment System Prompt configuration. I am using the request handler here to show that the handler can also add context when the value needs to be calculated at request time.

You can append that context in the request handler.

procedure agent_request_handler
 (p_param  in apex_ai.t_chat_request_handler_param,
  p_result in out nocopy apex_ai.t_chat_request_handler_result)
as
  l_context clob;
begin
  l_context :=
       CHR(10) || CHR(10)
    || 'Runtime application context:' || CHR(10)
    || '- Current date and time: ' || TO_CHAR(LOCALTIMESTAMP, 'YYYY-MM-DD HH24:MI:SS') || CHR(10)
    || '- User timezone: ' || 'America/Los_Angeles' || CHR(10)
    || '- App username: ' || apex_util.get_session_state('APP_USER') || CHR(10);

  p_result.request.system_prompt :=
       p_result.request.system_prompt
    || l_context;
end agent_request_handler;

The declarative agent configuration can remain stable while the handler adds request-specific operational context.

💡
Use runtime context to help the model make better decisions. Do not treat prompt context as an authorization mechanism.

Note: You can also use the 'Augment System Prompt' tools section of the APEX Agent to augment the system prompt with context.

Example 2: Redact Sensitive User Input

If the user prompt may contain values you do not want to send to the AI provider, you can inspect the outgoing chat messages and redact those values.

procedure redact_user_messages
 (p_messages in out nocopy apex_ai.t_chat_messages)
as
  l_idx pls_integer;
begin
  l_idx := p_messages.first;

  while l_idx is not null loop
    if p_messages(l_idx).chat_role = apex_ai.c_role_user then
      p_messages(l_idx).message :=
        regexp_replace(
          p_messages(l_idx).message,
          '([0-9]{3})-([0-9]{2})-([0-9]{4})',
          '[redacted-ssn]' );

      p_messages(l_idx).message :=
        regexp_replace(
          p_messages(l_idx).message,
          '([[:alnum:]_.-]+)@([[:alnum:]_.-]+)',
          '[redacted-email]' );
    end if;

    l_idx := p_messages.next(l_idx);
  end loop;
end redact_user_messages;

Then call it from the request handler:

procedure agent_request_handler
 (p_param  in apex_ai.t_chat_request_handler_param,
  p_result in out nocopy apex_ai.t_chat_request_handler_result)
as
begin
  redact_user_messages(p_result.request.messages);
end agent_request_handler;

This also gives you a clear place to centralize prompt normalization rules.

This is intentionally simple. Production redaction should be tested against your real data patterns and should avoid logging the unredacted source text.

A Note About Tool Authorization

In the first version of this post, I considered including an example in which the request handler removes tools based on the current user's authorization. After thinking about it more, I do not think that is the right primary example for APEX.

APEX already gives you declarative controls at the AI Agent tool level. You can use Authorization Schemes and Server-side Conditions to determine whether a tool is available. That is the better first place to handle tool availability because it keeps security close to the APEX component configuration and uses the same declarative model APEX developers already use elsewhere in the application.

Handlers can still inspect p_result.request.tools, and there may be advanced cases where modifying the tool list is useful. For example, you might be building tools dynamically through lower-level APEX_AI.CHAT calls rather than using a declarative APEX Agent configuration. But for normal APEX AI Agent tools, prefer the built-in Authorization Scheme and Server-side Condition properties.

For declarative APEX AI Agent tools, Authorization Schemes and Server-side Conditions are the right place to control whether a tool is available. If the same PL/SQL API is also callable from other entry points, apply the appropriate authorization pattern for those entry points too.

Response Handler Uses

Use the response handler when you want to inspect or change what the model returned.

Common examples:

  • Validate pending tool calls before they execute.

  • Validate tool arguments before execution.

  • Return a controlled tool result without running the declarative tool.

  • Stop the agent loop early.

  • Replace a refusal or error message with a user-friendly response.

  • Add standard disclaimers, links, or formatting to the final answer.

Oracle documents t_chat_response_handler_param as containing the handler invocation number, the normalized request, and pending_tool_calls. It documents t_chat_response_handler_result as containing the mutable response, messages, and early_exit.

The key field for tool interception is:

p_param.pending_tool_calls

Each pending tool call has: id, name, args, args_json

Example 3: Validate a Pending Tool Call

Suppose the model asks to run search_open_cases, but it passes MAX_ROWS as 1000. That may be valid JSON and still be a bad request. In this example, search_open_cases is the tool name, and MAX_ROWS is a tool parameter used by the query to limit the number of rows returned.

The response handler can inspect the pending tool call before it executes.

procedure agent_response_handler
 (p_param  in apex_ai.t_chat_response_handler_param,
  p_result in out nocopy apex_ai.t_chat_response_handler_result)
as
  l_call     apex_ai.t_chat_message_tool_call;
  l_max_rows number;
begin
  if p_result.response.type <> apex_ai.c_response_type_tool_calls then
    return;
  end if;

  for i in 1 .. p_param.pending_tool_calls.count loop
    l_call := p_param.pending_tool_calls(i);

    if l_call.name = 'search_open_cases' then
      l_max_rows := coalesce(l_call.args_json.get_number('MAX_ROWS'), 25);

      if l_max_rows > 50 then
        p_result.response.type := apex_ai.c_response_type_complete;
        p_result.response.message.chat_role := apex_ai.c_role_assistant;
        p_result.response.message.message :=
          'I cannot run that search because MAX_ROWS cannot exceed 50.';
        p_result.early_exit := true;
        return;
      end if;
    end if;
  end loop;
end agent_response_handler;

This does not execute the original tool. It stops the normal agent loop and returns a specific assistant message to the user.

This is different from calling apex_ai.set_tool_result with an error payload. set_tool_result creates a tool-result message in the conversation history. That can be useful when you want the model to see the failed tool result and decide how to respond on the next turn, but it does not necessarily show that error directly in the agent UI. For a validation failure that should be shown to the user immediately, return a complete assistant response and set p_result.early_exit to true.

That distinction matters. If a tool call should not run, reject it before execution. The tool procedure should still validate its inputs because it may be callable from paths outside the AI Agent loop.

Example 4: Modify the Final Assistant Response

The response handler also sees complete responses. That means you can standardize or enrich the final answer before it is returned to the user.

For example, you might append a standard note when the answer was generated from operational data.

procedure agent_response_handler
 (p_param  in apex_ai.t_chat_response_handler_param,
  p_result in out nocopy apex_ai.t_chat_response_handler_result)
as
begin
  if p_result.response.type = apex_ai.c_response_type_complete then
    p_result.response.message.message :=
         p_result.response.message.message
      || chr(10) || chr(10)
      || '_This answer was generated from current service data. '
      || 'Open the linked records before making customer commitments._';
  end if;
end agent_response_handler;

I would not use this for heavy rewriting. If the model is consistently producing the wrong shape of answer, fix the system prompt or tool result shape first. Use response modification for small, deterministic post-processing.

Request Handler vs Response Handler

Here is how I think about the two handlers:

Need Better Handler
Add current app context Request handler
Redact user input before the provider call Request handler
Inspect model-selected tools Response handler
Validate tool call arguments Response handler
Stop the agent loop early Response handler
Modify the final assistant message Response handler

If you can provide a stable context before the model is called, use the request handler. If you need to react to what the model actually chose, use the response handler.

Practical Guidance

Handlers are powerful, but they can also make behavior harder to reason about if you are not disciplined.

The rules I would follow:

  • Keep handlers small. Delegate real logic to package functions and procedures.

  • Make handler changes deterministic. Do not introduce random behavior or hidden state.

  • Log what you changed. If you redact input, limit arguments, stop a tool call, or modify a final response, record it in your agent log.

  • Use APEX Authorization Schemes and Server-side Conditions for declarative tool availability.

  • Prefer validating arguments before execution over cleaning up after execution.

  • Keep tool results small. Anything returned from a tool can become model context on the next turn.

  • Test multi-turn conversations. A handler change on invocation 1 can affect what happens on invocation 2.

Conclusion

APEX AI Agent handlers are not just logging hooks. They are control points inside the agent loop.

The request handler lets you shape what the model sees: prompts, messages, and runtime context. The response handler lets you shape what happens after the model responds: pending tool calls, early exits, and final assistant messages.

The important pattern is to keep the model advisory and keep the application deterministic. Let the model decide what it wants to do, but use APEX configuration, handlers, tools, and database rules to decide what is allowed to happen.

That is the practical boundary for production APEX agents:

  • The model can suggest.

  • The handler can intercept.

  • APEX configuration and tool code enforcement.

  • Database rules protect the data.

Once you treat handlers as part of the agent workflow, you can build agents that are easier to debug, safer to operate, and better aligned with the business rules already living in your APEX application.

Updated Sample Package

I updated the sample PL/SQL package from my previous post to include examples from this post. Link.