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

multi-agent-orchestration.mddocs/

Multi-Agent Orchestration

This document describes how to coordinate multiple agents using supervisor, sequence, parallel, loop, conditional, and planner patterns.

Capabilities

Supervisor Agent

A supervisor agent coordinates multiple sub-agents, delegating tasks and aggregating responses according to a specified strategy.

/**
 * Defines a supervisor agent that coordinates multiple sub-agents
 *
 * @param description - Description of the supervisor
 * @param outputKey - Key for output storage (optional)
 * @param subAgents - Array of sub-agent classes to coordinate
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SupervisorAgent {
    String description() default "";
    String outputKey() default "";
    Class<?>[] subAgents();
}

Usage Example:

// Define sub-agents with specific expertise
public interface WithdrawAgent {
    @SystemMessage("You are a banker that can only withdraw USD from a user account.")
    @UserMessage("Withdraw {{amountInUSD}} USD from {{withdrawUser}}'s account and return the new balance.")
    @Agent("A banker that withdraws USD from an account")
    String withdraw(@V("withdrawUser") String user, @V("amountInUSD") Double amount);
}

public interface CreditAgent {
    @SystemMessage("You are a banker that can only credit USD to a user account.")
    @UserMessage("Credit {{amountInUSD}} USD to {{creditUser}}'s account and return the new balance.")
    @Agent("A banker that credits USD to an account")
    String credit(@V("creditUser") String user, @V("amountInUSD") Double amount);
}

public interface ExchangeAgent {
    @UserMessage("""
        You are an operator exchanging money in different currencies.
        Use the tool to exchange {{amount}} {{originalCurrency}} into {{targetCurrency}}
        returning only the final amount provided by the tool and nothing else.
        """)
    @Agent(outputKey = "exchange", description = "Converts money between currencies")
    Double exchange(
        @V("originalCurrency") String originalCurrency,
        @V("amount") Double amount,
        @V("targetCurrency") String targetCurrency
    );
}

// Supervisor coordinates all banking operations
public interface BankingSupervisor {
    @SupervisorAgent(
        subAgents = { WithdrawAgent.class, CreditAgent.class, ExchangeAgent.class }
    )
    String handleBankingRequest(@V("request") String request);

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

// Usage
@Inject
BankingSupervisor supervisor;

String result = supervisor.handleBankingRequest(
    "Transfer $500 from Alice's account to Bob's account"
);

Supervisor Request Builder

Customize the initial request sent to the supervisor using @SupervisorRequest.

/**
 * Provides the initial request for a supervisor agent
 *
 * @return The request string to send to the supervisor
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SupervisorRequest {
}

// Applied to a static method
@SupervisorRequest
static String buildRequest(@V("param") Type param) {
    // Build and return custom request
}

Usage Example:

public interface StoryCreationSupervisor {
    @SupervisorAgent(
        outputKey = "story",
        subAgents = { CreativeWriter.class, StyleEditor.class }
    )
    ResultWithAgenticScope<String> createStory(@V("topic") String topic, @V("style") String style);

    @SupervisorRequest
    static String buildRequest(@V("topic") String topic, @V("style") String style) {
        return String.format(
            "Write a creative story about %s in %s style, then refine it for quality",
            topic, style
        );
    }

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

Sequence Agent

A sequence agent executes sub-agents in sequential order, passing outputs through the agentic scope.

/**
 * Defines an agent that executes sub-agents in sequential order
 *
 * @param description - Description of the sequence (optional)
 * @param outputKey - Key for final output (required)
 * @param subAgents - Array of sub-agent classes to execute in order
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SequenceAgent {
    String description() default "";
    String outputKey();
    Class<?>[] subAgents();
}

Usage Example:

// Define sequential steps
public interface ResearchAgent {
    @UserMessage("Research information about: {{topic}}")
    @Agent(description = "Researches a topic", outputKey = "research")
    String research(@V("topic") String topic);
}

public interface AnalysisAgent {
    @UserMessage("Analyze the following research and extract key insights: {{research}}")
    @Agent(description = "Analyzes research", outputKey = "analysis")
    String analyze(@V("research") String research);
}

public interface ReportAgent {
    @UserMessage("""
        Create a professional report based on the following analysis:
        {{analysis}}

        Format: {{format}}
        """)
    @Agent(description = "Generates a report", outputKey = "report")
    String generateReport(@V("analysis") String analysis, @V("format") String format);
}

// Compose them in a sequence
public interface ReportGenerator {
    @SequenceAgent(
        description = "Generates a research report",
        outputKey = "report",
        subAgents = { ResearchAgent.class, AnalysisAgent.class, ReportAgent.class }
    )
    ResultWithAgenticScope<String> generateReport(@V("topic") String topic, @V("format") String format);

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

// Usage
@Inject
ReportGenerator generator;

ResultWithAgenticScope<String> result = generator.generateReport("AI Safety", "PDF");
String report = result.result();
String research = result.agenticScope().readState("research");
String analysis = result.agenticScope().readState("analysis");

Parallel Agent

A parallel agent executes sub-agents concurrently, aggregating their results.

/**
 * Defines an agent that executes sub-agents in parallel
 *
 * @param description - Description of the parallel execution (optional)
 * @param outputKey - Key for output storage (optional)
 * @param subAgents - Array of sub-agent classes to execute in parallel
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ParallelAgent {
    String description() default "";
    String outputKey() default "";
    Class<?>[] subAgents();
}

Usage Example:

// Define independent analysis agents
public interface TechnicalAnalyzer {
    @UserMessage("Analyze technical aspects of: {{product}}")
    @Agent(description = "Technical analysis", outputKey = "technicalAnalysis")
    String analyzeTechnical(@V("product") String product);
}

public interface MarketAnalyzer {
    @UserMessage("Analyze market potential of: {{product}}")
    @Agent(description = "Market analysis", outputKey = "marketAnalysis")
    String analyzeMarket(@V("product") String product);
}

public interface CompetitorAnalyzer {
    @UserMessage("Analyze competitors for: {{product}}")
    @Agent(description = "Competitor analysis", outputKey = "competitorAnalysis")
    String analyzeCompetitors(@V("product") String product);
}

// Run analyses in parallel
public interface ProductAnalyzer {
    @ParallelAgent(
        description = "Comprehensive product analysis",
        outputKey = "analyses",
        subAgents = {
            TechnicalAnalyzer.class,
            MarketAnalyzer.class,
            CompetitorAnalyzer.class
        }
    )
    ResultWithAgenticScope<String> analyzeProduct(@V("product") String product);

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

// Usage
@Inject
ProductAnalyzer analyzer;

ResultWithAgenticScope<String> result = analyzer.analyzeProduct("Smart Watch X");
String technical = result.agenticScope().readState("technicalAnalysis");
String market = result.agenticScope().readState("marketAnalysis");
String competitor = result.agenticScope().readState("competitorAnalysis");

Loop Agent

A loop agent repeatedly executes sub-agents until an exit condition is met or max iterations is reached.

/**
 * Defines an agent that repeatedly executes sub-agents until condition is met
 *
 * @param description - Description of the loop (optional)
 * @param outputKey - Key for output storage (required)
 * @param maxIterations - Maximum number of loop iterations
 * @param subAgents - Array of sub-agent classes to execute in each iteration
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LoopAgent {
    String description() default "";
    String outputKey();
    int maxIterations();
    Class<?>[] subAgents();
}

Usage Example:

// Define evaluation and refinement agents
public interface QualityScorer {
    @UserMessage("""
        Score the quality of the following content on a scale of 0.0 to 1.0.
        Consider clarity, accuracy, and completeness.
        Respond with only the numeric score.

        Content: {{content}}
        """)
    @Agent(description = "Scores content quality", outputKey = "score")
    double scoreQuality(@V("content") String content);
}

public interface ContentRefiner {
    @UserMessage("""
        Improve the following content to address quality issues.
        Focus on clarity, accuracy, and completeness.

        Content: {{content}}
        """)
    @Agent(description = "Refines content", outputKey = "content")
    String refineContent(@V("content") String content);
}

// Loop until quality threshold is met
public interface ContentPolisher {
    @LoopAgent(
        description = "Refines content until quality threshold is met",
        outputKey = "content",
        maxIterations = 5,
        subAgents = { QualityScorer.class, ContentRefiner.class }
    )
    ResultWithAgenticScope<String> polishContent(@V("content") String initialContent);

    @ExitCondition
    static boolean isQualitySufficient(@V("score") double score) {
        return score >= 0.8;
    }

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

// Usage
@Inject
ContentPolisher polisher;

ResultWithAgenticScope<String> result = polisher.polishContent("Initial draft content...");
String polishedContent = result.result();
double finalScore = result.agenticScope().readState("score", 0.0);

Conditional Agent

A conditional agent executes sub-agents based on activation conditions.

/**
 * Defines an agent that conditionally executes sub-agents based on activation conditions
 *
 * @param description - Description (optional)
 * @param outputKey - Key for output storage (optional)
 * @param subAgents - Array of sub-agent classes with activation conditions
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ConditionalAgent {
    String description() default "";
    String outputKey() default "";
    Class<?>[] subAgents();
}

Usage Example:

public enum RequestCategory {
    MEDICAL, TECHNICAL, LEGAL, UNKNOWN
}

// Categorization agent
public interface RequestCategorizer {
    @UserMessage("""
        Analyze the following user request and categorize it as 'MEDICAL', 'TECHNICAL', or 'LEGAL'.
        If it doesn't fit, respond 'UNKNOWN'.
        Respond with only the category name.

        Request: {{request}}
        """)
    @Agent(description = "Categorizes requests", outputKey = "category")
    RequestCategory categorize(@V("request") String request);
}

// Expert agents
public interface MedicalExpert {
    @SystemMessage("You are a medical expert.")
    @UserMessage("Provide medical advice for: {{request}}")
    @Agent(description = "Medical expert", outputKey = "response")
    String provideMedicalAdvice(@V("request") String request);
}

public interface TechnicalExpert {
    @SystemMessage("You are a technical expert.")
    @UserMessage("Provide technical support for: {{request}}")
    @Agent(description = "Technical expert", outputKey = "response")
    String provideTechnicalSupport(@V("request") String request);
}

public interface LegalExpert {
    @SystemMessage("You are a legal expert.")
    @UserMessage("Provide legal guidance for: {{request}}")
    @Agent(description = "Legal expert", outputKey = "response")
    String provideLegalGuidance(@V("request") String request);
}

// Conditional routing
public interface ExpertRouter {
    @SequenceAgent(
        outputKey = "response",
        subAgents = { RequestCategorizer.class, ConditionalExpertAgent.class }
    )
    ResultWithAgenticScope<String> routeToExpert(@V("request") String request);
}

public interface ConditionalExpertAgent {
    @ConditionalAgent(
        outputKey = "response",
        subAgents = { MedicalExpert.class, TechnicalExpert.class, LegalExpert.class }
    )
    String consultExpert(@V("request") String request);

    @ActivationCondition(MedicalExpert.class)
    static boolean activateMedical(@V("category") RequestCategory category) {
        return category == RequestCategory.MEDICAL;
    }

    @ActivationCondition(TechnicalExpert.class)
    static boolean activateTechnical(@V("category") RequestCategory category) {
        return category == RequestCategory.TECHNICAL;
    }

    @ActivationCondition(LegalExpert.class)
    static boolean activateLegal(@V("category") RequestCategory category) {
        return category == RequestCategory.LEGAL;
    }

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

// Usage
@Inject
ExpertRouter router;

ResultWithAgenticScope<String> result = router.routeToExpert("I broke my leg, what should I do?");
String response = result.result();
RequestCategory category = result.agenticScope().readState("category");

Planner Agent

A planner agent creates and executes plans for complex workflows, dynamically orchestrating sub-agents.

/**
 * Defines a planner agent that creates and executes plans for complex workflows
 *
 * @param description - Description (optional)
 * @param outputKey - Key for output storage (optional)
 * @param subAgents - Array of sub-agent classes available to the planner
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface PlannerAgent {
    String description() default "";
    String outputKey() default "";
    Class<?>[] subAgents();
}

Usage Example:

// Define task-specific agents
public interface DataGatherer {
    @UserMessage("Gather data about: {{topic}}")
    @Agent(description = "Gathers relevant data", outputKey = "data")
    String gatherData(@V("topic") String topic);
}

public interface DataAnalyzer {
    @UserMessage("Analyze the following data: {{data}}")
    @Agent(description = "Analyzes data", outputKey = "analysis")
    String analyzeData(@V("data") String data);
}

public interface ReportWriter {
    @UserMessage("Write a report based on: {{analysis}}")
    @Agent(description = "Writes reports", outputKey = "report")
    String writeReport(@V("analysis") String analysis);
}

public interface DataVisualizer {
    @UserMessage("Create visualizations for: {{data}}")
    @Agent(description = "Creates visualizations", outputKey = "visualization")
    String createVisualization(@V("data") String data);
}

// Planner coordinates complex workflows
public interface TaskPlanner {
    @PlannerAgent(
        description = "Plans and executes complex data analysis tasks",
        outputKey = "result",
        subAgents = {
            DataGatherer.class,
            DataAnalyzer.class,
            ReportWriter.class,
            DataVisualizer.class
        }
    )
    ResultWithAgenticScope<String> executeTask(@V("task") String task);

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

// Usage - planner will determine which agents to use and in what order
@Inject
TaskPlanner planner;

ResultWithAgenticScope<String> result = planner.executeTask(
    "Analyze sales trends for Q4 2024 and create a visual dashboard"
);
String finalResult = result.result();

A2A Client Agent (Agent-to-Agent Communication)

An A2A client agent facilitates agent-to-agent communication following the A2A (Agent-to-Agent) protocol. This enables distributed agent systems where agents can invoke remote agents hosted on different servers or services.

/**
 * Defines an Agent-to-Agent (A2A) client agent
 * Enables communication with remote agents via A2A protocol
 *
 * @param description - Description of the agent (optional)
 * @param outputKey - Key for output storage (optional)
 * @param a2aServerUrl - URL of the A2A server hosting the remote agent (required)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface A2AClientAgent {
    String description() default "";
    String outputKey() default "";
    String a2aServerUrl();
}

Usage Example:

public interface RemoteStoryGenerator {
    @A2AClientAgent(
        a2aServerUrl = "http://story-service.example.com:8080",
        description = "Communicates with remote story generation service",
        outputKey = "story"
    )
    String generateStory(@V("topic") String topic, @V("style") String style);

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

// Usage in a multi-agent workflow
public interface DistributedContentCreator {
    @SequenceAgent(
        outputKey = "finalContent",
        subAgents = { RemoteStoryGenerator.class, LocalEditorAgent.class }
    )
    ResultWithAgenticScope<String> createContent(@V("topic") String topic, @V("style") String style);
}

// Usage
@Inject
RemoteStoryGenerator remoteAgent;

String story = remoteAgent.generateStory("space exploration", "sci-fi");

Combining Multiple Patterns

Complex workflows can combine multiple orchestration patterns.

Usage Example:

// Combine sequence, parallel, and loop patterns
public interface ComplexWorkflow {
    @SequenceAgent(
        outputKey = "finalResult",
        subAgents = {
            InitialProcessor.class,      // Step 1: Initial processing
            ParallelAnalysis.class,       // Step 2: Parallel analyses
            RefinementLoop.class,         // Step 3: Iterative refinement
            FinalSynthesis.class          // Step 4: Final synthesis
        }
    )
    ResultWithAgenticScope<String> executeWorkflow(@V("input") String input);
}

public interface ParallelAnalysis {
    @ParallelAgent(
        outputKey = "analyses",
        subAgents = { TechAnalyzer.class, BusinessAnalyzer.class, RiskAnalyzer.class }
    )
    String performAnalyses(@V("processedInput") String processedInput);
}

public interface RefinementLoop {
    @LoopAgent(
        description = "Refine results iteratively",
        outputKey = "refinedResult",
        maxIterations = 3,
        subAgents = { Evaluator.class, Refiner.class }
    )
    String refineResults(@V("analyses") String analyses);

    @ExitCondition
    static boolean isRefined(@V("quality") double quality) {
        return quality >= 0.9;
    }
}

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