CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-agentic

LangChain4j Agentic Framework provides a comprehensive Java library for building multi-agent AI systems with support for workflow orchestration, supervisor agents, planning-based execution, declarative configuration, agent-to-agent communication, and human-in-the-loop workflows.

Overview
Eval results
Files

conditional.mddocs/workflows/

Conditional Workflows

Execute agents conditionally based on runtime predicates for dynamic branching and selective execution.

Overview

Conditional workflows enable:

  • Dynamic routing based on state
  • Feature flags and A/B testing
  • Permission-based execution
  • Environment-specific behavior
  • Multi-branch if-else-if patterns

Factory Methods

static ConditionalAgentService<UntypedAgent> conditionalBuilder();
static <T> ConditionalAgentService<T> conditionalBuilder(Class<T> agentServiceClass);

Quick Start:

UntypedAgent conditional = AgenticServices.conditionalBuilder()
    .subAgents(
        scope -> "premium".equals(scope.readState("user_tier")),
        premiumAgent
    )
    .build();

Configuration

Adding Conditional Branches

ConditionalAgentService<T> subAgents(Predicate<AgenticScope> condition, Object... agents);
ConditionalAgentService<T> subAgents(String description, Predicate<AgenticScope> condition, Object... agents);
ConditionalAgentService<T> subAgent(Predicate<AgenticScope> condition, AgentExecutor agent);
ConditionalAgentService<T> subAgent(String description, Predicate<AgenticScope> condition, AgentExecutor agent);

Single branch:

.subAgents(
    scope -> (Boolean) scope.readState("feature_enabled", false),
    featureAgent
)

Multiple branches (if-else-if):

.subAgents(
    "Premium tier check",
    scope -> "premium".equals(scope.readState("user_tier")),
    premiumAnalyzer
)
.subAgents(
    "Standard tier check",
    scope -> "standard".equals(scope.readState("user_tier")),
    standardAnalyzer
)
.subAgents(
    "Basic tier fallback",
    scope -> true,  // Always execute if previous conditions failed
    basicAnalyzer
)

Fallback Strategies

ConditionalAgentService<T> fallbackValue(Object fallbackValue);
ConditionalAgentService<T> fallback(Function<AgenticScope, Object> fallbackFunction);

Static fallback:

.fallbackValue("Feature not available")

Dynamic fallback:

.fallback(scope -> {
    String tier = (String) scope.readState("user_tier");
    if ("premium".equals(tier)) {
        return premiumAgent.invoke(scope);
    }
    return "Upgrade required";
})

Execution Flow

  1. Condition predicate evaluated with current AgenticScope
  2. If condition returns true:
    • All sub-agents invoked
    • Results processed normally
  3. If condition returns false:
    • Sub-agents skipped
    • Fallback value/function used
    • If no fallback, returns null

Common Patterns

Feature Flags

interface FeatureFlaggedSystem {
    @ConditionalAgent(
        name = "experimental-feature",
        conditionMethod = "isFeatureEnabled",
        fallbackMethod = "legacyImplementation",
        subAgents = {ExperimentalFeature.class}
    )
    String processWithFeature(String input);

    default boolean isFeatureEnabled(AgenticScope scope) {
        String userId = (String) scope.readState("user_id");
        return featureFlagService.isEnabled("new-algorithm", userId);
    }

    default String legacyImplementation(AgenticScope scope) {
        String input = (String) scope.readState("input");
        return legacyProcessor.process(input);
    }
}

A/B Testing

UntypedAgent abTest = AgenticServices.conditionalBuilder()
    .condition(scope -> {
        String userId = (String) scope.readState("user_id");
        int bucket = Math.abs(userId.hashCode()) % 100;
        return bucket < 50; // 50% in variant A
    })
    .subAgents(variantA)
    .fallback(scope -> variantB.invoke(scope))
    .listener(experimentTrackingListener)
    .build();

Permission-Based Execution

interface SecureSystem {
    @ConditionalAgent(
        name = "admin-operation",
        conditionMethod = "hasAdminPermission",
        fallbackMethod = "accessDenied",
        subAgents = {AdminOperationAgent.class}
    )
    OperationResult performAdminOperation(String operation);

    default boolean hasAdminPermission(AgenticScope scope) {
        User user = (User) scope.readState("current_user");
        return user != null && user.hasRole("ADMIN");
    }

