CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-openai-deployment

Quarkus extension deployment module for OpenAI integration with LangChain4j providing build-time processing and CDI bean generation

Overview
Eval results
Files

cdi-beans.mddocs/

CDI Beans Reference

The deployment module automatically generates CDI beans for OpenAI models based on configuration, allowing injection of model instances throughout your Quarkus application without manual instantiation.

Capabilities

Chat Model Bean

Injectable bean for OpenAI chat models (GPT models) supporting conversational AI with single-turn and multi-turn interactions.

package dev.langchain4j.model.chat;

/**
 * Chat model interface for conversational AI.
 * Automatically created as CDI bean when chat model is enabled and selected.
 */
public interface ChatLanguageModel {

    /**
     * Generate a response for a single user message.
     *
     * @param userMessage The user's message
     * @return The model's response
     */
    String generate(String userMessage);

    /**
     * Generate a response for a chat conversation.
     *
     * @param messages The conversation history
     * @return The model's response
     */
    Response<AiMessage> generate(List<ChatMessage> messages);
}

Bean Characteristics:

  • Scope: @ApplicationScoped
  • Qualifier: @Default for default configuration, @ModelName("name") for named configurations
  • Default Bean: Yes (can be overridden by custom beans)
  • Injected Dependencies: Instance<ChatModelListener> for observability
  • Enabled By: quarkus.langchain4j.openai.chat-model.enabled=true (default)

Usage:

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.output.Response;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;

@ApplicationScoped
public class ChatService {

    @Inject
    ChatLanguageModel chatModel;

    public String chat(String message) {
        return chatModel.generate(message);
    }

    public String chatWithHistory(List<ChatMessage> messages) {
        Response<AiMessage> response = chatModel.generate(messages);
        return response.content().text();
    }
}

Streaming Chat Model Bean

Injectable bean for OpenAI streaming chat models supporting real-time token-by-token response streaming.

package dev.langchain4j.model.chat;

/**
 * Streaming chat model interface for real-time conversational AI.
 * Automatically created as CDI bean when chat model is enabled and selected.
 */
public interface StreamingChatLanguageModel {

    /**
     * Generate a streaming response for a user message.
     *
     * @param userMessage The user's message
     * @param handler Callback for streaming tokens
     */
    void generate(String userMessage, StreamingResponseHandler<AiMessage> handler);

    /**
     * Generate a streaming response for a chat conversation.
     *
     * @param messages The conversation history
     * @param handler Callback for streaming tokens
     */
    void generate(List<ChatMessage> messages, StreamingResponseHandler<AiMessage> handler);
}

Bean Characteristics:

  • Scope: @ApplicationScoped
  • Qualifier: @Default for default configuration, @ModelName("name") for named configurations
  • Default Bean: Yes (can be overridden by custom beans)
  • Injected Dependencies: Instance<ChatModelListener> for observability
  • Enabled By: quarkus.langchain4j.openai.chat-model.enabled=true (default)

Usage:

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.model.output.StreamingResponseHandler;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.function.Consumer;

@ApplicationScoped
public class StreamingChatService {

    @Inject
    StreamingChatLanguageModel streamingModel;

    public void streamChat(String message, Consumer<String> onToken) {
        streamingModel.generate(message, new StreamingResponseHandler<AiMessage>() {
            @Override
            public void onNext(String token) {
                onToken.accept(token);
            }

            @Override
            public void onComplete(Response<AiMessage> response) {
                // Streaming complete
                System.out.println("Total tokens: " + response.tokenUsage().totalTokenCount());
            }

            @Override
            public void onError(Throwable error) {
                // Handle error
                error.printStackTrace();
            }
        });
    }
}

Embedding Model Bean

Injectable bean for OpenAI embedding models converting text into vector representations for semantic search and similarity comparisons.

package dev.langchain4j.model.embedding;

/**
 * Embedding model interface for text vectorization.
 * Automatically created as CDI bean when embedding model is enabled and selected.
 */
public interface EmbeddingModel {

    /**
     * Embed a single text into a vector.
     *
     * @param text The text to embed
     * @return The embedding vector
     */
    Response<Embedding> embed(String text);

