• Uncategorised

Implementing LangGraph4j real world use case

Here is the java code showing the real world use case of using LangGraph in Java applications

package ai;

import org.bsc.langgraph4j.*;
import org.bsc.langgraph4j.checkpoint.MemorySaver;
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.StateSnapshot;

import java.util.*;
import java.util.concurrent.CompletableFuture;

import static org.bsc.langgraph4j.StateGraph.END;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.utils.CollectionsUtils.mapOf;

/**
 * Simple LangGraph4j Example: Payment Approval Workflow with Checkpointing
 * 
 * This demonstrates:
 * - Using plain HashMaps (no custom state classes)
 * - Conditional routing
 * - Real checkpointing with pause/resume using thread_id
 * - Human-in-the-loop approval pattern
 */
public class VerySimpleLangGraphExample {
    
    // Use interrupt() to pause the workflow at this node
    private static final String APPROVAL_NODE = "wait_approval";

    /**
     * Node 1: Validate the payment request
     */
    private static Map<String, Object> validatePayment(AgentState state) {
        int amount = (int) state.data().getOrDefault("amount", 0);
        String vendor = (String) state.data().getOrDefault("vendor", "Unknown");
        
        System.out.println("\n[VALIDATE] Checking payment request:");
        System.out.println("  Amount: $" + amount);
        System.out.println("  Vendor: " + vendor);
        
        // Basic validation
        boolean valid = amount > 0 && amount <= 100000;
        
        Map<String, Object> updates = new HashMap<>();
        updates.put("valid", valid);
        updates.put("status", valid ? "validated" : "rejected");
        
        if (!valid) {
            updates.put("reason", "Amount must be between $1 and $100,000");
        }
        
        System.out.println("  Result: " + (valid ? "✓ Valid" : "✗ Invalid"));
        return updates;
    }

    /**
     * Node 2: Auto-approve small payments
     */
    private static Map<String, Object> autoApprove(AgentState state) {
        int amount = (int) state.data().getOrDefault("amount", 0);
        
        System.out.println("\n[AUTO-APPROVE] Processing small payment: $" + amount);
        
        Map<String, Object> updates = new HashMap<>();
        updates.put("approved", true);
        updates.put("approver", "System");
        updates.put("status", "auto_approved");
        
        System.out.println("  Result: ✓ Auto-approved (under $1000)");
        return updates;
    }

    /**
     * Node 3: Wait for manager approval
     */
    private static Map<String, Object> waitForApproval(AgentState state) {
        int amount = (int) state.data().getOrDefault("amount", 0);
        boolean approved = (boolean) state.data().getOrDefault("approved", false);
        
        System.out.println("\n[APPROVAL] Large payment requires manager approval:");
        System.out.println("  Amount: $" + amount);
        System.out.println("  Status: " + (approved ? "Approved by manager" : "Waiting..."));
        
        Map<String, Object> updates = new HashMap<>();
        
        if (!approved) {
            updates.put("status", "pending_approval");
            System.out.println("  Action: Workflow paused, waiting for manager");
        } else {
            updates.put("status", "approved");
            updates.put("approver", "Manager");
            System.out.println("  Result: ✓ Approved by manager");
        }
        
        return updates;
    }

    /**
     * Node 4: Process the payment
     */
    private static Map<String, Object> processPayment(AgentState state) {
        int amount = (int) state.data().getOrDefault("amount", 0);
        String vendor = (String) state.data().getOrDefault("vendor", "Unknown");
        String approver = (String) state.data().getOrDefault("approver", "Unknown");
        
        System.out.println("\n[PROCESS] Processing payment:");
        System.out.println("  Amount: $" + amount);
        System.out.println("  Vendor: " + vendor);
        System.out.println("  Approved by: " + approver);
        
        // Simulate payment processing
        String transactionId = "TXN-" + System.currentTimeMillis();
        
        Map<String, Object> updates = new HashMap<>();
        updates.put("status", "completed");
        updates.put("transactionId", transactionId);
        updates.put("processed", true);
        
        System.out.println("  Result: ✓ Payment processed successfully");
        System.out.println("  Transaction ID: " + transactionId);
        return updates;
    }

    /**
     * Node 5: Reject payment
     */
    private static Map<String, Object> rejectPayment(AgentState state) {
        String reason = (String) state.data().getOrDefault("reason", "Invalid request");
        
        System.out.println("\n[REJECT] Payment rejected:");
        System.out.println("  Reason: " + reason);
        
        Map<String, Object> updates = new HashMap<>();
        updates.put("status", "rejected");
        updates.put("processed", false);
        
        return updates;
    }

