diff --git a/experimental/ai/impl/pom.xml b/experimental/ai/impl/pom.xml new file mode 100644 index 00000000..566efcc5 --- /dev/null +++ b/experimental/ai/impl/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental-ai-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-ai-impl + ServelessWorkflow:: Experimental:: AI:: Impl + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + io.serverlessworkflow + serverlessworkflow-experimental-ai-types + + + dev.langchain4j + langchain4j + 1.1.0 + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + diff --git a/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelCallExecutor.java b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelCallExecutor.java new file mode 100644 index 00000000..7d64bb9a --- /dev/null +++ b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelCallExecutor.java @@ -0,0 +1,52 @@ +/* + * 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.executors.ai; + +import io.serverlessworkflow.ai.api.types.CallAgentAI; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class AIChatModelCallExecutor implements CallableTask { + + @Override + public void init(CallAgentAI task, WorkflowApplication application, ResourceLoader loader) {} + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + WorkflowModelFactory modelFactory = workflowContext.definition().application().modelFactory(); + if (taskContext.task() instanceof CallAgentAI agenticAI) { + return CompletableFuture.completedFuture( + modelFactory.fromAny(new CallAgentAIExecutor().execute(agenticAI, input.asJavaObject()))); + } + throw new IllegalArgumentException( + "AIChatModelCallExecutor can only process CallAgentAI tasks, but received: " + + taskContext.task().getClass().getName()); + } + + @Override + public boolean accept(Class clazz) { + return CallAgentAI.class.isAssignableFrom(clazz); + } +} diff --git a/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelTaskExecutorFactory.java b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelTaskExecutorFactory.java new file mode 100644 index 00000000..c9f78360 --- /dev/null +++ b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/AIChatModelTaskExecutorFactory.java @@ -0,0 +1,21 @@ +/* + * 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.executors.ai; + +import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory; + +public class AIChatModelTaskExecutorFactory extends DefaultTaskExecutorFactory {} diff --git a/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/CallAgentAIExecutor.java b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/CallAgentAIExecutor.java new file mode 100644 index 00000000..b13ab474 --- /dev/null +++ b/experimental/ai/impl/src/main/java/io/serverlessworkflow/impl/executors/ai/CallAgentAIExecutor.java @@ -0,0 +1,41 @@ +/* + * 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.executors.ai; + +import dev.langchain4j.agentic.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.internal.AgentUtil; +import io.serverlessworkflow.ai.api.types.CallAgentAI; +import java.util.Map; + +public class CallAgentAIExecutor { + + private static final Cognisphere cognisphere = new Cognisphere(); + + public Object execute(CallAgentAI callAgentAI, Object input) { + AgentExecutor agentExecutor = AgentUtil.agentToExecutor(callAgentAI.getAgentInstance()); + + Map output = (Map) input; + + cognisphere.writeStates(output); + + Object result = agentExecutor.invoke(cognisphere); + + output.put(callAgentAI.getAgentInstance().outputName(), result); + return output; + } +} diff --git a/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask b/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask new file mode 100644 index 00000000..680fce77 --- /dev/null +++ b/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.ai.AIChatModelCallExecutor \ No newline at end of file diff --git a/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory b/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory new file mode 100644 index 00000000..a370284d --- /dev/null +++ b/experimental/ai/impl/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.ai.AIChatModelTaskExecutorFactory \ No newline at end of file diff --git a/experimental/ai/pom.xml b/experimental/ai/pom.xml new file mode 100644 index 00000000..6856cf14 --- /dev/null +++ b/experimental/ai/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-ai-parent + ServelessWorkflow:: Experimental:: AI:: Parent + pom + + impl + types + + \ No newline at end of file diff --git a/experimental/ai/types/pom.xml b/experimental/ai/types/pom.xml new file mode 100644 index 00000000..3136b81d --- /dev/null +++ b/experimental/ai/types/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental-ai-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-ai-types + ServelessWorkflow:: Experimental:: AI:: Types + + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + dev.langchain4j + langchain4j-agentic + 1.2.0-beta8-SNAPSHOT + + + + dev.langchain4j + langchain4j-open-ai + 1.2.0-SNAPSHOT + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + + \ No newline at end of file diff --git a/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallAgentAI.java b/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallAgentAI.java new file mode 100644 index 00000000..dfe2910c --- /dev/null +++ b/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallAgentAI.java @@ -0,0 +1,103 @@ +/* + * 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.ai.api.types; + +import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.internal.AgentInstance; +import dev.langchain4j.model.chat.ChatModel; +import io.serverlessworkflow.api.types.TaskBase; +import java.util.Objects; + +public class CallAgentAI extends TaskBase { + + private AgentInstance instance; + + public static Builder builder() { + return new Builder(); + } + + public AgentInstance getAgentInstance() { + return instance; + } + + public CallAgentAI setAgentInstance(Object object) { + this.instance = (AgentInstance) object; + return this; + } + + public static class Builder { + + private Class agentClass; + + private ChatModel chatModel; + + private String outputName; + + private Builder() {} + + public CallAgentAI.Builder withAgentClass(Class agentClass) { + this.agentClass = agentClass; + return this; + } + + public CallAgentAI.Builder withChatModel(ChatModel chatModel) { + this.chatModel = chatModel; + return this; + } + + public CallAgentAI.Builder withOutputName(String outputName) { + this.outputName = outputName; + return this; + } + + public AgenticInnerBuilder withAgent(Object agent) { + return new AgenticInnerBuilder().withAgent(agent); + } + + public CallAgentAI build() { + Objects.requireNonNull(agentClass, "agentClass must be provided"); + Objects.requireNonNull(chatModel, "chatModel must be provided"); + Objects.requireNonNull(outputName, "outputName must be provided"); + + if (outputName.isBlank()) { + throw new IllegalArgumentException("outputName must not be blank"); + } + + Object instance = + AgentServices.agentBuilder(agentClass) + .chatModel(chatModel) + .outputName(outputName) + .build(); + + return new AgenticInnerBuilder().withAgent(instance).build(); + } + + public static class AgenticInnerBuilder { + + private Object agent; + + public AgenticInnerBuilder withAgent(Object agent) { + this.agent = agent; + return this; + } + + public CallAgentAI build() { + return new CallAgentAI().setAgentInstance(agent); + } + } + } +} diff --git a/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallTaskAIChatModel.java b/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallTaskAIChatModel.java new file mode 100644 index 00000000..5dfec536 --- /dev/null +++ b/experimental/ai/types/src/main/java/io/serverlessworkflow/ai/api/types/CallTaskAIChatModel.java @@ -0,0 +1,37 @@ +/* + * 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.ai.api.types; + +import io.serverlessworkflow.api.types.CallTask; + +public class CallTaskAIChatModel extends CallTask { + + private CallAgentAI callAIChatModel; + + public CallTaskAIChatModel(CallAgentAI callAIChatModel) { + this.callAIChatModel = callAIChatModel; + } + + public CallAgentAI getCallAIChatModel() { + return callAIChatModel; + } + + @Override + public Object get() { + return callAIChatModel != null ? callAIChatModel : super.get(); + } +} diff --git a/experimental/pom.xml b/experimental/pom.xml index 3312207e..1c5efd8d 100644 --- a/experimental/pom.xml +++ b/experimental/pom.xml @@ -35,10 +35,16 @@ serverlessworkflow-fluent-func ${project.version} + + io.serverlessworkflow + serverlessworkflow-experimental-ai-types + ${project.version} + types lambda + ai - \ No newline at end of file + diff --git a/fluent/pom.xml b/fluent/pom.xml index ff19f4d5..caff7919 100644 --- a/fluent/pom.xml +++ b/fluent/pom.xml @@ -35,6 +35,11 @@ serverlessworkflow-fluent-spec ${project.version} + + io.serverlessworkflow + serverlessworkflow-experimental-ai-types + ${project.version} + @@ -43,4 +48,4 @@ func - \ No newline at end of file + diff --git a/fluent/spec/pom.xml b/fluent/spec/pom.xml index 5ec62a98..fdd54289 100644 --- a/fluent/spec/pom.xml +++ b/fluent/spec/pom.xml @@ -27,6 +27,10 @@ junit-jupiter-api test + + io.serverlessworkflow + serverlessworkflow-experimental-ai-types + - \ No newline at end of file + diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java index d4c70aec..67eaf431 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java @@ -136,6 +136,16 @@ public TASK callHTTP(Consumer itemsConfigurer) { return self(); } + public TASK callAgentAI(String name, Consumer itemsConfigurer) { + taskItemListBuilder.callAgentAI(name, itemsConfigurer); + return self(); + } + + public TASK callAgentAI(Consumer itemsConfigurer) { + taskItemListBuilder.callAgentAI(itemsConfigurer); + return self(); + } + public DoTask build() { this.doTask.setDo(this.taskItemListBuilder.build()); return this.doTask; diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java index bb2a34dc..934c903b 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.fluent.spec; +import io.serverlessworkflow.ai.api.types.CallTaskAIChatModel; import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; @@ -167,6 +168,19 @@ public SELF callHTTP(Consumer itemsConfigurer) { return this.callHTTP(UUID.randomUUID().toString(), itemsConfigurer); } + public SELF callAgentAI(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final CallAgentAITaskBuilder callAgentAIBuilder = new CallAgentAITaskBuilder(); + itemsConfigurer.accept(callAgentAIBuilder); + return addTaskItem( + new TaskItem( + name, new Task().withCallTask(new CallTaskAIChatModel(callAgentAIBuilder.build())))); + } + + public SELF callAgentAI(Consumer itemsConfigurer) { + return this.callAgentAI(UUID.randomUUID().toString(), itemsConfigurer); + } + /** * @return an immutable snapshot of all {@link TaskItem}s added so far */ diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallAgentAITaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallAgentAITaskBuilder.java new file mode 100644 index 00000000..ef69342e --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallAgentAITaskBuilder.java @@ -0,0 +1,43 @@ +/* + * 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.spec; + +import io.serverlessworkflow.ai.api.types.CallAgentAI; + +public class CallAgentAITaskBuilder extends TaskBaseBuilder { + + private final CallAgentAI callAgentAI; + + CallAgentAITaskBuilder() { + callAgentAI = new CallAgentAI(); + super.setTask(this.callAgentAI); + } + + @Override + protected CallAgentAITaskBuilder self() { + return this; + } + + public CallAgentAITaskBuilder withAgent(Object agentInstance) { + this.callAgentAI.setAgentInstance(agentInstance); + return this; + } + + public CallAgentAI build() { + return callAgentAI; + } +}