CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-api

Fluent DSL and Kotlin DSL for building autonomous agents with planning capabilities on the JVM, featuring annotation-based and programmatic configuration for agentic flows with Spring Boot integration

Overview
Eval results
Files

validation.mddocs/

Validation and Guardrails

Validation and guardrails protect agents from producing harmful or incorrect outputs. The framework supports content validation and guardrails for both user input and assistant responses.

Capabilities

GuardRail Interface

Base interface for all guardrails.

interface GuardRail

UserInputGuardRail

Validate user input before processing.

interface UserInputGuardRail : GuardRail {
    fun validate(input: String): ValidationResult
}

User Input Guardrail Example:

class ProfanityFilter : UserInputGuardRail {
    private val profanityList = loadProfanityList()

    override fun validate(input: String): ValidationResult {
        val foundProfanity = profanityList.any { word ->
            input.contains(word, ignoreCase = true)
        }

        return if (foundProfanity) {
            ValidationResult.invalid("Input contains inappropriate language")
        } else {
            ValidationResult.valid()
        }
    }
}

class InputLengthGuardRail(
    private val maxLength: Int
) : UserInputGuardRail {
    override fun validate(input: String): ValidationResult {
        return if (input.length > maxLength) {
            ValidationResult.invalid("Input exceeds maximum length of $maxLength characters")
        } else {
            ValidationResult.valid()
        }
    }
}

// Usage
@Action(description = "Process user query")
fun processQuery(query: String, context: ActionContext): Response {
    val guardrails = listOf(
        ProfanityFilter(),
        InputLengthGuardRail(5000)
    )

    return context.promptRunner()
        .withGuardRails(*guardrails.toTypedArray())
        .generateText(query)
        .let { Response(it) }
}

AssistantMessageGuardRail

Validate assistant responses before returning them.

interface AssistantMessageGuardRail : GuardRail {
    fun validate(message: AssistantMessage): ValidationResult
}

Assistant Message Guardrail Example:

class NoPersonalInfoGuardRail : AssistantMessageGuardRail {
    private val emailPattern = Regex("""\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b""")
    private val phonePattern = Regex("""\b\d{3}[-.]?\d{3}[-.]?\d{4}\b""")
    private val ssnPattern = Regex("""\b\d{3}-\d{2}-\d{4}\b""")

    override fun validate(message: AssistantMessage): ValidationResult {
        val content = message.content

        return when {
            emailPattern.containsMatchIn(content) ->
                ValidationResult.invalid("Response contains email address")

            phonePattern.containsMatchIn(content) ->
                ValidationResult.invalid("Response contains phone number")

            ssnPattern.containsMatchIn(content) ->
                ValidationResult.invalid("Response contains SSN")

            else -> ValidationResult.valid()
        }
    }
}

class ResponseQualityGuardRail : AssistantMessageGuardRail {
    override fun validate(message: AssistantMessage): ValidationResult {
        val content = message.content

        return when {
            content.length < 10 ->
                ValidationResult.invalid("Response too short")

            content.contains("I don't know", ignoreCase = true) &&
            content.length < 50 ->
                ValidationResult.invalid("Response lacks helpful information")

            else -> ValidationResult.valid()
        }
    }
}

// Usage
@Action(description = "Generate safe response")
fun generateSafeResponse(prompt: String, context: ActionContext): String {
    return context.promptRunner()
        .withGuardRails(
            NoPersonalInfoGuardRail(),
            ResponseQualityGuardRail()
        )
        .generateText(prompt)
}

GuardRailViolationException

Exception thrown when guardrail validation fails.

class GuardRailViolationException(
    message: String,
    val guardRail: GuardRail,
    cause: Throwable? = null
) : RuntimeException(message, cause)

Exception Handling Example:

@Action(description = "Process with error handling")
fun processWithErrorHandling(
    input: String,
    context: ActionContext
): ProcessResult {
    return try {
        val result = context.promptRunner()
            .withGuardRails(
                ContentSafetyGuardRail(),
                OutputValidityGuardRail()
            )
            .generateText(input)

        ProcessResult.success(result)
    } catch (e: GuardRailViolationException) {
        logger.warn("Guardrail violation: ${e.message}")
        ProcessResult.failed(
            reason = "Content policy violation",
            guardRail = e.guardRail.javaClass.simpleName
        )
    }
}

ContentValidator Interface

Generic content validator for typed validation.

interface ContentValidator<T> {
    fun validate(content: T): ValidationResult
}

Content Validator Example:

