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.
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
-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic final classHandle for completing a prompt postponed viaRequestInterceptor.RequestInterceptEvent.postpone(Duration).static classThe content of one prompt, handed to aRequestInterceptorbefore the orchestrator acts on it. -
Method Summary
Modifier and TypeMethodDescriptionvoidCalled with the user's input before the orchestrator acts on it.
-
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, nevernull
-