Core runtime module for Quarkus LangChain4j integration with declarative AI services, guardrails, and observability
Memory management in Quarkus LangChain4j enables conversation context persistence and few-shot learning through example seeding. The module provides both declarative memory seeding via annotations and imperative memory removal utilities.
import io.quarkiverse.langchain4j.SeedMemory;
import io.quarkiverse.langchain4j.ChatMemoryRemover;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;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 SeedMemory {
}Seeds chat memory with example interactions for few-shot prompting. The annotated method must:
List<ChatMessage>String parameter (the method name)The seed messages are only added if the chat memory for the given memory ID doesn't exist or is empty.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.SeedMemory;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import java.util.List;
@RegisterAiService
public interface SentimentAnalyzer {
Sentiment analyze(String text);
@SeedMemory
static List<ChatMessage> seedExamples() {
return List.of(
UserMessage.from("I love this product!"),
AiMessage.from("POSITIVE"),
UserMessage.from("This is terrible."),
AiMessage.from("NEGATIVE"),
UserMessage.from("It's okay, nothing special."),
AiMessage.from("NEUTRAL")
);
}
}You can provide different seed examples for different methods by accepting the method name parameter:
@RegisterAiService
public interface MultiTaskService {
Sentiment analyzeSentiment(String text);
Category categorize(String text);
@SeedMemory
static List<ChatMessage> seed(String methodName) {
if (methodName.equals("analyzeSentiment")) {
return List.of(
UserMessage.from("Great!"),
AiMessage.from("POSITIVE"),
UserMessage.from("Awful!"),
AiMessage.from("NEGATIVE")
);
}
if (methodName.equals("categorize")) {
return List.of(
UserMessage.from("Latest iPhone release"),
AiMessage.from("TECHNOLOGY"),
UserMessage.from("NBA finals game"),
AiMessage.from("SPORTS")
);
}
return List.of();
}
}package io.quarkiverse.langchain4j;
import java.util.List;
public final class ChatMemoryRemover {
public static void remove(Object aiService, Object memoryId);
public static void remove(Object aiService, List<Object> memoryIds);
}public static void remove(Object aiService, Object memoryId)Removes chat memory for a single memory ID from the underlying ChatMemoryStore.
Parameters:
aiService: The bean implementing the AI service (annotated with @RegisterAiService)memoryId: The memory ID to removeNote: Automatically unwraps CDI proxies.
public static void remove(Object aiService, List<Object> memoryIds)Removes chat memory for multiple memory IDs from the underlying ChatMemoryStore.
Parameters:
aiService: The bean implementing the AI service (annotated with @RegisterAiService)memoryIds: List of memory IDs to removeNote: Automatically unwraps CDI proxies.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.SeedMemory;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.service.SystemMessage;
import java.util.List;
@RegisterAiService
public interface EmailClassifier {
@SystemMessage("Classify emails as SPAM, IMPORTANT, or NORMAL")
String classify(String emailContent);
@SeedMemory
static List<ChatMessage> seedClassificationExamples() {
return List.of(
UserMessage.from("You won a lottery! Click here now!"),
AiMessage.from("SPAM"),
UserMessage.from("Meeting with CEO tomorrow at 9am"),
AiMessage.from("IMPORTANT"),
UserMessage.from("Lunch plans with Sarah?"),
AiMessage.from("NORMAL")
);
}
}import io.quarkiverse.langchain4j.ChatMemoryRemover;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ChatService {
@Inject
AssistantService assistant;
public void endUserSession(String userId) {
// Remove chat history for this user
ChatMemoryRemover.remove(assistant, userId);
}
public void clearMultipleUsers(List<String> userIds) {
// Remove chat history for multiple users at once
ChatMemoryRemover.remove(assistant, userIds);
}
}@RegisterAiService
public interface CodeReviewer {
@SystemMessage("Review code and provide feedback on quality, bugs, and improvements")
String reviewCode(String code);
@SeedMemory
static List<ChatMessage> seedReviewExamples() {
return List.of(
UserMessage.from("""
public void process(String input) {
System.out.println(input.toLowerCase());
}
"""),
AiMessage.from("""
Good: Simple and clear logic
Issues:
- No null check on input (NullPointerException risk)
- Side effect (printing) in a processing method
Suggestion: Return the result and handle null
"""),
UserMessage.from("""
public int calculate(int a, int b) {
return a / b;
}
"""),
AiMessage.from("""
Critical Issue: Division by zero when b=0
Suggestion: Add validation or use Optional<Integer>
""")
);
}
}@RegisterAiService
public interface LanguageProcessor {
String translate(String text, String targetLanguage);
String summarize(String text);
@SeedMemory
static List<ChatMessage> seed(String methodName) {
return switch (methodName) {
case "translate" -> List.of(
UserMessage.from("Translate to French: Hello"),
AiMessage.from("Bonjour"),
UserMessage.from("Translate to Spanish: Goodbye"),
AiMessage.from("Adiós")
);
case "summarize" -> List.of(
UserMessage.from("Summarize: The quick brown fox jumps over the lazy dog. This is a test."),
AiMessage.from("A sentence about a fox and a dog used for testing."),
UserMessage.from("Summarize: Technology advances rapidly. Innovation drives change."),
AiMessage.from("Technology and innovation create rapid change.")
);
default -> List.of();
};
}
}import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import io.quarkiverse.langchain4j.ChatMemoryRemover;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ApplicationScoped
public class SessionManager {
@Inject
CustomerSupportService supportService;
private final Map<String, Long> sessionTimestamps = new ConcurrentHashMap<>();
public void startSession(String userId) {
sessionTimestamps.put(userId, System.currentTimeMillis());
}
public void endSession(String userId) {
ChatMemoryRemover.remove(supportService, userId);
sessionTimestamps.remove(userId);
}
public void cleanupInactiveSessions(long maxIdleMillis) {
long now = System.currentTimeMillis();
List<String> inactiveUsers = sessionTimestamps.entrySet().stream()
.filter(e -> now - e.getValue() > maxIdleMillis)
.map(Map.Entry::getKey)
.toList();
if (!inactiveUsers.isEmpty()) {
ChatMemoryRemover.remove(supportService, inactiveUsers);
inactiveUsers.forEach(sessionTimestamps::remove);
}
}
}// Service with no memory (stateless)
@RegisterAiService(
chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class
)
public interface StatelessTranslator {
String translate(String text, String targetLanguage);
}import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import java.util.function.Supplier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class CustomChatMemoryProviderSupplier implements Supplier<ChatMemoryProvider> {
@Inject
ChatMemoryStore memoryStore;
@Override
public ChatMemoryProvider get() {
return memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(20) // Custom window size
.chatMemoryStore(memoryStore)
.build();
}
}
@RegisterAiService(
chatMemoryProviderSupplier = CustomChatMemoryProviderSupplier.class
)
public interface CustomMemoryService {
String chat(String message);
}import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import io.quarkiverse.langchain4j.ChatMemoryRemover;
import java.util.List;
@ApplicationScoped
public class UserManagementService {
@Inject
ChatService chatService;
public void deleteUserAccount(String userId) {
// Clean up user data
deleteUserData(userId);
// Remove chat history
ChatMemoryRemover.remove(chatService, userId);
}
public void bulkDeleteAccounts(List<String> userIds) {
// Clean up data for all users
userIds.forEach(this::deleteUserData);
// Remove all chat histories in one call
ChatMemoryRemover.remove(chatService, userIds);
}
}Configure chat memory behavior in application.properties:
# Maximum messages to keep in memory window
quarkus.langchain4j.chat-memory.memory-window.max-messages=10
# Memory store type (in-memory by default)
# Provide custom ChatMemoryStore bean for persistent storageInstall with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core