class EmailValidator : ContentValidator<String> {
    private val emailRegex = Regex("""^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$""")

    override fun validate(content: String): ValidationResult {
        return if (emailRegex.matches(content)) {
            ValidationResult.valid()
        } else {
            ValidationResult.invalid("Invalid email format")
        }
    }
}

class OrderValidator : ContentValidator<Order> {
    override fun validate(content: Order): ValidationResult {
        val errors = mutableListOf<String>()

        if (content.items.isEmpty()) {
            errors.add("Order must have at least one item")
        }

        if (content.total <= 0) {
            errors.add("Order total must be positive")
        }

        if (content.customerId.isBlank()) {
            errors.add("Customer ID is required")
        }

        return if (errors.isEmpty()) {
            ValidationResult.valid()
        } else {
            ValidationResult.invalid(errors.joinToString("; "))
        }
    }
}

// Usage
@Action(description = "Validate order")
fun validateOrder(order: Order): ValidationResult {
    return OrderValidator().validate(order)
}

ValidationResult

Result of validation operations.

data class ValidationResult(
    val isValid: Boolean,
    val errors: List<String> = emptyList(),
    val warnings: List<String> = emptyList()
) {
    companion object {
        fun valid(): ValidationResult =
            ValidationResult(isValid = true)

        fun invalid(error: String): ValidationResult =
            ValidationResult(isValid = false, errors = listOf(error))

        fun invalid(errors: List<String>): ValidationResult =
            ValidationResult(isValid = false, errors = errors)

        fun withWarnings(warnings: List<String>): ValidationResult =
            ValidationResult(isValid = true, warnings = warnings)
    }
}

LLM-Based Validation

Use LLMs to perform semantic validation.

class SemanticContentGuardRail : AssistantMessageGuardRail {
    override fun validate(message: AssistantMessage): ValidationResult {
        // Use LLM to evaluate content appropriateness
        val evaluation = evaluatorLlm.createObject<ContentEvaluation>("""
            Evaluate this response for:
            - Factual accuracy concerns
            - Potential biases
            - Inappropriate content
            - Professional tone

            Response: ${message.content}
        """)

        return if (evaluation.isAppropriate) {
            ValidationResult.valid()
        } else {
            ValidationResult.invalid(evaluation.issues.joinToString("; "))
        }
    }
}

data class ContentEvaluation(
    val isAppropriate: Boolean,
    val issues: List<String>,
    val confidence: Double
)

@Action(description = "Generate with semantic validation")
fun generateWithSemanticValidation(
    prompt: String,
    context: ActionContext
): String {
    return context.promptRunner()
        .withGuardRails(SemanticContentGuardRail())
        .generateText(prompt)
}

Multi-Layer Validation

Combine multiple validation layers.

class ComprehensiveGuardRail : AssistantMessageGuardRail {
    private val validators = listOf(
        NoPersonalInfoGuardRail(),
        ResponseQualityGuardRail(),
        ProfessionalToneGuardRail(),
        FactualAccuracyGuardRail()
    )

    override fun validate(message: AssistantMessage): ValidationResult {
        val allErrors = mutableListOf<String>()
        val allWarnings = mutableListOf<String>()

        for (validator in validators) {
            val result = validator.validate(message)
            if (!result.isValid) {
                allErrors.addAll(result.errors)
            }
            allWarnings.addAll(result.warnings)
        }

        return if (allErrors.isEmpty()) {
            ValidationResult.withWarnings(allWarnings)
        } else {
            ValidationResult.invalid(allErrors)
        }
    }
}

Domain-Specific Guardrails

Create guardrails for specific domains.

class MedicalContentGuardRail : AssistantMessageGuardRail {
    override fun validate(message: AssistantMessage): ValidationResult {
        val content = message.content

        // Check for medical disclaimers
        val hasMedicalInfo = content.contains("diagnos", ignoreCase = true) ||
                            content.contains("treatment", ignoreCase = true) ||
                            content.contains("medication", ignoreCase = true)

        val hasDisclaimer = content.contains("consult", ignoreCase = true) ||
                           content.contains("medical professional", ignoreCase = true)

        return when {
            hasMedicalInfo && !hasDisclaimer ->
                ValidationResult.invalid(
                    "Medical content must include disclaimer to consult healthcare professional"
                )

            else -> ValidationResult.valid()
        }
    }
}