    /**
     * Embed a text segment into a vector.
     *
     * @param textSegment The text segment to embed
     * @return The embedding vector
     */
    Response<Embedding> embed(TextSegment textSegment);

    /**
     * Embed multiple texts into vectors in a single API call.
     *
     * @param textSegments The text segments to embed
     * @return List of embedding vectors
     */
    Response<List<Embedding>> embedAll(List<TextSegment> textSegments);
}

Bean Characteristics:

  • Scope: @ApplicationScoped
  • Qualifier: @Default for default configuration, @ModelName("name") for named configurations
  • Default Bean: Yes (can be overridden by custom beans)
  • Unremovable: Yes (always included in application even if not directly injected)
  • Enabled By: quarkus.langchain4j.openai.embedding-model.enabled=true (default)

Usage:

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.output.Response;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.stream.Collectors;

@ApplicationScoped
public class EmbeddingService {

    @Inject
    EmbeddingModel embeddingModel;

    public float[] getEmbedding(String text) {
        Response<Embedding> response = embeddingModel.embed(text);
        return response.content().vector();
    }

    public List<float[]> getEmbeddings(List<String> texts) {
        List<TextSegment> segments = texts.stream()
            .map(TextSegment::from)
            .collect(Collectors.toList());

        Response<List<Embedding>> response = embeddingModel.embedAll(segments);
        return response.content().stream()
            .map(Embedding::vector)
            .collect(Collectors.toList());
    }
}

Moderation Model Bean

Injectable bean for OpenAI moderation models detecting potentially harmful content across multiple safety categories.

package dev.langchain4j.model.moderation;

/**
 * Moderation model interface for content safety checks.
 * Automatically created as CDI bean when moderation model is enabled and selected.
 */
public interface ModerationModel {

    /**
     * Moderate a single text for harmful content.
     *
     * @param text The text to moderate
     * @return Moderation results with category scores
     */
    Response<Moderation> moderate(String text);

    /**
     * Moderate multiple texts for harmful content.
     *
     * @param messages The texts to moderate
     * @return List of moderation results
     */
    Response<List<Moderation>> moderateAll(List<String> messages);
}

Bean Characteristics:

  • Scope: @ApplicationScoped
  • Qualifier: @Default for default configuration, @ModelName("name") for named configurations
  • Default Bean: Yes (can be overridden by custom beans)
  • Enabled By: quarkus.langchain4j.openai.moderation-model.enabled=true (default)

Usage:

import dev.langchain4j.model.moderation.Moderation;
import dev.langchain4j.model.moderation.ModerationModel;
import dev.langchain4j.model.output.Response;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.Map;
import java.util.HashMap;

@ApplicationScoped
public class ModerationService {

    @Inject
    ModerationModel moderationModel;

    public boolean isContentSafe(String text) {
        Response<Moderation> response = moderationModel.moderate(text);
        Moderation moderation = response.content();
        return !moderation.flagged();
    }

    public Map<String, Boolean> getCategoryFlags(String text) {
        Response<Moderation> response = moderationModel.moderate(text);
        Moderation moderation = response.content();

        Map<String, Boolean> flags = new HashMap<>();
        flags.put("hate", moderation.hate());
        flags.put("hateThreatening", moderation.hateThreatening());
        flags.put("selfHarm", moderation.selfHarm());
        flags.put("sexual", moderation.sexual());
        flags.put("sexualMinors", moderation.sexualMinors());
        flags.put("violence", moderation.violence());
        flags.put("violenceGraphic", moderation.violenceGraphic());
        return flags;
    }
}

Image Model Bean

Injectable bean for OpenAI image generation models (DALL-E) creating images from text descriptions.

package dev.langchain4j.model.image;

/**
 * Image model interface for AI image generation.
 * Automatically created as CDI bean when image model is enabled and selected.
 */
public interface ImageModel {

    /**
     * Generate an image from a text description.
     *
     * @param prompt The text description of the image to generate
     * @return The generated image
     */
    Response<Image> generate(String prompt);

    /**
     * Edit an existing image based on a text description.
     *
     * @param image The image to edit
     * @param prompt The description of the desired edit
     * @return The edited image
     */
    Response<Image> edit(Image image, String prompt);
}

