CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core

Core runtime module for Quarkus LangChain4j integration with declarative AI services, guardrails, and observability

Overview
Eval results
Files

guardrails.mddocs/

Guardrails

The guardrails framework provides comprehensive validation and transformation capabilities for tool inputs and outputs. Guardrails execute before and after tool execution to enforce business rules, security policies, data quality standards, and compliance requirements.

Core Imports

import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrails;
import io.quarkiverse.langchain4j.guardrails.ToolOutputGuardrails;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrail;
import io.quarkiverse.langchain4j.guardrails.ToolOutputGuardrail;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrailRequest;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrailResult;
import io.quarkiverse.langchain4j.guardrails.ToolOutputGuardrailRequest;
import io.quarkiverse.langchain4j.guardrails.ToolOutputGuardrailResult;
import io.quarkiverse.langchain4j.guardrails.ToolInvocationContext;
import io.quarkiverse.langchain4j.guardrails.ToolMetadata;
import io.quarkiverse.langchain4j.guardrails.ToolGuardrailException;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrailAccumulator;
import io.quarkiverse.langchain4j.guardrails.OutputTokenAccumulator;

Annotations

@ToolInputGuardrails

package io.quarkiverse.langchain4j.guardrails;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ToolInputGuardrails {
    Class<? extends ToolInputGuardrail>[] value();
}

Specifies guardrails to execute before tool execution. Guardrails are executed in array order with fail-fast semantics. All implementations must be CDI beans.

@ToolOutputGuardrails

package io.quarkiverse.langchain4j.guardrails;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ToolOutputGuardrails {
    Class<? extends ToolOutputGuardrail>[] value();
}

Specifies guardrails to execute after tool execution. Guardrails are executed in array order with fail-fast semantics. All implementations must be CDI beans.

@OutputGuardrailAccumulator

package io.quarkiverse.langchain4j.guardrails;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OutputGuardrailAccumulator {
    Class<? extends OutputTokenAccumulator> value();
}

Configures token accumulation when output guardrails are applied on streamed responses. The accumulator is invoked before guardrails are applied to streaming model output, allowing control over how tokens are grouped before validation.

Behavior:

  • Applied to AI service methods that return streaming responses (Multi<String>)
  • The accumulator transforms the token stream before guardrails execute
  • Guardrails are invoked for each item emitted by the accumulator's Multi
  • If not specified, all tokens accumulate before guardrails execute (default behavior)
  • The accumulator class must be a CDI bean implementing OutputTokenAccumulator
  • If the accumulator's Multi emits an error, guardrails are not called and the error propagates
  • When the accumulator's Multi completes, guardrails are called with any remaining accumulated tokens

Usage Example:

import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.guardrails.ToolOutputGuardrails;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrailAccumulator;
import io.smallrye.mutiny.Multi;

@RegisterAiService
public interface StreamingAssistant {

    @ToolOutputGuardrails(ContentFilterGuardrail.class)
    @OutputGuardrailAccumulator(SentenceAccumulator.class)
    Multi<String> streamResponse(String prompt);
}

@ApplicationScoped
public class SentenceAccumulator implements OutputTokenAccumulator {
    @Override
    public Multi<String> accumulate(Multi<String> tokens) {
        // Accumulate tokens into complete sentences before applying guardrails
        StringBuilder buffer = new StringBuilder();
        return tokens.onItem().transform(token -> {
            buffer.append(token);
            String text = buffer.toString();
            if (text.endsWith(".") || text.endsWith("!") || text.endsWith("?")) {
                String result = text;
                buffer.setLength(0);
                return result;
            }
            return null; // Don't emit yet, continue accumulating
        }).select().nonNull();
    }
}

Interfaces

ToolInputGuardrail

package io.quarkiverse.langchain4j.guardrails;

public interface ToolInputGuardrail 
    extends ToolGuardrail<ToolInputGuardrailRequest, ToolInputGuardrailResult> {
    
    ToolInputGuardrailResult validate(ToolInputGuardrailRequest request);
}

Validates tool input parameters before execution. Implementations should be CDI beans.

Use Cases:

  • Input parameter validation (format, range, business rules)
  • Authorization checks (permissions, rate limiting)
  • Input sanitization or transformation
  • Security validations (injection prevention, access control)

