Core runtime module for Quarkus LangChain4j integration with declarative AI services, guardrails, and observability
Tools enable AI services to perform actions and access external systems. The Quarkus LangChain4j Core module provides annotations for tool integration, method-level tool configuration, and comprehensive error handling.
import io.quarkiverse.langchain4j.ToolBox;
import io.quarkiverse.langchain4j.HandleToolArgumentError;
import io.quarkiverse.langchain4j.HandleToolExecutionError;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.service.tool.ToolErrorHandlerResult;
import dev.langchain4j.service.tool.ToolErrorContext;package io.quarkiverse.langchain4j;
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 ToolBox {
Class<?>[] value();
}The @ToolBox annotation overrides the service-level tool configuration for a specific method. All specified tool classes must be CDI beans.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.ToolBox;
@RegisterAiService(tools = { EmailTool.class, DatabaseTool.class })
public interface AssistantService {
// Uses service-level tools: EmailTool and DatabaseTool
String help(String request);
// Overrides to use only WebSearchTool for this method
@ToolBox(WebSearchTool.class)
String searchAndRespond(String query);
// Overrides to use no tools for this method
@ToolBox({})
String chatWithoutTools(String message);
}package io.quarkiverse.langchain4j;
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 HandleToolArgumentError {
}Handles ToolArgumentsException thrown during tool invocation (when the AI provides invalid arguments). The annotated method must be:
Throwable and/or ToolErrorContext (in any order)String or ToolErrorHandlerResultThe returned string/result is sent back to the AI model as an error message.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.HandleToolArgumentError;
import dev.langchain4j.service.tool.ToolErrorContext;
import dev.langchain4j.service.tool.ToolErrorHandlerResult;
@RegisterAiService(tools = { EmailTool.class })
public interface AssistantWithErrorHandling {
String chat(String message);
@HandleToolArgumentError
static String handleArgumentError(Throwable error, ToolErrorContext context) {
return "Invalid arguments for tool " + context.toolName() +
": " + error.getMessage() + ". Please try again with correct arguments.";
}
}Using ToolErrorHandlerResult for more control:
@RegisterAiService(tools = { EmailTool.class })
public interface AdvancedAssistant {
String chat(String message);
@HandleToolArgumentError
static ToolErrorHandlerResult handleArgumentError(Throwable error, ToolErrorContext context) {
if (error.getMessage().contains("missing email")) {
return ToolErrorHandlerResult.builder()
.errorMessage("Email address is required. Please provide a valid email.")
.shouldRetry(true)
.build();
}
return ToolErrorHandlerResult.builder()
.errorMessage("Invalid arguments: " + error.getMessage())
.shouldRetry(false)
.build();
}
}package io.quarkiverse.langchain4j;
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 HandleToolExecutionError {
}Handles ToolExecutionException thrown during tool execution (when the tool itself fails). Same signature requirements as @HandleToolArgumentError.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.HandleToolExecutionError;
import dev.langchain4j.service.tool.ToolErrorContext;
@RegisterAiService(tools = { DatabaseTool.class })
public interface DataAssistant {
String query(String request);
@HandleToolExecutionError
static String handleExecutionError(Throwable error, ToolErrorContext context) {
if (error instanceof java.sql.SQLException) {
return "Database is temporarily unavailable. Please try again later.";
}
return "Failed to execute " + context.toolName() + ": " + error.getMessage();
}
}package dev.langchain4j.service.tool;
public interface ToolErrorContext {
String toolName();
String arguments();
Object memoryId();
}Provides context information about the tool error for use in error handlers.
import jakarta.enterprise.context.ApplicationScoped;
import dev.langchain4j.agent.tool.Tool;
@ApplicationScoped
public class EmailTool {
@Tool("Send an email to a recipient with subject and body")
public String sendEmail(String to, String subject, String body) {
// Implementation
emailService.send(to, subject, body);
return "Email sent successfully to " + to;
}
}
@RegisterAiService(tools = { EmailTool.class })
public interface EmailAssistant {
String help(String request);
}@ApplicationScoped
public class EmailTool {
@Tool("Send an email")
public String sendEmail(String to, String subject, String body) {
return "Email sent";
}
}
@ApplicationScoped
public class SearchTool {
@Tool("Search the web")
public String search(String query) {
return "Search results for: " + query;
}
}
@ApplicationScoped
public class DatabaseTool {
@Tool("Query the database")
public String query(String sql) {
return "Query results";
}
}
@RegisterAiService(tools = { EmailTool.class, DatabaseTool.class })
public interface MultiToolAssistant {
// Uses EmailTool and DatabaseTool
String help(String request);
// Uses only SearchTool
@ToolBox(SearchTool.class)
String search(String query);
// Uses all three tools
@ToolBox({ EmailTool.class, DatabaseTool.class, SearchTool.class })
String helpWithSearch(String request);
// Uses no tools
@ToolBox({})
String simpleChat(String message);
}import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.HandleToolArgumentError;
import io.quarkiverse.langchain4j.HandleToolExecutionError;
import dev.langchain4j.service.tool.ToolErrorContext;
import dev.langchain4j.service.tool.ToolErrorHandlerResult;
@RegisterAiService(tools = { PaymentTool.class, EmailTool.class })
public interface PaymentAssistant {
String processRequest(String request);
@HandleToolArgumentError
static ToolErrorHandlerResult handleArgumentError(Throwable error, ToolErrorContext context) {
String toolName = context.toolName();
String message = error.getMessage();
if (toolName.equals("processPayment")) {
if (message.contains("amount")) {
return ToolErrorHandlerResult.builder()
.errorMessage("Invalid payment amount. Please provide a positive number.")
.shouldRetry(true)
.build();
}
if (message.contains("account")) {
return ToolErrorHandlerResult.builder()
.errorMessage("Invalid account number format. Use format: XXXX-XXXX-XXXX")
.shouldRetry(true)
.build();
}
}
return ToolErrorHandlerResult.builder()
.errorMessage("Invalid arguments for " + toolName + ": " + message)
.shouldRetry(false)
.build();
}
@HandleToolExecutionError
static String handleExecutionError(Throwable error, ToolErrorContext context) {
String toolName = context.toolName();
if (toolName.equals("processPayment")) {
if (error instanceof InsufficientFundsException) {
return "Payment failed: Insufficient funds in account.";
}
if (error instanceof AccountLockedException) {
return "Payment failed: Account is locked. Please contact support.";
}
return "Payment processing failed: " + error.getMessage();
}
if (toolName.equals("sendEmail")) {
return "Failed to send notification email. Payment was processed successfully.";
}
return "Tool execution failed: " + error.getMessage();
}
}@RegisterAiService(tools = { WeatherTool.class })
public interface WeatherAssistant {
String getWeather(String location);
@HandleToolExecutionError
static String handleError(Throwable error) {
if (error instanceof java.net.UnknownHostException) {
return "Weather service is currently unavailable. Please try again later.";
}
return "Failed to retrieve weather data: " + error.getMessage();
}
}@RegisterAiService(tools = { CalculatorTool.class })
public interface CalculatorAssistant {
String calculate(String expression);
@HandleToolArgumentError
static String handleArgumentError(ToolErrorContext context) {
return "Invalid mathematical expression provided for " + context.toolName() +
". Please use valid numbers and operators.";
}
}@RegisterAiService(
tools = { RecursiveTool.class },
maxSequentialToolInvocations = 3
)
public interface LimitedAssistant {
String process(String request);
}If the AI tries to call tools more than 3 times sequentially, the request fails with an error.
import dev.langchain4j.service.tool.ToolProvider;
import java.util.function.Supplier;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class DynamicToolProviderSupplier implements Supplier<ToolProvider> {
@Override
public ToolProvider get() {
return new ToolProvider() {
@Override
public java.util.List<dev.langchain4j.agent.tool.ToolSpecification> provideTools(
Object memoryId) {
// Return different tools based on user context
if (isAdminUser(memoryId)) {
return adminTools();
}
return standardTools();
}
};
}
}
@RegisterAiService(
toolProviderSupplier = DynamicToolProviderSupplier.class
)
public interface ContextualAssistant {
String help(String request);
}@RegisterAiService(
tools = { EmailTool.class, CalendarTool.class },
toolProviderSupplier = RegisterAiService.BeanIfExistsToolProviderSupplier.class
)
public interface HybridAssistant {
String help(String request);
}This service has access to both the static tools (EmailTool, CalendarTool) and any tools provided dynamically by the ToolProvider CDI bean.
Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core@1.7.0