Skip to content

Response Validation & Guardrails

Arian Amiramjadi edited this page Dec 24, 2025 · 1 revision

Response Validation & Guardrails

Validate AI responses with built-in and custom validators.

Quick Start

// Ensure response isn't empty and has reasonable length
response, err := ai.Claude().
    NoEmptyResponse().
    MaxLength(1000).
    Ask("Explain Go concurrency")

Built-in Validators

Length Constraints

ai.Claude().
    MinLength(50).      // At least 50 characters
    MaxLength(500).     // At most 500 characters
    Ask("...")

Word Count

ai.Claude().
    WordCount(10, 100). // Between 10-100 words
    Ask("...")

Content Requirements

ai.Claude().
    MustContain("conclusion").     // Must include this text
    MustNotContain("I don't know"). // Must not include this
    Ask("...")

Pattern Matching

ai.Claude().
    MustMatch(`\d{4}-\d{2}-\d{2}`).    // Must contain date
    MustNotMatch(`(?i)error|failed`).  // No error messages
    Ask("...")

JSON Validation

// Ensure valid JSON
ai.Claude().
    JSON().
    MustBeJSON().
    Ask("Return JSON with name and age")

// Validate against schema (struct)
type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
ai.Claude().
    MustBeJSONSchema(&Person{}).
    Ask("Return person data")

Empty Response Check

ai.Claude().
    NoEmptyResponse().
    Ask("...")

Preset Guardrails

Safe Content

Blocks common XSS/injection patterns:

ai.Claude().
    SafeContent().
    Ask("Generate HTML snippet")

Concise Response

Limits response length:

ai.Claude().
    ConciseResponse(200).  // Max 200 words
    Ask("...")

Strict JSON

Combines JSON mode with validation:

ai.Claude().
    StrictJSON().
    Ask("Return structured data")

Custom Validators

Function Validator

ai.Claude().
    ValidateWith("no-profanity", func(content string) error {
        badWords := []string{"bad", "words"}
        for _, word := range badWords {
            if strings.Contains(content, word) {
                return fmt.Errorf("contains prohibited word: %s", word)
            }
        }
        return nil
    }).
    Ask("...")

Interface Validator

type SentimentValidator struct {
    requiredSentiment string
}

func (v *SentimentValidator) Name() string { return "SentimentCheck" }

func (v *SentimentValidator) Validate(content string) error {
    // Check sentiment...
    if !hasSentiment(content, v.requiredSentiment) {
        return &ai.ValidationError{
            Validator: v.Name(),
            Message:   "wrong sentiment",
            Content:   content,
        }
    }
    return nil
}

ai.Claude().
    Validate(&SentimentValidator{requiredSentiment: "positive"}).
    Ask("...")

Composite Validators

All Must Pass

lengthValidator := &ai.MinLengthValidator{MinChars: 100}
jsonValidator := &ai.JSONValidator{}

ai.Claude().
    Validate(ai.AllOf("length-and-json", lengthValidator, jsonValidator)).
    Ask("...")

Any Can Pass

ai.Claude().
    Validate(ai.AnyOf("flexible",
        &containsValidator{substr: "yes"},
        &containsValidator{substr: "agreed"},
    )).
    Ask("Do you agree?")

Validation Errors

response, err := ai.Claude().
    MaxLength(10).
    Ask("Tell me a long story")

if err != nil {
    if ve, ok := err.(*ai.ValidationError); ok {
        fmt.Printf("Validation failed: %s\n", ve.Validator)
        fmt.Printf("Message: %s\n", ve.Message)
        // Content is still available
        fmt.Printf("Content was: %s\n", ve.Content[:100])
    }
}

Content Filters

Transform or filter content after response:

// Trim whitespace
ai.Claude().
    WithFilter(ai.TrimFilter()).
    Ask("...")

// Truncate if too long
ai.Claude().
    WithFilter(ai.MaxLengthFilter(500)).
    Ask("...")

// Custom filter
ai.Claude().
    WithFilter(func(content string) (string, error) {
        // Remove markdown
        cleaned := stripMarkdown(content)
        return cleaned, nil
    }).
    Ask("...")

Chaining Validators

Validators run in order; first failure stops:

ai.Claude().
    NoEmptyResponse().      // Check 1: not empty
    MinLength(50).          // Check 2: long enough
    MaxLength(1000).        // Check 3: not too long
    MustContain("summary"). // Check 4: has summary
    MustBeJSON().           // Check 5: valid JSON
    Ask("...")

Clear Validators

builder := ai.Claude().
    MaxLength(100).
    MustContain("hello")

// Remove all validators
builder.ClearValidators()

Debug Mode

See validation results:

ai.Debug = true

ai.Claude().
    NoEmptyResponse().
    MaxLength(100).
    Ask("Hello")

// Output:
// ✓ Validation passed [NoEmptyResponse]
// ✓ Validation passed [MaxLength]

Use Cases

API Response Validation

type APIResponse struct {
    Status string `json:"status"`
    Data   any    `json:"data"`
}

response, err := ai.Claude().
    StrictJSON().
    MustBeJSONSchema(&APIResponse{}).
    Ask("Generate API response for user profile")

Content Moderation

response, err := ai.Claude().
    SafeContent().
    MustNotContain("confidential").
    MustNotMatch(`(?i)password|secret|api.?key`).
    ValidateWith("length-check", func(s string) error {
        if len(s) > 10000 {
            return errors.New("response too long")
        }
        return nil
    }).
    Ask("Generate documentation")

Structured Extraction

type Entity struct {
    Name  string   `json:"name"`
    Type  string   `json:"type"`
    Tags  []string `json:"tags"`
}

ai.Claude().
    StrictJSON().
    MustBeJSONSchema(&Entity{}).
    NoEmptyResponse().
    Ask("Extract entity from: Apple Inc. is a technology company")

Clone this wiki locally