    default OperationResult accessDenied(AgenticScope scope) {
        return new OperationResult(false, "Access denied: admin role required");
    }
}

Environment-Specific Behavior

UntypedAgent envConditional = AgenticServices.conditionalBuilder()
    .condition(scope -> {
        String env = (String) scope.readState("environment");
        return "production".equals(env);
    })
    .subAgents(productionAgent)
    .fallback(scope -> {
        String env = (String) scope.readState("environment");
        if ("staging".equals(env)) {
            return stagingAgent.invoke(scope);
        }
        return developmentAgent.invoke(scope);
    })
    .beforeCall(scope -> {
        String env = System.getenv("APP_ENV");
        scope.writeState("environment", env != null ? env : "development");
    })
    .build();

Complex Multi-Factor Conditions

UntypedAgent smartRouter = AgenticServices.conditionalBuilder()
    .condition(scope -> {
        // Multiple factors
        String tier = (String) scope.readState("user_tier");
        boolean isPremium = "premium".equals(tier) || "enterprise".equals(tier);

        int dataSize = (Integer) scope.readState("data_size");
        boolean isLarge = dataSize > 1000;

        int hour = LocalTime.now().getHour();
        boolean isOffPeak = hour < 8 || hour > 20;

        boolean hasResources = resourceManager.checkAvailability("gpu");

        return isPremium && isLarge && (isOffPeak || hasResources);
    })
    .subAgents(intensiveProcessor)
    .fallback(scope -> {
        int dataSize = (Integer) scope.readState("data_size");
        if (dataSize > 1000) {
            jobQueue.enqueue(scope);
            return "Job queued for off-peak processing";
        }
        return standardProcessor.invoke(scope);
    })
    .build();

Activation Conditions for Individual Agents

Use @ActivationCondition to conditionally activate agents within larger workflows:

Example:

interface DataPipeline {
    @SequenceAgent(
        name = "data-pipeline",
        subAgents = {DataFetcher.class, DataEnricher.class, DataValidator.class}
    )
    String processPipeline(String input);

    default boolean shouldEnrichData(AgenticScope scope) {
        boolean enabled = (Boolean) scope.readState("enable_enrichment");
        String dataType = (String) scope.readState("data_type");
        return enabled && "customer".equals(dataType);
    }
}

interface DataEnricher {
    @Agent(name = "enricher", outputKey = "enriched_data")
    @ActivationCondition(conditionMethod = "shouldEnrichData")
    String enrichData(AgenticScope scope) {
        String data = (String) scope.readState("raw_data");
        return enrichedData;
    }
}

Declarative API

Example:

interface SmartProcessingSystem {
    @ConditionalAgent(
        name = "smart-validator",
        conditionMethod = "shouldUseAdvancedValidation",
        fallbackMethod = "basicValidation",
        subAgents = {AdvancedValidator.class}
    )
    ValidationResult validate(String data);

    default boolean shouldUseAdvancedValidation(AgenticScope scope) {
        String data = (String) scope.readState("input_data");
        int complexity = calculateComplexity(data);
        boolean isPremium = (Boolean) scope.readState("is_premium_user");
        return complexity > 10 && isPremium;
    }

    default ValidationResult basicValidation(AgenticScope scope) {
        String data = (String) scope.readState("input_data");
        return new ValidationResult(true, "Basic validation passed");
    }
}

Error Handling

UntypedAgent conditional = AgenticServices.conditionalBuilder()
    .condition(scope -> (Boolean) scope.readState("should_execute"))
    .subAgents(riskyAgent)
    .errorHandler(errorContext -> {
        AgenticScope scope = errorContext.agenticScope();
        Integer retryCount = scope.readState("retry_count", 0);

        if (retryCount < 3) {
            scope.writeState("retry_count", retryCount + 1);
            return new ErrorRecoveryResult("retry", true);
        }
        return new ErrorRecoveryResult("max_retries_exceeded", false);
    })
    .fallback(scope -> safeAgent.invoke(scope))
    .build();

See Also

  • Loop Workflows - Iterative execution
  • Sequential Workflows - Sequential execution
  • Declarative Annotations - All workflow annotations

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-agentic

docs

index.md

tile.json