Bean Characteristics:

  • Scope: @ApplicationScoped
  • Qualifier: @Default for default configuration, @ModelName("name") for named configurations
  • Default Bean: Yes (can be overridden by custom beans)
  • Enabled By: quarkus.langchain4j.openai.image-model.enabled=true (default)

Usage:

import dev.langchain4j.data.image.Image;
import dev.langchain4j.model.image.ImageModel;
import dev.langchain4j.model.output.Response;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.net.URI;

@ApplicationScoped
public class ImageService {

    @Inject
    ImageModel imageModel;

    public String generateImage(String description) {
        Response<Image> response = imageModel.generate(description);
        Image image = response.content();

        // URL to the generated image
        URI url = image.url();
        return url != null ? url.toString() : null;
    }

    public String generateImageBase64(String description) {
        Response<Image> response = imageModel.generate(description);
        Image image = response.content();

        // Base64-encoded image data (when response-format=b64_json)
        return image.base64Data();
    }
}

Named Configuration Injection

Using @ModelName Qualifier

Inject models from named configurations using the @ModelName qualifier annotation.

package io.quarkiverse.langchain4j;

/**
 * Qualifier annotation for named model configurations.
 * Used to inject models from specific named configurations.
 */
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ModelName {
    /**
     * The name of the model configuration.
     * Corresponds to the configuration name in properties.
     */
    String value();
}

Usage with Multiple Configurations:

Configuration in application.properties:

# Default configuration
quarkus.langchain4j.openai.api-key=sk-default
quarkus.langchain4j.openai.chat-model.model-name=gpt-4o-mini

# Premium configuration
quarkus.langchain4j.openai.premium.api-key=sk-premium
quarkus.langchain4j.openai.premium.chat-model.model-name=gpt-4o

# Fast configuration
quarkus.langchain4j.openai.fast.api-key=sk-fast
quarkus.langchain4j.openai.fast.chat-model.model-name=gpt-3.5-turbo

Injection in code:

import dev.langchain4j.model.chat.ChatLanguageModel;
import io.quarkiverse.langchain4j.ModelName;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MultiModelService {

    @Inject
    ChatLanguageModel defaultModel;  // Uses default configuration

    @Inject
    @ModelName("premium")
    ChatLanguageModel premiumModel;  // Uses premium configuration

    @Inject
    @ModelName("fast")
    ChatLanguageModel fastModel;  // Uses fast configuration

    public String processWithBestModel(String query, boolean premium) {
        if (premium) {
            return premiumModel.generate(query);
        } else {
            return fastModel.generate(query);
        }
    }
}

Bean Lifecycle

Bean Creation

Beans are created lazily at first injection by default (ApplicationScoped behavior). The deployment module configures them to be created during runtime initialization phase for validation and startup time optimization.

Bean Initialization

During bean creation:

  1. Configuration is loaded and validated
  2. API key is checked (when integration enabled)
  3. LangChain4j builder is initialized with all configured parameters
  4. Quarkus-specific enhancements are applied (proxy, TLS, logging)
  5. Chat model listeners are discovered and injected (for chat models)
  6. Model instance is created and cached

Bean Shutdown

During application shutdown:

  1. OpenAI clients are closed gracefully
  2. Thread pools are shut down
  3. Cached data is cleared
  4. Resources are released

The deployment module registers shutdown handlers to ensure clean resource cleanup.

Integration with Listeners

Chat Model Listeners

Chat and streaming chat model beans automatically inject all available ChatModelListener implementations for observability and monitoring.

package dev.langchain4j.model.chat.listener;

/**
 * Listener interface for chat model events.
 * All CDI beans implementing this interface are automatically injected into chat models.
 */
public interface ChatModelListener {

    /**
     * Called when a request is sent to the chat model.
     */
    void onRequest(ChatModelRequest request);

    /**
     * Called when a response is received from the chat model.
     */
    void onResponse(ChatModelResponse response);

    /**
     * Called when an error occurs.
     */
    void onError(ChatModelErrorEvent errorEvent);
}

Creating a Listener:

