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
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.
Base interface for all guardrails.
interface GuardRailValidate 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) }
}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)
}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
)
}
}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)
}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)
}
}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)
}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)
}
}
}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()
}
}
}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 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")
}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()
}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@0.3.0docs