    /**
     * Routing: Check if payment is valid
     */
    private static String routeAfterValidation(AgentState state) {
        boolean valid = (boolean) state.data().getOrDefault("valid", false);
        return valid ? "check_amount" : "reject";
    }

    /**
     * Routing: Check if payment needs approval
     */
    private static String routeByAmount(AgentState state) {
        int amount = (int) state.data().getOrDefault("amount", 0);
        boolean approved = (boolean) state.data().getOrDefault("approved", false);
        
        if (amount < 1000) {
            return "auto_approve";
        } else {
            // If already approved, go to process, else wait for approval
            return approved ? "process" : "wait_approval";
        }
    }


    /**
     * Build the payment approval workflow
     */
    public static CompiledGraph<AgentState> buildPaymentWorkflow() throws Exception {
        
        StateGraph<AgentState> workflow = new StateGraph<>(AgentState::new);
        
        // Add all nodes
        workflow.addNode("validate", state -> CompletableFuture.completedFuture(validatePayment(state)));
        workflow.addNode("auto_approve", state -> CompletableFuture.completedFuture(autoApprove(state)));
        workflow.addNode("wait_approval", state -> CompletableFuture.completedFuture(waitForApproval(state)));
        workflow.addNode("process", state -> CompletableFuture.completedFuture(processPayment(state)));
        workflow.addNode("reject", state -> CompletableFuture.completedFuture(rejectPayment(state)));
        
        // Define workflow edges
        workflow.addEdge(START, "validate");
        
        // After validation: go to check_amount or reject
        workflow.addConditionalEdges(
            "validate",
            state -> CompletableFuture.completedFuture(routeAfterValidation(state)),
            Map.of(
                "check_amount", "check_amount",
                "reject", "reject"
            )
        );
        
        // Dummy node for routing
        workflow.addNode("check_amount", state -> CompletableFuture.completedFuture(new HashMap<>()));
        
        // Route by amount
        workflow.addConditionalEdges(
            "check_amount",
            state -> CompletableFuture.completedFuture(routeByAmount(state)),
            Map.of(
                "auto_approve", "auto_approve",
                "wait_approval", "wait_approval",
                "process", "process"
            )
        );
        
        // After wait_approval: always go to process
        // (we interrupt after wait_approval, so it only continues when approved)
        workflow.addEdge("wait_approval", "process");
        
        // Terminal nodes
        workflow.addEdge("auto_approve", "process");
        workflow.addEdge("process", END);
        workflow.addEdge("reject", END);
        
        // Compile WITH MemorySaver for checkpointing
        // Use interruptAfter to pause AFTER wait_approval node
        // This allows the node to execute and show "Waiting..." status
        CompileConfig config = CompileConfig.builder()
            .checkpointSaver(new MemorySaver())
            .interruptAfter("wait_approval")  // Pause AFTER executing this node
            .build();
        
        return workflow.compile(config);
    }

