CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-agentic

Quarkus extension that integrates LangChain4j's agentic capabilities, enabling developers to build AI agent-based applications using declarative patterns with support for multiple agent types, agent-to-agent communication, and CDI integration.

Overview
Eval results
Files

agent-definition.mddocs/

Agent Definition

This document describes how to define basic AI agents using the @Agent annotation and related LangChain4j service annotations.

Capabilities

Basic Agent Declaration

Define a basic AI agent using the @Agent annotation on an interface method.

/**
 * Defines a basic AI agent
 *
 * @param value - Description of the agent (alias for description)
 * @param description - Description of the agent's purpose
 * @param outputKey - Key under which the agent's output is stored in AgenticScope (optional)
 * @return The agent's response
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Agent {
    String value() default "";
    String description() default "";
    String outputKey() default "";
}

Usage Example:

public interface CustomerSupportAgent {
    @Agent(description = "Handles customer support inquiries", outputKey = "response")
    String handleInquiry(@V("inquiry") String inquiry, @V("customerType") String customerType);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

User Message Template

Define the user message template that the agent will receive. Use {{variableName}} placeholders for variables passed via @V annotated parameters.

/**
 * Defines the user message template for an agent method
 *
 * @param value - The template string with {{variable}} placeholders
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface UserMessage {
    String value();
}

Usage Example:

public interface TranslationAgent {
    @UserMessage("""
        Translate the following text from {{sourceLanguage}} to {{targetLanguage}}.
        Maintain the original tone and style.

        Text to translate: {{text}}
        """)
    @Agent(description = "Translates text between languages", outputKey = "translation")
    String translate(
        @V("text") String text,
        @V("sourceLanguage") String sourceLang,
        @V("targetLanguage") String targetLang
    );

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

System Message

Define a system message that provides context and instructions to the AI model about its role and behavior.

/**
 * Defines the system message for an agent
 *
 * @param value - The system message content
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SystemMessage {
    String value();
}

Usage Example:

public interface CodeReviewAgent {
    @SystemMessage("""
        You are an experienced software engineer specialized in code reviews.
        Your task is to review code for best practices, potential bugs, security issues,
        and suggest improvements. Be constructive and specific in your feedback.
        """)
    @UserMessage("""
        Review the following {{language}} code and provide detailed feedback:

        ```{{language}}
        {{code}}
        ```
        """)
    @Agent(description = "Reviews code for quality and issues", outputKey = "review")
    String reviewCode(@V("code") String code, @V("language") String language);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

Variable Parameter Binding

Bind method parameters to template variables using the @V annotation.

/**
 * Marks a method parameter as a variable accessible in templates and agentic scope
 *
 * @param value - The variable name used in templates
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface V {
    String value();
}

Usage Example:

public interface SentimentAnalyzer {
    @UserMessage("Analyze the sentiment of the following text and classify it as positive, negative, or neutral: {{text}}")
    @Agent(description = "Analyzes text sentiment", outputKey = "sentiment")
    String analyzeSentiment(@V("text") String text);
}

// Usage
@Inject
SentimentAnalyzer analyzer;

String result = analyzer.analyzeSentiment("I absolutely love this product!");

Tool Integration

Agents can use tools (external functions) to extend their capabilities. Tools are defined using the @Tool annotation on methods.

/**
 * Marks a method as a tool that agents can use
 *
 * @param value - Description of what the tool does
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Tool {
    String value() default "";
    String name() default "";
}

Usage Example:

@ApplicationScoped
public class CalculatorTools {
    @Tool("Adds two numbers together")
    public int add(int a, int b) {
        return a + b;
    }

    @Tool("Multiplies two numbers together")
    public int multiply(int a, int b) {
        return a * b;
    }
}

public interface MathAgent {
    @SystemMessage("You are a math assistant. Use the provided tools to perform calculations.")
    @UserMessage("{{question}}")
    @Agent(description = "Answers math questions using tools", outputKey = "answer")
    String solveMath(@V("question") String question);

    @ToolsSupplier
    static Object[] tools() {
        return new Object[] { new CalculatorTools() };
    }

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

Agent on Class Methods

The @Agent annotation can also be used on class methods (not just interface methods), useful for non-AI agents or agents with custom logic.

/**
 * Agent annotation can be applied to class methods as well
 */
@Agent(value = "Description", outputKey = "key")
public ReturnType methodName() {
    // Implementation
}

Usage Example:

@ApplicationScoped
public class DataProcessorAgent {
    @Inject
    Logger logger;

    @Agent(value = "Processes and normalizes data", outputKey = "processedData")
    public String processData(@V("rawData") String rawData) {
        logger.info("Processing data");
        // Custom processing logic
        return rawData.trim().toLowerCase();
    }
}

Return Types

Agents can return various types depending on the use case:

// Simple string return
@Agent
String processText(@V("input") String input);

// Structured object return
@Agent
CustomerResponse handleCustomer(@V("request") String request);

// Primitive types
@Agent
int calculateScore(@V("data") String data);

@Agent
double evaluateQuality(@V("content") String content);

@Agent
boolean isValid(@V("input") String input);

