Skip to content

Commit da3ad8e

Browse files
Introduce LangChain4j Agentic Workflow Implementation (#681)
* Introduce LangChain4j Agentic Workflow Implementation Signed-off-by: Ricardo Zanini <[email protected]> * Remove skip agentic modules from CI Signed-off-by: Ricardo Zanini <[email protected]> * Fix langchain4j mvn command Signed-off-by: Ricardo Zanini <[email protected]> * Wrap up langchain4j integration work; add CI to run integration tests Signed-off-by: Ricardo Zanini <[email protected]> * Adjust langchain4j dependency management Signed-off-by: Ricardo Zanini <[email protected]> * Add asMap to AgenticModel Signed-off-by: Ricardo Zanini <[email protected]> --------- Signed-off-by: Ricardo Zanini <[email protected]>
1 parent 8d85aea commit da3ad8e

File tree

34 files changed

+1532
-155
lines changed

34 files changed

+1532
-155
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: sdk-java Integration Tests
2+
on:
3+
issue_comment:
4+
types: [ created ]
5+
6+
jobs:
7+
run-integration-tests:
8+
# 2) Only run if the comment is exactly "/run integration-tests"
9+
# and it’s on a pull request (not on an issue)
10+
if: >
11+
github.event.comment.body == '/run integration-tests' &&
12+
github.event.issue.pull_request != null
13+
runs-on: ubuntu-latest
14+
15+
permissions:
16+
contents: read
17+
pull-requests: write
18+
checks: write
19+
id-token: write
20+
21+
steps:
22+
# 3) Checkout the **PR’s** code
23+
- name: Checkout PR code
24+
uses: actions/checkout@v4
25+
with:
26+
repository: ${{ github.event.issue.pull_request.head.repo.full_name }}
27+
ref: ${{ github.event.issue.pull_request.head.ref }}
28+
token: ${{ secrets.GITHUB_TOKEN }}
29+
30+
# 4) Set up Java/Maven (cache enabled)
31+
- name: Set up JDK 17
32+
uses: actions/setup-java@v4
33+
with:
34+
distribution: temurin
35+
java-version: 17
36+
cache: maven
37+
38+
# 5) Run only the IT suite
39+
- name: Run integration-tests profile
40+
run: mvn -B -f pom.xml clean verify -Pintegration-tests

.github/workflows/maven-verify.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# This workflow will build a Java project with Maven
2-
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3-
41
name: sdk-java Verify
52

63
on:
@@ -14,22 +11,26 @@ on:
1411
jobs:
1512
build:
1613
runs-on: ubuntu-latest
14+
1715
steps:
18-
- uses: actions/checkout@v4
16+
# 1. Checkout this repo
17+
- name: Checkout sdk-java
18+
uses: actions/checkout@v4
1919

20+
# 2. Set up JDK 17 for both builds
2021
- name: Set up JDK 17
2122
uses: actions/setup-java@v4
2223
with:
2324
distribution: temurin
2425
java-version: 17
2526
cache: 'maven'
2627

28+
# 3. Verify the main sdk-java project, excluding the two agentic modules
2729
- name: Verify with Maven
2830
run: |
29-
mvn -B -f pom.xml clean install verify \
30-
-pl ",!fluent/agentic" -pl ",!experimental/agentic" \
31-
-am
31+
mvn -B -f pom.xml clean install verify -am
3232
33+
# 4. Verify examples
3334
- name: Verify Examples with Maven
3435
run: |
3536
mvn -B -f examples/pom.xml clean install verify

experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,17 @@
1515
*/
1616
package io.serverlessworkflow.impl.expressions.agentic;
1717

18-
import dev.langchain4j.agentic.cognisphere.Cognisphere;
19-
import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere;
18+
import dev.langchain4j.agentic.scope.AgenticScope;
2019
import io.serverlessworkflow.impl.WorkflowModel;
2120
import io.serverlessworkflow.impl.expressions.func.JavaModel;
2221
import java.util.Collection;
23-
import java.util.Collections;
22+
import java.util.Map;
2423
import java.util.Optional;
2524

2625
class AgenticModel extends JavaModel {
2726

28-
private final Cognisphere cognisphere;
29-
30-
AgenticModel(Object object, Cognisphere cognisphere) {
31-
super(object);
32-
this.cognisphere = cognisphere;
27+
AgenticModel(AgenticScope agenticScope) {
28+
super(agenticScope);
3329
}
3430

3531
@Override
@@ -39,17 +35,20 @@ public void setObject(Object obj) {
3935

4036
@Override
4137
public Collection<WorkflowModel> asCollection() {
42-
return object instanceof Collection value
43-
? new AgenticModelCollection(value, cognisphere)
44-
: Collections.emptyList();
38+
throw new UnsupportedOperationException("Not supported yet.");
39+
}
40+
41+
@Override
42+
public Optional<Map<String, Object>> asMap() {
43+
return Optional.of(((AgenticScope) object).state());
4544
}
4645

4746
@Override
4847
public <T> Optional<T> as(Class<T> clazz) {
49-
if (Cognisphere.class.isAssignableFrom(clazz)) {
50-
return Optional.of(clazz.cast(cognisphere));
51-
} else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) {
52-
return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object)));
48+
if (AgenticScope.class.isAssignableFrom(clazz)) {
49+
return Optional.of(clazz.cast(object));
50+
} else if (Map.class.isAssignableFrom(clazz)) {
51+
return Optional.of(clazz.cast(((AgenticScope) object).state()));
5352
} else {
5453
return super.as(clazz);
5554
}

experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,37 @@
1515
*/
1616
package io.serverlessworkflow.impl.expressions.agentic;
1717

18-
import dev.langchain4j.agentic.cognisphere.Cognisphere;
19-
import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere;
18+
import dev.langchain4j.agentic.scope.AgenticScope;
19+
import dev.langchain4j.agentic.scope.ResultWithAgenticScope;
2020
import io.serverlessworkflow.impl.WorkflowModel;
2121
import io.serverlessworkflow.impl.expressions.func.JavaModelCollection;
2222
import java.util.Collection;
2323
import java.util.Optional;
2424

2525
class AgenticModelCollection extends JavaModelCollection {
2626

27-
private final Cognisphere cognisphere;
27+
private final AgenticScope agenticScope;
2828

29-
AgenticModelCollection(Collection<?> object, Cognisphere cognisphere) {
29+
AgenticModelCollection(Collection<?> object, AgenticScope agenticScope) {
3030
super(object);
31-
this.cognisphere = cognisphere;
31+
this.agenticScope = agenticScope;
3232
}
3333

34-
AgenticModelCollection(Cognisphere cognisphere) {
35-
this.cognisphere = cognisphere;
34+
AgenticModelCollection(AgenticScope agenticScope) {
35+
this.agenticScope = agenticScope;
3636
}
3737

3838
@Override
3939
protected WorkflowModel nextItem(Object obj) {
40-
return new AgenticModel(obj, cognisphere);
40+
return new AgenticModel((AgenticScope) obj);
4141
}
4242

4343
@Override
4444
public <T> Optional<T> as(Class<T> clazz) {
45-
if (Cognisphere.class.isAssignableFrom(clazz)) {
46-
return Optional.of(clazz.cast(cognisphere));
47-
} else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) {
48-
return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object)));
45+
if (AgenticScope.class.isAssignableFrom(clazz)) {
46+
return Optional.of(clazz.cast(agenticScope));
47+
} else if (ResultWithAgenticScope.class.isAssignableFrom(clazz)) {
48+
return Optional.of(clazz.cast(new ResultWithAgenticScope<>(agenticScope, object)));
4949
} else {
5050
return super.as(clazz);
5151
}

experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,87 +15,96 @@
1515
*/
1616
package io.serverlessworkflow.impl.expressions.agentic;
1717

18-
import dev.langchain4j.agentic.cognisphere.Cognisphere;
19-
import dev.langchain4j.agentic.cognisphere.CognisphereRegistry;
18+
import dev.langchain4j.agentic.scope.AgenticScope;
2019
import io.cloudevents.CloudEvent;
2120
import io.cloudevents.CloudEventData;
2221
import io.serverlessworkflow.impl.WorkflowModel;
2322
import io.serverlessworkflow.impl.WorkflowModelCollection;
2423
import io.serverlessworkflow.impl.WorkflowModelFactory;
24+
import io.serverlessworkflow.impl.expressions.agentic.langchain4j.AgenticScopeRegistryAssessor;
25+
import io.serverlessworkflow.impl.expressions.func.JavaModel;
2526
import java.time.OffsetDateTime;
2627
import java.util.Map;
2728

2829
class AgenticModelFactory implements WorkflowModelFactory {
2930

30-
private Cognisphere cognisphere = CognisphereRegistry.createEphemeralCognisphere();
31-
32-
private final AgenticModel TrueModel = new AgenticModel(Boolean.TRUE, cognisphere);
33-
private final AgenticModel FalseModel = new AgenticModel(Boolean.FALSE, cognisphere);
34-
private final AgenticModel NullModel = new AgenticModel(null, cognisphere);
35-
36-
public void setCognishere(Cognisphere cognisphere) {
37-
this.cognisphere = cognisphere;
38-
}
39-
31+
/**
32+
* Applies any change to the model after running as task. We will always set it to a @AgenticScope
33+
* object since @AgentExecutor is always adding the output to the agenticScope. We just have to
34+
* make sure that agenticScope is always passed to the next input task.
35+
*
36+
* @param prev the global AgenticScope object getting updated by the workflow context
37+
* @param obj the same AgenticScope object updated by the AgentExecutor
38+
* @return the workflow context model holding the agenticScope object.
39+
*/
4040
@Override
4141
public WorkflowModel fromAny(WorkflowModel prev, Object obj) {
42-
((AgenticModel) prev).setObject(obj);
42+
// We ignore `obj` since it's already included in `prev` within the agenticScope instance
4343
return prev;
4444
}
4545

4646
@Override
4747
public WorkflowModel combine(Map<String, WorkflowModel> workflowVariables) {
48-
return new AgenticModel(workflowVariables, cognisphere);
48+
// TODO: create a new agenticScope object in the AgenticScopeRegistryAssessor per branch
49+
// TODO: Since we share the same agenticScope object, both branches are updating the same
50+
// instance, so for now we return the first key.
51+
return workflowVariables.values().iterator().next();
4952
}
5053

5154
@Override
5255
public WorkflowModelCollection createCollection() {
53-
return new AgenticModelCollection(cognisphere);
56+
throw new UnsupportedOperationException();
5457
}
5558

59+
// TODO: all these methods can use agenticScope as long as we have access to the `outputName`
60+
5661
@Override
5762
public WorkflowModel from(boolean value) {
58-
return value ? TrueModel : FalseModel;
63+
return new JavaModel(value);
5964
}
6065

6166
@Override
6267
public WorkflowModel from(Number value) {
63-
return new AgenticModel(value, cognisphere);
68+
return new JavaModel(value);
6469
}
6570

6671
@Override
6772
public WorkflowModel from(String value) {
68-
return new AgenticModel(value, cognisphere);
73+
return new JavaModel(value);
6974
}
7075

7176
@Override
7277
public WorkflowModel from(CloudEvent ce) {
73-
return new AgenticModel(ce, cognisphere);
78+
return new JavaModel(ce);
7479
}
7580

7681
@Override
7782
public WorkflowModel from(CloudEventData ce) {
78-
return new AgenticModel(ce, cognisphere);
83+
return new JavaModel(ce);
7984
}
8085

8186
@Override
8287
public WorkflowModel from(OffsetDateTime value) {
83-
return new AgenticModel(value, cognisphere);
88+
return new JavaModel(value);
8489
}
8590

8691
@Override
8792
public WorkflowModel from(Map<String, Object> map) {
88-
cognisphere.writeStates(map);
89-
return new AgenticModel(map, cognisphere);
93+
final AgenticScope agenticScope = new AgenticScopeRegistryAssessor().getAgenticScope();
94+
agenticScope.writeStates(map);
95+
return new AgenticModel(agenticScope);
9096
}
9197

9298
@Override
9399
public WorkflowModel fromNull() {
94-
return NullModel;
100+
return new JavaModel(null);
95101
}
96102

97103
@Override
98104
public WorkflowModel fromOther(Object value) {
99-
return new AgenticModel(value, cognisphere);
105+
if (value instanceof AgenticScope) {
106+
return new AgenticModel((AgenticScope) value);
107+
}
108+
return new JavaModel(value);
100109
}
101110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.expressions.agentic.langchain4j;
17+
18+
import dev.langchain4j.agentic.internal.AgenticScopeOwner;
19+
import dev.langchain4j.agentic.scope.AgenticScopeRegistry;
20+
import dev.langchain4j.agentic.scope.DefaultAgenticScope;
21+
import java.util.Objects;
22+
import java.util.UUID;
23+
import java.util.concurrent.atomic.AtomicReference;
24+
25+
public class AgenticScopeRegistryAssessor implements AgenticScopeOwner {
26+
27+
private final AtomicReference<AgenticScopeRegistry> agenticScopeRegistry =
28+
new AtomicReference<>();
29+
private final String agentId;
30+
private DefaultAgenticScope agenticScope;
31+
private Object memoryId;
32+
33+
public AgenticScopeRegistryAssessor(String agentId) {
34+
Objects.requireNonNull(agentId, "Agent id cannot be null");
35+
this.agentId = agentId;
36+
}
37+
38+
// TODO: have access to the workflow definition and assign its name instead
39+
public AgenticScopeRegistryAssessor() {
40+
this.agentId = UUID.randomUUID().toString();
41+
}
42+
43+
public void setMemoryId(Object memoryId) {
44+
this.memoryId = memoryId;
45+
}
46+
47+
public DefaultAgenticScope getAgenticScope() {
48+
if (agenticScope != null) {
49+
return agenticScope;
50+
}
51+
52+
if (memoryId != null) {
53+
this.agenticScope = registry().getOrCreate(memoryId);
54+
} else {
55+
this.agenticScope = registry().createEphemeralAgenticScope();
56+
}
57+
return this.agenticScope;
58+
}
59+
60+
@Override
61+
public AgenticScopeOwner withAgenticScope(DefaultAgenticScope agenticScope) {
62+
this.agenticScope = agenticScope;
63+
return this;
64+
}
65+
66+
@Override
67+
public AgenticScopeRegistry registry() {
68+
agenticScopeRegistry.compareAndSet(null, new AgenticScopeRegistry(agentId));
69+
return agenticScopeRegistry.get();
70+
}
71+
}

experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class JavaModel implements WorkflowModel {
2828

2929
protected Object object;
3030

31-
protected JavaModel(Object object) {
31+
public JavaModel(Object object) {
3232
this.object = asJavaObject(object);
3333
}
3434

0 commit comments

Comments
 (0)