OutputTokenAccumulator

package io.quarkiverse.langchain4j.guardrails;

import io.smallrye.mutiny.Multi;

public interface OutputTokenAccumulator {
    /**
     * Accumulate tokens before applying the guardrails.
     * The guardrails are invoked for each item emitted by the produced Multi.
     *
     * If the returned Multi emits an error, the guardrail chain is not called and the error is propagated.
     * If the returned Multi completes, the guardrail chain is called with the remaining accumulated tokens.
     *
     * @param tokens the input token stream
     * @return the Multi producing the accumulated tokens
     */
    Multi<String> accumulate(Multi<String> tokens);
}

Interface for accumulating tokens when output guardrails are applied on streamed responses. Implementations must be CDI beans and are selected via the @OutputGuardrailAccumulator annotation.

Use Cases:

  • Accumulate by sentence boundaries (wait for '.', '!', '?')
  • Accumulate by paragraph (wait for double newlines)
  • Accumulate by word count (emit every N tokens)
  • Accumulate by time window (emit every N milliseconds)
  • Accumulate by semantic units (wait for complete thoughts)

Example - Sentence Boundary Accumulator:

import io.quarkiverse.langchain4j.guardrails.OutputTokenAccumulator;
import io.smallrye.mutiny.Multi;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class SentenceAccumulator implements OutputTokenAccumulator {
    @Override
    public Multi<String> accumulate(Multi<String> tokens) {
        StringBuilder buffer = new StringBuilder();
        return tokens.onItem().transform(token -> {
            buffer.append(token);
            String text = buffer.toString();
            // Emit when we have a complete sentence
            if (text.matches(".*[.!?]\\s*$")) {
                String result = text;
                buffer.setLength(0);
                return result;
            }
            return null; // Continue accumulating
        }).select().nonNull();
    }
}

Example - Word Count Accumulator:

@ApplicationScoped
public class WordCountAccumulator implements OutputTokenAccumulator {
    private static final int WORDS_PER_CHUNK = 50;

    @Override
    public Multi<String> accumulate(Multi<String> tokens) {
        List<String> buffer = new ArrayList<>();
        return tokens.onItem().transform(token -> {
            buffer.add(token);
            String combined = String.join("", buffer);
            long wordCount = Arrays.stream(combined.split("\\s+")).count();

            if (wordCount >= WORDS_PER_CHUNK) {
                String result = combined;
                buffer.clear();
                return result;
            }
            return null; // Continue accumulating
        }).select().nonNull()
        .onCompletion().ifNotEmpty().invoke(() -> {
            // Emit remaining tokens on completion
            if (!buffer.isEmpty()) {
                return String.join("", buffer);
            }
        });
    }
}

ToolOutputGuardrail

package io.quarkiverse.langchain4j.guardrails;

public interface ToolOutputGuardrail 
    extends ToolGuardrail<ToolOutputGuardrailRequest, ToolOutputGuardrailResult> {
    
    ToolOutputGuardrailResult validate(ToolOutputGuardrailRequest request);
}

Validates and transforms tool output after execution. Implementations should be CDI beans.

Use Cases:

  • Output validation (format, content, size constraints)
  • Sensitive data filtering (PII redaction, data masking)
  • Result transformation (truncation, summarization)
  • Compliance checks (data privacy, content policies)

ToolGuardrail (Base)

package io.quarkiverse.langchain4j.guardrails;

public interface ToolGuardrail<P extends ToolGuardrailRequest<P>, 
                                R extends ToolGuardrailResult<R>> {
    
    R validate(P request);
}

Base interface for all tool guardrails. Guardrails use synchronous/blocking APIs and cannot execute on Vert.x event loop.

Request Types

ToolInputGuardrailRequest

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.agent.tool.ToolExecutionRequest;
import io.vertx.core.json.JsonObject;

public record ToolInputGuardrailRequest(
    ToolExecutionRequest executionRequest,
    ToolMetadata toolMetadata,
    ToolInvocationContext invocationContext
) implements ToolGuardrailRequest<ToolInputGuardrailRequest> {
    
    // Convenience methods
    public String toolName();
    public String arguments();
    public Object memoryId();
    public JsonObject argumentsAsJson();
}

