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

lifecycle-control.mddocs/

Lifecycle and Control Flow

This document describes how to control agent execution with activation conditions, exit conditions, error handlers, and human-in-the-loop integration.

Capabilities

Activation Condition

Defines a condition for activating a specific sub-agent in conditional execution.

/**
 * Defines activation condition for a sub-agent in @ConditionalAgent
 * Method must be static and return boolean
 *
 * @param subAgentClass - The sub-agent class this condition applies to
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ActivationCondition {
    Class<?> value();
}

// Usage
@ActivationCondition(MySubAgent.class)
static boolean shouldActivate(@V("condition") ConditionType condition) {
    return condition == ConditionType.EXPECTED;
}

Usage Example:

public enum Priority {
    LOW, MEDIUM, HIGH, CRITICAL
}

public interface LowPriorityAgent {
    @Agent(description = "Handles low priority tasks", outputKey = "result")
    String handle(@V("task") String task);
}

public interface HighPriorityAgent {
    @Agent(description = "Handles high priority tasks", outputKey = "result")
    String handle(@V("task") String task);
}

public interface CriticalPriorityAgent {
    @Agent(description = "Handles critical tasks", outputKey = "result")
    String handle(@V("task") String task);
}

public interface PriorityRouter {
    @ConditionalAgent(
        outputKey = "result",
        subAgents = { LowPriorityAgent.class, HighPriorityAgent.class, CriticalPriorityAgent.class }
    )
    String route(@V("task") String task, @V("priority") Priority priority);

    @ActivationCondition(LowPriorityAgent.class)
    static boolean activateLow(@V("priority") Priority priority) {
        return priority == Priority.LOW || priority == Priority.MEDIUM;
    }

    @ActivationCondition(HighPriorityAgent.class)
    static boolean activateHigh(@V("priority") Priority priority) {
        return priority == Priority.HIGH;
    }

    @ActivationCondition(CriticalPriorityAgent.class)
    static boolean activateCritical(@V("priority") Priority priority) {
        return priority == Priority.CRITICAL;
    }

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

Exit Condition

Defines an exit condition for loop agents to determine when to stop iteration.

/**
 * Defines exit condition for @LoopAgent
 * Method must be static and return boolean
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ExitCondition {
}

// Usage
@ExitCondition
static boolean shouldExit(@V("metric") double metric) {
    return metric >= threshold;
}

Usage Example:

public interface QualityChecker {
    @UserMessage("Rate the quality of this content (0.0-1.0): {{content}}")
    @Agent(description = "Checks content quality", outputKey = "qualityScore")
    double checkQuality(@V("content") String content);
}

public interface ContentImprover {
    @UserMessage("Improve the following content: {{content}}")
    @Agent(description = "Improves content", outputKey = "content")
    String improveContent(@V("content") String content);
}

public interface IterativeImprover {
    @LoopAgent(
        description = "Iteratively improves content until quality threshold is met",
        outputKey = "content",
        maxIterations = 10,
        subAgents = { QualityChecker.class, ContentImprover.class }
    )
    ResultWithAgenticScope<String> improve(@V("content") String initialContent);

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

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

// Usage
@Inject
IterativeImprover improver;

ResultWithAgenticScope<String> result = improver.improve("Initial draft...");
String improvedContent = result.result();
double finalScore = result.agenticScope().readState("qualityScore", 0.0);
int iterations = result.agenticScope().readState("iterations", 0);

Error Handler

Handles errors during agent execution and determines recovery strategy.

/**
 * Handles errors during agent execution
 * Method must be static
 * Parameter type must be ErrorContext
 * Return type must be ErrorRecoveryResult
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface ErrorHandler {
}

// Usage
@ErrorHandler
static ErrorRecoveryResult handleError(ErrorContext context) {
    // Inspect error and determine recovery strategy
    if (shouldRetry) {
        return ErrorRecoveryResult.retry();
    }
    return ErrorRecoveryResult.throwException();
}

Usage Example:

import dev.langchain4j.agentic.agent.ErrorContext;
import dev.langchain4j.agentic.agent.ErrorRecoveryResult;
import dev.langchain4j.agentic.agent.MissingArgumentException;

public interface ResilientAgent {
    @Agent(description = "Processes data with error recovery", outputKey = "result")
    String processData(@V("data") String data, @V("options") String options);

    @ErrorHandler
    static ErrorRecoveryResult handleError(ErrorContext context) {
        System.err.println("Error in agent: " + context.agentName());
        System.err.println("Exception: " + context.exception().getMessage());

        // Handle missing argument by providing default
        if (context.exception() instanceof MissingArgumentException mEx) {
            String argName = mEx.argumentName();
            if ("options".equals(argName)) {
                context.agenticScope().writeState("options", "default");
                return ErrorRecoveryResult.retry();
            }
        }

        // Handle timeout by retrying once
        if (context.exception() instanceof TimeoutException) {
            Integer retryCount = context.agenticScope().readState("retryCount", 0);
            if (retryCount < 1) {
                context.agenticScope().writeState("retryCount", retryCount + 1);
                return ErrorRecoveryResult.retry();
            }
        }

        // For other errors, throw exception
        return ErrorRecoveryResult.throwException();
    }

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

Human-in-the-Loop

Marks a point where human intervention or approval is required.

/**
 * Marks a point requiring human intervention
 * Method must be static with void return type
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface HumanInTheLoop {
}

// Usage
@HumanInTheLoop
static void requestHumanReview(@V("data") DataType data) {
    // Notify human, log request, etc.
}

Usage Example:

public interface ReviewWorkflow {
    @Agent(description = "Generates content for review", outputKey = "draft")
    String generateDraft(@V("topic") String topic);

    @HumanInTheLoop
    static void requestHumanReview(@V("draft") String draft, @V("topic") String topic) {
        System.out.println("=== Human Review Required ===");
        System.out.println("Topic: " + topic);
        System.out.println("Draft: " + draft);
        System.out.println("Please review and approve/reject");
        // In production: send to review queue, notify reviewer, etc.
    }

    @HumanInTheLoopResponseSupplier
    static String getReviewResponse() {
        // Fetch human response from queue, UI, etc.
        // For demo purposes, auto-approve
        return "approved";
    }

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

Output Handler

Marks a method as handling agent output for custom processing.

/**
 * Marks a method as an output handler
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Output {
}

// Usage
@Output
void processOutput(@V("result") String result) {
    // Custom output handling
}

Usage Example:

public interface DataPipeline {
    @Agent(description = "Processes data", outputKey = "processed")
    String process(@V("input") String input);

    @Output
    void handleOutput(@V("processed") String processed, @V("input") String original) {
        System.out.println("Original: " + original);
        System.out.println("Processed: " + processed);
        // Additional processing: save to database, send notification, etc.
    }

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

Supervisor Request Builder

Customizes the initial request sent to a supervisor agent.

/**
 * Provides the initial request for a supervisor agent
 * Method must be static and return String
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface SupervisorRequest {
}

// Usage
@SupervisorRequest
static String buildRequest(@V("param") Type param) {
    return "Custom request: " + param;
}

Usage Example:

public interface ContentCreationSupervisor {
    @SupervisorAgent(
        outputKey = "content",
        responseStrategy = SupervisorResponseStrategy.SUMMARY,
        subAgents = { Researcher.class, Writer.class, Editor.class }
    )
    ResultWithAgenticScope<String> createContent(
        @V("topic") String topic,
        @V("audience") String audience,
        @V("tone") String tone
    );

    @SupervisorRequest
    static String buildRequest(
        @V("topic") String topic,
        @V("audience") String audience,
        @V("tone") String tone
    ) {
        return String.format(
            "Create comprehensive content about '%s' for %s audience with %s tone. " +
            "Ensure thorough research, engaging writing, and careful editing.",
            topic, audience, tone
        );
    }

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

Complex Workflow Example

Combining multiple lifecycle and control mechanisms:

public interface ComplexWorkflowAgent {
    @SequenceAgent(
        outputKey = "finalResult",
        subAgents = { DataCollector.class, ConditionalProcessor.class, IterativeRefiner.class }
    )
    ResultWithAgenticScope<String> executeWorkflow(
        @V("input") String input,
        @V("mode") String mode
    );

    @ErrorHandler
    static ErrorRecoveryResult handleError(ErrorContext context) {
        // Global error handling for the workflow
        if (context.exception() instanceof ValidationException) {
            // Log and provide default values
            context.agenticScope().writeState("validated", false);
            return ErrorRecoveryResult.retry();
        }
        return ErrorRecoveryResult.throwException();
    }

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

public interface ConditionalProcessor {
    @ConditionalAgent(
        outputKey = "processed",
        subAgents = { FastProcessor.class, ThoroughProcessor.class }
    )
    String process(@V("data") String data, @V("mode") String mode);

    @ActivationCondition(FastProcessor.class)
    static boolean useFastProcessor(@V("mode") String mode) {
        return "fast".equalsIgnoreCase(mode);
    }

    @ActivationCondition(ThoroughProcessor.class)
    static boolean useThoroughProcessor(@V("mode") String mode) {
        return "thorough".equalsIgnoreCase(mode);
    }
}

public interface IterativeRefiner {
    @LoopAgent(
        description = "Refines output iteratively",
        outputKey = "refined",
        maxIterations = 5,
        subAgents = { QualityEvaluator.class, Refiner.class }
    )
    String refine(@V("processed") String processed);

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

    @HumanInTheLoop
    static void reviewIfNeeded(@V("refined") String refined, @V("quality") double quality) {
        if (quality < 0.95) {
            System.out.println("Quality below optimal, human review recommended");
        }
    }
}

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