// Return with scope access
@Agent
ResultWithAgenticScope<String> processWithScope(@V("input") String input);

// Enums for classification
@Agent
SentimentType classifySentiment(@V("text") String text);

Usage Example:

public enum RequestCategory {
    TECHNICAL, SALES, SUPPORT, BILLING
}

public interface RequestClassifier {
    @UserMessage("""
        Classify the following customer request into one of these categories:
        TECHNICAL, SALES, SUPPORT, BILLING

        Request: {{request}}

        Respond with only the category name.
        """)
    @Agent(description = "Classifies customer requests", outputKey = "category")
    RequestCategory classify(@V("request") String request);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

Multiple Methods in One Agent Interface

An agent interface can define multiple agent methods, each with its own behavior.

/**
 * Multiple @Agent methods can exist in the same interface
 */
public interface MultiMethodAgent {
    @Agent
    String methodOne(@V("input1") String input);

    @Agent
    String methodTwo(@V("input2") String input);
}

Usage Example:

public interface ContentAgent {
    @UserMessage("Write a {{length}} summary of: {{text}}")
    @Agent(description = "Summarizes text", outputKey = "summary")
    String summarize(@V("text") String text, @V("length") String length);

    @UserMessage("Extract {{count}} key points from: {{text}}")
    @Agent(description = "Extracts key points", outputKey = "keyPoints")
    String extractKeyPoints(@V("text") String text, @V("count") int count);

    @UserMessage("Translate the following text to {{language}}: {{text}}")
    @Agent(description = "Translates text", outputKey = "translation")
    String translate(@V("text") String text, @V("language") String language);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

Injecting Agents

Agents defined as interfaces are automatically available as CDI beans and can be injected using @Inject.

/**
 * Inject agent interfaces using standard CDI injection
 */
@Inject
AgentInterface agentField;

Usage Example:

@ApplicationScoped
public class CustomerService {
    @Inject
    CustomerSupportAgent supportAgent;

    @Inject
    SentimentAnalyzer sentimentAnalyzer;

    public CustomerResponse handleCustomer(String inquiry) {
        // Analyze sentiment first
        String sentiment = sentimentAnalyzer.analyzeSentiment(inquiry);

        // Handle based on sentiment
        String response = supportAgent.handleInquiry(inquiry, sentiment);

        return new CustomerResponse(response, sentiment);
    }
}

Agent with RAG (Retrieval Augmented Generation)

Agents can be enhanced with content retrieval for RAG use cases.

Usage Example:

public interface KnowledgeAgent {
    @SystemMessage("You are a knowledge assistant. Use the retrieved context to answer questions accurately.")
    @UserMessage("Question: {{question}}")
    @Agent(description = "Answers questions using knowledge base", outputKey = "answer")
    String answer(@V("question") String question);

    @ContentRetrieverSupplier
    static ContentRetriever contentRetriever() {
        // Set up vector store and retriever
        EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

        return new EmbeddingStoreContentRetriever(embeddingStore, embeddingModel);
    }

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

MCP ToolBox Integration

Agents can use tools from MCP (Model Context Protocol) servers via the @McpToolBox annotation.

/**
 * Enables agent methods to use tools from MCP servers
 * When applied, the method will use tools from specified MCP servers
 * If no servers are specified, uses all available MCP servers
 *
 * @param value - Names of MCP servers to use (optional, defaults to all servers)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface McpToolBox {
    String[] value() default {};
}

Package: io.quarkiverse.langchain4j.mcp.runtime

Important Limitation: Currently, @McpToolBox can only be used on agents with a single method. This restriction will be lifted in future versions.

Usage Example:

import io.quarkiverse.langchain4j.mcp.runtime.McpToolBox;

// Agent with single method using all MCP servers
public interface FileSystemAgent {
    @McpToolBox  // Uses all configured MCP servers
    @UserMessage("{{request}}")
    @Agent(description = "Performs file system operations", outputKey = "result")
    String execute(@V("request") String request);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

// Agent using specific MCP servers
public interface DataProcessorAgent {
    @McpToolBox({"database-server", "api-server"})  // Uses only named servers
    @UserMessage("Process this data: {{data}}")
    @Agent(description = "Processes data using MCP tools", outputKey = "result")
    String processData(@V("data") String data);

    @ChatModelSupplier
    static ChatModel chatModel() {
        return new OpenAiChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName("gpt-4")
            .build();
    }
}

// Usage
@Inject
FileSystemAgent fsAgent;

String result = fsAgent.execute("List all files in /tmp directory");

Configuration: MCP servers must be configured in application.properties:

# Enable MCP ToolProvider generation (default: true)
quarkus.langchain4j.mcp.generate-tool-provider=true

# Configure MCP servers
quarkus.langchain4j.mcp.servers.database-server.url=http://localhost:8081
quarkus.langchain4j.mcp.servers.api-server.url=http://localhost:8082

Install with Tessl CLI

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

docs

agent-definition.md

error-handling.md

index.md

lifecycle-control.md

memory-parameters.md

multi-agent-orchestration.md

resource-suppliers.md

runtime-support.md

scope-state.md

tile.json