From 8bb44ac5562a3e4820c4e809ddaad173f10a5108 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Thu, 31 Jul 2025 18:42:30 -0400 Subject: [PATCH 1/6] Introduce LangChain4j Agentic Workflow Implementation Signed-off-by: Ricardo Zanini --- .github/workflows/maven-verify.yml | 25 +- .../expressions/agentic/AgenticModel.java | 17 +- .../agentic/AgenticModelCollection.java | 2 +- .../agentic/AgenticModelFactory.java | 56 ++-- .../CognisphereRegistryAssessor.java | 70 +++++ .../impl/expressions/func/JavaModel.java | 2 +- fluent/agentic-langchain4j/pom.xml | 68 +++++ .../langchain4j/AbstractAgentService.java | 84 ++++++ .../ConditionalAgentServiceImpl.java | 67 +++++ .../langchain4j/LC4JWorkflowBuilder.java | 66 +++++ .../langchain4j/LoopAgentServiceImpl.java | 63 +++++ .../langchain4j/ParallelAgentServiceImpl.java | 50 ++++ .../SequentialAgentServiceImpl.java | 45 ++++ .../WorkflowDefinitionBuilder.java | 25 ++ .../WorkflowInvocationHandler.java | 176 ++++++++++++ .../fluent/agentic/langchain4j/Agents.java | 253 ++++++++++++++++++ .../fluent/agentic/langchain4j/Models.java | 49 ++++ .../SequentialAgentServiceImplTest.java | 119 ++++++++ .../agentic/langchain4j/WorkflowAgentsIT.java | 80 ++++++ fluent/agentic/pom.xml | 29 +- .../fluent/agentic/AgentAdapters.java | 9 +- .../fluent/agentic/AgentDoTaskBuilder.java | 6 + .../agentic/AgentTaskItemListBuilder.java | 13 +- .../fluent/agentic/AgentWorkflowBuilder.java | 5 +- .../fluent/agentic/LoopAgentsBuilder.java | 2 +- .../fluent/agentic/spi/AgentDoFluent.java | 6 + .../agentic/AgentWorkflowBuilderTest.java | 4 +- .../fluent/agentic/Agents.java | 24 +- .../fluent/agentic/AgentsUtils.java | 4 +- .../fluent/agentic/WorkflowTests.java | 101 +++---- fluent/pom.xml | 37 +++ .../fluent/spec/BaseWorkflowBuilder.java | 4 + 32 files changed, 1434 insertions(+), 127 deletions(-) create mode 100644 experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java create mode 100644 fluent/agentic-langchain4j/pom.xml create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LC4JWorkflowBuilder.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ParallelAgentServiceImpl.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImpl.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowDefinitionBuilder.java create mode 100644 fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java create mode 100644 fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java create mode 100644 fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Models.java create mode 100644 fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java create mode 100644 fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowAgentsIT.java diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 2dd7539d..a34291bc 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -1,6 +1,3 @@ -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - name: sdk-java Verify on: @@ -14,9 +11,21 @@ on: jobs: build: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 + # 1. Checkout this repo + - name: Checkout sdk-java + uses: actions/checkout@v4 + + # 2. (Temporary) Checkout the langchain4j repo at agentic-module + - name: Checkout langchain4j (agentic-module) + uses: actions/checkout@v4 + with: + repository: mariofusco/langchain4j + ref: agentic-module + path: langchain4j + # 3. Set up JDK 17 for both builds - name: Set up JDK 17 uses: actions/setup-java@v4 with: @@ -24,12 +33,20 @@ jobs: java-version: 17 cache: 'maven' + # 4. Build the external agentic module + - name: Build langchain4j-agentic + run: | + mvn -B -U -T12C clean package -DskipTests + mvn -B -f langchain4j/langchain4j-agentic/pom.xml clean install -DskipTests + + # 5. Verify the main sdk-java project, excluding the two agentic modules - name: Verify with Maven run: | mvn -B -f pom.xml clean install verify \ -pl ",!fluent/agentic" -pl ",!experimental/agentic" \ -am + # 6. Verify examples - name: Verify Examples with Maven run: | mvn -B -f examples/pom.xml clean install verify diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java index fb119ba1..b8b723f7 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java @@ -16,20 +16,15 @@ package io.serverlessworkflow.impl.expressions.agentic; import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.expressions.func.JavaModel; import java.util.Collection; -import java.util.Collections; import java.util.Optional; class AgenticModel extends JavaModel { - private final Cognisphere cognisphere; - - AgenticModel(Object object, Cognisphere cognisphere) { - super(object); - this.cognisphere = cognisphere; + AgenticModel(Cognisphere cognisphere) { + super(cognisphere); } @Override @@ -39,17 +34,13 @@ public void setObject(Object obj) { @Override public Collection asCollection() { - return object instanceof Collection value - ? new AgenticModelCollection(value, cognisphere) - : Collections.emptyList(); + throw new UnsupportedOperationException("Not supported yet."); } @Override public Optional as(Class clazz) { if (Cognisphere.class.isAssignableFrom(clazz)) { - return Optional.of(clazz.cast(cognisphere)); - } else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) { - return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object))); + return Optional.of(clazz.cast(object)); } else { return super.as(clazz); } diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java index e9440fb5..58402b0e 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java @@ -37,7 +37,7 @@ class AgenticModelCollection extends JavaModelCollection { @Override protected WorkflowModel nextItem(Object obj) { - return new AgenticModel(obj, cognisphere); + return new AgenticModel((Cognisphere) obj); } @Override diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java index fe57b99e..43bf27cb 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java @@ -16,86 +16,96 @@ package io.serverlessworkflow.impl.expressions.agentic; import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelCollection; import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.expressions.agentic.langchain4j.CognisphereRegistryAssessor; +import io.serverlessworkflow.impl.expressions.func.JavaModel; import java.time.OffsetDateTime; import java.util.Map; class AgenticModelFactory implements WorkflowModelFactory { - private Cognisphere cognisphere = CognisphereRegistry.createEphemeralCognisphere(); - - private final AgenticModel TrueModel = new AgenticModel(Boolean.TRUE, cognisphere); - private final AgenticModel FalseModel = new AgenticModel(Boolean.FALSE, cognisphere); - private final AgenticModel NullModel = new AgenticModel(null, cognisphere); - - public void setCognishere(Cognisphere cognisphere) { - this.cognisphere = cognisphere; - } - + /** + * Applies any change to the model after running as task. We will always set it to + * a @DefaultCognisphere object since @AgentExecutor is always adding the output to the + * cognisphere. We just have to make sure that cognisphere is always passed to the next input + * task. + * + * @param prev the global Cognisphere object getting updated by the workflow context + * @param obj the same Cognisphere object updated by the AgentExecutor + * @return the workflow context model holding the cognisphere object. + */ @Override public WorkflowModel fromAny(WorkflowModel prev, Object obj) { - ((AgenticModel) prev).setObject(obj); + // We ignore `obj` since it's already included in `prev` within the Cognisphere instance return prev; } @Override public WorkflowModel combine(Map workflowVariables) { - return new AgenticModel(workflowVariables, cognisphere); + // TODO: create a new cognisphere object in the CognisphereRegistryAssessor per branch + // TODO: Since we share the same cognisphere object, both branches are updating the same + // instance, so for now we return the first key. + return workflowVariables.values().iterator().next(); } @Override public WorkflowModelCollection createCollection() { - return new AgenticModelCollection(cognisphere); + throw new UnsupportedOperationException(); } + // TODO: all these methods can use Cognisphere as long as we have access to the `outputName` + @Override public WorkflowModel from(boolean value) { - return value ? TrueModel : FalseModel; + return new JavaModel(value); } @Override public WorkflowModel from(Number value) { - return new AgenticModel(value, cognisphere); + return new JavaModel(value); } @Override public WorkflowModel from(String value) { - return new AgenticModel(value, cognisphere); + return new JavaModel(value); } @Override public WorkflowModel from(CloudEvent ce) { - return new AgenticModel(ce, cognisphere); + return new JavaModel(ce); } @Override public WorkflowModel from(CloudEventData ce) { - return new AgenticModel(ce, cognisphere); + return new JavaModel(ce); } @Override public WorkflowModel from(OffsetDateTime value) { - return new AgenticModel(value, cognisphere); + return new JavaModel(value); } @Override public WorkflowModel from(Map map) { + final Cognisphere cognisphere = new CognisphereRegistryAssessor().getCognisphere(); cognisphere.writeStates(map); - return new AgenticModel(map, cognisphere); + return new AgenticModel(cognisphere); } @Override public WorkflowModel fromNull() { - return NullModel; + return new JavaModel(null); } @Override public WorkflowModel fromOther(Object value) { - return new AgenticModel(value, cognisphere); + if (value instanceof Cognisphere) { + return new AgenticModel((Cognisphere) value); + } + return new JavaModel(value); } } diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java new file mode 100644 index 00000000..e7be366d --- /dev/null +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.expressions.agentic.langchain4j; + +import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; +import dev.langchain4j.agentic.internal.CognisphereOwner; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +public class CognisphereRegistryAssessor implements CognisphereOwner { + + private final AtomicReference cognisphereRegistry = new AtomicReference<>(); + private final String agentId; + private DefaultCognisphere cognisphere; + private Object memoryId; + + public CognisphereRegistryAssessor(String agentId) { + Objects.requireNonNull(agentId, "Agent id cannot be null"); + this.agentId = agentId; + } + + // TODO: have access to the workflow definition and assign its name instead + public CognisphereRegistryAssessor() { + this.agentId = UUID.randomUUID().toString(); + } + + public void setMemoryId(Object memoryId) { + this.memoryId = memoryId; + } + + public DefaultCognisphere getCognisphere() { + if (cognisphere != null) { + return cognisphere; + } + + if (memoryId != null) { + this.cognisphere = registry().getOrCreate(memoryId); + } else { + this.cognisphere = registry().createEphemeralCognisphere(); + } + return this.cognisphere; + } + + @Override + public CognisphereOwner withCognisphere(DefaultCognisphere cognisphere) { + this.cognisphere = cognisphere; + return this; + } + + @Override + public CognisphereRegistry registry() { + cognisphereRegistry.compareAndSet(null, new CognisphereRegistry(agentId)); + return cognisphereRegistry.get(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java index 884d0fe4..897b8d5c 100644 --- a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java @@ -28,7 +28,7 @@ public class JavaModel implements WorkflowModel { protected Object object; - protected JavaModel(Object object) { + public JavaModel(Object object) { this.object = asJavaObject(object); } diff --git a/fluent/agentic-langchain4j/pom.xml b/fluent/agentic-langchain4j/pom.xml new file mode 100644 index 00000000..3aa6ac92 --- /dev/null +++ b/fluent/agentic-langchain4j/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-fluent + 8.0.0-SNAPSHOT + + + serverlessworkflow-fluent-agentic-langchain4j + Serverless Workflow :: Fluent :: Agentic LangChain4j + Agentic Workflow DSL Implementation for langchain4j-agentic + + + + io.serverlessworkflow + serverlessworkflow-experimental-agentic + + + io.serverlessworkflow + serverlessworkflow-fluent-agentic + + + dev.langchain4j + langchain4j-agentic + + + + org.slf4j + slf4j-simple + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + test + + + dev.langchain4j + langchain4j-ollama + test + ${version.dev.langchain4j} + + + io.serverlessworkflow + serverlessworkflow-fluent-agentic + test-jar + test + + + + + + + + \ No newline at end of file diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java new file mode 100644 index 00000000..52ea002f --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; +import dev.langchain4j.agentic.internal.AgentSpecification; +import dev.langchain4j.agentic.internal.CognisphereOwner; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.lang.reflect.Proxy; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class AbstractAgentService implements WorkflowDefinitionBuilder { + + // Workflow OutputAs + private static final Function DEFAULT_OUTPUT_FUNCTION = cognisphere -> null; + + protected final WorkflowApplication.Builder workflowExecBuilder; + protected final AgentWorkflowBuilder workflowBuilder; + protected final Class agentServiceClass; + + protected AbstractAgentService(Class agentServiceClass) { + this.workflowBuilder = AgentWorkflowBuilder.workflow().outputAs(DEFAULT_OUTPUT_FUNCTION); + this.agentServiceClass = agentServiceClass; + this.workflowExecBuilder = WorkflowApplication.builder(); + } + + @SuppressWarnings("unchecked") + public T build() { + return (T) + Proxy.newProxyInstance( + this.agentServiceClass.getClassLoader(), + new Class[] {agentServiceClass, AgentSpecification.class, CognisphereOwner.class}, + new WorkflowInvocationHandler( + this.workflowBuilder.build(), this.workflowExecBuilder, this.agentServiceClass)); + } + + @SuppressWarnings("unchecked") + public S beforeCall(Consumer beforeCall) { + this.workflowBuilder.inputFrom( + cog -> { + beforeCall.accept(cog); + return cog; + }, + Cognisphere.class); + return (S) this; + } + + @SuppressWarnings("unchecked") + public S outputName(String outputName) { + Function outputFunction = cog -> cog.readState(outputName); + this.workflowBuilder.outputAs(outputFunction, DefaultCognisphere.class); + this.workflowBuilder.document( + d -> d.metadata(m -> m.metadata(META_KEY_OUTPUTNAME, outputName))); + return (S) this; + } + + @SuppressWarnings("unchecked") + public S output(Function output) { + this.workflowBuilder.outputAs(output, Cognisphere.class); + return (S) this; + } + + @Override + public Workflow getDefinition() { + return this.workflowBuilder.build(); + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java new file mode 100644 index 00000000..f96318b0 --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.workflow.ConditionalAgentService; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +public class ConditionalAgentServiceImpl + extends AbstractAgentService> + implements ConditionalAgentService { + + private ConditionalAgentServiceImpl(Class agentServiceClass) { + super(agentServiceClass); + } + + public static ConditionalAgentService builder(Class agentServiceClass) { + return new ConditionalAgentServiceImpl<>(agentServiceClass); + } + + @Override + public ConditionalAgentService subAgents(Object... agents) { + this.workflowBuilder.tasks(t -> t.sequence(agents)); + return this; + } + + @Override + public ConditionalAgentService subAgents(List agentExecutors) { + return this.subAgents(agentExecutors.toArray()); + } + + @Override + public ConditionalAgentService subAgents(Predicate condition, Object... agents) { + this.workflowBuilder.tasks( + t -> Arrays.stream(agents).forEach(agent -> t.when(condition).agent(agent))); + return this; + } + + @Override + public ConditionalAgentService subAgents( + Predicate condition, List agentExecutors) { + return this.subAgents(condition, agentExecutors.toArray()); + } + + @Override + public ConditionalAgentService subAgent( + Predicate condition, AgentExecutor agentExecutor) { + this.workflowBuilder.tasks(t -> t.when(condition).agent(agentExecutor)); + return this; + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LC4JWorkflowBuilder.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LC4JWorkflowBuilder.java new file mode 100644 index 00000000..7d5428be --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LC4JWorkflowBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.UntypedAgent; +import dev.langchain4j.agentic.workflow.ConditionalAgentService; +import dev.langchain4j.agentic.workflow.LoopAgentService; +import dev.langchain4j.agentic.workflow.ParallelAgentService; +import dev.langchain4j.agentic.workflow.SequentialAgentService; +import dev.langchain4j.agentic.workflow.WorkflowAgentsBuilder; + +public class LC4JWorkflowBuilder implements WorkflowAgentsBuilder { + + @Override + public SequentialAgentService sequenceBuilder() { + return SequentialAgentServiceImpl.builder(UntypedAgent.class); + } + + @Override + public SequentialAgentService sequenceBuilder(Class agentServiceClass) { + return SequentialAgentServiceImpl.builder(agentServiceClass); + } + + @Override + public ParallelAgentService parallelBuilder() { + return ParallelAgentServiceImpl.builder(UntypedAgent.class); + } + + @Override + public ParallelAgentService parallelBuilder(Class agentServiceClass) { + return ParallelAgentServiceImpl.builder(agentServiceClass); + } + + @Override + public LoopAgentService loopBuilder() { + return LoopAgentServiceImpl.builder(UntypedAgent.class); + } + + @Override + public LoopAgentService loopBuilder(Class agentServiceClass) { + return LoopAgentServiceImpl.builder(agentServiceClass); + } + + @Override + public ConditionalAgentService conditionalBuilder() { + return ConditionalAgentServiceImpl.builder(UntypedAgent.class); + } + + @Override + public ConditionalAgentService conditionalBuilder(Class agentServiceClass) { + return ConditionalAgentServiceImpl.builder(agentServiceClass); + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java new file mode 100644 index 00000000..3c2874cf --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.workflow.LoopAgentService; +import io.serverlessworkflow.fluent.agentic.LoopAgentsBuilder; +import java.util.List; +import java.util.function.Predicate; + +public class LoopAgentServiceImpl extends AbstractAgentService> + implements LoopAgentService { + + private final LoopAgentsBuilder loopAgentsBuilder = new LoopAgentsBuilder(); + + private LoopAgentServiceImpl(Class agentServiceClass) { + super(agentServiceClass); + } + + public static LoopAgentService builder(Class agentServiceClass) { + return new LoopAgentServiceImpl<>(agentServiceClass); + } + + @Override + public LoopAgentService maxIterations(int maxIterations) { + this.loopAgentsBuilder.maxIterations(maxIterations); + return this; + } + + @Override + public LoopAgentService exitCondition(Predicate exitCondition) { + this.loopAgentsBuilder.exitCondition(exitCondition); + return this; + } + + @Override + public LoopAgentService subAgents(Object... agents) { + this.loopAgentsBuilder.subAgents(agents); + this.workflowBuilder.tasks(t -> t.loop(this.loopAgentsBuilder)); + return this; + } + + @Override + public LoopAgentService subAgents(List agentExecutors) { + this.loopAgentsBuilder.subAgents(agentExecutors.toArray()); + this.workflowBuilder.tasks(t -> t.loop(this.loopAgentsBuilder)); + return this; + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ParallelAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ParallelAgentServiceImpl.java new file mode 100644 index 00000000..54c1247d --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ParallelAgentServiceImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.workflow.ParallelAgentService; +import java.util.List; +import java.util.concurrent.ExecutorService; + +public class ParallelAgentServiceImpl extends AbstractAgentService> + implements ParallelAgentService { + + private ParallelAgentServiceImpl(Class agentServiceClass) { + super(agentServiceClass); + } + + public static ParallelAgentService builder(Class agentServiceClass) { + return new ParallelAgentServiceImpl<>(agentServiceClass); + } + + @Override + public ParallelAgentService executorService(ExecutorService executorService) { + this.workflowExecBuilder.withExecutorFactory(() -> executorService); + return this; + } + + @Override + public ParallelAgentService subAgents(Object... agents) { + this.workflowBuilder.tasks(t -> t.parallel(agents)); + return this; + } + + @Override + public ParallelAgentService subAgents(List agentExecutors) { + return this.subAgents(agentExecutors.toArray()); + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImpl.java new file mode 100644 index 00000000..1fc25707 --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImpl.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.workflow.SequentialAgentService; +import java.util.List; + +public class SequentialAgentServiceImpl + extends AbstractAgentService> + implements SequentialAgentService { + + private SequentialAgentServiceImpl(Class agentServiceClass) { + super(agentServiceClass); + } + + public static SequentialAgentServiceImpl builder(Class agentServiceClass) { + return new SequentialAgentServiceImpl<>(agentServiceClass); + } + + @Override + public SequentialAgentService subAgents(Object... agents) { + this.workflowBuilder.tasks(t -> t.sequence(agents)); + return this; + } + + @Override + public SequentialAgentService subAgents(List agentExecutors) { + this.subAgents(agentExecutors.toArray()); + return this; + } +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowDefinitionBuilder.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowDefinitionBuilder.java new file mode 100644 index 00000000..cf3315a9 --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowDefinitionBuilder.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import io.serverlessworkflow.api.types.Workflow; + +public interface WorkflowDefinitionBuilder { + + String META_KEY_OUTPUTNAME = "outputName"; + + Workflow getDefinition(); +} diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java new file mode 100644 index 00000000..51238286 --- /dev/null +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java @@ -0,0 +1,176 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agentic.UntypedAgent; +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.CognisphereAccess; +import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; +import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import dev.langchain4j.agentic.internal.AgentInvoker; +import dev.langchain4j.agentic.internal.AgentSpecification; +import dev.langchain4j.agentic.internal.CognisphereOwner; +import dev.langchain4j.service.MemoryId; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.expressions.agentic.langchain4j.CognisphereRegistryAssessor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +public class WorkflowInvocationHandler implements InvocationHandler, CognisphereOwner { + + private final Workflow workflow; + private final WorkflowApplication.Builder workflowApplicationBuilder; + private final CognisphereRegistryAssessor cognisphereRegistryAssessor; + + WorkflowInvocationHandler( + Workflow workflow, + WorkflowApplication.Builder workflowApplicationBuilder, + Class agentServiceClass) { + this.workflow = workflow; + this.workflowApplicationBuilder = workflowApplicationBuilder; + this.cognisphereRegistryAssessor = new CognisphereRegistryAssessor(agentServiceClass.getName()); + } + + @SuppressWarnings("unchecked") + private static void writeCognisphereState(Cognisphere cognisphere, Method method, Object[] args) { + if (method.getDeclaringClass() == UntypedAgent.class) { + cognisphere.writeStates((Map) args[0]); + } else { + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + int index = i; + AgentInvoker.optionalParameterName(parameters[i]) + .ifPresent(argName -> cognisphere.writeState(argName, args[index])); + } + } + } + + private String outputName() { + Object outputName = + this.workflow + .getDocument() + .getMetadata() + .getAdditionalProperties() + .get(WorkflowDefinitionBuilder.META_KEY_OUTPUTNAME); + if (outputName != null) { + return outputName.toString(); + } + return null; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + CognisphereRegistry registry = registry(); + // outputName + if (method.getDeclaringClass() == AgentSpecification.class) { + return switch (method.getName()) { + case "outputName" -> outputName(); + default -> + throw new UnsupportedOperationException( + "Unknown method on AgentInstance class : " + method.getName()); + }; + } + // withCognisphere + if (method.getDeclaringClass() == CognisphereOwner.class) { + // Ingest the workflow input as a Cognisphere object + // Later, retrieve it and start the workflow with it as input. + return switch (method.getName()) { + case "withCognisphere" -> this.withCognisphere((DefaultCognisphere) args[0]); + case "registry" -> registry; + default -> + throw new UnsupportedOperationException( + "Unknown method on CognisphereOwner class : " + method.getName()); + }; + } + // getCognisphere + // evictCognisphere + if (method.getDeclaringClass() == CognisphereAccess.class) { + return switch (method.getName()) { + case "getCognisphere" -> registry().get(args[0]); + case "evictCognisphere" -> registry().evict(args[0]); + default -> + throw new UnsupportedOperationException( + "Unknown method on CognisphereAccess class : " + method.getName()); + }; + } + + // invoke + return executeWorkflow(currentCognisphere(method, args), method, args); + } + + private Object executeWorkflow(DefaultCognisphere cognisphere, Method method, Object[] args) { + writeCognisphereState(cognisphere, method, args); + + try (WorkflowApplication app = workflowApplicationBuilder.build()) { + // TODO improve result handling + DefaultCognisphere output = + app.workflowDefinition(workflow) + .instance(cognisphere) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Workflow hasn't returned a Cognisphere object.")); + Object result = output.readState(outputName()); + + return method.getReturnType().equals(ResultWithCognisphere.class) + ? new ResultWithCognisphere<>(output, result) + : result; + + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException( + "Failed to execute workflow: " + + workflow.getDocument().getName() + + " - Cognisphere: " + + cognisphere, + e); + } + } + + private DefaultCognisphere currentCognisphere(Method method, Object[] args) { + Object memoryId = memoryId(method, args); + this.cognisphereRegistryAssessor.setMemoryId(memoryId); + return this.cognisphereRegistryAssessor.getCognisphere(); + } + + private Object memoryId(Method method, Object[] args) { + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].getAnnotation(MemoryId.class) != null) { + return args[i]; + } + } + return null; + } + + @Override + public CognisphereOwner withCognisphere(DefaultCognisphere cognisphere) { + this.cognisphereRegistryAssessor.withCognisphere(cognisphere); + return this; + } + + @Override + public CognisphereRegistry registry() { + return this.cognisphereRegistryAssessor.registry(); + } +} diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java new file mode 100644 index 00000000..5bb3c5a3 --- /dev/null +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java @@ -0,0 +1,253 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.agent.tool.Tool; +import dev.langchain4j.agentic.Agent; +import dev.langchain4j.agentic.cognisphere.CognisphereAccess; +import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import dev.langchain4j.service.MemoryId; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; +import java.util.List; + +public class Agents { + + public interface ExpertRouterAgent { + + @Agent + String ask(@V("request") String request); + } + + public interface ExpertRouterAgentWithMemory extends CognisphereAccess { + + @Agent + String ask(@MemoryId String memoryId, @V("request") String request); + } + + public interface CategoryRouter { + + @UserMessage( + """ + Analyze the following user request and categorize it as 'legal', 'medical' or 'technical'. + In case the request doesn't belong to any of those categories categorize it as 'unknown'. + Reply with only one of those words and nothing else. + The user request is: '{{request}}'. + """) + @Agent("Categorize a user request") + RequestCategory classify(@V("request") String request); + } + + public enum RequestCategory { + LEGAL, + MEDICAL, + TECHNICAL, + UNKNOWN + } + + public interface RouterAgent { + + @UserMessage( + """ + Analyze the following user request and categorize it as 'legal', 'medical' or 'technical', + then forward the request as it is to the corresponding expert provided as a tool. + Finally return the answer that you received from the expert without any modification. + + The user request is: '{{it}}'. + """) + @Agent + String askToExpert(String request); + } + + public interface MedicalExpert { + + @UserMessage( + """ + You are a medical expert. + Analyze the following user request under a medical point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A medical expert") + @Agent("A medical expert") + String medical(@V("request") String request); + } + + public interface MedicalExpertWithMemory { + + @UserMessage( + """ + You are a medical expert. + Analyze the following user request under a medical point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A medical expert") + @Agent("A medical expert") + String medical(@MemoryId String memoryId, @V("request") String request); + } + + public interface LegalExpert { + + @UserMessage( + """ + You are a legal expert. + Analyze the following user request under a legal point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A legal expert") + @Agent("A legal expert") + String legal(@V("request") String request); + } + + public interface LegalExpertWithMemory { + + @UserMessage( + """ + You are a legal expert. + Analyze the following user request under a legal point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A legal expert") + @Agent("A legal expert") + String legal(@MemoryId String memoryId, @V("request") String request); + } + + public interface TechnicalExpert { + + @UserMessage( + """ + You are a technical expert. + Analyze the following user request under a technical point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A technical expert") + @Agent("A technical expert") + String technical(@V("request") String request); + } + + public interface TechnicalExpertWithMemory { + + @UserMessage( + """ + You are a technical expert. + Analyze the following user request under a technical point of view and provide the best possible answer. + The user request is {{request}}. + """) + @Tool("A technical expert") + @Agent("A technical expert") + String technical(@MemoryId String memoryId, @V("request") String request); + } + + public interface CreativeWriter { + + @UserMessage( + """ + You are a creative writer. + Generate a draft of a story long no more than 3 sentence around the given topic. + Return only the story and nothing else. + The topic is {{topic}}. + """) + @Agent("Generate a story based on the given topic") + String generateStory(@V("topic") String topic); + } + + public interface AudienceEditor { + + @UserMessage( + """ + You are a professional editor. + Analyze and rewrite the following story to better align with the target audience of {{audience}}. + Return only the story and nothing else. + The story is "{{story}}". + """) + @Agent("Edit a story to better fit a given audience") + String editStory(@V("story") String story, @V("audience") String audience); + } + + public interface StyleEditor { + + @UserMessage( + """ + You are a professional editor. + Analyze and rewrite the following story to better fit and be more coherent with the {{style}} style. + Return only the story and nothing else. + The story is "{{story}}". + """) + @Agent("Edit a story to better fit a given style") + String editStory(@V("story") String story, @V("style") String style); + } + + public interface StyleScorer { + + @UserMessage( + """ + You are a critical reviewer. + Give a review score between 0.0 and 1.0 for the following story based on how well it aligns with the style '{{style}}'. + Return only the score and nothing else. + + The story is: "{{story}}" + """) + @Agent("Score a story based on how well it aligns with a given style") + double scoreStyle(@V("story") String story, @V("style") String style); + } + + public interface StyleReviewLoop { + + @Agent("Review the given story to ensure it aligns with the specified style") + String scoreAndReview(@V("story") String story, @V("style") String style); + } + + public interface StyledWriter extends CognisphereAccess { + + @Agent + ResultWithCognisphere writeStoryWithStyle( + @V("topic") String topic, @V("style") String style); + } + + public interface FoodExpert { + + @UserMessage( + """ + You are a great evening planner. + Propose a list of 3 meals matching the given mood. + The mood is {{mood}}. + For each meal, just give the name of the meal. + Provide a list with the 3 items and nothing else. + """) + @Agent + List findMeal(@V("mood") String mood); + } + + public interface MovieExpert { + + @UserMessage( + """ + You are a great evening planner. + Propose a list of 3 movies matching the given mood. + The mood is {mood}. + Provide a list with the 3 items and nothing else. + """) + @Agent + List findMovie(@V("mood") String mood); + } + + public record EveningPlan(String movie, String meal) {} + + public interface EveningPlannerAgent { + + @Agent + List plan(@V("mood") String mood); + } +} diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Models.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Models.java new file mode 100644 index 00000000..c3be6250 --- /dev/null +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Models.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.ollama.OllamaChatModel; +import java.time.Duration; + +public class Models { + + private static final String OLLAMA_DEFAULT_URL = "http://127.0.0.1:11434"; + public static final String OLLAMA_ENV_URL = System.getenv("OLLAMA_BASE_URL"); + + private static final String OLLAMA_BASE_URL = + OLLAMA_ENV_URL != null ? OLLAMA_ENV_URL : OLLAMA_DEFAULT_URL; + + public static final ChatModel BASE_MODEL = + OllamaChatModel.builder() + .baseUrl(OLLAMA_BASE_URL) + .modelName("qwen2.5:7b") + .timeout(Duration.ofMinutes(10)) + .temperature(0.0) + .logRequests(true) + .logResponses(true) + .build(); + + public static final ChatModel PLANNER_MODEL = + OllamaChatModel.builder() + .baseUrl(OLLAMA_BASE_URL) + .modelName("qwen3:8b") + .timeout(Duration.ofMinutes(10)) + .temperature(0.0) + .logRequests(true) + .logResponses(true) + .build(); +} diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java new file mode 100644 index 00000000..957635a6 --- /dev/null +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.OutputAsFunction; +import io.serverlessworkflow.fluent.agentic.AgentsUtils; +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class SequentialAgentServiceImplTest { + @Test + void shouldBuildEmptyWorkflow_byDefault() { + // given + SequentialAgentServiceImpl service = + SequentialAgentServiceImpl.builder(DummyAgent.class); + + // when + Workflow wf = service.getDefinition(); + + // then + assertNotNull(wf, "Workflow definition should not be null"); + assertTrue(wf.getDo().isEmpty(), "There should be no tasks by default"); + } + + @Test + void shouldApplyBeforeCallConsumer_toInput() { + // given + SequentialAgentServiceImpl service = + (SequentialAgentServiceImpl) + SequentialAgentServiceImpl.builder(DummyAgent.class) + .beforeCall(ctx -> ctx.writeState("foo", "bar")); + + // when + Workflow wf = service.getDefinition(); + + // then + assertNotNull(wf.getInput(), "Input should not be null"); + } + + @Test + void shouldSetOutputName_inDocumentMetadata() { + // given + String outputName = "myOutputName"; + SequentialAgentServiceImpl service = + (SequentialAgentServiceImpl) + SequentialAgentServiceImpl.builder(DummyAgent.class).outputName(outputName); + + // when + Workflow wf = service.getDefinition(); + + // then + assertNotNull(wf.getDocument().getName(), "Workflow name should not be null"); + assertNotNull(wf.getOutput().getAs(), "Workflow outputAs should not be null"); + assertInstanceOf(OutputAsFunction.class, wf.getOutput().getAs()); + } + + @Test + void shouldSetOutputFunction_extension() { + // given + Function fn = ctx -> 42; + SequentialAgentServiceImpl service = + (SequentialAgentServiceImpl) + SequentialAgentServiceImpl.builder(DummyAgent.class).output(fn); + + // when + Workflow wf = service.getDefinition(); + + // then + assertNotNull(wf.getOutput(), "Output should not be null"); + } + + @Test + void shouldBuildSequenceTasks_withSubAgents() { + // given + Object agentA = AgentsUtils.newMovieExpert(); + Object agentB = AgentsUtils.newMovieExpert(); + SequentialAgentServiceImpl service = + (SequentialAgentServiceImpl) + SequentialAgentServiceImpl.builder(DummyAgent.class).subAgents(agentA, agentB); + + // when + Workflow wf = service.getDefinition(); + + // then + assertEquals(2, wf.getDo().size(), "There should be exactly two tasks"); + + wf.getDo() + .forEach( + t -> { + assertNotNull(t, "Task should not be null"); + Object task = t.getTask().getCallTask().get(); + assertInstanceOf( + CallJava.CallJavaFunction.class, task, "Task should be a CallTaskJava"); + }); + } + + static class DummyAgent {} +} diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowAgentsIT.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowAgentsIT.java new file mode 100644 index 00000000..52e4d86f --- /dev/null +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowAgentsIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.fluent.agentic.langchain4j; + +import static io.serverlessworkflow.fluent.agentic.langchain4j.Agents.AudienceEditor; +import static io.serverlessworkflow.fluent.agentic.langchain4j.Agents.CreativeWriter; +import static io.serverlessworkflow.fluent.agentic.langchain4j.Agents.StyleEditor; +import static io.serverlessworkflow.fluent.agentic.langchain4j.Models.BASE_MODEL; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import dev.langchain4j.agentic.AgenticServices; +import dev.langchain4j.agentic.UntypedAgent; +import dev.langchain4j.agentic.workflow.WorkflowAgentsBuilder; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class WorkflowAgentsIT { + + @Test + void sequential_agents_tests() { + WorkflowAgentsBuilder builder = new LC4JWorkflowBuilder(); + + CreativeWriter creativeWriter = + spy( + AgenticServices.agentBuilder(CreativeWriter.class) + .chatModel(BASE_MODEL) + .outputName("story") + .build()); + + AudienceEditor audienceEditor = + spy( + AgenticServices.agentBuilder(AudienceEditor.class) + .chatModel(BASE_MODEL) + .outputName("story") + .build()); + + StyleEditor styleEditor = + spy( + AgenticServices.agentBuilder(StyleEditor.class) + .chatModel(BASE_MODEL) + .outputName("story") + .build()); + + UntypedAgent novelCreator = + builder + .sequenceBuilder() + .subAgents(creativeWriter, audienceEditor, styleEditor) + .outputName("story") + .build(); + + Map input = + Map.of( + "topic", "dragons and wizards", + "style", "fantasy", + "audience", "young adults"); + + String story = (String) novelCreator.invoke(input); + System.out.println(story); + + verify(creativeWriter).generateStory("dragons and wizards"); + verify(audienceEditor).editStory(any(), eq("young adults")); + verify(styleEditor).editStory(any(), eq("fantasy")); + } +} diff --git a/fluent/agentic/pom.xml b/fluent/agentic/pom.xml index 849bf747..8946e189 100644 --- a/fluent/agentic/pom.xml +++ b/fluent/agentic/pom.xml @@ -12,12 +12,6 @@ Serverless Workflow :: Fluent :: Agentic serverlessworkflow-fluent-agentic - - 17 - 17 - UTF-8 - - io.serverlessworkflow @@ -31,12 +25,12 @@ dev.langchain4j langchain4j-agentic + org.slf4j slf4j-simple test - org.junit.jupiter junit-jupiter-api @@ -50,19 +44,36 @@ org.assertj assertj-core + test dev.langchain4j langchain4j-ollama test - 1.2.0-SNAPSHOT + ${version.dev.langchain4j} io.serverlessworkflow serverlessworkflow-experimental-agentic - ${project.version} test + + + + maven-jar-plugin + ${version.jar.plugin} + + + + + test-jar + + + + + + + \ No newline at end of file diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java index ebcde632..a6bcde34 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java @@ -18,8 +18,9 @@ import static dev.langchain4j.agentic.internal.AgentUtil.agentsToExecutors; import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; -import dev.langchain4j.agentic.internal.AgentInstance; +import dev.langchain4j.agentic.internal.AgentSpecification; import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; import java.util.List; import java.util.function.Function; @@ -31,11 +32,11 @@ public final class AgentAdapters { private AgentAdapters() {} public static List toExecutors(Object... agents) { - return agentsToExecutors(Stream.of(agents).map(AgentInstance.class::cast).toArray()); + return agentsToExecutors(Stream.of(agents).map(AgentSpecification.class::cast).toArray()); } - public static Function toFunction(AgentExecutor exec) { - return exec::invoke; + public static Function toFunction(AgentExecutor exec) { + return exec::execute; } public static LoopPredicateIndex toWhile(Predicate exit) { diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java index a69a4bd1..526deac0 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java @@ -57,6 +57,12 @@ public AgentDoTaskBuilder loop(String name, Consumer builder) return self(); } + @Override + public AgentDoTaskBuilder loop(String name, LoopAgentsBuilder builder) { + this.listBuilder().loop(name, builder); + return self(); + } + @Override public AgentDoTaskBuilder parallel(String name, Object... agents) { this.listBuilder().parallel(name, agents); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java index 60329e43..7066ce28 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java @@ -15,7 +15,7 @@ */ package io.serverlessworkflow.fluent.agentic; -import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; @@ -57,7 +57,8 @@ public AgentTaskItemListBuilder agent(String name, Object agent) { .forEach( exec -> this.delegate.callFn( - name, fn -> fn.function(AgentAdapters.toFunction(exec), Cognisphere.class))); + name, + fn -> fn.function(AgentAdapters.toFunction(exec), DefaultCognisphere.class))); return self(); } @@ -73,6 +74,12 @@ public AgentTaskItemListBuilder sequence(String name, Object... agents) { public AgentTaskItemListBuilder loop(String name, Consumer consumer) { final LoopAgentsBuilder builder = new LoopAgentsBuilder(); consumer.accept(builder); + this.loop(name, builder); + return self(); + } + + @Override + public AgentTaskItemListBuilder loop(String name, LoopAgentsBuilder builder) { this.addTaskItem(new TaskItem(name, new Task().withForTask(builder.build()))); return self(); } @@ -86,7 +93,7 @@ public AgentTaskItemListBuilder parallel(String name, Object... agents) { for (int i = 0; i < execs.size(); i++) { AgentExecutor ex = execs.get(i); fork.branch( - "branch-" + i + "-" + name, AgentAdapters.toFunction(ex), Cognisphere.class); + "branch-" + i + "-" + name, AgentAdapters.toFunction(ex), DefaultCognisphere.class); } }); return self(); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java index bd943ebc..e0eeb0c6 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java @@ -15,12 +15,13 @@ */ package io.serverlessworkflow.fluent.agentic; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; import io.serverlessworkflow.fluent.spec.BaseWorkflowBuilder; import java.util.UUID; public class AgentWorkflowBuilder - extends BaseWorkflowBuilder< - AgentWorkflowBuilder, AgentDoTaskBuilder, AgentTaskItemListBuilder> { + extends BaseWorkflowBuilder + implements FuncTransformations { AgentWorkflowBuilder(final String name, final String namespace, final String version) { super(name, namespace, version); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java index f98089c8..5f3ef831 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java @@ -31,7 +31,7 @@ public class LoopAgentsBuilder { private final FuncTaskItemListBuilder funcDelegate; private final ForTaskFunction forTask; - LoopAgentsBuilder() { + public LoopAgentsBuilder() { this.forTask = new ForTaskFunction(); this.forTask.setFor(new ForTaskConfiguration()); this.funcDelegate = new FuncTaskItemListBuilder(); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java index c23f8ef3..aaa7176f 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java @@ -40,6 +40,12 @@ default SELF loop(Consumer builder) { return loop("loop-" + UUID.randomUUID(), builder); } + SELF loop(String name, LoopAgentsBuilder builder); + + default SELF loop(LoopAgentsBuilder builder) { + return loop("loop-" + UUID.randomUUID(), builder); + } + SELF parallel(String name, Object... agents); default SELF parallel(Object... agents) { diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java index 008fe48b..7b8543c5 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java @@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.spy; -import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.cognisphere.Cognisphere; import io.serverlessworkflow.api.types.ForkTask; import io.serverlessworkflow.api.types.Task; @@ -43,7 +43,7 @@ class AgentWorkflowBuilderTest { public void verifyAgentCall() { Agents.MovieExpert movieExpert = spy( - AgentServices.agentBuilder(Agents.MovieExpert.class) + AgenticServices.agentBuilder(Agents.MovieExpert.class) .outputName("movies") .chatModel(BASE_MODEL) .build()); diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java index 7215ddd6..a0c970ab 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java @@ -16,7 +16,7 @@ package io.serverlessworkflow.fluent.agentic; import dev.langchain4j.agentic.Agent; -import dev.langchain4j.agentic.internal.AgentInstance; +import dev.langchain4j.agentic.internal.AgentSpecification; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import java.util.List; @@ -36,7 +36,7 @@ interface MovieExpert { List findMovie(@V("mood") String mood); } - interface SettingAgent extends AgentInstance { + interface SettingAgent extends AgentSpecification { @UserMessage( """ @@ -49,7 +49,7 @@ interface SettingAgent extends AgentInstance { String invoke(@V("style") String style); } - interface HeroAgent extends AgentInstance { + interface HeroAgent extends AgentSpecification { @UserMessage( """ @@ -60,7 +60,7 @@ interface HeroAgent extends AgentInstance { String invoke(@V("style") String style); } - interface ConflictAgent extends AgentInstance { + interface ConflictAgent extends AgentSpecification { @UserMessage( """ @@ -72,7 +72,7 @@ interface ConflictAgent extends AgentInstance { String invoke(@V("style") String style); } - interface FactAgent extends AgentInstance { + interface FactAgent extends AgentSpecification { @UserMessage( """ @@ -82,7 +82,7 @@ interface FactAgent extends AgentInstance { String invoke(@V("fact") String fact); } - interface CultureAgent extends AgentInstance { + interface CultureAgent extends AgentSpecification { @UserMessage( """ @@ -94,7 +94,7 @@ interface CultureAgent extends AgentInstance { List invoke(@V("fact") String fact); } - interface TechnologyAgent extends AgentInstance { + interface TechnologyAgent extends AgentSpecification { @UserMessage( """ @@ -106,7 +106,7 @@ interface TechnologyAgent extends AgentInstance { List invoke(@V("fact") String fact); } - interface StorySeedAgent extends AgentInstance { + interface StorySeedAgent extends AgentSpecification { @UserMessage( """ @@ -117,7 +117,7 @@ You are a science fiction writer. Given the following title, come up with a shor String invoke(@V("title") String title); } - interface PlotAgent extends AgentInstance { + interface PlotAgent extends AgentSpecification { @UserMessage( """ @@ -129,7 +129,7 @@ interface PlotAgent extends AgentInstance { String invoke(@V("premise") String premise); } - interface SceneAgent extends AgentInstance { + interface SceneAgent extends AgentSpecification { @UserMessage( """ @@ -141,7 +141,7 @@ interface SceneAgent extends AgentInstance { String invoke(@V("plot") String plot); } - interface MeetingInvitationDraft extends AgentInstance { + interface MeetingInvitationDraft extends AgentSpecification { @UserMessage( """ @@ -161,7 +161,7 @@ String invoke( @V("agenda") String agenda); } - interface MeetingInvitationStyle extends AgentInstance { + interface MeetingInvitationStyle extends AgentSpecification { @UserMessage( """ diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java index a59f62e1..f44d2a22 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java @@ -18,7 +18,7 @@ import static io.serverlessworkflow.fluent.agentic.Models.BASE_MODEL; import static org.mockito.Mockito.spy; -import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.AgenticServices; public final class AgentsUtils { @@ -26,7 +26,7 @@ private AgentsUtils() {} public static Agents.MovieExpert newMovieExpert() { return spy( - AgentServices.agentBuilder(Agents.MovieExpert.class) + AgenticServices.agentBuilder(Agents.MovieExpert.class) .outputName("movies") .chatModel(BASE_MODEL) .build()); diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java index 8bbbe62d..7bacd768 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java @@ -18,23 +18,20 @@ import static io.serverlessworkflow.fluent.agentic.Agents.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.AgenticServices; +import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; import dev.langchain4j.agentic.workflow.HumanInTheLoop; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowApplication; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Collectors; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -56,10 +53,15 @@ public void testAgent() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - String result = - app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + DefaultCognisphere result = + app.workflowDefinition(workflow) + .instance(topic) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow(); - assertEquals("storySeedAgent", result); + assertEquals("storySeedAgent", result.readState("premise")); } } @@ -91,10 +93,15 @@ public void testAgents() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - String result = - app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + DefaultCognisphere result = + app.workflowDefinition(workflow) + .instance(topic) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow(); - assertEquals("sceneAgent", result); + assertEquals("sceneAgent", result.readState("story")); } } @@ -122,10 +129,15 @@ public void testSequence() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - String result = - app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + DefaultCognisphere result = + app.workflowDefinition(workflow) + .instance(topic) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow(); - assertEquals("sceneAgent", result); + assertEquals("sceneAgent", result.readState("story")); } } @@ -154,25 +166,21 @@ public void testParallel() throws ExecutionException, InterruptedException { topic.put("style", "sci-fi"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - Map result = - app.workflowDefinition(workflow).instance(topic).start().get().asMap().orElseThrow(); - - assertEquals(3, result.size()); - assertTrue(result.containsKey("branch-0-story")); - assertTrue(result.containsKey("branch-1-story")); - assertTrue(result.containsKey("branch-2-story")); - - Set values = - result.values().stream().map(Object::toString).collect(Collectors.toSet()); + DefaultCognisphere result = + app.workflowDefinition(workflow) + .instance(topic) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow(); - assertTrue(values.contains("Fake conflict response")); - assertTrue(values.contains("Fake hero response")); - assertTrue(values.contains("Fake setting response")); + assertEquals("Fake conflict response", result.readState("setting")); + assertEquals("Fake hero response", result.readState("hero")); + assertEquals("Fake setting response", result.readState("conflict")); } } @Test - // TODO: callFn must be replace with a .output() method once it's available public void testSeqAndThenParallel() throws ExecutionException, InterruptedException { final FactAgent factAgent = mock(FactAgent.class); final CultureAgent cultureAgent = mock(CultureAgent.class); @@ -197,15 +205,6 @@ public void testSeqAndThenParallel() throws ExecutionException, InterruptedExcep .tasks( d -> d.sequence("fact", factAgent) - .callFn( - f -> - f.function( - (Function>) - fact -> { - Map result = new HashMap<>(); - result.put("fact", fact); - return result; - })) .parallel("cultureAndTechnology", cultureAgent, technologyAgent)) .build(); @@ -213,20 +212,22 @@ public void testSeqAndThenParallel() throws ExecutionException, InterruptedExcep topic.put("fact", "alien"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - Map result = - app.workflowDefinition(workflow).instance(topic).start().get().asMap().orElseThrow(); - - assertEquals(2, result.size()); - assertTrue(result.containsKey("branch-0-cultureAndTechnology")); - assertTrue(result.containsKey("branch-1-cultureAndTechnology")); + DefaultCognisphere result = + app.workflowDefinition(workflow) + .instance(topic) + .start() + .get() + .as(DefaultCognisphere.class) + .orElseThrow(); - assertEquals(cultureTraits, result.get("branch-0-cultureAndTechnology")); - assertEquals(technologyTraits, result.get("branch-1-cultureAndTechnology")); + assertEquals(cultureTraits, result.readState("culture")); + assertEquals(technologyTraits, result.readState("technology")); } } @Test - @Disabled("HumanLoop not implemented yet") + @Disabled( + "HumanInTheLoop is not a dev.langchain4j.agentic.internal.AgentSpecification, we should treat it differently once it's implemented") public void humanInTheLoop() throws ExecutionException, InterruptedException { final MeetingInvitationDraft meetingInvitationDraft = mock(MeetingInvitationDraft.class); when(meetingInvitationDraft.invoke( @@ -246,7 +247,7 @@ public void humanInTheLoop() throws ExecutionException, InterruptedException { AtomicReference request = new AtomicReference<>(); HumanInTheLoop humanInTheLoop = - AgentServices.humanInTheLoopBuilder() + AgenticServices.humanInTheLoopBuilder() .description( "What level of formality would you like? (please reply with “formal”, “casual”, or “friendly”)") .inputName("style") @@ -273,15 +274,15 @@ public void humanInTheLoop() throws ExecutionException, InterruptedException { initialValues.put("agenda", "Discuss project updates"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - String result = + DefaultCognisphere result = app.workflowDefinition(workflow) .instance(initialValues) .start() .get() - .asText() + .as(DefaultCognisphere.class) .orElseThrow(); - assertEquals("Styled meeting invitation for John Doe", result); + assertEquals("Styled meeting invitation for John Doe", result.readState("styled")); } } } diff --git a/fluent/pom.xml b/fluent/pom.xml index 51aac994..d88e774c 100644 --- a/fluent/pom.xml +++ b/fluent/pom.xml @@ -16,6 +16,10 @@ 17 17 UTF-8 + + + 1.3.0-beta9-SNAPSHOT + 1.3.0-SNAPSHOT @@ -40,6 +44,38 @@ serverlessworkflow-fluent-func ${project.version} + + io.serverlessworkflow + serverlessworkflow-fluent-agentic + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-fluent-agentic-langchain4j + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-experimental-agentic + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-experimental-lambda + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-fluent-agentic + ${project.version} + test-jar + test + + + dev.langchain4j + langchain4j-agentic + ${version.dev.langchain4j.agentic} + @@ -47,6 +83,7 @@ spec func agentic + agentic-langchain4j \ No newline at end of file diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java index 2ab0bb17..3aed00ad 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java @@ -21,6 +21,7 @@ import io.serverlessworkflow.api.types.Output; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.fluent.spec.spi.TransformationHandlers; +import java.util.UUID; import java.util.function.Consumer; public abstract class BaseWorkflowBuilder< @@ -42,6 +43,9 @@ protected BaseWorkflowBuilder(final String name, final String namespace, final S this.document.setNamespace(namespace); this.document.setVersion(version); this.document.setDsl(DSL); + if (this.document.getName() == null || this.document.getName().isEmpty()) { + this.document.setName(UUID.randomUUID().toString()); + } this.workflow = new Workflow(); this.workflow.setDocument(this.document); } From 32e1f5471eb363ad7d5754e4534d8ae7fa3de024 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Wed, 6 Aug 2025 19:45:56 -0400 Subject: [PATCH 2/6] Remove skip agentic modules from CI Signed-off-by: Ricardo Zanini --- .github/workflows/maven-verify.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index a34291bc..a2efd054 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -43,7 +43,6 @@ jobs: - name: Verify with Maven run: | mvn -B -f pom.xml clean install verify \ - -pl ",!fluent/agentic" -pl ",!experimental/agentic" \ -am # 6. Verify examples From eeee93de824d2470b18a797019514031a7d482ae Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Wed, 6 Aug 2025 19:47:55 -0400 Subject: [PATCH 3/6] Fix langchain4j mvn command Signed-off-by: Ricardo Zanini --- .github/workflows/maven-verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index a2efd054..b9cdac6a 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -36,7 +36,7 @@ jobs: # 4. Build the external agentic module - name: Build langchain4j-agentic run: | - mvn -B -U -T12C clean package -DskipTests + mvn -B -U -T12C -f langchain4j/pom.xml clean package -DskipTests mvn -B -f langchain4j/langchain4j-agentic/pom.xml clean install -DskipTests # 5. Verify the main sdk-java project, excluding the two agentic modules From 9a4858ac7f4797a32f861c72f70f6a97bad85696 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Thu, 7 Aug 2025 12:59:34 -0400 Subject: [PATCH 4/6] Wrap up langchain4j integration work; add CI to run integration tests Signed-off-by: Ricardo Zanini --- .github/workflows/integration-tests.yml | 40 ++++++++++ .github/workflows/maven-verify.yml | 23 +----- .../expressions/agentic/AgenticModel.java | 11 ++- .../agentic/AgenticModelCollection.java | 24 +++--- .../agentic/AgenticModelFactory.java | 35 +++++---- ...java => AgenticScopeRegistryAssessor.java} | 39 +++++----- .../langchain4j/AbstractAgentService.java | 30 +++++--- .../ConditionalAgentServiceImpl.java | 8 +- .../langchain4j/LoopAgentServiceImpl.java | 4 +- .../WorkflowInvocationHandler.java | 76 ++++++++++--------- .../fluent/agentic/langchain4j/Agents.java | 10 +-- .../SequentialAgentServiceImplTest.java | 4 +- .../fluent/agentic/AgentAdapters.java | 8 +- .../agentic/AgentTaskItemListBuilder.java | 8 +- .../fluent/agentic/LoopAgentsBuilder.java | 6 +- .../agentic/AgentWorkflowBuilderTest.java | 6 +- .../fluent/agentic/WorkflowTests.java | 26 +++---- fluent/pom.xml | 9 ++- pom.xml | 31 ++++++++ 19 files changed, 235 insertions(+), 163 deletions(-) create mode 100644 .github/workflows/integration-tests.yml rename experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/{CognisphereRegistryAssessor.java => AgenticScopeRegistryAssessor.java} (55%) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000..37cac254 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,40 @@ +name: sdk-java Integration Tests +on: + issue_comment: + types: [ created ] + +jobs: + run-integration-tests: + # 2) Only run if the comment is exactly "/run integration-tests" + # and it’s on a pull request (not on an issue) + if: > + github.event.comment.body == '/run integration-tests' && + github.event.issue.pull_request != null + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + checks: write + id-token: write + + steps: + # 3) Checkout the **PR’s** code + - name: Checkout PR code + uses: actions/checkout@v4 + with: + repository: ${{ github.event.issue.pull_request.head.repo.full_name }} + ref: ${{ github.event.issue.pull_request.head.ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + # 4) Set up Java/Maven (cache enabled) + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + cache: maven + + # 5) Run only the IT suite + - name: Run integration-tests profile + run: mvn -B -f pom.xml clean verify -Pintegration-tests \ No newline at end of file diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index b9cdac6a..74e3dd69 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -17,15 +17,7 @@ jobs: - name: Checkout sdk-java uses: actions/checkout@v4 - # 2. (Temporary) Checkout the langchain4j repo at agentic-module - - name: Checkout langchain4j (agentic-module) - uses: actions/checkout@v4 - with: - repository: mariofusco/langchain4j - ref: agentic-module - path: langchain4j - - # 3. Set up JDK 17 for both builds + # 2. Set up JDK 17 for both builds - name: Set up JDK 17 uses: actions/setup-java@v4 with: @@ -33,19 +25,12 @@ jobs: java-version: 17 cache: 'maven' - # 4. Build the external agentic module - - name: Build langchain4j-agentic - run: | - mvn -B -U -T12C -f langchain4j/pom.xml clean package -DskipTests - mvn -B -f langchain4j/langchain4j-agentic/pom.xml clean install -DskipTests - - # 5. Verify the main sdk-java project, excluding the two agentic modules + # 3. Verify the main sdk-java project, excluding the two agentic modules - name: Verify with Maven run: | - mvn -B -f pom.xml clean install verify \ - -am + mvn -B -f pom.xml clean install verify -am - # 6. Verify examples + # 4. Verify examples - name: Verify Examples with Maven run: | mvn -B -f examples/pom.xml clean install verify diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java index b8b723f7..70db1337 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java @@ -15,16 +15,17 @@ */ package io.serverlessworkflow.impl.expressions.agentic; -import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.scope.AgenticScope; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.expressions.func.JavaModel; import java.util.Collection; +import java.util.Map; import java.util.Optional; class AgenticModel extends JavaModel { - AgenticModel(Cognisphere cognisphere) { - super(cognisphere); + AgenticModel(AgenticScope agenticScope) { + super(agenticScope); } @Override @@ -39,8 +40,10 @@ public Collection asCollection() { @Override public Optional as(Class clazz) { - if (Cognisphere.class.isAssignableFrom(clazz)) { + if (AgenticScope.class.isAssignableFrom(clazz)) { return Optional.of(clazz.cast(object)); + } else if (Map.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(((AgenticScope) object).state())); } else { return super.as(clazz); } diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java index 58402b0e..2ea0e382 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.impl.expressions.agentic; -import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import dev.langchain4j.agentic.scope.AgenticScope; +import dev.langchain4j.agentic.scope.ResultWithAgenticScope; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.expressions.func.JavaModelCollection; import java.util.Collection; @@ -24,28 +24,28 @@ class AgenticModelCollection extends JavaModelCollection { - private final Cognisphere cognisphere; + private final AgenticScope agenticScope; - AgenticModelCollection(Collection object, Cognisphere cognisphere) { + AgenticModelCollection(Collection object, AgenticScope agenticScope) { super(object); - this.cognisphere = cognisphere; + this.agenticScope = agenticScope; } - AgenticModelCollection(Cognisphere cognisphere) { - this.cognisphere = cognisphere; + AgenticModelCollection(AgenticScope agenticScope) { + this.agenticScope = agenticScope; } @Override protected WorkflowModel nextItem(Object obj) { - return new AgenticModel((Cognisphere) obj); + return new AgenticModel((AgenticScope) obj); } @Override public Optional as(Class clazz) { - if (Cognisphere.class.isAssignableFrom(clazz)) { - return Optional.of(clazz.cast(cognisphere)); - } else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) { - return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object))); + if (AgenticScope.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(agenticScope)); + } else if (ResultWithAgenticScope.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(new ResultWithAgenticScope<>(agenticScope, object))); } else { return super.as(clazz); } diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java index 43bf27cb..9f95c0d5 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java @@ -15,13 +15,13 @@ */ package io.serverlessworkflow.impl.expressions.agentic; -import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.scope.AgenticScope; import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelCollection; import io.serverlessworkflow.impl.WorkflowModelFactory; -import io.serverlessworkflow.impl.expressions.agentic.langchain4j.CognisphereRegistryAssessor; +import io.serverlessworkflow.impl.expressions.agentic.langchain4j.AgenticScopeRegistryAssessor; import io.serverlessworkflow.impl.expressions.func.JavaModel; import java.time.OffsetDateTime; import java.util.Map; @@ -29,25 +29,24 @@ class AgenticModelFactory implements WorkflowModelFactory { /** - * Applies any change to the model after running as task. We will always set it to - * a @DefaultCognisphere object since @AgentExecutor is always adding the output to the - * cognisphere. We just have to make sure that cognisphere is always passed to the next input - * task. + * Applies any change to the model after running as task. We will always set it to a @AgenticScope + * object since @AgentExecutor is always adding the output to the agenticScope. We just have to + * make sure that agenticScope is always passed to the next input task. * - * @param prev the global Cognisphere object getting updated by the workflow context - * @param obj the same Cognisphere object updated by the AgentExecutor - * @return the workflow context model holding the cognisphere object. + * @param prev the global AgenticScope object getting updated by the workflow context + * @param obj the same AgenticScope object updated by the AgentExecutor + * @return the workflow context model holding the agenticScope object. */ @Override public WorkflowModel fromAny(WorkflowModel prev, Object obj) { - // We ignore `obj` since it's already included in `prev` within the Cognisphere instance + // We ignore `obj` since it's already included in `prev` within the agenticScope instance return prev; } @Override public WorkflowModel combine(Map workflowVariables) { - // TODO: create a new cognisphere object in the CognisphereRegistryAssessor per branch - // TODO: Since we share the same cognisphere object, both branches are updating the same + // TODO: create a new agenticScope object in the AgenticScopeRegistryAssessor per branch + // TODO: Since we share the same agenticScope object, both branches are updating the same // instance, so for now we return the first key. return workflowVariables.values().iterator().next(); } @@ -57,7 +56,7 @@ public WorkflowModelCollection createCollection() { throw new UnsupportedOperationException(); } - // TODO: all these methods can use Cognisphere as long as we have access to the `outputName` + // TODO: all these methods can use agenticScope as long as we have access to the `outputName` @Override public WorkflowModel from(boolean value) { @@ -91,9 +90,9 @@ public WorkflowModel from(OffsetDateTime value) { @Override public WorkflowModel from(Map map) { - final Cognisphere cognisphere = new CognisphereRegistryAssessor().getCognisphere(); - cognisphere.writeStates(map); - return new AgenticModel(cognisphere); + final AgenticScope agenticScope = new AgenticScopeRegistryAssessor().getAgenticScope(); + agenticScope.writeStates(map); + return new AgenticModel(agenticScope); } @Override @@ -103,8 +102,8 @@ public WorkflowModel fromNull() { @Override public WorkflowModel fromOther(Object value) { - if (value instanceof Cognisphere) { - return new AgenticModel((Cognisphere) value); + if (value instanceof AgenticScope) { + return new AgenticModel((AgenticScope) value); } return new JavaModel(value); } diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/AgenticScopeRegistryAssessor.java similarity index 55% rename from experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java rename to experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/AgenticScopeRegistryAssessor.java index e7be366d..01ccd1cd 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/CognisphereRegistryAssessor.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/langchain4j/AgenticScopeRegistryAssessor.java @@ -15,27 +15,28 @@ */ package io.serverlessworkflow.impl.expressions.agentic.langchain4j; -import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; -import dev.langchain4j.agentic.internal.CognisphereOwner; +import dev.langchain4j.agentic.internal.AgenticScopeOwner; +import dev.langchain4j.agentic.scope.AgenticScopeRegistry; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; import java.util.Objects; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; -public class CognisphereRegistryAssessor implements CognisphereOwner { +public class AgenticScopeRegistryAssessor implements AgenticScopeOwner { - private final AtomicReference cognisphereRegistry = new AtomicReference<>(); + private final AtomicReference agenticScopeRegistry = + new AtomicReference<>(); private final String agentId; - private DefaultCognisphere cognisphere; + private DefaultAgenticScope agenticScope; private Object memoryId; - public CognisphereRegistryAssessor(String agentId) { + public AgenticScopeRegistryAssessor(String agentId) { Objects.requireNonNull(agentId, "Agent id cannot be null"); this.agentId = agentId; } // TODO: have access to the workflow definition and assign its name instead - public CognisphereRegistryAssessor() { + public AgenticScopeRegistryAssessor() { this.agentId = UUID.randomUUID().toString(); } @@ -43,28 +44,28 @@ public void setMemoryId(Object memoryId) { this.memoryId = memoryId; } - public DefaultCognisphere getCognisphere() { - if (cognisphere != null) { - return cognisphere; + public DefaultAgenticScope getAgenticScope() { + if (agenticScope != null) { + return agenticScope; } if (memoryId != null) { - this.cognisphere = registry().getOrCreate(memoryId); + this.agenticScope = registry().getOrCreate(memoryId); } else { - this.cognisphere = registry().createEphemeralCognisphere(); + this.agenticScope = registry().createEphemeralAgenticScope(); } - return this.cognisphere; + return this.agenticScope; } @Override - public CognisphereOwner withCognisphere(DefaultCognisphere cognisphere) { - this.cognisphere = cognisphere; + public AgenticScopeOwner withAgenticScope(DefaultAgenticScope agenticScope) { + this.agenticScope = agenticScope; return this; } @Override - public CognisphereRegistry registry() { - cognisphereRegistry.compareAndSet(null, new CognisphereRegistry(agentId)); - return cognisphereRegistry.get(); + public AgenticScopeRegistry registry() { + agenticScopeRegistry.compareAndSet(null, new AgenticScopeRegistry(agentId)); + return agenticScopeRegistry.get(); } } diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java index 52ea002f..c7ed7d83 100644 --- a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/AbstractAgentService.java @@ -15,10 +15,12 @@ */ package io.serverlessworkflow.fluent.agentic.langchain4j; -import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; +import dev.langchain4j.agentic.agent.ErrorContext; +import dev.langchain4j.agentic.agent.ErrorRecoveryResult; import dev.langchain4j.agentic.internal.AgentSpecification; -import dev.langchain4j.agentic.internal.CognisphereOwner; +import dev.langchain4j.agentic.internal.AgenticScopeOwner; +import dev.langchain4j.agentic.scope.AgenticScope; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; import io.serverlessworkflow.impl.WorkflowApplication; @@ -29,7 +31,8 @@ public abstract class AbstractAgentService implements WorkflowDefinitionBuilder { // Workflow OutputAs - private static final Function DEFAULT_OUTPUT_FUNCTION = cognisphere -> null; + private static final Function DEFAULT_OUTPUT_FUNCTION = + agenticScope -> null; protected final WorkflowApplication.Builder workflowExecBuilder; protected final AgentWorkflowBuilder workflowBuilder; @@ -46,34 +49,39 @@ public T build() { return (T) Proxy.newProxyInstance( this.agentServiceClass.getClassLoader(), - new Class[] {agentServiceClass, AgentSpecification.class, CognisphereOwner.class}, + new Class[] {agentServiceClass, AgentSpecification.class, AgenticScopeOwner.class}, new WorkflowInvocationHandler( this.workflowBuilder.build(), this.workflowExecBuilder, this.agentServiceClass)); } @SuppressWarnings("unchecked") - public S beforeCall(Consumer beforeCall) { + public S beforeCall(Consumer beforeCall) { this.workflowBuilder.inputFrom( cog -> { beforeCall.accept(cog); return cog; }, - Cognisphere.class); + AgenticScope.class); return (S) this; } @SuppressWarnings("unchecked") public S outputName(String outputName) { - Function outputFunction = cog -> cog.readState(outputName); - this.workflowBuilder.outputAs(outputFunction, DefaultCognisphere.class); + Function outputFunction = cog -> cog.readState(outputName); + this.workflowBuilder.outputAs(outputFunction, DefaultAgenticScope.class); this.workflowBuilder.document( d -> d.metadata(m -> m.metadata(META_KEY_OUTPUTNAME, outputName))); return (S) this; } @SuppressWarnings("unchecked") - public S output(Function output) { - this.workflowBuilder.outputAs(output, Cognisphere.class); + public S output(Function output) { + this.workflowBuilder.outputAs(output, AgenticScope.class); + return (S) this; + } + + @SuppressWarnings("unchecked") + public S errorHandler(Function errorHandler) { return (S) this; } diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java index f96318b0..2b3b0e2c 100644 --- a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/ConditionalAgentServiceImpl.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.fluent.agentic.langchain4j; -import dev.langchain4j.agentic.cognisphere.Cognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.scope.AgenticScope; import dev.langchain4j.agentic.workflow.ConditionalAgentService; import java.util.Arrays; import java.util.List; @@ -46,7 +46,7 @@ public ConditionalAgentService subAgents(List agentExecutors) } @Override - public ConditionalAgentService subAgents(Predicate condition, Object... agents) { + public ConditionalAgentService subAgents(Predicate condition, Object... agents) { this.workflowBuilder.tasks( t -> Arrays.stream(agents).forEach(agent -> t.when(condition).agent(agent))); return this; @@ -54,13 +54,13 @@ public ConditionalAgentService subAgents(Predicate condition, Ob @Override public ConditionalAgentService subAgents( - Predicate condition, List agentExecutors) { + Predicate condition, List agentExecutors) { return this.subAgents(condition, agentExecutors.toArray()); } @Override public ConditionalAgentService subAgent( - Predicate condition, AgentExecutor agentExecutor) { + Predicate condition, AgentExecutor agentExecutor) { this.workflowBuilder.tasks(t -> t.when(condition).agent(agentExecutor)); return this; } diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java index 3c2874cf..d7bc5b17 100644 --- a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/LoopAgentServiceImpl.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.fluent.agentic.langchain4j; -import dev.langchain4j.agentic.cognisphere.Cognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.scope.AgenticScope; import dev.langchain4j.agentic.workflow.LoopAgentService; import io.serverlessworkflow.fluent.agentic.LoopAgentsBuilder; import java.util.List; @@ -42,7 +42,7 @@ public LoopAgentService maxIterations(int maxIterations) { } @Override - public LoopAgentService exitCondition(Predicate exitCondition) { + public LoopAgentService exitCondition(Predicate exitCondition) { this.loopAgentsBuilder.exitCondition(exitCondition); return this; } diff --git a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java index 51238286..0f7ce656 100644 --- a/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java +++ b/fluent/agentic-langchain4j/src/main/java/io/serverlessworkflow/fluent/agentic/langchain4j/WorkflowInvocationHandler.java @@ -16,29 +16,29 @@ package io.serverlessworkflow.fluent.agentic.langchain4j; import dev.langchain4j.agentic.UntypedAgent; -import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.CognisphereAccess; -import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; -import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; import dev.langchain4j.agentic.internal.AgentInvoker; import dev.langchain4j.agentic.internal.AgentSpecification; -import dev.langchain4j.agentic.internal.CognisphereOwner; +import dev.langchain4j.agentic.internal.AgenticScopeOwner; +import dev.langchain4j.agentic.scope.AgenticScope; +import dev.langchain4j.agentic.scope.AgenticScopeAccess; +import dev.langchain4j.agentic.scope.AgenticScopeRegistry; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; +import dev.langchain4j.agentic.scope.ResultWithAgenticScope; import dev.langchain4j.service.MemoryId; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.expressions.agentic.langchain4j.CognisphereRegistryAssessor; +import io.serverlessworkflow.impl.expressions.agentic.langchain4j.AgenticScopeRegistryAssessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Map; import java.util.concurrent.ExecutionException; -public class WorkflowInvocationHandler implements InvocationHandler, CognisphereOwner { +public class WorkflowInvocationHandler implements InvocationHandler, AgenticScopeOwner { private final Workflow workflow; private final WorkflowApplication.Builder workflowApplicationBuilder; - private final CognisphereRegistryAssessor cognisphereRegistryAssessor; + private final AgenticScopeRegistryAssessor agenticScopeRegistryAssessor; WorkflowInvocationHandler( Workflow workflow, @@ -46,19 +46,21 @@ public class WorkflowInvocationHandler implements InvocationHandler, Cognisphere Class agentServiceClass) { this.workflow = workflow; this.workflowApplicationBuilder = workflowApplicationBuilder; - this.cognisphereRegistryAssessor = new CognisphereRegistryAssessor(agentServiceClass.getName()); + this.agenticScopeRegistryAssessor = + new AgenticScopeRegistryAssessor(agentServiceClass.getName()); } @SuppressWarnings("unchecked") - private static void writeCognisphereState(Cognisphere cognisphere, Method method, Object[] args) { + private static void writeAgenticScopeState( + AgenticScope agenticScope, Method method, Object[] args) { if (method.getDeclaringClass() == UntypedAgent.class) { - cognisphere.writeStates((Map) args[0]); + agenticScope.writeStates((Map) args[0]); } else { Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { int index = i; AgentInvoker.optionalParameterName(parameters[i]) - .ifPresent(argName -> cognisphere.writeState(argName, args[index])); + .ifPresent(argName -> agenticScope.writeState(argName, args[index])); } } } @@ -78,7 +80,7 @@ private String outputName() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - CognisphereRegistry registry = registry(); + AgenticScopeRegistry registry = registry(); // outputName if (method.getDeclaringClass() == AgentSpecification.class) { return switch (method.getName()) { @@ -89,23 +91,23 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl }; } // withCognisphere - if (method.getDeclaringClass() == CognisphereOwner.class) { - // Ingest the workflow input as a Cognisphere object + if (method.getDeclaringClass() == AgenticScopeOwner.class) { + // Ingest the workflow input as a AgenticScope object // Later, retrieve it and start the workflow with it as input. return switch (method.getName()) { - case "withCognisphere" -> this.withCognisphere((DefaultCognisphere) args[0]); + case "withAgenticScope" -> this.withAgenticScope((DefaultAgenticScope) args[0]); case "registry" -> registry; default -> throw new UnsupportedOperationException( "Unknown method on CognisphereOwner class : " + method.getName()); }; } - // getCognisphere + // getAgenticScope // evictCognisphere - if (method.getDeclaringClass() == CognisphereAccess.class) { + if (method.getDeclaringClass() == AgenticScopeAccess.class) { return switch (method.getName()) { - case "getCognisphere" -> registry().get(args[0]); - case "evictCognisphere" -> registry().evict(args[0]); + case "getAgenticScope" -> registry().get(args[0]); + case "evictAgenticScope" -> registry().evict(args[0]); default -> throw new UnsupportedOperationException( "Unknown method on CognisphereAccess class : " + method.getName()); @@ -116,41 +118,41 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return executeWorkflow(currentCognisphere(method, args), method, args); } - private Object executeWorkflow(DefaultCognisphere cognisphere, Method method, Object[] args) { - writeCognisphereState(cognisphere, method, args); + private Object executeWorkflow(DefaultAgenticScope agenticScope, Method method, Object[] args) { + writeAgenticScopeState(agenticScope, method, args); try (WorkflowApplication app = workflowApplicationBuilder.build()) { // TODO improve result handling - DefaultCognisphere output = + DefaultAgenticScope output = app.workflowDefinition(workflow) - .instance(cognisphere) + .instance(agenticScope) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow( () -> new IllegalArgumentException( "Workflow hasn't returned a Cognisphere object.")); Object result = output.readState(outputName()); - return method.getReturnType().equals(ResultWithCognisphere.class) - ? new ResultWithCognisphere<>(output, result) + return method.getReturnType().equals(ResultWithAgenticScope.class) + ? new ResultWithAgenticScope<>(output, result) : result; } catch (ExecutionException | InterruptedException e) { throw new RuntimeException( "Failed to execute workflow: " + workflow.getDocument().getName() - + " - Cognisphere: " - + cognisphere, + + " - AgenticScope: " + + agenticScope, e); } } - private DefaultCognisphere currentCognisphere(Method method, Object[] args) { + private DefaultAgenticScope currentCognisphere(Method method, Object[] args) { Object memoryId = memoryId(method, args); - this.cognisphereRegistryAssessor.setMemoryId(memoryId); - return this.cognisphereRegistryAssessor.getCognisphere(); + this.agenticScopeRegistryAssessor.setMemoryId(memoryId); + return this.agenticScopeRegistryAssessor.getAgenticScope(); } private Object memoryId(Method method, Object[] args) { @@ -164,13 +166,13 @@ private Object memoryId(Method method, Object[] args) { } @Override - public CognisphereOwner withCognisphere(DefaultCognisphere cognisphere) { - this.cognisphereRegistryAssessor.withCognisphere(cognisphere); + public AgenticScopeOwner withAgenticScope(DefaultAgenticScope agenticScope) { + this.agenticScopeRegistryAssessor.withAgenticScope(agenticScope); return this; } @Override - public CognisphereRegistry registry() { - return this.cognisphereRegistryAssessor.registry(); + public AgenticScopeRegistry registry() { + return this.agenticScopeRegistryAssessor.registry(); } } diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java index 5bb3c5a3..d29a01af 100644 --- a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/Agents.java @@ -17,8 +17,8 @@ import dev.langchain4j.agent.tool.Tool; import dev.langchain4j.agentic.Agent; -import dev.langchain4j.agentic.cognisphere.CognisphereAccess; -import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import dev.langchain4j.agentic.scope.AgenticScopeAccess; +import dev.langchain4j.agentic.scope.ResultWithAgenticScope; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; @@ -32,7 +32,7 @@ public interface ExpertRouterAgent { String ask(@V("request") String request); } - public interface ExpertRouterAgentWithMemory extends CognisphereAccess { + public interface ExpertRouterAgentWithMemory extends AgenticScopeAccess { @Agent String ask(@MemoryId String memoryId, @V("request") String request); @@ -209,10 +209,10 @@ public interface StyleReviewLoop { String scoreAndReview(@V("story") String story, @V("style") String style); } - public interface StyledWriter extends CognisphereAccess { + public interface StyledWriter extends AgenticScopeAccess { @Agent - ResultWithCognisphere writeStoryWithStyle( + ResultWithAgenticScope writeStoryWithStyle( @V("topic") String topic, @V("style") String style); } diff --git a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java index 957635a6..1902f2ce 100644 --- a/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java +++ b/fluent/agentic-langchain4j/src/test/java/io/serverlessworkflow/fluent/agentic/langchain4j/SequentialAgentServiceImplTest.java @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.scope.AgenticScope; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.api.types.func.CallJava; import io.serverlessworkflow.api.types.func.OutputAsFunction; @@ -78,7 +78,7 @@ void shouldSetOutputName_inDocumentMetadata() { @Test void shouldSetOutputFunction_extension() { // given - Function fn = ctx -> 42; + Function fn = ctx -> 42; SequentialAgentServiceImpl service = (SequentialAgentServiceImpl) SequentialAgentServiceImpl.builder(DummyAgent.class).output(fn); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java index a6bcde34..8127174d 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java @@ -17,10 +17,10 @@ import static dev.langchain4j.agentic.internal.AgentUtil.agentsToExecutors; -import dev.langchain4j.agentic.cognisphere.Cognisphere; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; import dev.langchain4j.agentic.internal.AgentSpecification; +import dev.langchain4j.agentic.scope.AgenticScope; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; import java.util.List; import java.util.function.Function; @@ -35,11 +35,11 @@ public static List toExecutors(Object... agents) { return agentsToExecutors(Stream.of(agents).map(AgentSpecification.class::cast).toArray()); } - public static Function toFunction(AgentExecutor exec) { + public static Function toFunction(AgentExecutor exec) { return exec::execute; } - public static LoopPredicateIndex toWhile(Predicate exit) { + public static LoopPredicateIndex toWhile(Predicate exit) { return (model, item, idx) -> !exit.test(model); } } diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java index 7066ce28..a26f0b45 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.fluent.agentic; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.fluent.agentic.spi.AgentDoFluent; @@ -58,7 +58,7 @@ public AgentTaskItemListBuilder agent(String name, Object agent) { exec -> this.delegate.callFn( name, - fn -> fn.function(AgentAdapters.toFunction(exec), DefaultCognisphere.class))); + fn -> fn.function(AgentAdapters.toFunction(exec), DefaultAgenticScope.class))); return self(); } @@ -93,7 +93,9 @@ public AgentTaskItemListBuilder parallel(String name, Object... agents) { for (int i = 0; i < execs.size(); i++) { AgentExecutor ex = execs.get(i); fork.branch( - "branch-" + i + "-" + name, AgentAdapters.toFunction(ex), DefaultCognisphere.class); + "branch-" + i + "-" + name, + AgentAdapters.toFunction(ex), + DefaultAgenticScope.class); } }); return self(); diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java index 5f3ef831..01b71015 100644 --- a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.fluent.agentic; -import dev.langchain4j.agentic.cognisphere.Cognisphere; import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.scope.AgenticScope; import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.func.ForTaskFunction; import io.serverlessworkflow.fluent.func.FuncTaskItemListBuilder; @@ -60,8 +60,8 @@ public LoopAgentsBuilder maxIterations(int maxIterations) { return this; } - public LoopAgentsBuilder exitCondition(Predicate exitCondition) { - this.forTask.withWhile(AgentAdapters.toWhile(exitCondition), Cognisphere.class); + public LoopAgentsBuilder exitCondition(Predicate exitCondition) { + this.forTask.withWhile(AgentAdapters.toWhile(exitCondition), AgenticScope.class); return this; } diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java index 7b8543c5..63477fba 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.spy; import dev.langchain4j.agentic.AgenticServices; -import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.scope.AgenticScope; import io.serverlessworkflow.api.types.ForkTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; @@ -117,7 +117,7 @@ void loopWithMaxIterationsAndExitCondition() { Agents.MovieExpert expert = newMovieExpert(); AtomicInteger max = new AtomicInteger(4); - Predicate exit = + Predicate exit = cog -> { // stop when we already have at least one movie picked in state var movies = cog.readState("movies", null); @@ -189,7 +189,7 @@ void testWorkflowCallFnBare() { @Test @DisplayName("workflow callFn with Java DSL guard attaches predicate") void testWorkflowCallFnWithPredicate() { - Predicate guard = cog -> true; + Predicate guard = cog -> true; Workflow wf = AgentWorkflowBuilder.workflow() diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java index 7bacd768..acf5c411 100644 --- a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.when; import dev.langchain4j.agentic.AgenticServices; -import dev.langchain4j.agentic.cognisphere.DefaultCognisphere; +import dev.langchain4j.agentic.scope.DefaultAgenticScope; import dev.langchain4j.agentic.workflow.HumanInTheLoop; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowApplication; @@ -53,12 +53,12 @@ public void testAgent() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(topic) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals("storySeedAgent", result.readState("premise")); @@ -93,12 +93,12 @@ public void testAgents() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(topic) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals("sceneAgent", result.readState("story")); @@ -129,12 +129,12 @@ public void testSequence() throws ExecutionException, InterruptedException { topic.put("title", "A Great Story"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(topic) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals("sceneAgent", result.readState("story")); @@ -166,12 +166,12 @@ public void testParallel() throws ExecutionException, InterruptedException { topic.put("style", "sci-fi"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(topic) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals("Fake conflict response", result.readState("setting")); @@ -212,12 +212,12 @@ public void testSeqAndThenParallel() throws ExecutionException, InterruptedExcep topic.put("fact", "alien"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(topic) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals(cultureTraits, result.readState("culture")); @@ -274,12 +274,12 @@ public void humanInTheLoop() throws ExecutionException, InterruptedException { initialValues.put("agenda", "Discuss project updates"); try (WorkflowApplication app = WorkflowApplication.builder().build()) { - DefaultCognisphere result = + DefaultAgenticScope result = app.workflowDefinition(workflow) .instance(initialValues) .start() .get() - .as(DefaultCognisphere.class) + .as(DefaultAgenticScope.class) .orElseThrow(); assertEquals("Styled meeting invitation for John Doe", result.readState("styled")); diff --git a/fluent/pom.xml b/fluent/pom.xml index d88e774c..29d36008 100644 --- a/fluent/pom.xml +++ b/fluent/pom.xml @@ -17,9 +17,10 @@ 17 UTF-8 - - 1.3.0-beta9-SNAPSHOT - 1.3.0-SNAPSHOT + + 1.3.0-beta9 + + 1.3.0 @@ -74,7 +75,7 @@ dev.langchain4j langchain4j-agentic - ${version.dev.langchain4j.agentic} + ${version.dev.langchain4j.beta} diff --git a/pom.xml b/pom.xml index 471a0028..e778cdd1 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,8 @@ java true + + **/*IT.java @@ -442,6 +444,9 @@ ${version.surefire.plugin} -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m + + ${integration-tests.includes} + @@ -450,6 +455,9 @@ ${version.failsafe.plugin} -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m + + ${integration-tests.includes} + @@ -565,5 +573,28 @@ + + integration-tests + + false + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + run-integration-tests + + integration-test + verify + + + + + + + From 5bcea72a6b9ec972e6464d69feef9507ddb5d921 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Thu, 7 Aug 2025 13:09:22 -0400 Subject: [PATCH 5/6] Adjust langchain4j dependency management Signed-off-by: Ricardo Zanini --- fluent/pom.xml | 10 ---------- pom.xml | 8 ++++++-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/fluent/pom.xml b/fluent/pom.xml index 29d36008..508064ba 100644 --- a/fluent/pom.xml +++ b/fluent/pom.xml @@ -16,11 +16,6 @@ 17 17 UTF-8 - - - 1.3.0-beta9 - - 1.3.0 @@ -72,11 +67,6 @@ test-jar test - - dev.langchain4j - langchain4j-agentic - ${version.dev.langchain4j.beta} - diff --git a/pom.xml b/pom.xml index e778cdd1..513cf126 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,11 @@ 2.0.17 9.0.1.Final 6.0.0 - 1.3.0-beta9-SNAPSHOT + + 1.3.0-beta9 + + 1.3.0 + true dev.langchain4j langchain4j-agentic - ${version.dev.langchain4j} + ${version.dev.langchain4j.beta} From ed04e6fcea6233cd587b8575632352363f19974b Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Thu, 7 Aug 2025 14:11:55 -0400 Subject: [PATCH 6/6] Add asMap to AgenticModel Signed-off-by: Ricardo Zanini --- .../impl/expressions/agentic/AgenticModel.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java index 70db1337..3d352812 100644 --- a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java @@ -38,6 +38,11 @@ public Collection asCollection() { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Optional> asMap() { + return Optional.of(((AgenticScope) object).state()); + } + @Override public Optional as(Class clazz) { if (AgenticScope.class.isAssignableFrom(clazz)) {