AI request interception - reroute anonymous chat to a cheaper model¶
Version v1
You can apply this model as a recipe (Drupal 10.3 or later) to your own Drupal site:
1 2 3 4 5 6 7 8 9 10 11 | |
Purpose¶
Nothing in this model calls an AI provider. It sits in the middle of a call somebody else makes.
The AI module dispatches an event around every call that goes through its
provider proxy, and the ai_eca_interceptor submodule of AI Integration - ECA
turns four of those moments into ECA events: before the provider is called,
after it has answered, when a streamed answer has finished, and when the call
threw. This model listens to the first of them and rewrites where the call goes.
The rule it implements is a cost rule. A chat request made on behalf of an anonymous visitor is answered by a cheap model; a request made on behalf of anyone with an account keeps whatever provider and model the calling code chose.
The caller does not have to be your code¶
That is the point of intercepting rather than acting. An AI Assistant, an automator, an agent someone else shipped, a custom module of your own - every one of them reaches the provider through the same proxy, so every one of them passes this model on the way out. You do not have to own the caller to govern the call.
Three components¶
The event is "AI request: before provider call", with its operation type filter
set to chat. The three filter fields are combined into a single wildcard of
the form operation_type:provider_id:model_id, and an empty field means *, so
this one subscribes as chat:*:* - every chat call, whatever provider and
model. The fields are matched exactly and resolve no tokens, so a typo, or a
token placed in a filter field, silently narrows the wildcard to something no
call ever matches. That is the first thing to check when a model like this never
fires.
The condition is ECA's own "Role of current user", set to anonymous. It is an
ordinary ECA condition and knows nothing about AI. Swap it for a permission
check, a time-of-day comparison or a data residency lookup and the rule changes
without the rest of the model moving.
The action is "AI request: reroute provider/model". Both of its fields are required, and that is a contract rather than strictness: a model id means nothing without the provider it belongs to, because model ids are not portable between providers.
What the reroute action actually does¶
The provider and the model on the event are read-only. The action does not change them.
It records the intent, writing ai_eca_interceptor.reroute.provider_id and
ai_eca_interceptor.reroute.model_id into the event metadata. A subscriber then
runs at priority -100, after every ECA action and after the guardrail
applier, performs the call against the target itself, and installs the result as
the forced output - so the originally chosen provider is never called. On
success it records ai_eca_interceptor.reroute.executed in the metadata, which a
later model can read through [event:metadata].
The subscriber carries the request over intact. It copies the event's configuration onto the target provider, so a temperature or token limit an earlier action set still applies after the reroute, and it hands the target a normalized copy of the request's tags without touching the event's own, so provider-side routing, logging and cost attribution stay correct. It also stands aside when an output has already been forced - by "AI request: block" in its fallback mode, or by "AI request: force chat output" - so the first action to produce an answer wins.
If the target provider throws, the failure is logged as AI ECA reroute failed
and no forced output is set, so the original provider runs as if nothing had
happened. The reroute fails open.
For a cost rule that is the right behavior: the visitor still gets an answer,
and the worst case is that the answer cost more than intended. For a rule about
where data is allowed to go it is not, and that is worth knowing before
promising anything to an auditor. A rule that has to hold uses "AI request:
block" in its exception mode instead, which refuses the call rather than
letting it through.
Because the rerouted call is made inside the same request, it dispatches its own "AI request: before provider call" event. A recursion guard on the subscriber denies the reroute action access while a reroute is in flight, so the rerouted call cannot reroute again.
Before this does anything on your site¶
The provider and model shipped here, openai and gpt-4o-mini, are
placeholders for a target you have configured. Change both fields of the reroute
action to a provider id from your own AI provider configuration and a model that
provider serves. Both fields support token replacement, so the target can also
come from a token that an earlier action in a larger model sets.
Until they are changed, the reroute attempt fails and is logged, and the call falls back to the provider the caller chose. The site keeps working; it simply does not save anything.
Requirements¶
ai_eca_interceptor ships inside AI Integration - ECA and installs on its own -
it does not depend on the parent module, though the two are complementary and
can be enabled together. It requires the AI module at 1.4 or newer, because
the recovery path it shares relies on AiExceptionEvent::setForcedOutputObject(),
which AI core added in 1.4.0. The condition comes from ECA's own eca_user
submodule.
There is nothing else to set up. This model creates no content type, no field
and no role: the anonymous role always exists on every Drupal site.
Leave the event's subscriber priority alone. ECA runs at priority 0 on this
event, ahead of the guardrail applier at -10 and the reroute subscriber at
-100, and that ordering is what lets an action record an intent the
subscribers then carry out. Lowering it puts the model behind them and the
reroute silently stops happening.
Dependencies¶
- config
- filter.format.restricted_html
- user.role.anonymous
- module
- ai_eca_interceptor
- eca_user
- filter
- modeler_api
- system