    public static void main(String[] args) {
        try {
            System.out.println("╔════════════════════════════════════════════════════════════╗");
            System.out.println("║      Payment Approval Workflow with Checkpointing         ║");
            System.out.println("╚════════════════════════════════════════════════════════════╝");
            
            CompiledGraph<AgentState> app = buildPaymentWorkflow();
            List<NodeOutput<AgentState>> outputs = new ArrayList<>();
            
            /**
            // Scenario 1: Small payment (auto-approved)
            System.out.println("\n" + "=".repeat(70));
            System.out.println("SCENARIO 1: Small Payment ($500) - Should Auto-Approve");
            System.out.println("=".repeat(70));
            
            Map<String, Object> smallPayment = new HashMap<>();
            smallPayment.put("amount", 500);
            smallPayment.put("vendor", "Office Supplies Inc");
            smallPayment.put("approved", false);
            
            app.stream(smallPayment).forEach(outputs::add);
            
            if (!outputs.isEmpty()) {
                AgentState finalState = outputs.get(outputs.size() - 1).state();
                System.out.println("\n" + "-".repeat(70));
                System.out.println("FINAL STATUS: " + finalState.data().get("status"));
                System.out.println("Transaction ID: " + finalState.data().get("transactionId"));
                System.out.println("-".repeat(70));
            }
            **/
            // Scenario 2: Large payment with REAL checkpointing (pause/resume)
            System.out.println("\n\n" + "=".repeat(70));
            System.out.println("SCENARIO 2: Large Payment ($5000) - With Checkpointing");
            System.out.println("=".repeat(70));
            
            System.out.println("\n--- Step 1: Submit request (workflow will pause) ---");
            
            // Create initial request
            Map<String, Object> largePayment = new HashMap<>();
            largePayment.put("amount", 5000);
            largePayment.put("vendor", "Enterprise Software Corp");
            largePayment.put("approved", false);
            
            // IMPORTANT: Use thread_id for checkpointing
            String threadId = "payment_request_001";
            RunnableConfig config = RunnableConfig.builder()
                .threadId(threadId)
                .build();
            
            outputs.clear();
            app.stream(largePayment, config).forEach(output -> {
                outputs.add(output);
                System.out.println("  Executed node: " + output.node());
            });
            
            if (!outputs.isEmpty()) {
                AgentState pausedState = outputs.get(outputs.size() - 1).state();
                System.out.println("\n" + "-".repeat(70));
                System.out.println("STATUS: " + pausedState.data().get("status"));
                System.out.println("✋ Workflow PAUSED at approval step");
                System.out.println("State saved with thread_id: " + threadId);
                System.out.println("-".repeat(70));
            }
            
            System.out.println("\n--- Simulating time passing... Manager reviews request ---");
            Thread.sleep(1000);
            
            System.out.println("\n--- Step 2: Manager approves, RESUME from checkpoint ---");
            
            // Get the current state from checkpoint
            StateSnapshot<AgentState> snapshot = app.getState(config);
            
            // Update the approval in the current state
            Map<String, Object> updates = new HashMap<>();
            updates.put("approved", true);
            
            // Update the state at "wait_approval" node (where it paused)
            app.updateState(config, updates, "wait_approval");
            
            // Now stream with null to continue from where it paused
            // It will execute process node and complete
            outputs.clear();
            app.stream(null, config).forEach(output -> {
                outputs.add(output);
                System.out.println("  Executed node: " + output.node());
            });
            
            if (!outputs.isEmpty()) {
                AgentState finalState = outputs.get(outputs.size() - 1).state();
                System.out.println("\n" + "-".repeat(70));
                System.out.println("FINAL STATUS: " + finalState.data().get("status"));
                System.out.println("Transaction ID: " + finalState.data().get("transactionId"));
                System.out.println("✓ Workflow RESUMED and completed successfully!");
                System.out.println("-".repeat(70));
            }
            
            // Scenario 3: Invalid payment
            System.out.println("\n\n" + "=".repeat(70));
            System.out.println("SCENARIO 3: Invalid Payment ($0) - Should Be Rejected");
            System.out.println("=".repeat(70));
            
            Map<String, Object> invalidPayment = new HashMap<>();
            invalidPayment.put("amount", 0);
            invalidPayment.put("vendor", "Invalid Vendor");
            invalidPayment.put("approved", false);
            
            outputs.clear();
            app.stream(invalidPayment).forEach(outputs::add);
            
            if (!outputs.isEmpty()) {
                AgentState finalState = outputs.get(outputs.size() - 1).state();
                System.out.println("\n" + "-".repeat(70));
                System.out.println("FINAL STATUS: " + finalState.data().get("status"));
                System.out.println("Reason: " + finalState.data().get("reason"));
                System.out.println("-".repeat(70));
            }
            
            System.out.println("\n\n" + "=".repeat(70));
            System.out.println("✓ All scenarios completed successfully!");
            System.out.println("=".repeat(70));
            System.out.println("\nKey Features Demonstrated:");
            System.out.println("  ✓ Plain HashMap state (no custom classes)");
            System.out.println("  ✓ Conditional routing based on amount");
            System.out.println("  ✓ Auto-approval for small payments");
            System.out.println("  ✓ REAL checkpointing with pause/resume using thread_id");
            System.out.println("  ✓ Human-in-the-loop for large payments");
            System.out.println("  ✓ Validation and error handling");
            System.out.println("\nHow Checkpointing Works:");
            System.out.println("  1. Compile workflow with MemorySaver");
            System.out.println("  2. First call: stream(data, config) with thread_id");
            System.out.println("  3. Workflow pauses, state saved automatically");
            System.out.println("  4. Second call: stream(updates, SAME thread_id)");
            System.out.println("  5. Workflow resumes from checkpoint!");
            
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

You may also like...