Reliability

Guardrails: Constraining Model Behavior

⏱ 14 min

Lesson 1’s gotcha said a system instruction isn’t a security boundary. This lesson is about the two things that actually are closer to one: wp_ai_client_prevent_prompt, a real filter that can stop a prompt from ever being sent, and prompt-level “do not” instructions, which are worth using but need to be understood for what they really are, a strong nudge, not an enforceable rule. Knowing which kind of control you’re reaching for, and when neither one is actually enough, is what this lesson covers.

What you'll learn in this lesson
Two different kinds of guardrail
Code-level (deterministic) versus prompt-level (probabilistic), and when each applies.
wp_ai_client_prevent_prompt
The real filter that blocks a prompt entirely before it reaches a provider.
What happens when a prompt is prevented
How is_supported_*() and generate_*() behave once blocked.
The real limits of "do not" instructions
Why they reduce, not eliminate, unwanted output.
Prerequisites

Course 1’s wp_ai_client_prompt() basics and Lesson 1’s system instruction patterns from this course. No new setup is required, this lesson adds one new hook on top of what you already have working.

Step 1: Two different kinds of guardrail

A prompt-level constraint, something like “do not include personal data in your response,” is read by the model and followed most of the time, but it’s ultimately a request, not a rule the model is mechanically incapable of breaking. A code-level guardrail runs in your own PHP, before or around the call, and doesn’t depend on the model cooperating at all. wp_ai_client_prevent_prompt is the code-level option, it can stop a prompt from ever leaving your server, deterministically, every time the condition you write is met.

Step 2: wp_ai_client_prevent_prompt in practice

The filter receives a boolean $prevent value and the WP_AI_Client_Prompt_Builder instance representing the prompt about to be sent. Returning true blocks it entirely. A direct, common use is restricting AI features to trusted roles:

// File: my-plugin/class-ai-guardrails.php
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $builder ) {
	if ( ! current_user_can( 'manage_options' ) ) {
		return true;
	}
	return $prevent;
}, 10, 2 );

This runs for every prompt sent through wp_ai_client_prompt() anywhere on the site once the filter is added, which makes it a good place for a broad, site-wide rule.

Step 3: A narrower, feature-specific guardrail

Blocking by capability is useful, but often you want to block one specific feature under one specific condition, a simple rate limit per user is a common real case, tracked with a transient rather than by inspecting the builder’s internal state:

// File: my-plugin/class-ai-guardrails.php
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $builder ) {
	$user_id       = get_current_user_id();
	$transient_key = 'my_plugin_ai_calls_' . $user_id;
	$calls_this_hour = (int) get_transient( $transient_key );

	if ( $calls_this_hour >= 20 ) {
		return true;
	}

	set_transient( $transient_key, $calls_this_hour + 1, HOUR_IN_SECONDS );
	return $prevent;
}, 10, 2 );

Keeping the condition in your own tracked state, a transient, an option, a flag your feature already sets, rather than trying to inspect deep internals of $builder, keeps this guardrail simple and easy to reason about on its own.

Step 4: What happens once a prompt is prevented

Once wp_ai_client_prevent_prompt returns true for a given prompt, the SDK’s own is_supported_*() methods start returning false for it, which is useful for hiding an AI-powered button in the UI before a user even tries to use it, and any generate_*() call on that same builder returns a WP_Error instead of making a request. Always check for that:

// File: my-plugin/class-ai-guardrails.php
$result = wp_ai_client_prompt()
	->with_text( $ticket_body )
	->generate_text();

if ( is_wp_error( $result ) ) {
	return 'AI drafting is not available for your account right now.';
}

Step 5: Prompt-level “do not” instructions, and their real limits

Alongside code-level blocking, plain constraints inside a system instruction are still worth using, “do not invent a fact not present in the input,” “do not include markdown formatting,” “do not exceed 160 characters.” They reduce how often a model produces something you don’t want. They do not guarantee it never happens. Treat every “do not” instruction as lowering a probability, not closing a door, and pair anything that actually matters, personal data exposure, factual fabrication in published content, with the validation step covered in the next lesson, not with wording alone.

Do not treat a 'do not' instruction as sufficient on its own

If a requirement genuinely cannot be violated, no destructive action without human confirmation, no personal data in a public-facing response, back it with a real code check: a wp_ai_client_prevent_prompt block, a permission check, or output validation after the call. Wording in a system instruction is a real and useful layer, it is not the last layer.

Test it: confirm a prevented prompt fails safely

wp-env run cli wp eval '
add_filter( "wp_ai_client_prevent_prompt", function ( $prevent, $builder ) {
	return true;
}, 10, 2 );

$result = wp_ai_client_prompt()
	->with_text( "Say hello." )
	->generate_text();

var_dump( is_wp_error( $result ) );
'

You should see bool(true), confirming the call was blocked before it reached a provider at all, rather than a provider-side rejection or an unhandled exception.

Recap

wp_ai_client_prevent_prompt is a real, code-level guardrail that can stop a prompt from ever being sent, filtered on a boolean $prevent and the builder itself, with prevented calls returning false from is_supported_*() and a WP_Error from generate_*(). Prompt-level “do not” instructions are a useful, complementary layer, but they reduce unwanted output rather than eliminating it, anything that truly cannot be allowed to happen needs a code-level check behind the wording, not instead of it.

Resources & further reading

← Designing Tool-Calling Prompts for Abilities Handling Ambiguous or Bad Model Output →