Components:

  • executionRequest: Tool execution request from LLM (required)
  • toolMetadata: Tool metadata (optional)
  • invocationContext: Invocation context (optional)

Validation: Constructor throws IllegalArgumentException if executionRequest is null.

ToolOutputGuardrailRequest

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.service.tool.ToolExecutionResult;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
import io.vertx.core.json.JsonObject;

public record ToolOutputGuardrailRequest(
    ToolExecutionResult executionResult,
    ToolExecutionRequest executionRequest,
    ToolMetadata toolMetadata,
    ToolInvocationContext invocationContext
) implements ToolGuardrailRequest<ToolOutputGuardrailRequest> {
    
    // Convenience methods
    public String toolName();
    public String resultText();
    public boolean isError();
    public Object memoryId();
    public JsonObject argumentsAsJson();
}

Components:

  • executionResult: Tool execution result (required)
  • executionRequest: Original tool request (optional)
  • toolMetadata: Tool metadata (optional)
  • invocationContext: Invocation context (optional)

Validation: Constructor throws IllegalArgumentException if executionResult is null.

ToolGuardrailRequest (Sealed)

package io.quarkiverse.langchain4j.guardrails;

public sealed interface ToolGuardrailRequest<T extends ToolGuardrailRequest<T>>
    permits ToolInputGuardrailRequest, ToolOutputGuardrailRequest {
}

Base for all guardrail request objects. Request objects are immutable value objects.

Result Types

ToolInputGuardrailResult

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.agent.tool.ToolExecutionRequest;

public record ToolInputGuardrailResult(
    boolean isSuccess,
    String errorMessage,
    Throwable cause,
    boolean isFatalFailure,
    ToolExecutionRequest modifiedRequest
) implements ToolGuardrailResult<ToolInputGuardrailResult> {
    
    // Factory methods
    public static ToolInputGuardrailResult success();
    
    public static ToolInputGuardrailResult successWith(ToolExecutionRequest modifiedRequest);
    
    public static ToolInputGuardrailResult failure(String errorMessage);
    
    public static ToolInputGuardrailResult fatal(Throwable failure);
    
    public static ToolInputGuardrailResult fatal(String errorMessage, Throwable cause);
}

Components:

  • isSuccess: Whether validation passed
  • errorMessage: Error message (null if success)
  • cause: Exception cause (null unless fatal)
  • isFatalFailure: Whether failure is fatal (throws exception)
  • modifiedRequest: Modified request (null unless modified)

ToolOutputGuardrailResult

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.service.tool.ToolExecutionResult;

public record ToolOutputGuardrailResult(
    boolean isSuccess,
    String errorMessage,
    Throwable cause,
    boolean isFatalFailure,
    ToolExecutionResult modifiedResult
) implements ToolGuardrailResult<ToolOutputGuardrailResult> {
    
    // Factory methods
    public static ToolOutputGuardrailResult success();
    
    public static ToolOutputGuardrailResult successWith(ToolExecutionResult modifiedResult);
    
    public static ToolOutputGuardrailResult failure(String errorMessage);
    
    public static ToolOutputGuardrailResult fatal(Throwable failure);
    
    public static ToolOutputGuardrailResult fatal(String errorMessage, Throwable cause);
}

Components:

  • isSuccess: Whether validation passed
  • errorMessage: Error message (null if success)
  • cause: Exception cause (null unless fatal)
  • isFatalFailure: Whether failure is fatal (throws exception)
  • modifiedResult: Modified result (null unless modified)

ToolGuardrailResult (Sealed)

package io.quarkiverse.langchain4j.guardrails;

public sealed interface ToolGuardrailResult<T extends ToolGuardrailResult<T>>
    permits ToolInputGuardrailResult, ToolOutputGuardrailResult {
}

Base for all guardrail validation results. Result objects are immutable.

Result Outcomes:

  1. Success - Continue with original request/result
  2. Success with Modification - Continue with modified request/result
  3. Non-Fatal Failure - Error returned to LLM, conversation continues
  4. Fatal Failure - Exception thrown, execution stops