import dev.langchain4j.model.chat.listener.ChatModelListener;
import dev.langchain4j.model.chat.listener.ChatModelRequest;
import dev.langchain4j.model.chat.listener.ChatModelResponse;
import dev.langchain4j.model.chat.listener.ChatModelErrorEvent;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class CustomChatListener implements ChatModelListener {

    @Override
    public void onRequest(ChatModelRequest request) {
        // Log request, collect metrics, etc.
        System.out.println("Chat request: " + request.messages().size() + " messages");
    }

    @Override
    public void onResponse(ChatModelResponse response) {
        // Log response, track token usage, etc.
        System.out.println("Chat response: " + response.tokenUsage().totalTokenCount() + " tokens");
    }

    @Override
    public void onError(ChatModelErrorEvent errorEvent) {
        // Handle errors, send alerts, etc.
        System.err.println("Chat error: " + errorEvent.error().getMessage());
    }
}

The listener is automatically discovered and injected into all chat and streaming chat model beans.

Disabled Model Instances

When quarkus.langchain4j.openai.enable-integration=false, the deployment module creates "disabled" model instances that throw descriptive exceptions on use.

@Inject
ChatLanguageModel chatModel;

public void example() {
    // Throws: RuntimeException: ChatModel is disabled because quarkus.langchain4j.openai.enable-integration is set to false
    chatModel.generate("Hello");
}

This allows applications to compile and tests to run without valid OpenAI credentials, while clearly indicating when models are accessed in a disabled state.

Bean Producers

Custom Bean Production

You can override the default beans by producing your own custom beans. The default beans are marked with @DefaultBean, allowing custom beans to take precedence.

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import java.time.Duration;

public class CustomModelProducer {

    @Produces
    @ApplicationScoped
    public ChatLanguageModel customChatModel() {
        return OpenAiChatModel.builder()
            .apiKey(System.getenv("CUSTOM_API_KEY"))
            .modelName("gpt-4o")
            .temperature(0.5)
            .timeout(Duration.ofSeconds(30))
            .build();
    }
}

Your custom bean will be used instead of the auto-generated default bean.

Conditional Bean Production

Produce beans conditionally based on configuration or runtime conditions:

import io.quarkus.arc.profile.IfBuildProfile;
import dev.langchain4j.model.chat.ChatLanguageModel;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;

public class ConditionalModelProducer {

    @Produces
    @ApplicationScoped
    @IfBuildProfile("test")
    public ChatLanguageModel testChatModel() {
        // Return mock or test implementation in test profile
        return new MockChatModel();
    }
}

Type Safety

All injected beans are type-safe with full IDE support for:

  • Auto-completion
  • Type checking
  • Refactoring
  • Documentation lookup

The deployment module uses proper Java types from LangChain4j library, ensuring compile-time safety and preventing runtime errors from type mismatches.

Scope Considerations

ApplicationScoped

All generated model beans use @ApplicationScoped scope, meaning:

  • A single instance is created per application
  • The instance is shared across all injections
  • Thread-safe for concurrent use
  • Efficient resource utilization

Concurrency: OpenAI model implementations are thread-safe and can handle concurrent requests safely. Multiple threads can inject and use the same bean instance simultaneously.

Singleton Pattern

The ApplicationScoped beans effectively implement the singleton pattern, but with CDI benefits:

  • Lifecycle management by the container
  • Support for interceptors and decorators
  • Proper integration with Quarkus contexts
  • Clean shutdown handling

Programmatic Lookup

In addition to injection, beans can be looked up programmatically using CDI's Instance API:

import dev.langchain4j.model.chat.ChatLanguageModel;
import io.quarkiverse.langchain4j.ModelName;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

public class DynamicModelSelector {

    @Inject
    Instance<ChatLanguageModel> allChatModels;

    public ChatLanguageModel selectModel(String configName) {
        if ("default".equals(configName)) {
            return allChatModels.select().get();
        } else {
            return allChatModels.select(ModelName.Literal.of(configName)).get();
        }
    }
}

This allows runtime selection of models based on dynamic conditions.

Install with Tessl CLI

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

docs

build-items.md

cdi-beans.md

configuration.md

dev-ui.md

index.md

runtime-components.md

tile.json