Guardrails System
When to Use
Use guardrails when you need to intercept AI requests before they reach the provider (pre-processing) or after receiving a response (post-processing). Required for user-facing AI features.
Guardrails are plugins that process AI requests before and/or after they reach the provider. Use them for content moderation, PII filtering, prompt injection detection, etc.
Decision
| Situation | Choose | Why |
|---|---|---|
| Content moderation | Pre + post guardrail | Block unsafe input and output |
| PII filtering | Pre guardrail | Scrub before sending to provider |
| Prompt injection detection | Pre guardrail | Catch injection attempts before processing |
| AI-based moderation | NonDeterministicGuardrailInterface |
Guardrail itself uses AI; receives AiProviderPluginManager |
| Streaming response | Avoid NonStreamableGuardrailInterface |
Skipped for streaming calls automatically |
| Redact content mid-stream | StreamableGuardrailInterface (1.4) |
Buffer streamed output and redact before client sees it |
| Site-wide enforcement | Global guardrail sets (1.4) | Applied to every request before caller-attached sets |
Plugin Attribute
use Drupal\ai\Attribute\AiGuardrail;
#[AiGuardrail(
id: 'my_guardrail',
label: new TranslatableMarkup('My Guardrail'),
description: new TranslatableMarkup('Blocks unsafe content'),
)]
class MyGuardrail extends AiGuardrailPluginBase {
// Implement pre/post processing methods
}
ID convention: Must match or be prefixed by group. E.g., group "safety" -> ID must be "safety" or "safety:pii_filter".
Services
| Service | Purpose |
|---|---|
plugin.manager.ai_guardrail |
Plugin manager |
Drupal\ai\Guardrail\AiGuardrailRepository |
Loads configured guardrails |
ai.guardrail_helper |
Helper for guardrail operations |
Drupal\ai\EventSubscriber\GuardrailsEventSubscriber |
Applies guardrails on pre/post events |
How Guardrails Work
PreGenerateResponseEventfires- Changed in 1.4:
GlobalGuardrailsEventSubscriber(priority 100) prepends site-wide guardrail sets fromai.settingsconfig before any caller-attached sets — global sets always run first GuardrailsEventSubscriberloads the full merged guardrail set list from the input- Each guardrail's pre-processing runs (can modify input or block request)
- Provider processes the request
PostGenerateResponseEventfires- Each guardrail's post-processing runs (can modify or reject output)
Config Entities
| Entity Type | Interface | Purpose |
|---|---|---|
ai_guardrail |
AiGuardrailEntityInterface |
Individual guardrail config entity |
ai_guardrail_set |
AiGuardrailSetInterface |
Groups guardrails with pre/post lists and a stop threshold |
AiGuardrailSetInterface key methods:
getPreGenerateGuardrails()— guardrails that run before AI generationgetPostGenerateGuardrails()— guardrails that run after AI generationgetStopThreshold()— float threshold forStopResultscores
AiGuardrailInterface Methods
interface AiGuardrailInterface {
public function label(): string;
public function isAvailable(): bool;
public function processInput(InputInterface $input): GuardrailResultInterface;
public function processOutput(OutputInterface $output): GuardrailResultInterface;
}
Guardrail Result Types
| Result Class | stop() |
Purpose |
|---|---|---|
PassResult |
false |
Input/output passes without changes |
StopResult |
true |
Block the request; includes a $score (float) compared against set threshold |
RewriteInputResult |
false |
Rewrite the input before sending to provider |
RewriteOutputResult |
false |
Rewrite the output before returning to caller |
All result types extend AbstractResult(string $message, AiGuardrailInterface $guardrail, array $context).
Specialized Guardrail Interfaces
| Interface | Purpose |
|---|---|
NonDeterministicGuardrailInterface |
Guardrail that uses AI itself (e.g., LLM-based moderation). Receives AiProviderPluginManager via setAiPluginManager() |
NonStreamableGuardrailInterface |
Marker interface — guardrail cannot process streamed responses (skipped for streaming calls) |
StreamableGuardrailInterface |
New in 1.4: Evaluate streamed output mid-stream. getStartRegex() begins buffering, getStopRegex() ends it, and processStreamedBuffer(string $buffered): GuardrailResultInterface decides — used to redact sensitive content (e.g. an unreleased product name) before it reaches the client |
Repository: AiGuardrailRepository
Autowired service for loading guardrail entities:
| Method | Returns |
|---|---|
getGuardrailById($id) |
AiGuardrailInterface or null |
getAllGuardrails() |
AiGuardrailInterface[] |
getGuardrailSetById($id) |
AiGuardrailSetInterface or null |
getAllGuardrailSets() |
AiGuardrailSetInterface[] |
$repo = \Drupal::service('Drupal\ai\Guardrail\AiGuardrailRepository');
$guardrail = $repo->getGuardrailById('safety:pii_filter');
$set = $repo->getGuardrailSetById('my_guardrail_set');
$all = $repo->getAllGuardrailSets();
Built-in Guardrail Plugins
| Plugin | Purpose |
|---|---|
regexp_guardrail |
Block inputs/outputs matching a configurable regex pattern (fixed in 1.3.5 — processOutput() now executes the pattern) |
input_length_limit |
Changed in 1.4: Built-in DoS protection — blocks requests whose input text exceeds a configurable character limit |
restrict_to_topic |
New in 1.4: Non-deterministic (LLM-based) guardrail that blocks inputs/outputs outside a configured topic. 1.4.2 added a re-entrancy guard so its internal LLM call can't recurse into global guardrails, and it now parses the classifier response via the ai.prompt_json_decode service |
Global Guardrails (Changed in 1.4)
Configure site-wide guardrail sets that apply to every AI request regardless of caller:
# ai.settings
global_guardrails:
- my_pii_guardrail_set
- my_content_moderation_set
These sets are prepended to any caller-attached guardrail sets. Global sets cannot be bypassed by callers. Configure at /admin/config/ai/settings.
InputInterface: Multiple Guardrail Sets (Changed in 1.4)
In 1.3.x, each input held a single guardrail set. In 1.4.x, inputs hold multiple sets. The old methods are deprecated:
| 1.3.x (deprecated in 1.4) | 1.4.x replacement |
|---|---|
setGuardrailSet($set) |
addGuardrailSet($set) or setGuardrailSets([$set]) |
getGuardrailSet() |
getGuardrailSets() — returns array keyed by set ID |
// 1.3.x (still works, deprecated)
$input->setGuardrailSet($guardrailSet);
// 1.4.x (recommended)
$input->addGuardrailSet($guardrailSet); // Add one set; replaces if same ID
$input->setGuardrailSets([$set1, $set2]); // Replace all sets
$sets = $input->getGuardrailSets(); // Returns array keyed by set ID
Built-in Moderation
The ModeratePreRequestEventSubscriber intercepts chat calls and routes them through a separate moderation provider if configured (migrated from the deprecated ai_external_moderation module).
Common Mistakes
| Mistake | Why it's wrong |
|---|---|
| Plugin ID that does not match its group prefix | ID must match or be prefixed by group — group "safety" means ID safety or safety:pii_filter |
Applying a streaming-incompatible guardrail without NonStreamableGuardrailInterface |
It will run against streamed calls; mark it and it is skipped instead |
| Not enabling guardrails on user-facing features | Prompt injection can bypass agent instructions |
Using setGuardrailSet() on 1.4.x |
Deprecated — use addGuardrailSet() or setGuardrailSets() |
See Also
- Events System
- AI Agents
- Security
- Reference:
web/modules/contrib/ai/src/Guardrail/