> For the complete documentation index, see [llms.txt](https://docs.umbraco.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.umbraco.com/ai-in-umbraco/extending/guardrails.md).

# Custom Guardrail Evaluators

Create custom guardrail evaluators to enforce domain-specific safety and compliance rules.

Guardrail evaluators are the components that perform the actual content evaluation within guardrail rules. Umbraco.AI ships with built-in evaluators (Contains, Regex, LLM Judge), but you can create custom evaluators for domain-specific needs.

## Overview

Each evaluator:

* Has a unique ID and display name
* Defines what it checks (prohibited content, sensitive patterns, brand compliance, etc.)
* Can be code-based (deterministic, fast) or model-based (LLM-powered, nuanced)
* Optionally accepts configuration with a dynamic UI schema
* Is auto-discovered at startup

## Creating a Code-Based Evaluator

Code-based evaluators use deterministic logic like pattern matching. They run during streaming and provide fast responses.

### Step 1: Define Configuration

Create a configuration class with `[AIField]` attributes for backoffice UI generation:

{% code title="ProfanityFilterConfig.cs" %}

```csharp
using Umbraco.AI.Core.EditableModels;

public class ProfanityFilterConfig
{
    [AIField(
        Label = "Word List",
        Description = "Comma-separated list of words to flag",
        EditorUiAlias = "Umb.PropertyEditorUi.TextArea",
        SortOrder = 1)]
    public string WordList { get; set; } = string.Empty;

    [AIField(
        Label = "Ignore Case",
        Description = "Case-insensitive matching",
        EditorUiAlias = "Umb.PropertyEditorUi.Toggle",
        SortOrder = 2)]
    public bool IgnoreCase { get; set; } = true;
}
```

{% endcode %}

### Step 2: Implement the Evaluator

Extend `AIGuardrailEvaluatorBase<TConfig>` and apply the `[AIGuardrailEvaluator]` attribute:

{% code title="ProfanityFilterEvaluator.cs" %}

```csharp
using Microsoft.Extensions.AI;
using Umbraco.AI.Core.EditableModels;
using Umbraco.AI.Core.Guardrails.Evaluators;

[AIGuardrailEvaluator("profanity-filter", "Profanity Filter",
    Type = AIGuardrailEvaluatorType.CodeBased)]
public class ProfanityFilterEvaluator
    : AIGuardrailEvaluatorBase<ProfanityFilterConfig>
{
    public override string Description =>
        "Flags content containing words from a configurable word list";

    public ProfanityFilterEvaluator(IAIEditableModelSchemaBuilder schemaBuilder)
        : base(schemaBuilder)
    { }

    public override Task<AIGuardrailResult> EvaluateAsync(
        string content,
        IReadOnlyList<ChatMessage> conversationHistory,
        AIGuardrailConfig config,
        CancellationToken cancellationToken)
    {
        var evalConfig = config.Deserialize<ProfanityFilterConfig>()
            ?? new ProfanityFilterConfig();

        var words = evalConfig.WordList
            .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        var comparison = evalConfig.IgnoreCase
            ? StringComparison.OrdinalIgnoreCase
            : StringComparison.Ordinal;

        var matched = words.FirstOrDefault(w => content.Contains(w, comparison));
        var flagged = matched is not null;

        return Task.FromResult(new AIGuardrailResult
        {
            EvaluatorId = Id,
            Flagged = flagged,
            Score = flagged ? 1.0 : 0.0,
            Reason = flagged ? $"Content contains prohibited word: {matched}" : null
        });
    }
}
```

{% endcode %}

The evaluator is automatically discovered and available in the backoffice when creating guardrail rules.

## Creating a Model-Based Evaluator

Model-based evaluators use an AI model to perform nuanced evaluation. They run after streaming completes (on the full response).

{% code title="BrandComplianceEvaluator.cs" %}

```csharp
using System.Text.Json;
using Microsoft.Extensions.AI;
using Umbraco.AI.Core.Chat;
using Umbraco.AI.Core.EditableModels;
using Umbraco.AI.Core.Guardrails.Evaluators;
using Umbraco.AI.Core.RuntimeContext;

public class BrandComplianceConfig
{
    [AIField(
        Label = "Brand Guidelines",
        Description = "The brand guidelines to evaluate against",
        EditorUiAlias = "Umb.PropertyEditorUi.TextArea",
        SortOrder = 1)]
    public string Guidelines { get; set; } = string.Empty;

    [AIField(
        Label = "Compliance Threshold",
        Description = "Score below this threshold flags content (0-1)",
        EditorUiAlias = "Umb.PropertyEditorUi.Slider",
        EditorConfig = "[{\"alias\":\"minValue\",\"value\":0},{\"alias\":\"maxValue\",\"value\":1},{\"alias\":\"step\",\"value\":0.1}]",
        SortOrder = 2)]
    public double Threshold { get; set; } = 0.8;
}

[AIGuardrailEvaluator("brand-compliance", "Brand Compliance",
    Type = AIGuardrailEvaluatorType.ModelBased)]
public class BrandComplianceEvaluator
    : AIGuardrailEvaluatorBase<BrandComplianceConfig>
{
    private readonly IAIChatService _chatService;
    private readonly IAIRuntimeContextAccessor _runtimeContextAccessor;

    public override string Description =>
        "Uses an AI model to evaluate content against brand guidelines";

    public BrandComplianceEvaluator(
        IAIChatService chatService,
        IAIRuntimeContextAccessor runtimeContextAccessor,
        IAIEditableModelSchemaBuilder schemaBuilder)
        : base(schemaBuilder)
    {
        _chatService = chatService;
        _runtimeContextAccessor = runtimeContextAccessor;
    }

    public override async Task<AIGuardrailResult> EvaluateAsync(
        string content,
        IReadOnlyList<ChatMessage> conversationHistory,
        AIGuardrailConfig config,
        CancellationToken cancellationToken)
    {
        var evalConfig = config.Deserialize<BrandComplianceConfig>()
            ?? new BrandComplianceConfig();

        // Prevent infinite recursion when the evaluator itself uses AI
        _runtimeContextAccessor.Context?.SetValue(
            Constants.ContextKeys.IsGuardrailEvaluation, true);

        try
        {
            var prompt = $"""
                Evaluate this content against the following brand guidelines.
                Return a JSON object: {{ "score": 0-1, "reasoning": "..." }}

                Guidelines: {evalConfig.Guidelines}

                Content: {content}
                """;

            var messages = new List<ChatMessage>
            {
                new(ChatRole.User, prompt)
            };

            var response = await _chatService.GetChatResponseAsync(
                chat => chat.WithAlias("guardrail-evaluation"),
                messages,
                cancellationToken);

            // Parse the score from the response
            // (simplified - add robust JSON parsing in production)
            var text = response.Message.Text ?? string.Empty;
            var score = ParseScore(text);
            var flagged = score < evalConfig.Threshold;

            return new AIGuardrailResult
            {
                EvaluatorId = Id,
                Flagged = flagged,
                Score = score,
                Reason = flagged
                    ? $"Brand compliance score {score:F2} below threshold {evalConfig.Threshold:F2}"
                    : null
            };
        }
        finally
        {
            _runtimeContextAccessor.Context?.SetValue(
                Constants.ContextKeys.IsGuardrailEvaluation, false);
        }
    }

    private static double ParseScore(string text)
    {
        // Extract score from JSON response
        var start = text.IndexOf('{');
        var end = text.LastIndexOf('}');
        if (start >= 0 && end > start)
        {
            var json = text.Substring(start, end - start + 1);
            using var doc = JsonDocument.Parse(json);
            if (doc.RootElement.TryGetProperty("score", out var scoreEl))
            {
                return scoreEl.GetDouble();
            }
        }
        return 0.0;
    }
}
```

{% endcode %}

{% hint style="warning" %}
Model-based evaluators must set the `IsGuardrailEvaluation` context flag before making AI calls. The flag prevents the guardrail middleware from evaluating the evaluator's own requests, which would cause infinite recursion.
{% endhint %}

## Evaluator Registration

Evaluators are auto-discovered via the `[AIGuardrailEvaluator]` attribute and `IDiscoverable` interface. No manual registration is needed.

To manually add or exclude evaluators in a Composer:

{% code title="GuardrailComposer.cs" %}

```csharp
public class GuardrailComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AIGuardrailEvaluators()
            .Add<ProfanityFilterEvaluator>()
            .Exclude<ContainsGuardrailEvaluator>();
    }
}
```

{% endcode %}

## Requiring a Capability

If your evaluator depends on an experimental capability, such as Decision, mark it with `[AIRequiresCapability]`. The attribute hides the evaluator from the evaluator listing endpoint while that capability is disabled. You can apply it more than once when an evaluator needs more than one capability. The same attribute works on custom test graders.

{% hint style="warning" %}
`[AIRequiresCapability]` only hides the evaluator from the listing. It does not stop the evaluator from running. A saved rule that already references the evaluator by ID still runs it. Check `IAIExperimentalFeatures.IsCapabilityEnabled` in `EvaluateAsync` yourself, and return a flagged result when the capability is off.
{% endhint %}

{% code title="SpamCheckEvaluator.cs" %}

```csharp
#pragma warning disable UMBRACOAI_DECISION

using Microsoft.Extensions.AI;
using Umbraco.AI.Core.Decision;
using Umbraco.AI.Core.Guardrails.Evaluators;
using Umbraco.AI.Core.Models;
using Umbraco.AI.Core.Settings;

public class SpamCheckConfig
{
}

[AIGuardrailEvaluator("spam-check", "Spam Check",
    Type = AIGuardrailEvaluatorType.ModelBased)]
[AIRequiresCapability(AICapability.Decision)]
public class SpamCheckEvaluator : AIGuardrailEvaluatorBase<SpamCheckConfig>
{
    private readonly IAIDecisionService _decisionService;
    private readonly IAIExperimentalFeatures _experimentalFeatures;

    public override string Description => "Flags content that looks like spam";

    public SpamCheckEvaluator(
        IAIDecisionService decisionService,
        IAIExperimentalFeatures experimentalFeatures,
        IAIGuardrailEvaluatorInfrastructure infrastructure)
        : base(infrastructure)
    {
        _decisionService = decisionService;
        _experimentalFeatures = experimentalFeatures;
    }

    public override async Task<AIGuardrailResult> EvaluateAsync(
        string content,
        IReadOnlyList<ChatMessage> conversationHistory,
        AIGuardrailConfig config,
        CancellationToken cancellationToken)
    {
        // The attribute does not stop execution, so fail safe here
        if (!_experimentalFeatures.IsCapabilityEnabled(AICapability.Decision))
        {
            return new AIGuardrailResult
            {
                EvaluatorId = Id,
                Flagged = true,
                Score = 0,
                Reason = "Decision is turned off, so this rule can't run."
            };
        }

        // The content to judge goes in the state, not on the question
        AIDecisionResponse<AIBinaryDecisionAnswer> response = await _decisionService.AskAsync(
            new AIBinaryDecisionQuestion
            {
                Instructions = "Is this content spam?"
            },
            state: content,
            cancellationToken: cancellationToken);

        var isSpam = response.Answer.IsTrue(0.7);

        return new AIGuardrailResult
        {
            EvaluatorId = Id,
            Flagged = isSpam,
            Score = response.Answer.TrueProbability,
            Reason = isSpam ? "Content looks like spam" : null
        };
    }
}
```

{% endcode %}

## AIGuardrailResult

Every evaluator returns an `AIGuardrailResult`:

| Property      | Type           | Description                                                           |
| ------------- | -------------- | --------------------------------------------------------------------- |
| `EvaluatorId` | `string`       | ID of the evaluator that produced this result                         |
| `Flagged`     | `bool`         | Whether the content was flagged                                       |
| `Score`       | `double?`      | Confidence score (0-1). Code-based evaluators typically return 0 or 1 |
| `Reason`      | `string?`      | Human-readable explanation                                            |
| `Metadata`    | `JsonElement?` | Evaluator-specific metadata (matched patterns, LLM reasoning, etc.)   |

## Built-in Evaluators

| ID               | Name                  | Type        | Description                                                                                                                                                                                                      |
| ---------------- | --------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contains`       | Contains              | Code-based  | Flags content containing a specific substring                                                                                                                                                                    |
| `regex`          | Regex Match           | Code-based  | Flags content matching a regular expression pattern                                                                                                                                                              |
| `llm-judge`      | LLM Safety Judge      | Model-based | Uses an AI model to evaluate content for safety and compliance                                                                                                                                                   |
| `decision-judge` | Decision Safety Judge | Model-based | Uses a calibrated AI decision to judge content for safety and compliance (experimental). See [Decision Safety Judge Evaluator](/ai-in-umbraco/backoffice/managing-guardrails.md#decision-safety-judge-evaluator) |

## Related

* [Guardrails Concept](/ai-in-umbraco/concepts/guardrails.md) - Understanding guardrails
* [Managing Guardrails](/ai-in-umbraco/backoffice/managing-guardrails.md) - Backoffice guide
* [Custom Providers](/ai-in-umbraco/extending/providers.md) - Creating AI providers


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.umbraco.com/ai-in-umbraco/extending/guardrails.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
