tessl install tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-core@1.5.0Quarkus LangChain4j Core provides runtime integration for LangChain4j with the Quarkus framework, enabling declarative AI service creation through CDI annotations.
This document demonstrates complete, production-ready implementations of common AI service patterns.
A complete customer support bot with memory, tools, and error handling.
import io.quarkiverse.langchain4j.RegisterAiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
@RegisterAiService(
modelName = "gpt-3.5-turbo",
tools = {OrderTool.class, KnowledgeBaseTool.class, TicketTool.class}
)
public interface CustomerSupportBot {
@SystemMessage("""
You are a helpful customer support agent.
Be professional, empathetic, and concise.
Use the available tools to look up orders, search knowledge base, or create tickets.
""")
String chat(@MemoryId String customerId, @UserMessage String message);
}import dev.langchain4j.agent.tool.Tool;
import io.quarkiverse.langchain4j.guardrails.ToolInputGuardrails;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class OrderTool {
@Inject
OrderService orderService;
@Tool("Look up order details by order ID")
@ToolInputGuardrails(AuthorizationGuardrail.class)
public String getOrderDetails(String orderId) {
Order order = orderService.findById(orderId);
if (order == null) {
return "Order not found: " + orderId;
}
return String.format(
"Order %s: Status=%s, Items=%d, Total=$%.2f, Estimated Delivery=%s",
order.getId(), order.getStatus(), order.getItemCount(),
order.getTotal(), order.getEstimatedDelivery()
);
}
@Tool("Cancel an order")
@ToolInputGuardrails({AuthorizationGuardrail.class, CancellationPolicyGuardrail.class})
public String cancelOrder(String orderId, String reason) {
try {
orderService.cancel(orderId, reason);
return "Order " + orderId + " has been cancelled successfully.";
} catch (OrderException e) {
return "Unable to cancel order: " + e.getMessage();
}
}
}
@ApplicationScoped
public class KnowledgeBaseTool {
@Inject
KnowledgeBaseService kb;
@Tool("Search knowledge base for answers to common questions")
public String searchKnowledgeBase(String query) {
List<Article> articles = kb.search(query, 3);
if (articles.isEmpty()) {
return "No relevant articles found.";
}
return articles.stream()
.map(a -> a.getTitle() + ": " + a.getSummary())
.collect(Collectors.joining("\n\n"));
}
}
@ApplicationScoped
public class TicketTool {
@Inject
TicketService ticketService;
@Tool("Create a support ticket for complex issues")
public String createTicket(String subject, String description, String priority) {
Ticket ticket = ticketService.create(subject, description, priority);
return String.format(
"Ticket #%s created. A support specialist will respond within %s hours.",
ticket.getId(), ticket.getSlaHours()
);
}
}import io.quarkiverse.langchain4j.guardrails.*;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class AuthorizationGuardrail implements ToolInputGuardrail {
@Inject
SecurityService security;
@Override
public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
Object memoryId = request.memoryId();
String orderId = request.argumentsAsJson().getString("orderId");
if (!security.canAccessOrder(memoryId.toString(), orderId)) {
return ToolInputGuardrailResult.failure(
"You don't have permission to access this order."
);
}
return ToolInputGuardrailResult.success();
}
}@Path("/support")
public class SupportResource {
@Inject
CustomerSupportBot bot;
@POST
@Path("/chat")
public String chat(@QueryParam("customerId") String customerId, String message) {
return bot.chat(customerId, message);
}
}An AI assistant that reviews code with context-aware feedback.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.SeedMemory;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.data.message.ChatMessage;
import java.util.List;
@RegisterAiService(modelName = "gpt-4")
public interface CodeReviewer {
@SystemMessage("""
You are an expert code reviewer with deep knowledge of:
- Clean code principles
- Security best practices
- Performance optimization
- Design patterns
Provide constructive, specific feedback with examples.
Focus on: correctness, security, performance, maintainability.
""")
@UserMessage("""
Review this {language} code:
```{language}
{code}
```
Focus areas: {focusAreas}
""")
String reviewCode(String language, String code, String focusAreas);
@SeedMemory
static List<ChatMessage> provideExamples() {
return List.of(
UserMessage.from("""
Review this Java code:
```java
public String process(String input) {
return input.toLowerCase();
}
```
Focus areas: null safety
"""),
AiMessage.from("""
**Issue**: Missing null check
**Recommendation**: Add null validation:
```java
public String process(String input) {
if (input == null) {
throw new IllegalArgumentException("input cannot be null");
}
return input.toLowerCase();
}
```
**Alternative**: Use Objects.requireNonNull() for concise null checking.
""")
);
}
}
// Usage
@Inject CodeReviewer reviewer;
String feedback = reviewer.reviewCode(
"java",
userSubmittedCode,
"security, performance"
);Extract and analyze information from documents.
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.PdfUrl;
import io.quarkiverse.langchain4j.response.ResponseAugmenter;
import dev.langchain4j.service.UserMessage;
@RegisterAiService(modelName = "gpt-4")
public interface DocumentAnalyzer {
@UserMessage("Summarize this document in 3-5 bullet points")
@ResponseAugmenter(CitationAugmenter.class)
String summarize(@PdfUrl String documentUrl);
@UserMessage("Extract all dates, amounts, and parties from this contract")
ContractData extractContractData(@PdfUrl String contractUrl);
@UserMessage("Analyze sentiment and key themes in this report")
AnalysisResult analyzeSentiment(@PdfUrl String reportUrl);
}
public record ContractData(
List<String> parties,
List<LocalDate> dates,
List<BigDecimal> amounts,
String summary
) {}
public record AnalysisResult(
String sentiment, // "positive", "negative", "neutral"
List<String> keyThemes,
double confidenceScore
) {}
// Response Augmenter adds citations
@ApplicationScoped
public class CitationAugmenter implements AiResponseAugmenter<String> {
@Override
public String augment(String response, ResponseAugmenterParams params) {
String documentUrl = extractDocumentUrl(params);
return response + "\n\n*Source: " + documentUrl + "*";
}
private String extractDocumentUrl(ResponseAugmenterParams params) {
// Extract from params.variables() or params.userMessage()
return params.variables().get("documentUrl").toString();
}
}Handle multiple tenants with isolated data and configurations.
import io.quarkiverse.langchain4j.RegisterAiService;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
@RegisterAiService(
tools = {TenantDataTool.class, TenantAnalyticsTool.class}
)
public interface TenantAssistant {
String chat(@MemoryId TenantContext context, @UserMessage String message);
}
public record TenantContext(String tenantId, String userId) {
@Override
public boolean equals(Object o) {
if (!(o instanceof TenantContext that)) return false;
return tenantId.equals(that.tenantId) && userId.equals(that.userId);
}
@Override
public int hashCode() {
return Objects.hash(tenantId, userId);
}
}
@ApplicationScoped
public class TenantDataTool {
@Inject
TenantService tenantService;
@Tool("Query tenant-specific data")
@ToolInputGuardrails(TenantIsolationGuardrail.class)
public String queryData(String query, TenantContext context) {
return tenantService.query(context.tenantId(), query);
}
}
@ApplicationScoped
public class TenantIsolationGuardrail implements ToolInputGuardrail {
@Override
public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
TenantContext context = (TenantContext) request.memoryId();
// Ensure tenant can only access their own data
if (requestAccessesOtherTenant(request, context.tenantId())) {
return ToolInputGuardrailResult.fatal(
"Tenant isolation violation detected",
new SecurityException("Cross-tenant access attempt")
);
}
return ToolInputGuardrailResult.success();
}
}
// Custom Memory Provider for tenant isolation
@ApplicationScoped
public class TenantMemoryProvider implements ChatMemoryProvider, ChatMemoryRemovable {
private final Map<TenantContext, ChatMemory> memories = new ConcurrentHashMap<>();
@Override
public ChatMemory get(Object memoryId) {
TenantContext context = (TenantContext) memoryId;
return memories.computeIfAbsent(context,
ctx -> MessageWindowChatMemory.withMaxMessages(100));
}
@Override
public void remove(Object... ids) {
for (Object id : ids) {
memories.remove((TenantContext) id);
}
}
}Enhance AI responses with knowledge from your data sources.
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.data.segment.TextSegment;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class ProductKnowledgeRetriever implements ContentRetriever {
@Inject
EmbeddingStore<TextSegment> embeddingStore;
@Inject
EmbeddingModel embeddingModel;
@Override
public List<Content> retrieve(Query query) {
Embedding queryEmbedding = embeddingModel.embed(query.text()).content();
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
queryEmbedding,
5, // max results
0.7 // min similarity score
);
return matches.stream()
.map(match -> new Content(match.embedded().text()))
.collect(Collectors.toList());
}
}
@ApplicationScoped
@Produces
public class RAGConfiguration {
@Inject
ProductKnowledgeRetriever retriever;
@Produces
@ApplicationScoped
public RetrievalAugmentor createAugmentor() {
return DefaultRetrievalAugmentor.builder()
.contentRetriever(retriever)
.contentAggregator(new ReRankingContentAggregator())
.build();
}
}
@RegisterAiService(modelName = "gpt-4")
public interface ProductAssistant {
// RAG automatically injects relevant product knowledge
String answerProductQuestion(String question);
}Comprehensive security validation for tool execution.
@ApplicationScoped
public class DatabaseTool {
@Tool("Execute database query")
@ToolInputGuardrails({
SqlInjectionGuardrail.class,
RateLimitGuardrail.class,
AuthorizationGuardrail.class
})
@ToolOutputGuardrails({
PiiRedactionGuardrail.class,
DataVolumeGuardrail.class
})
public String query(String sql) {
return database.execute(sql);
}
@HandleToolExecutionError
public static String handleError(Throwable error, ToolErrorContext context) {
if (error instanceof SQLException) {
return "Database query failed. Please check your query syntax.";
}
return "An unexpected error occurred: " + error.getMessage();
}
}
@ApplicationScoped
public class SqlInjectionGuardrail implements ToolInputGuardrail {
private static final Pattern SQL_INJECTION_PATTERN =
Pattern.compile(".*(union|exec|execute|drop|delete|insert|update).*",
Pattern.CASE_INSENSITIVE);
@Override
public ToolInputGuardrailResult validate(ToolInputGuardrailRequest request) {
String sql = request.argumentsAsJson().getString("sql");
if (SQL_INJECTION_PATTERN.matcher(sql).matches()) {
return ToolInputGuardrailResult.fatal(
"Potentially dangerous SQL detected",
new SecurityException("SQL injection attempt")
);
}
return ToolInputGuardrailResult.success();
}
}
@ApplicationScoped
public class PiiRedactionGuardrail implements ToolOutputGuardrail {
@Override
public ToolOutputGuardrailResult validate(ToolOutputGuardrailRequest request) {
if (request.isError()) {
return ToolOutputGuardrailResult.success();
}
String result = request.resultText();
String redacted = redactPii(result);
if (!result.equals(redacted)) {
ToolExecutionResult modified = ToolExecutionResult.builder()
.text(redacted)
.build();
return ToolOutputGuardrailResult.successWith(modified);
}
return ToolOutputGuardrailResult.success();
}
private String redactPii(String text) {
// Redact emails, SSNs, credit cards, etc.
return text
.replaceAll("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", "[EMAIL]")
.replaceAll("\\b\\d{3}-\\d{2}-\\d{4}\\b", "[SSN]")
.replaceAll("\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b", "[CARD]");
}
}Real-time streaming responses for interactive dashboards.
import io.smallrye.mutiny.Multi;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.response.ResponseAugmenter;
@RegisterAiService(
modelName = "gpt-4",
tools = {MetricsTool.class, ChartTool.class}
)
public interface DashboardAssistant {
@ResponseAugmenter(StreamingFormatterAugmenter.class)
Multi<String> analyzeMetrics(String query);
}
@ApplicationScoped
public class StreamingFormatterAugmenter implements AiResponseAugmenter<String> {
@Override
public Multi<String> augment(Multi<String> stream, ResponseAugmenterParams params) {
return Multi.createFrom().concat(
Multi.createFrom().item("π **Analysis:**\n\n"),
stream.map(this::formatChunk),
Multi.createFrom().item("\n\n---\n*Generated at " +
Instant.now() + "*")
);
}
private String formatChunk(String chunk) {
// Add markdown formatting, syntax highlighting, etc.
return chunk;
}
}
// Server-Sent Events endpoint
@Path("/dashboard")
public class DashboardResource {
@Inject
DashboardAssistant assistant;
@GET
@Path("/analyze")
@Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> analyze(@QueryParam("query") String query) {
return assistant.analyzeMetrics(query);
}
}Intelligently route requests to cost-effective models.
import io.quarkiverse.langchain4j.ModelName;
import dev.langchain4j.model.chat.ChatModel;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class SmartRouter {
@Inject
Instance<ChatModel> models;
@Inject
ComplexityAnalyzer analyzer;
@Inject
CostEstimatorService costEstimator;
public String route(String query) {
int complexity = analyzer.analyze(query);
String modelName = selectModel(complexity);
ChatModel model = models.select(ModelName.Literal.of(modelName)).get();
Response<AiMessage> response = model.generate(query);
logCost(modelName, response);
return response.content().text();
}
private String selectModel(int complexity) {
if (complexity > 8) {
return "gpt-4"; // Complex reasoning
} else if (complexity > 5) {
return "gpt-3.5-turbo"; // Moderate complexity
} else {
return "gpt-3.5-turbo-fast"; // Simple queries
}
}
private void logCost(String modelName, Response<AiMessage> response) {
TokenUsage usage = response.tokenUsage();
Cost cost = costEstimator.estimate(modelName,
usage.inputTokenCount(),
usage.outputTokenCount());
logger.info("Request cost: {} (model: {})", cost, modelName);
}
}
@ApplicationScoped
public class ComplexityAnalyzer {
public int analyze(String query) {
int score = 0;
// Longer queries are typically more complex
if (query.length() > 500) score += 3;
else if (query.length() > 200) score += 2;
else if (query.length() > 100) score += 1;
// Keywords indicating complexity
String lower = query.toLowerCase();
if (lower.contains("analyze") || lower.contains("compare")) score += 2;
if (lower.contains("calculate") || lower.contains("solve")) score += 2;
if (lower.contains("why") || lower.contains("how")) score += 1;
// Multiple questions = higher complexity
long questionCount = query.chars().filter(ch -> ch == '?').count();
score += (int) Math.min(questionCount, 3);
return Math.min(score, 10);
}
}Each scenario demonstrates:
β Proper CDI integration - Using @ApplicationScoped and @Inject
β Security validation - Guardrails for authorization and input validation
β Error handling - @HandleToolExecutionError for graceful degradation
β Memory management - Per-user or per-tenant conversation history
β Tool design - Clear descriptions and parameter naming
β Cost optimization - Smart model selection based on complexity
β Streaming support - Progressive response delivery
β Response augmentation - Adding citations and formatting