Interface RequestInterceptor

All Superinterfaces:
Serializable
Functional Interface:
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

@FunctionalInterface public interface RequestInterceptor extends Serializable
Intercepts the user's input before the orchestrator acts on it. Configured via AIOrchestrator.Builder.withRequestInterceptor(RequestInterceptor), the interceptor is invoked for every prompt — a submit through a connected input component as well as the programmatic AIOrchestrator.prompt(String) entry points — with the user's message text and attachments. It can validate the content and reject the prompt, sanitize or replace the text, and replace the attachments (for example to convert an uploaded file to a format the LLM accepts):
 AIOrchestrator.builder(provider, systemPrompt)
         .withRequestInterceptor(event -> {
             if (containsBlockedTerms(event.getUserMessage())) {
                 event.reject("Please rephrase your message.");
                 return;
             }
             event.setUserMessage(maskPii(event.getUserMessage()));
         }).build();
 

The interceptor runs before the prompt has any effect: before the message appears in the message list, before controller and RequestListener hooks, before the conversation history entry, and before the LLM request is built. Everything downstream sees only the processed content. A silently rejected prompt leaves no trace in the UI or the history; rejecting with a user-facing message shows the original prompt and the reason in the message list only — never in the history or a request. Note that attachments pending in a configured file receiver have already been taken from it when the interceptor runs, so they are not resubmitted with the next prompt if this one is rejected, dropped, or fails after being postponed. Prompts whose original text is blank are dropped before the interceptor runs.

Throwing from the interceptor aborts the prompt the same way as a rejection, except that the exception is reported to the ResponseListener and AIController.onResponse(Throwable), and propagates to the caller of the prompt entry point. Throw only for failures; use reject for expected validation outcomes.

Threading: the interceptor is called on the UI thread under the session lock, and unless the prompt is postponed its result is used as soon as it returns — keep synchronous work short. Long-running work (e.g. heavy media conversion or a remote moderation call) should instead postpone the prompt, run on the application's own threads, and resume through the returned RequestInterceptor.RequestContinuation.

Postponing: while a prompt is postponed nothing is shown in the UI and further prompts are ignored, so show a pending indicator and disable the input before scheduling the work, and clean up when completing the continuation. Server push must be enabled — e.g. with @Push on the application shell class — for the resumed turn to reach the browser without user interaction. Capture the UI before scheduling the work and wrap component changes made from the completing thread in ui.access(...):

 .withRequestInterceptor(event -> {
     var continuation = event.postpone(Duration.ofSeconds(10));
     var ui = UI.getCurrent();
     input.setEnabled(false);
     moderationService.checkAsync(event.getUserMessage())
             .whenComplete((verdict, error) -> {
                 ui.access(() -> input.setEnabled(true));
                 if (error != null) {
                     continuation.fail(error);
                     return;
                 }
                 if (!verdict.allowed()) {
                     event.reject("Please rephrase your message.");
                 }
                 continuation.proceed();
             });
 })
 
A failure after postponing — RequestInterceptor.RequestContinuation.fail(java.lang.Throwable) or the timeout — is reported to the ResponseListener and AIController.onResponse(Throwable) only; it cannot propagate to the caller of the prompt entry point, which has long returned.

Serialization: the interceptor is stored on the serializable orchestrator and survives session serialization with it — unlike the LLM provider, it needs no reconnect step. A lambda implementation must therefore only capture serializable state; reference non-serializable services (e.g. a moderation client) indirectly instead of capturing them. A prompt that is postponed when the session is serialized does not survive: completing its continuation afterwards has no effect, and the deserialized orchestrator accepts new prompts once reconnected.

Since:
25.3
Author:
Vaadin Ltd
  • Method Details

    • intercept

      Called with the user's input before the orchestrator acts on it. Mutate the event to change what is sent, or reject it to cancel the prompt.
      Parameters:
      event - the event carrying the prompt content, never null