# How to Implement Custom Validation Rules in Embabel: A Complete Guide to Guard-Rails

> Learn to implement custom validation rules in Embabel using guard-rails. This guide covers UserInputGuardRail and AssistantMessageGuardRail for robust agent input and output control.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-08

---

**Embabel's guard-rail framework lets you enforce custom validation rules by implementing the `UserInputGuardRail` or `AssistantMessageGuardRail` interfaces, returning a `ValidationResult`, and registering your implementation via Spring beans or the `AgentBuilder`.**

The Embabel agent framework provides a robust validation layer that intercepts both user input and assistant output through a guard-rail system. By implementing custom validation rules in Embabel, you can enforce security policies, business constraints, and data quality checks before or after LLM processing. This guide walks through the core interfaces, implementation patterns, and registration methods based on the actual source code in the `embabel/embabel-agent` repository.

## Core Guard-Rail Interfaces for Custom Validation

Embabel defines two primary interfaces in `com/embabel/agent/api/validation/guardrails/` that handle different stages of the agent lifecycle.

### UserInputGuardRail (Pre-LLM Validation)

The `UserInputGuardRail` interface validates incoming prompts **before** they reach the LLM. Implement this interface when you need to enforce input policies such as profanity filtering, required field validation, or PII detection.

The interface requires a single method:

```java
@NotNull ValidationResult validate(@NotNull String input, @NotNull Blackboard blackboard)

```

### AssistantMessageGuardRail (Post-LLM Validation)

The `AssistantMessageGuardRail` interface validates the assistant's response **after** the LLM generates output, including structured data. Use this to ensure business rule compliance, output schema validation, or security constraints on generated content.

The method signature accepts an `AssistantMessage`:

```java
@NotNull ValidationResult validate(@NotNull AssistantMessage input, @NotNull Blackboard blackboard)

```