class FinancialAdviceGuardRail : AssistantMessageGuardRail {
    override fun validate(message: AssistantMessage): ValidationResult {
        val content = message.content

        val hasFinancialAdvice = content.contains("invest", ignoreCase = true) ||
                                content.contains("stock", ignoreCase = true) ||
                                content.contains("financial advice", ignoreCase = true)

        val hasDisclaimer = content.contains("not financial advice", ignoreCase = true) ||
                           content.contains("consult financial advisor", ignoreCase = true)

        return when {
            hasFinancialAdvice && !hasDisclaimer ->
                ValidationResult.invalid(
                    "Financial content must include disclaimer"
                )

            else -> ValidationResult.valid()
        }
    }
}

Conditional Guardrails

Apply guardrails conditionally based on context.

class ConditionalGuardRailApplier(
    private val context: ActionContext
) {
    fun getGuardRails(): List<GuardRail> {
        val guardrails = mutableListOf<GuardRail>()

        // Always apply basic safety
        guardrails.add(BasicSafetyGuardRail())

        // Apply domain-specific guardrails based on context
        val domain = context.get("domain", String::class.java)
        when (domain) {
            "medical" -> guardrails.add(MedicalContentGuardRail())
            "financial" -> guardrails.add(FinancialAdviceGuardRail())
            "legal" -> guardrails.add(LegalContentGuardRail())
        }

        // Apply user-level guardrails
        val userLevel = context.get("user_level", String::class.java)
        if (userLevel == "child") {
            guardrails.add(ChildSafeContentGuardRail())
        }

        return guardrails
    }
}

@Action(description = "Generate with conditional guardrails")
fun generateWithConditionalGuardrails(
    prompt: String,
    context: ActionContext
): String {
    val applier = ConditionalGuardRailApplier(context)
    val guardrails = applier.getGuardRails()

    return context.promptRunner()
        .withGuardRails(*guardrails.toTypedArray())
        .generateText(prompt)
}

Retry with Guardrail Feedback

Retry generation with guardrail feedback.

@Action(description = "Generate with retry on guardrail failure")
fun generateWithRetry(
    prompt: String,
    context: ActionContext
): String {
    var attempts = 0
    val maxAttempts = 3

    while (attempts < maxAttempts) {
        try {
            return context.promptRunner()
                .withGuardRails(
                    ContentSafetyGuardRail(),
                    QualityGuardRail()
                )
                .generateText(prompt)
        } catch (e: GuardRailViolationException) {
            attempts++
            logger.warn("Guardrail violation (attempt $attempts): ${e.message}")

            if (attempts >= maxAttempts) {
                throw IllegalStateException(
                    "Failed to generate acceptable content after $maxAttempts attempts",
                    e
                )
            }

            // Modify prompt to address violation
            val adjustedPrompt = """
                $prompt

                Note: Previous attempt failed validation.
                Issue: ${e.message}
                Please ensure the response meets content guidelines.
            """.trimIndent()
        }
    }

    throw IllegalStateException("Unexpected failure in retry loop")
}

Validation Metrics

Track validation metrics for monitoring.

class ValidationMetricsCollector : AssistantMessageGuardRail {
    private val metrics = mutableMapOf<String, Int>()

    override fun validate(message: AssistantMessage): ValidationResult {
        // Perform actual validation
        val result = actualValidate(message)

        // Collect metrics
        if (result.isValid) {
            incrementMetric("validations_passed")
        } else {
            incrementMetric("validations_failed")
            result.errors.forEach { error ->
                incrementMetric("error_$error")
            }
        }

        return result
    }

    private fun incrementMetric(name: String) {
        metrics[name] = metrics.getOrDefault(name, 0) + 1
    }

    fun getMetrics(): Map<String, Int> = metrics.toMap()
}

Types

interface GuardRail

interface ValidationResult {
    val isValid: Boolean
    val errors: List<String>
    val warnings: List<String>
}

interface AssistantMessage : Message {
    val content: String
    val toolCalls: List<ToolCall>?
}

class GuardRailViolationException(
    message: String,
    val guardRail: GuardRail,
    cause: Throwable? = null
) : RuntimeException(message, cause)

Install with Tessl CLI

npx tessl i tessl/maven-com-embabel-agent--embabel-agent-api

docs

actions-goals.md

agent-definition.md

builtin-tools.md

chat.md

conditions.md

events.md

human-in-the-loop.md

index.md

invocation.md

io-binding.md

llm-interaction.md

models.md

planning-workflows.md

platform-management.md

runtime-context.md

state-management.md

streaming.md

subagents.md

tools.md

type-system.md

typed-operations.md

validation.md

tile.json