diff --git a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java index ccf77f37..d91fb86c 100644 --- a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java +++ b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java @@ -122,7 +122,7 @@ public static AgentTaskConfigurer loop(Predicate exitCondition, Ob } public static AgentTaskConfigurer loop( - Predicate exitCondition, int maxIterations, Object... agents) { + int maxIterations, Predicate exitCondition, Object... agents) { return list -> list.loop( l -> l.subAgents(agents).exitCondition(exitCondition).maxIterations(maxIterations)); diff --git a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/LC4JEquivalenceIT.java b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/LC4JEquivalenceIT.java index 492b373c..90ee0f6a 100644 --- a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/LC4JEquivalenceIT.java +++ b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/LC4JEquivalenceIT.java @@ -18,6 +18,8 @@ import static io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder.workflow; import static io.serverlessworkflow.fluent.agentic.dsl.AgenticDSL.conditional; import static io.serverlessworkflow.fluent.agentic.dsl.AgenticDSL.doTasks; +import static io.serverlessworkflow.fluent.agentic.dsl.AgenticDSL.fn; +import static io.serverlessworkflow.fluent.agentic.dsl.AgenticDSL.loop; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -29,6 +31,7 @@ import io.serverlessworkflow.api.types.func.CallTaskJava; import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -45,10 +48,7 @@ public void sequentialWorkflow() { var audienceEditor = AgentsUtils.newAudienceEditor(); var styleEditor = AgentsUtils.newStyleEditor(); - Workflow wf = - workflow("seqFlow") - .sequence("process", creativeWriter, audienceEditor, styleEditor) - .build(); + Workflow wf = workflow("seqFlow").sequence(creativeWriter, audienceEditor, styleEditor).build(); List items = wf.getDo(); assertThat(items).hasSize(3); @@ -77,13 +77,18 @@ public void sequentialWorkflow() { @Test @DisplayName("Looping agents via DSL.loop(...)") public void loopWorkflow() { - - var scorer = AgentsUtils.newStyleScorer(); - var editor = AgentsUtils.newStyleEditor(); + var creativeWriter = AgentsUtils.newCreativeWriter(); + var styleScorer = AgentsUtils.newStyleScorer(); + var styleEditor = AgentsUtils.newStyleEditor(); Workflow wf = AgentWorkflowBuilder.workflow("retryFlow") - .loop("reviewLoop", c -> c.readState("score", 0).doubleValue() >= 0.8, scorer, editor) + .agent(creativeWriter) + .loop( + "reviewLoop", + c -> c.readState("score", 0).doubleValue() >= 0.8, + styleScorer, + styleEditor) .build(); List items = wf.getDo(); @@ -118,7 +123,7 @@ public void loopWorkflowWithMaxIterations() { Predicate until = s -> s.readState("score", 0).doubleValue() >= 0.8; - Workflow wf = workflow("retryFlow").loop(until, scorer, 5, editor).build(); + Workflow wf = workflow("retryFlow").tasks(loop(5, until, scorer, editor)).build(); List items = wf.getDo(); assertThat(items).hasSize(1); @@ -144,13 +149,53 @@ public void loopWorkflowWithMaxIterations() { assertThat(result).containsKey("story"); } + public record EveningPlan(String movie, String meal) {} + @Test @DisplayName("Parallel agents via DSL.parallel(...)") public void parallelWorkflow() { var foodExpert = AgentsUtils.newFoodExpert(); var movieExpert = AgentsUtils.newMovieExpert(); - Workflow wf = workflow("forkFlow").parallel("fanout", foodExpert, movieExpert).build(); + workflow("forkFlow") + .tasks( + d -> + d.parallel(foodExpert, movieExpert) + .callFn( + fn( + f -> { + Map> asMap = (Map>) f; + List result = new ArrayList<>(); + int max = + asMap.values().stream() + .map(List::size) + .min(Integer::compareTo) + .orElse(0); + for (int i = 0; i < max; i++) { + result.add( + new EveningPlan( + asMap.get("movies").get(i), asMap.get("meals").get(i))); + } + return result; + }))) + .build(); + + Workflow wf = + workflow("forkFlow") + .tasks( + d -> + d.parallel("fanout", foodExpert, movieExpert) + .callFn( + fn( + (Map> m) -> { + var movies = m.getOrDefault("movies", List.of()); + var meals = m.getOrDefault("meals", List.of()); + return java.util.stream.IntStream.range( + 0, Math.min(movies.size(), meals.size())) + .mapToObj(i -> new EveningPlan(movies.get(i), meals.get(i))) + .toList(); + }))) + .build(); List items = wf.getDo(); assertThat(items).hasSize(1); diff --git a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/README.md b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/README.md index a9c2e469..2d1d0319 100644 --- a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/README.md +++ b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/README.md @@ -2,6 +2,8 @@ # Sequential workflow ### Common part: +
Click to expand + ```java public interface AudienceEditor { @@ -65,33 +67,49 @@ Map input = Map.of( "audience", "young adults" ); ``` - -### LangChain4j -```java -UntypedAgent novelCreator = AgenticServices - .sequenceBuilder() - .subAgents(creativeWriter, audienceEditor, styleEditor) - .outputName("story") - .build(); +
+ + + + + + + + -```java -Workflow wf = workflow("seqFlow").sequence("process", creativeWriter, audienceEditor, styleEditor).build(); + + +
LangChain4jServerless Workflow
+
+UntypedAgent novelCreator = AgenticServices
+    .sequenceBuilder()
+    .subAgents(creativeWriter, audienceEditor, styleEditor)
+    .outputName("story")
+    .build();
 
 String story = (String) novelCreator.invoke(input);
-```
 
-### Serverless Workflow
+
+
+
+
+Workflow wf = workflow("seqFlow")
+    .sequence(creativeWriter, audienceEditor, styleEditor)
+    .build();
+ 
+ 
 
-try (WorkflowApplication app = WorkflowApplication.builder().build()) {
-    String result = app.workflowDefinition(wf).instance(input).start().get().asText().orElseThrow();
-} catch (Exception e) {
-    throw new RuntimeException("Workflow execution failed", e);
-}
-```
+String result = app.workflowDefinition(wf).instance(input).start().get().asText().orElseThrow();
+
+
+
+
### Loop workflow ### Common part: +
Click to expand + ```java interface StyleEditor { @@ -136,8 +154,23 @@ StyleScorer styleScorer = AgenticServices ``` -### LangChain4j -```java +
+ + + + + + + + + + +
LangChain4jServerless Workflow
+
+UntypedAgent styleReviewLoop = AgenticServices
+        .loopBuilder()
+        .subAgents(styleScorer, styleEditor)
+        .maxIterations(5)
+        .exitCondition(agenticScope -> agenticScope.readState("score", 0.0) >= 0.8)
+        .build();
+
 StyledWriter styledWriter = AgenticServices
         .sequenceBuilder(StyledWriter.class)
         .subAgents(creativeWriter, styleReviewLoop)
@@ -145,29 +178,38 @@ StyledWriter styledWriter = AgenticServices
         .build();
 
 String story = styledWriter.writeStoryWithStyle("dragons and wizards", "comedy");
-```
-
-### Serverless Workflow
-```java
-Predicate until = s -> s.readState("score", 0).doubleValue() >= 0.8;
 
-Workflow wf = workflow("retryFlow").loop(until, scorer, 5, editor).build();
+
+
+
+
+Map<String, Object> input =  Map.of("story", "dragons and wizards","style", "comedy");
+Predicate until = s -> s.readState("score", 0).doubleValue() >= 0.8;
+ 
+ 
+ 
+ 
+ 
+ 
+Workflow wf = workflow("retryFlow")
+        .agent(creativeWriter)
+        .tasks(loop(5,  c -> c.readState("score", 0).doubleValue() >= 0.8, styleScorer, styleEditor))
+        .build();
+ 
+String result = app.workflowDefinition(wf).instance(input).start().get().asText().orElseThrow();
 
-Map input =
-        Map.of(
-                "story", "dragons and wizards",
-                "style", "comedy");
-
-try (WorkflowApplication app = WorkflowApplication.builder().build()) {
-  String result = app.workflowDefinition(wf).instance(input).start().get().asText().orElseThrow();
-} catch (Exception e) {
-  throw new RuntimeException("Workflow execution failed", e);
-}
-```
+
+
+
### Parallel workflow ### Common part: +
Click to expand + ```java public interface FoodExpert { @@ -206,65 +248,78 @@ MovieExpert movieExpert = AgenticServices .outputName("movies") .build(); ``` - -### LangChain4j -```java +
+ + + + + + + + + + + + +
LangChain4jServerless Workflow
+
+
 EveningPlannerAgent eveningPlannerAgent = AgenticServices
-        .parallelBuilder(EveningPlannerAgent.class)
-        .subAgents(foodExpert, movieExpert)
-        .executor(Executors.newFixedThreadPool(2))
-        .outputName("plans")
-        .output(agenticScope -> {
-            List movies = agenticScope.readState("movies", List.of());
-            List meals = agenticScope.readState("meals", List.of());
-
-            List moviesAndMeals = new ArrayList<>();
-            for (int i = 0; i < movies.size(); i++) {
-                if (i >= meals.size()) {
-                    break;
-                }
-                moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i)));
-            }
-            return moviesAndMeals;
-        })
-        .build();
+    .parallelBuilder(EveningPlannerAgent.class)
+    .subAgents(foodExpert, movieExpert)
+    .executor(Executors.newFixedThreadPool(2))
+    .outputName("plans")
+    .output(agenticScope -> {
+        List movies = agenticScope.readState("movies", List.of());
+        List meals = agenticScope.readState("meals", List.of());
+        List moviesAndMeals = new ArrayList<>();
+        for (int i = 0; i < movies.size(); i++) {
+        if (i >= meals.size()) {
+            break;
+        }
+        moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i)));
+        }
+        return moviesAndMeals;
+    })
+    .build();
 
 List plans = eveningPlannerAgent.plan("romantic");
-```
-
-### Serverless Workflow
-```java
-Workflow wf = workflow("forkFlow").parallel("fanout", foodExpert, movieExpert).build();
-
-Map input = Map.of("mood", "I am hungry and bored");
-
-Map result;
-try (WorkflowApplication app = WorkflowApplication.builder().build()) {
-    result = app.workflowDefinition(wf).instance(input).start().get().asMap().orElseThrow();
-} catch (Exception e) {
-   throw new RuntimeException("Workflow execution failed", e);
-}
-```
-
-### Error handling
-### Common part:
-```java
-
-```
-
-### LangChain4j
-```java
 
-```
-
-### Serverless Workflow
+
+
+
+
+
+    Workflow wf = workflow("forkFlow")
+            .tasks(d -> d.parallel(foodExpert, movieExpert)
+                    .callFn(fn((Map<String, List> m) -> {
+                      var movies = m.getOrDefault("movies", List.of());
+                      var meals  = m.getOrDefault("meals",  List.of());
+                      return java.util.stream.IntStream
+                              .range(0, Math.min(movies.size(), meals.size()))
+                              .mapToObj(i -> new EveningPlan(movies.get(i), meals.get(i)))
+                              .toList();
+                    }))
+            ).build();
+ 
+ 
+ 
+ 
+ 
+ 
+Map<String, Object> input = Map.of("mood", "I am hungry and bored");
+
+List result = app.workflowDefinition(wf).instance(input).start().get().asMap().orElseThrow();
+
+
+
+
-```java - -``` ### Human-in-the-loop ### Common part: +
Click to expand + ```java public record HumanInTheLoop(Consumer requestWriter, Supplier responseReader) { @@ -302,9 +357,17 @@ HumanInTheLoop humanInTheLoop = AgenticServices .responseReader(() -> System.console().readLine()) .build(); ``` - -### LangChain4j -```java +
+ + + + + + + + + + + +
LangChain4jServerless Workflow
+
+
 SupervisorAgent horoscopeAgent = AgenticServices
         .supervisorBuilder()
         .chatModel(PLANNER_MODEL)
@@ -312,18 +375,24 @@ SupervisorAgent horoscopeAgent = AgenticServices
         .build();
 
 horoscopeAgent.invoke("My name is Mario. What is my horoscope?")
-```
 
-### Serverless Workflow
-
-```java
-Workflow wf = workflow("seqFlow").sequence("process", astrologyAgent, humanInTheLoop).build();
-
-Map input = Map.of("request", "My name is Mario. What is my horoscope?");
-
-try (WorkflowApplication app = WorkflowApplication.builder().build()) {
-  String result = app.workflowDefinition(wf).instance(input).start().get().asMap().orElseThrow();
-} catch (Exception e) {
-  throw new RuntimeException("Workflow execution failed", e);
-}
-```
\ No newline at end of file
+
+
+
+
+
+Workflow wf = workflow("seqFlow")
+    .sequence(astrologyAgent, humanInTheLoop)
+    .build();
+
+ 
+ 
+String result = app.workflowDefinition(wf).instance("My name is Mario. What is my horoscope?").start().get().asMap().orElseThrow();
+
+
+
+
\ No newline at end of file