Both interfaces return a `ValidationResult` object defined in [`com/embabel/common/core/validation/ValidationResult.java`](https://github.com/embabel/embabel-agent/blob/main/com/embabel/common/core/validation/ValidationResult.java). If validation fails (`valid == false`), Embabel throws a `GuardRailViolationException` and aborts the LLM execution.

## Step-by-Step Implementation Guide

Follow these architectural steps to create and register your custom validation rules.

### 1. Create Your Guard-Rail Implementation

Write a class implementing either `UserInputGuardRail` or `AssistantMessageGuardRail`. The implementation should inspect the incoming data and identify violations.

### 2. Return a ValidationResult with Errors

Inside the `validate` method, construct a `List<ValidationError>` for any detected problems. Each `ValidationError` requires:

- **Error code**: A string identifier (e.g., `"PROFANITY_DETECTED"`)
- **Message**: Human-readable description
- **Severity**: `ValidationSeverity.CRITICAL`, `ValidationSeverity.INFO`, etc.

Return `new ValidationResult(isValid, errors)` where `isValid` is `true` only if the errors list is empty.

### 3. Register via Spring Auto-Configuration

Declare your guard-rail as a `@Bean` in a `@Configuration` class. Embabel's auto-configuration automatically detects and wires these beans into the agent pipeline.

### 4. Register Programmatically with AgentBuilder

Alternatively, pass guard-rail instances directly to the builder using the var-args method `.withGuardRails()`:

```java
AgentBuilder.builder()
    .withModel(myModel)
    .withGuardRails(new NoProfanityGuardRail(), new StructuredOutputGuardRail())
    .build();

```

## Complete Code Examples

These examples are adapted from the integration tests in [`embabel-agent-autoconfigure/models/embabel-agent-openai-autoconfigure/src/test/java/com/embabel/agent/config/models/openai/LLMOpenAiGuardRailsIntegrationIT.java`](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-autoconfigure/models/embabel-agent-openai-autoconfigure/src/test/java/com/embabel/agent/config/models/openai/LLMOpenAiGuardRailsIntegrationIT.java).

### Validating User Input for Content Policy

This `UserInputGuardRail` implementation checks for prohibited language before sending input to the LLM:

```java
// src/main/java/com/example/guardrails/NoProfanityGuardRail.java
package com.example.guardrails;

import com.embabel.agent.api.validation.guardrails.UserInputGuardRail;
import com.embabel.common.core.validation.ValidationError;
import com.embabel.common.core.validation.ValidationResult;
import com.embabel.common.core.validation.ValidationSeverity;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

public record NoProfanityGuardRail() implements UserInputGuardRail {

    @Override
    public @NotNull ValidationResult validate(@NotNull String input, @NotNull Blackboard blackboard) {
        List<ValidationError> errors = new ArrayList<>();

        // Simple check - replace with real NLP filter in production
        if (input.toLowerCase().contains("badword")) {
            errors.add(new ValidationError(
                    "PROFANITY_DETECTED",
                    "User input contains prohibited language.",
                    ValidationSeverity.CRITICAL));
        }

        return new ValidationResult(errors.isEmpty(), errors);
    }
}

```

### Validating Assistant Output Structure

This `AssistantMessageGuardRail` ensures structured output contains required fields:

```java
// src/main/java/com/example/guardrails/StructuredOutputGuardRail.java
package com.example.guardrails;

import com.embabel.agent.api.validation.guardrails.AssistantMessageGuardRail;
import com.embabel.common.core.validation.ValidationError;
import com.embabel.common.core.validation.ValidationResult;
import com.embabel.common.core.validation.ValidationSeverity;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import com.embabel.agent.api.model.AssistantMessage;

public record StructuredOutputGuardRail() implements AssistantMessageGuardRail {

    @Override
    public @NotNull ValidationResult validate(@NotNull AssistantMessage input,
                                               @NotNull Blackboard blackboard) {
        List<ValidationError> errors = new ArrayList<>();

        // Ensure order messages contain required item field
        if (input.getContent().contains("\"order\"")) {
            if (!input.getContent().contains("\"item\":\"")) {
                errors.add(new ValidationError(
                        "MISSING_ITEM",
                        "Structured order output is missing the required 'item' field.",
                        ValidationSeverity.INFO));
            }
        }

        return new ValidationResult(errors.isEmpty(), errors);
    }
}

```

### Spring Configuration Registration

Register both guard-rails as Spring beans in [`src/main/java/com/example/config/GuardRailConfiguration.java`](https://github.com/embabel/embabel-agent/blob/main/src/main/java/com/example/config/GuardRailConfiguration.java):

```java
package com.example.config;

import com.example.guardrails.NoProfanityGuardRail;
import com.example.guardrails.StructuredOutputGuardRail;
import com.embabel.agent.api.validation.guardrails.UserInputGuardRail;
import com.embabel.agent.api.validation.guardrails.AssistantMessageGuardRail;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GuardRailConfiguration {

    @Bean
    public UserInputGuardRail profanityGuardRail() {
        return new NoProfanityGuardRail();
    }

    @Bean
    public AssistantMessageGuardRail structuredOutputGuardRail() {
        return new StructuredOutputGuardRail();
    }
}

```

## How Validation Failures Are Handled

When you implement custom validation rules in Embabel, the framework enforces strict failure semantics. If any guard-rail returns `valid == false`, Embabel immediately throws a `GuardRailViolationException` containing the list of `ValidationError` objects. This aborts the current LLM execution, preventing invalid inputs from reaching the model or invalid outputs from reaching the user.

The severity level in `ValidationError` (e.g., `ValidationSeverity.CRITICAL` vs `ValidationSeverity.INFO`) allows you to categorize violations, though any severity level triggers the exception if `valid` is false.

## Summary

- **Implement** either `UserInputGuardRail` (pre-LLM) or `AssistantMessageGuardRail` (post-LLM) from `com/embabel/agent/api/validation/guardrails/`.
- **Return** a `ValidationResult` containing a boolean flag and a list of `ValidationError` objects with codes, messages, and severity levels.
- **Register** via Spring beans for auto-configuration or programmatically via `AgentBuilder.withGuardRails()` as shown in [`ParallelToolLoopGuardRailIT.java`](https://github.com/embabel/embabel-agent/blob/main/ParallelToolLoopGuardRailIT.java).
- **Handle** validation failures through `GuardRailViolationException` which aborts execution when any guard-rail returns invalid.

## Frequently Asked Questions

### What happens if multiple guard-rails are registered and one fails?

Embabel processes all guard-rails in the order they are registered. If any guard-rail returns `valid == false`, the framework immediately throws a `GuardRailViolationException` and aborts the LLM call. You can stack multiple rules (e.g., profanity filtering plus domain validation) using the var-args `.withGuardRails()` method or by declaring multiple `@Bean` methods.

### Can I use guard-rails with structured output and tool calling?

Yes. The `AssistantMessageGuardRail` interface validates the final assistant message, which includes structured JSON output and tool call results. As demonstrated in the test suite at [`LLMOpenAiGuardRailsIntegrationIT.java`](https://github.com/embabel/embabel-agent/blob/main/LLMOpenAiGuardRailsIntegrationIT.java), guard-rails work seamlessly with parallel tool loops and structured response formats.

### How do I access conversation context inside a guard-rail?

The `validate` method receives a `Blackboard` parameter that provides access to the conversation state and context. You can use this to implement context-aware validation rules that depend on previous messages or session data, though the specific `Blackboard` API methods depend on your Embabel version.

### What is the difference between CRITICAL and INFO severity levels?

While both severity levels trigger a `GuardRailViolationException` when `valid` is false, they allow you to categorize errors for logging and monitoring purposes. `ValidationSeverity.CRITICAL` typically indicates security violations or policy breaches, while `ValidationSeverity.INFO` might indicate missing optional fields or minor formatting issues that you want to flag without necessarily blocking in all environments.