Context Types

ToolInvocationContext

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.service.tool.InvocationContext;
import java.util.Map;

public record ToolInvocationContext(
    InvocationContext context
) {
    public InvocationContext context();
    public Object parameter(String key);
    public Object memoryId();
    public Map<String, Object> parameters();
    public boolean hasParameter(String key);
}

Components:

  • context: Underlying LangChain4j invocation context (required)

Common Parameter Keys:

  • "user": User ID or principal
  • "session": Session ID
  • "tenant": Tenant ID for multi-tenant apps
  • "requestId": Request correlation ID

Validation: Constructor throws IllegalArgumentException if context is null.

ToolMetadata

package io.quarkiverse.langchain4j.guardrails;

import dev.langchain4j.agent.tool.ToolSpecification;

public record ToolMetadata(
    ToolSpecification specification,
    Object toolInstance
) {
    public String toolName();
    public String description();
}

Components:

  • specification: Tool specification from LangChain4j (required)
  • toolInstance: Actual tool instance (optional)

Validation: Constructor throws IllegalArgumentException if specification is null.

Exception Types

ToolGuardrailException

package io.quarkiverse.langchain4j.guardrails;

import io.quarkiverse.langchain4j.runtime.PreventsErrorHandlerExecution;

public class ToolGuardrailException extends RuntimeException 
    implements PreventsErrorHandlerExecution {
    
    public ToolGuardrailException(String message);
    
    public ToolGuardrailException(String message, boolean fatal);
    
    public ToolGuardrailException(String message, Throwable cause);
    
    public ToolGuardrailException(String message, Throwable cause, boolean fatal);
    
    public boolean isFatal();
}

Thrown when tool guardrail validation fails critically.

Thrown When:

  • Input guardrail returns fatal failure
  • Output guardrail returns fatal failure
  • Guardrail bean cannot be looked up via CDI

Usage Examples

Basic Input Validation

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrail;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrailRequest;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrailResult;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrails;
import dev.langchain4j.agent.tool.Tool;
import io.vertx.core.json.JsonObject;

@ApplicationScoped
public class EmailValidator implements ToolInputGuardrail {
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
    
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        JsonObject args = request.argumentsAsJson();
        String email = args.getString("to");
        
        if (email == null || email.isEmpty()) {
            return ToolInputGuardrailResult.failure("Email address is required");
        }
        
