JavAI Workflow

repository·main·Indexed 41 days ago

https://github.com/czelabueno/jai-workflow

A Java library for building, orchestrating, and monitoring agentic AI workflows and RAG systems using a stateful, graph-based approach. It supports synchronous and asynchronous nodes, conditional routing, and JIT modification of workflow structures. Version 0.3.0 introduces a three-stage lifecycle (definition, build, run) and supports patterns such as parallelization, routing, and branching. Includes built-in tools for state debugging and Graphviz-based visualization.

Tokens
2.8K
Snippets
4
Records
9
Agent score
1%

What's inside JavAI Workflow

  1. How JavAI Workflow works: Core Concepts

    main

    JavAI Workflow is a stateful, graph-based engine for building agentic AI systems in Java. It uses a directed graph to orchestrate complex workflows, allowing for cycles, loops, and conditional logic. The system is built around three primary abstractions:

    1. Node: The smallest unit of work. A node encapsulates a specific task or function that processes a stateful bean and updates it. Nodes can be synchronous or asynchronous (streaming).
    2. Module: A reusable collection of nodes grouped to perform a higher-level function. Modules can be decoupled and integrated into different workflows.
    3. Workflow: The top-level directed graph consisting of nodes, modules, and edges. It manages state transitions and ensures nodes process the stateful bean in the correct sequence.
  2. Workflow Lifecycle and Patterns (v0.3.0+)

    main

    Starting from version 0.3.0, the workflow lifecycle is divided into three distinct stages: definition, build, and run. This allows you to customize behavior at each stage.

    Supported workflow patterns include:

    • one-way
    • parallelization (Note: not supported at run time)
    • routing
    • branching
    • conditional

    New node types available in v0.3.0 include Split, Merge, Parallel, and Conditional (which requires a list of expected return nodes).

  3. How state management works in jAI Workflow

    main

    In jAI Workflow, state is central to execution.

    1. Initialization: Every graph execution starts with an initial state.
    2. Transfer: This state is passed between nodes during execution.
    3. Updates: After a node executes, it updates the internal state using its return value.
    4. Customization: The specific logic for how the graph updates its internal state is determined by user-defined functions.

    To implement this, you typically define a stateful bean by extending AbstractStatefulBean and creating functions that manipulate this bean.

  4. Visualize and debug workflows

    main

    jAI Workflow provides tools to inspect the execution path and visualize the graph structure.

    Generate Workflow Images

    You can generate SVG images of your workflow at two different stages:

    • Definition time: Shows the static structure of the graph.
    • Runtime: Shows the graph with the actual computed transitions used during execution.

    Use workflow.getWorkflowImage(path) to save the image.

    Inspect Transitions

    To see a text-based log of every transition that occurred during execution (including order, timestamp, and payload), use workflow.prettyTransitions().

    // Generate static image
    workflow.getWorkflowImage("image/my-workflow.svg");
    
    // Generate runtime image
    workflow.getWorkflowImage("image/my-computed-workflow.svg");
    
    // Print transition log
    System.out.println(workflow.prettyTransitions());
  5. Debug and Visualize JavAI Workflows

    main

    JavAI Workflow provides tools for observability and debugging:

    • State Debugging: During execution, you can retrieve a list of ComputedTransition objects containing details on execution order, datetime, and inputs/outputs for each node.
    • Visualization: You can generate workflow images at definition time or runtime. Images can be output as a File or a BufferedImage.
    • Styling: When using the Graphviz implementation, you can use StyleGraph.SKETCHY as a StyleAttribute to generate images in an Excalidraw-like style.
  6. Build a workflow with nodes, transitions, and conditional edges

    main

    To build a workflow, follow these steps:

    1. Define a Stateful Bean: Extend AbstractStatefulBean to hold your workflow's data.
    2. Define Functions: Create logic (often in static methods) that takes the stateful bean as input and returns an updated version or a specific payload (like a UserMessage).
    3. Create Nodes: Use Node.from(name, function) for standard nodes and StreamingNode.from(name, function, model) for nodes that support streaming LLM responses.
    4. Define Transitions: Use Transition.from(source, target) for direct paths. For logic-based routing, use Conditional.eval(expression, logic, options).
    5. Initialize Workflow: Create a DefaultJAiWorkflow specifying the stateful bean, the list of transitions, the start node, and whether to run in streaming mode.
    6. Execute: Use answerStream(question) for streaming responses or standard execution methods.
    // 1. Define state
    public class MyStatefulBean extends AbstractStatefulBean {
      private List<String> relevantDocuments;
      private String webSearchResponse;
    }
    
    // 2. Define nodes
    Node<MyStatefulBean, MyStatefulBean> retrieveNode = Node.from(
            "Retrieve Node", 
            obj -> MyStatefulBeanFunctions.extractRelevantDocuments(obj, documents));
    
    StreamingNode<MyStatefulBean> generateAnswerNode = StreamingNode.from(
            "Generation Node",
            obj -> MyStatefulBeanFunctions.generateUserMessageUsingPrompt(obj),
            streamingModel);
    
    // 3. Create workflow with transitions and conditional logic
    DefaultJAiWorkflow<MyStatefulBean> workflow = new DefaultJAiWorkflow<MyStatefulBean>(
            myStatefulBean,
            List.of(
                    Transition.from(retrieveNode, webSearchNode),
                    Transition.from(webSearchNode, Conditional.eval("webSearchResponse",
                            obj -> obj.webSearchResponse != null ? generateAnswerNode : retrieveNode,
                            List.of(retrieveNode, generateAnswerNode))),
                    Transition.from(generateAnswerNode, WorkflowStateName.END)),
            retrieveNode, 
            true // streaming mode
    );
  7. Install jAI Workflow dependencies

    main

    To use jAI Workflow in your Java project, choose the dependency based on whether you want LangChain4j integration or a standalone core implementation.

    With LangChain4j Integration

    Use this if you want to leverage LangChain4j features to define custom workflows.

    Standalone Core

    Use this if you want to use jAI Workflow without LangChain4j or with a different framework.

    <!-- With LangChain4j Integration -->
    <dependency>
      <groupId>io.github.czelabueno</groupId>
      <artifactId>jai-workflow-langchain4j</artifactId>
      <version>0.3.0</version>
    </dependency>
    
    <!-- Standalone Core -->
    <dependency>
      <groupId>io.github.czelabueno</groupId>
      <artifactId>jai-workflow-core</artifactId>
      <version>0.3.0</version>
    </dependency>
  8. Build a custom agentic workflow with JavAI Workflow

    main

    To build a workflow, you define a stateful bean (a POJO to hold your application state), create nodes for specific tasks, and then compose them into a DefaultJAiWorkflow using transitions. You can use Node.from for standard tasks and StreamingNode.from for streaming LLM tasks. Transitions can be simple one-way paths or Conditional paths that evaluate logic to decide the next node.

    class MyStatefulBean extends AbstractStatefulBean {
      private List<String> documents;
      // other additional input/output fields that you want to store
    }
    
    StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder()
            .apiKey(System.getenv("OPENAI_API_KEY"))
            .modelName(GPT_4_O_MINI)
            .build();
    
    var parserDocsNode = Node.from("Docs", obj -> transformDocsFromUrl(obj, uris));
    var retrieveNode = Node.from("Vector DB", obj -> extractRelevantDocumentsFromVectorDB(obj));
    var generateAnswerNode = StreamingNode.from("Generate",obj -> generateAnswer(obj), model);
    
    var jAiWorkflow = new DefaultJAiWorkflow<MyStatefulBean>(
            new MyStatefulBean(),
            List.of(Transition.from(parserDocsNode, retrieveNode),
                    Transition.from(retrieveNode, Conditional.eval("enoughData", 
                            obj -> obj.isEnoughData() ? generateAnswerNode : parserDocsNode, 
                            List.of(parserDocsNode, generateAnswerNode))),
                    Transition.from(generateAnswerNode, WorkflowStateName.END)),
            retrieveNode, // Start node
            true // Run in streaming mode
    );
    
    // Chat with your JAiWorkflow
    var question = "Summarizes the importance of building AI agentic systems";
    Flux<String> response = jAiWorkflow.answerStream(question);
  9. Modify workflows at runtime (JIT modification)

    main

    You can dynamically alter a workflow's structure immediately after it has run using Just-In-Time (JIT) modification methods:

    • myworkflow.putEdge(..): Add new edges.
    • myworkflow.addNode(..): Add new nodes.
    • myworkflow.startNode(..): Override the starting node.