Core runtime module for Quarkus LangChain4j integration with declarative AI services, guardrails, and observability
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.
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;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.
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.
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:
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();
}
}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:
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:
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);
}
});
}
}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:
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.
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.
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.
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.
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 passederrorMessage: Error message (null if success)cause: Exception cause (null unless fatal)isFatalFailure: Whether failure is fatal (throws exception)modifiedRequest: Modified request (null unless modified)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 passederrorMessage: Error message (null if success)cause: Exception cause (null unless fatal)isFatalFailure: Whether failure is fatal (throws exception)modifiedResult: Modified result (null unless modified)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:
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 IDValidation: Constructor throws IllegalArgumentException if context is null.
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.
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:
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;
}
}@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";
}
}@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();
}
}@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();
}
}@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;
}
}@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()
);
}
}
}@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);
}
}@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();
}
}@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);
}
}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 LLMInstall with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core@1.7.0