        if (!EMAIL_PATTERN.matcher(email).matches()) {
            return ToolInputGuardrailResult.failure(
                "Invalid email format: " + email
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

@ApplicationScoped
public class EmailTool {
    @Tool("Send an email to a recipient")
    @ToolInputGuardrails(EmailValidator.class)
    public String sendEmail(String to, String subject, String body) {
        // Implementation
        return "Email sent to " + to;
    }
}

Authorization Guardrail

@ApplicationScoped
public class AdminAuthorizationGuardrail implements ToolInputGuardrail {
    @Inject
    SecurityService security;
    
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        Object memoryId = request.memoryId();
        
        if (!security.isAdmin(memoryId)) {
            return ToolInputGuardrailResult.failure(
                "Administrative privileges required for " + request.toolName()
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

@ApplicationScoped
public class AdminTool {
    @Tool("Delete user account")
    @ToolInputGuardrails(AdminAuthorizationGuardrail.class)
    public String deleteUser(String userId) {
        // Implementation
        return "User " + userId + " deleted";
    }
}

Input Transformation

@ApplicationScoped
public class InputSanitizer implements ToolInputGuardrail {
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        JsonObject args = request.argumentsAsJson();
        String input = args.getString("query");
        
        // Sanitize input
        String sanitized = input
            .replaceAll("<script>", "")
            .replaceAll("javascript:", "")
            .trim();
        
        if (!input.equals(sanitized)) {
            // Create modified request
            ToolExecutionRequest modified = ToolExecutionRequest.builder()
                .name(request.toolName())
                .arguments(JsonObject.of("query", sanitized).encode())
                .build();
            
            return ToolInputGuardrailResult.successWith(modified);
        }
        
        return ToolInputGuardrailResult.success();
    }
}

Output Filtering

@ApplicationScoped
public class PIIFilter implements ToolOutputGuardrail {
    private static final Pattern SSN_PATTERN = 
        Pattern.compile("\\d{3}-\\d{2}-\\d{4}");
    private static final Pattern CREDIT_CARD_PATTERN = 
        Pattern.compile("\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}");
    
    @Override
    public ToolOutputGuardrailResult validate(ToolOutputGuardrailRequest request) {
        String result = request.resultText();
        
        String filtered = result;
        filtered = SSN_PATTERN.matcher(filtered).replaceAll("***-**-****");
        filtered = CREDIT_CARD_PATTERN.matcher(filtered).replaceAll("**** **** **** ****");
        
        if (!result.equals(filtered)) {
            ToolExecutionResult modified = ToolExecutionResult.builder()
                .resultText(filtered)
                .build();
            
            return ToolOutputGuardrailResult.successWith(modified);
        }
        
        return ToolOutputGuardrailResult.success();
    }
}

@ApplicationScoped
public class CustomerDataTool {
    @Tool("Retrieve customer information")
    @ToolOutputGuardrails(PIIFilter.class)
    public String getCustomerInfo(String customerId) {
        // Implementation may return PII
        return database.getCustomer(customerId).toString();
    }
}

Multiple Guardrails

@ApplicationScoped
public class RateLimitGuardrail implements ToolInputGuardrail {
    @Inject
    RateLimiter rateLimiter;
    
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        Object memoryId = request.memoryId();
        
        if (!rateLimiter.allowRequest(memoryId, request.toolName())) {
            return ToolInputGuardrailResult.failure(
                "Rate limit exceeded. Please try again later."
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

@ApplicationScoped
public class CostLimitGuardrail implements ToolInputGuardrail {
    @Inject
    BillingService billing;
    
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        Object memoryId = request.memoryId();
        
        if (billing.hasExceededBudget(memoryId)) {
            return ToolInputGuardrailResult.failure(
                "Monthly budget exceeded. Please upgrade your plan."
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

@ApplicationScoped
public class ExpensiveTool {
    @Tool("Run expensive computation")
    @ToolInputGuardrails({ RateLimitGuardrail.class, CostLimitGuardrail.class })
    public String compute(String input) {
        // Expensive operation
        return result;
    }
}

Fatal Failure Example

@ApplicationScoped
public class DataValidationGuardrail implements ToolInputGuardrail {
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        JsonObject args = request.argumentsAsJson();
        String data = args.getString("data");
        
        try {
            validateDataIntegrity(data);
            return ToolInputGuardrailResult.success();
        } catch (DataCorruptionException e) {
            // Fatal error - stop execution immediately
            return ToolInputGuardrailResult.fatal(
                "Data integrity violation detected",
                e
            );
        } catch (InvalidFormatException e) {
            // Non-fatal - let AI retry
            return ToolInputGuardrailResult.failure(
                "Invalid data format: " + e.getMessage()
            );
        }
    }
}

Output Size Limiting

@ApplicationScoped
public class OutputSizeLimiter implements ToolOutputGuardrail {
    private static final int MAX_LENGTH = 1000;
    
    @Override
    public ToolOutputGuardrailResult validate(ToolOutputGuardrailRequest request) {
        String result = request.resultText();
        
        if (result.length() > MAX_LENGTH) {
            String truncated = result.substring(0, MAX_LENGTH) + "... (truncated)";
            
            ToolExecutionResult modified = ToolExecutionResult.builder()
                .resultText(truncated)
                .build();
            
            return ToolOutputGuardrailResult.successWith(modified);
        }
        
        return ToolOutputGuardrailResult.success();
    }
}

@ApplicationScoped
public class DocumentTool {
    @Tool("Fetch document content")
    @ToolOutputGuardrails(OutputSizeLimiter.class)
    public String getDocument(String documentId) {
        return documentService.load(documentId);
    }
}

Using Context Parameters

@ApplicationScoped
public class TenantIsolationGuardrail implements ToolInputGuardrail {
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        ToolInvocationContext context = request.invocationContext();
        
        if (context == null || !context.hasParameter("tenant")) {
            return ToolInputGuardrailResult.fatal(
                new IllegalStateException("Tenant context required")
            );
        }
        
        String tenant = (String) context.parameter("tenant");
        JsonObject args = request.argumentsAsJson();
        String resourceId = args.getString("resourceId");
        
        if (!resourceBelongsToTenant(resourceId, tenant)) {
            return ToolInputGuardrailResult.failure(
                "Access denied: Resource does not belong to your tenant"
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

Comprehensive Example

@ApplicationScoped
public class PaymentInputGuardrail implements ToolInputGuardrail {
    @Inject
    SecurityService security;
    
    @Inject
    FraudDetectionService fraud;
    
    @Override
    public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
        JsonObject args = request.argumentsAsJson();
        
        // Validate amount
        Double amount = args.getDouble("amount");
        if (amount == null || amount <= 0) {
            return ToolInputGuardrailResult.failure(
                "Invalid amount. Must be a positive number."
            );
        }
        
        if (amount > 10000) {
            return ToolInputGuardrailResult.failure(
                "Amount exceeds maximum limit of $10,000"
            );
        }
        
        // Validate account
        String accountNumber = args.getString("accountNumber");
        if (!isValidAccountFormat(accountNumber)) {
            return ToolInputGuardrailResult.failure(
                "Invalid account number format"
            );
        }
        
        // Check authorization
        Object userId = request.memoryId();
        if (!security.canMakePayment(userId, accountNumber)) {
            return ToolInputGuardrailResult.failure(
                "You are not authorized to make payments from this account"
            );
        }
        
        // Fraud detection
        if (fraud.isSuspicious(userId, amount, accountNumber)) {
            return ToolInputGuardrailResult.fatal(
                "Suspicious activity detected",
                new FraudException("Payment blocked by fraud detection")
            );
        }
        
        return ToolInputGuardrailResult.success();
    }
}

@ApplicationScoped
public class PaymentOutputGuardrail implements ToolOutputGuardrail {
    @Override
    public ToolOutputGuardrailResult validate(ToolOutputGuardrailRequest request) {
        if (request.isError()) {
            // Don't expose internal error details
            String sanitized = "Payment processing failed. Please contact support.";
            
            ToolExecutionResult modified = ToolExecutionResult.builder()
                .resultText(sanitized)
                .build();
            
            return ToolOutputGuardrailResult.successWith(modified);
        }
        
        // Remove sensitive transaction IDs from output
        String result = request.resultText();
        String filtered = result.replaceAll("txn_\\w+", "txn_*****");
        
        if (!result.equals(filtered)) {
            ToolExecutionResult modified = ToolExecutionResult.builder()
                .resultText(filtered)
                .build();
            
            return ToolOutputGuardrailResult.successWith(modified);
        }
        
        return ToolOutputGuardrailResult.success();
    }
}

@ApplicationScoped
public class PaymentTool {
    @Tool("Process a payment from an account")
    @ToolInputGuardrails(PaymentInputGuardrail.class)
    @ToolOutputGuardrails(PaymentOutputGuardrail.class)
    public String processPayment(String accountNumber, double amount, String description) {
        // Implementation
        return paymentService.process(accountNumber, amount, description);
    }
}

Execution Flow

1. AI Service Method Invocation
   ↓
2. Input Guardrails (in array order, fail-fast)
   → Validation → [Success/Failure/Modify/Fatal]
   ↓
3. Tool Execution (if guardrails pass)
   ↓
4. Output Guardrails (in array order, fail-fast)
   → Validation → [Success/Failure/Modify/Fatal]
   ↓
5. Return to LLM

Important Notes

  • Thread Safety: Guardrails use synchronous/blocking APIs
  • Event Loop: Cannot execute on Vert.x event loop - tools with guardrails must be blocking
  • CDI Required: All guardrail implementations must be CDI beans
  • Fail-Fast: First failure stops the chain
  • Immutability: Request and result objects are immutable
  • Fatal vs Non-Fatal: Fatal failures throw exceptions; non-fatal failures return errors to LLM

Install with Tessl CLI

npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core@1.7.0

docs

ai-services.md

authentication.md

cost-estimation.md

guardrails.md

index.md

media-content.md

memory.md

observability.md

response-augmentation.md

tools.md

tile.json