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(ResponseListener.ResponseEvent), 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 before scheduling
the work and hide it 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();
progressBar.setVisible(true);
moderationService.checkAsync(event.getUserMessage())
.whenComplete((verdict, error) -> {
ui.access(() -> progressBar.setVisible(false));
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(ResponseListener.ResponseEvent) 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
-