Quarkus deployment extension for LangChain4j integration providing build-time processing, BuildItem APIs, and configuration for integrating Large Language Models into Quarkus applications
Processors contain @BuildStep annotated methods that execute during the Quarkus build process. This document covers all 12 processor classes in the deployment extension.
A processor is a class containing one or more @BuildStep methods. These methods:
BuildProducer<T> parameters@Record to pass data to runtimeonlyIf or onlyIfNot@BuildStep
public void myBuildStep(
// Input: Consume BuildItems
List<SomeBuildItem> inputs,
SingleBuildItem singleInput,
// Output: Produce BuildItems
BuildProducer<OutputBuildItem> output) {
// Process inputs and produce outputs
output.produce(new OutputBuildItem(...));
}Use @Record to pass build-time data to runtime:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
public void recordData(MyRecorder recorder, List<SomeBuildItem> items) {
// Pass data to runtime via recorder
recorder.recordSomething(items);
}Package: io.quarkiverse.langchain4j.deployment
Main processor for model provider selection and CDI bean creation.
Feature Registration:
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem("langchain4j");
}Index Dependencies:
@BuildStep
void indexDependencies(BuildProducer<IndexDependencyBuildItem> producer) {
producer.produce(new IndexDependencyBuildItem("dev.langchain4j", "langchain4j-core"));
producer.produce(new IndexDependencyBuildItem("dev.langchain4j", "langchain4j"));
}Provider Selection:
@BuildStep
public void handleProviders(
BeanDiscoveryFinishedBuildItem beanDiscoveryFinished,
List<ChatModelProviderCandidateBuildItem> chatCandidates,
List<EmbeddingModelProviderCandidateBuildItem> embeddingCandidates,
List<ImageModelProviderCandidateBuildItem> imageCandidates,
List<ModerationModelProviderCandidateBuildItem> moderationCandidates,
List<ScoringModelProviderCandidateBuildItem> scoringCandidates,
List<RequestChatModelBeanBuildItem> requestChatModelBeans,
LangChain4jBuildConfig buildConfig,
BuildProducer<SelectedChatModelProviderBuildItem> selectedChat,
BuildProducer<SelectedEmbeddingModelCandidateBuildItem> selectedEmbedding,
BuildProducer<SelectedImageModelProviderBuildItem> selectedImage,
BuildProducer<SelectedModerationModelProviderBuildItem> selectedModeration,
BuildProducer<SelectedScoringModelProviderBuildItem> selectedScoring) {
// Select providers based on:
// 1. Injection points (bean usage)
// 2. Configuration
// 3. Explicit requests via RequestXXXBeanBuildItem
}Purpose:
Selected*BuildItem instances for chosen providersUsage Pattern: Extensions register provider candidates, then BeansProcessor selects winners and publishes selection BuildItems that extensions can consume to create actual beans.
Package: io.quarkiverse.langchain4j.deployment
Main processor for AI service registration, tool integration, and guardrails processing.
Service Discovery:
@BuildStep
void discover(
CombinedIndexBuildItem indexBuildItem,
BuildProducer<DeclarativeAiServiceBuildItem> producer) {
// Scan for @RegisterAiService annotations
// Produce DeclarativeAiServiceBuildItem for each
}Service Creation:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void create(
List<DeclarativeAiServiceBuildItem> aiServices,
List<ToolMethodBuildItem> tools,
ToolsMetadataBuildItem toolsMetadata,
AiServicesRecorder recorder,
BuildProducer<GeneratedBeanBuildItem> generatedBeans) {
// Generate CDI beans for AI services
// Integrate tools
// Configure guardrails
// Wire up memory providers
}Purpose:
@RegisterAiService annotated interfacesUsage Pattern:
Consumes DeclarativeAiServiceBuildItem and ToolMethodBuildItem to generate complete AI service implementations.
Package: io.quarkiverse.langchain4j.deployment
Discovers and validates tool methods, creates tool specifications.
Tool Discovery:
@BuildStep
void discover(
CombinedIndexBuildItem index,
BuildProducer<ToolMethodBuildItem> producer) {
IndexView indexView = index.getIndex();
// Find all methods annotated with @Tool
for (AnnotationInstance annotation :
indexView.getAnnotations(LangChain4jDotNames.TOOL)) {
MethodInfo method = annotation.target().asMethod();
// Create ToolMethodCreateInfo
ToolMethodCreateInfo createInfo = analyzeToolMethod(method);
producer.produce(new ToolMethodBuildItem(method, createInfo));
}
}Tool Validation:
@BuildStep
void validate(
List<ToolMethodBuildItem> tools,
ValidationPhaseBuildItem validationPhase,
BuildProducer<ValidationErrorBuildItem> errors) {
for (ToolMethodBuildItem tool : tools) {
// Validate tool method signatures
// Check parameter types
// Verify return types
// Report errors
}
}Tool Specification Creation:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void createSpecs(
List<ToolMethodBuildItem> tools,
ToolsRecorder recorder,
BuildProducer<ToolsMetadataBuildItem> metadata) {
// Create ToolSpecification for each tool
// Generate JSON schemas for parameters
// Produce consolidated metadata
}Purpose:
@Tool annotated methodsToolSpecification instancesUsage Pattern:
Extensions can consume ToolMethodBuildItem to customize tool behavior or add tool-related functionality.
Package: io.quarkiverse.langchain4j.deployment
Configures chat memory providers.
Memory Provider Creation:
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void createMemoryProvider(
ChatMemoryBuildConfig config,
ChatMemoryRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
ChatMemoryBuildConfig.Type type = config.type();
// Create appropriate ChatMemoryProvider bean
syntheticBeans.produce(
SyntheticBeanBuildItem
.configure(ChatMemoryProvider.class)
.defaultBean()
.supplier(recorder.chatMemoryProviderSupplier(type))
.done()
);
}Purpose:
ChatMemoryProvider beans based on configurationPackage: io.quarkiverse.langchain4j.deployment
Discovers and configures local (in-process) embedding models.
Model Discovery:
@BuildStep
void discover(
CombinedIndexBuildItem index,
BuildProducer<InProcessEmbeddingBuildItem> producer) {
// Scan for in-process embedding model implementations
// Produce InProcessEmbeddingBuildItem for each
}Bean Generation:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void generateBeans(
List<InProcessEmbeddingBuildItem> models,
InProcessEmbeddingRecorder recorder,
BuildProducer<GeneratedBeanBuildItem> generatedBeans) {
for (InProcessEmbeddingBuildItem model : models) {
// Generate CDI bean for in-process embedding model
// Configure ONNX runtime if needed
}
}Purpose:
Package: io.quarkiverse.langchain4j.deployment
Processes structured prompts and templates.
Template Processing:
@BuildStep
void processTemplates(
CombinedIndexBuildItem index,
LangChain4jBuildConfig config,
BuildProducer<ProcessedTemplateBuildItem> producer) {
// Process @SystemMessage and @UserMessage templates
// Validate template variables
// Handle {response schema} placeholder based on config
}Purpose:
Package: io.quarkiverse.langchain4j.deployment
Configures observability for guardrail execution (metrics and tracing).
Metrics Registration:
@BuildStep
void registerMetrics(
Capabilities capabilities,
BuildProducer<MetricsBuildItem> metrics) {
if (capabilities.isPresent(Capability.MICROMETER)) {
// Register guardrail execution metrics
// Track success/failure rates
// Monitor execution times
}
}Purpose:
Package: io.quarkiverse.langchain4j.deployment
Registers chat model listeners for observability.
Listener Registration:
@BuildStep
void registerListeners(
Capabilities capabilities,
BuildProducer<AdditionalBeanBuildItem> beans) {
// Register OpenTelemetry listeners if available
if (capabilities.isPresent(Capability.OPENTELEMETRY)) {
beans.produce(AdditionalBeanBuildItem.unremovableOf(
OpenTelemetryChatModelListener.class
));
}
// Register Micrometer listeners if available
if (capabilities.isPresent(Capability.MICROMETER)) {
beans.produce(AdditionalBeanBuildItem.unremovableOf(
MicrometerChatModelListener.class
));
}
}Purpose:
ChatModelListener implementationsPackage: io.quarkiverse.langchain4j.deployment
Configures ONNX Runtime for native image builds.
JNI Configuration:
@BuildStep(onlyIf = NativeBuild.class)
void configureJni(
RequireOnnxRuntimeBuildItem requireOnnx,
BuildProducer<JniRuntimeAccessBuildItem> jniAccess) {
// Register JNI classes for ONNX Runtime
// Configure native library loading
}Reflection Registration:
@BuildStep(onlyIf = NativeBuild.class)
void registerReflection(
RequireOnnxRuntimeBuildItem requireOnnx,
BuildProducer<ReflectiveClassBuildItem> reflection) {
// Register classes used by ONNX Runtime via reflection
}Runtime Initialization:
@BuildStep(onlyIf = NativeBuild.class)
void configureInitialization(
RequireOnnxRuntimeBuildItem requireOnnx,
BuildProducer<RuntimeInitializedClassBuildItem> runtimeInit) {
// Configure classes to initialize at runtime
// Required for JNI library loading
}Purpose:
Package: io.quarkiverse.langchain4j.deployment
Registers OpenNLP resources for native image.
Resource Registration:
@BuildStep(onlyIf = NativeBuild.class)
void registerResources(BuildProducer<NativeImageResourceBuildItem> resources) {
// Register .bin files (OpenNLP models)
resources.produce(new NativeImageResourceBuildItem("*.bin"));
}Reflection Configuration:
@BuildStep(onlyIf = NativeBuild.class)
void registerReflection(BuildProducer<ReflectiveClassBuildItem> reflection) {
// Register OpenNLP classes for reflection
}Purpose:
Package: io.quarkiverse.langchain4j.deployment.devservice
Manages Ollama DevServices container lifecycle.
@BuildSteps(onlyIfNot = IsNormal.class)
public class DevServicesOllamaProcessor {
// Only runs in dev/test mode, not production
}Container Startup:
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
DevServicesResultBuildItem startContainer(
LangChain4jBuildConfig config,
List<DevServicesChatModelRequiredBuildItem> chatModelRequests,
List<DevServicesEmbeddingModelRequiredBuildItem> embeddingModelRequests,
DevServicesRecorder recorder) {
if (!config.devservices().enabled()) {
return null;
}
// Start Ollama container
// Pull requested models
// Preload models if configured
// Configure base URLs for providers
}Purpose:
Usage Pattern:
Extensions produce DevServicesChatModelRequiredBuildItem or DevServicesEmbeddingModelRequiredBuildItem to request DevServices support.
Package: io.quarkiverse.langchain4j.deployment.devui
Integrates with Quarkus Dev UI.
Card Registration:
@BuildStep(onlyIf = IsDevelopment.class)
void registerCards(
List<DeclarativeAiServiceBuildItem> aiServices,
List<ToolMethodBuildItem> tools,
List<AdditionalDevUiCardBuildItem> additionalCards,
BuildProducer<CardPageBuildItem> cards) {
// Create main LangChain4j card
CardPageBuildItem card = new CardPageBuildItem();
// Add AI services section
// Add tools section
// Add additional custom cards
cards.produce(card);
}RPC Endpoints:
@BuildStep(onlyIf = IsDevelopment.class)
@Record(ExecutionTime.STATIC_INIT)
void createRpcEndpoints(
DevUIRecorder recorder,
BuildProducer<JsonRPCProvidersBuildItem> rpcProducers) {
// Create RPC endpoints for Dev UI interactions
// Enable testing AI services from UI
// Provide model status information
}Purpose:
Package: io.quarkiverse.langchain4j.deployment.devui
Manages Open WebUI container for Dev UI.
Container Management:
@BuildStep(onlyIf = IsDevelopment.class)
void manageContainer(
LangChain4jBuildConfig config,
BuildProducer<DevServicesResultBuildItem> devServices) {
// Start Open WebUI container
// Connect to Ollama DevServices
// Provide web interface for model interaction
}Purpose:
Run BuildSteps only in specific modes:
// Only in native builds
@BuildStep(onlyIf = NativeBuild.class)
void nativeOnly() { }
// Only in dev/test (not production)
@BuildStep(onlyIfNot = IsNormal.class)
void devTestOnly() { }
// Only in development mode
@BuildStep(onlyIf = IsDevelopment.class)
void devOnly() { }Separate BuildSteps for different execution phases:
// Build-time processing
@BuildStep
void buildTime(
CombinedIndexBuildItem index,
BuildProducer<MyBuildItem> producer) {
// Analyze code, discover annotations
}
// Static initialization (before main())
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void staticInit(MyRecorder recorder, List<MyBuildItem> items) {
// Create runtime objects, configure system
}
// Runtime initialization (during startup)
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void runtimeInit(MyRecorder recorder) {
// Initialize runtime services
}@BuildStep
void combine(
List<DeclarativeAiServiceBuildItem> services,
List<ToolMethodBuildItem> tools,
ToolsMetadataBuildItem metadata,
BuildProducer<CombinedResultBuildItem> output) {
// Combine multiple sources
Map<String, List<ToolMethodCreateInfo>> toolsByClass =
metadata.getMetadata();
for (DeclarativeAiServiceBuildItem service : services) {
// Correlate services with tools
List<ClassInfo> toolClasses = service.getToolClassInfos();
// Process combination
}
}// Phase 1: Provider registration
@BuildStep
void registerProvider(BuildProducer<ChatModelProviderCandidateBuildItem> producer) {
producer.produce(new ChatModelProviderCandidateBuildItem("my-provider"));
}
// Phase 2: Provider selection (in BeansProcessor)
// Produces SelectedChatModelProviderBuildItem
// Phase 3: Bean creation based on selection
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void createBeans(
List<SelectedChatModelProviderBuildItem> selected,
MyRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> beans) {
for (SelectedChatModelProviderBuildItem item : selected) {
if ("my-provider".equals(item.getProvider())) {
// Create beans for this provider
}
}
}Here's a complete example of a custom processor:
package com.example.deployment;
import io.quarkiverse.langchain4j.deployment.items.ChatModelProviderCandidateBuildItem;
import io.quarkiverse.langchain4j.deployment.items.SelectedChatModelProviderBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import java.util.List;
public class MyCustomProcessor {
private static final String PROVIDER_NAME = "my-provider";
// Step 1: Register as a provider candidate
@BuildStep
void registerProvider(BuildProducer<ChatModelProviderCandidateBuildItem> producer) {
producer.produce(new ChatModelProviderCandidateBuildItem(PROVIDER_NAME));
}
// Step 2: React to provider selection
@BuildStep
@Record(ExecutionTime.STATIC_INIT)
void onProviderSelected(
List<SelectedChatModelProviderBuildItem> selected,
MyRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> beans) {
for (SelectedChatModelProviderBuildItem item : selected) {
if (PROVIDER_NAME.equals(item.getProvider())) {
String configName = item.getConfigName();
// Create chat model bean
beans.produce(
SyntheticBeanBuildItem
.configure(ChatLanguageModel.class)
.supplier(recorder.createChatModel(configName))
.done()
);
}
}
}
}BuildSteps execute in dependency order. The typical flow is:
Dependencies between BuildSteps are determined automatically based on BuildItem consumption and production.
Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core-deployment@1.7.0