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

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:

@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:

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

Both interfaces return a ValidationResult object defined in 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():

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.

Validating User Input for Content Policy

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

// 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:

// 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:

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.
  • 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →