Skip to content

Commit ec4758a

Browse files
committed
feat: workflow improvements
1 parent 1ce5c1c commit ec4758a

File tree

28 files changed

+540
-378
lines changed

28 files changed

+540
-378
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.javaaidev.agenticpatterns.chainworkflow;
2+
3+
import java.util.Map;
4+
import org.springframework.core.Ordered;
5+
6+
public interface ChainStep<Request, Response> extends Ordered {
7+
8+
/**
9+
* Call the current step
10+
*
11+
* @param request Task input
12+
* @param context Shared context between different steps
13+
* @param chain The chain, see {@linkplain WorkflowChain}
14+
* @return Task output
15+
*/
16+
Response call(Request request, Map<String, Object> context,
17+
WorkflowChain<Request, Response> chain);
18+
}
Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.javaaidev.agenticpatterns.chainworkflow;
22

3+
import com.javaaidev.agenticpatterns.taskexecution.AbstractTaskExecutionAgentBuilder;
34
import com.javaaidev.agenticpatterns.taskexecution.TaskExecutionAgent;
45
import io.micrometer.observation.ObservationRegistry;
56
import java.lang.reflect.Type;
67
import java.util.Map;
8+
import java.util.function.Consumer;
9+
import java.util.function.Function;
710
import org.jspecify.annotations.Nullable;
811
import org.springframework.ai.chat.client.ChatClient;
12+
import org.springframework.ai.chat.client.ChatClient.ChatClientRequestSpec;
913
import org.springframework.core.Ordered;
14+
import org.springframework.util.Assert;
1015

1116
/**
1217
* A step in the chain
@@ -16,7 +21,7 @@
1621
*/
1722
public abstract class ChainStepAgent<Request, Response> extends
1823
TaskExecutionAgent<Request, Response> implements
19-
Ordered {
24+
Ordered, ChainStep<Request, Response> {
2025

2126
protected ChainStepAgent(ChatClient chatClient,
2227
@Nullable ObservationRegistry observationRegistry) {
@@ -29,14 +34,62 @@ protected ChainStepAgent(ChatClient chatClient,
2934
super(chatClient, responseType, observationRegistry);
3035
}
3136

32-
/**
33-
* Call the current step
34-
*
35-
* @param request Task input
36-
* @param context Shared context between different steps
37-
* @param chain The chain, see {@linkplain WorkflowChain}
38-
* @return Task output
39-
*/
40-
protected abstract Response call(Request request, Map<String, Object> context,
41-
WorkflowChain<Request, Response> chain);
37+
public ChainStepAgent(ChatClient chatClient,
38+
String promptTemplate,
39+
@Nullable Type responseType,
40+
@Nullable Function<Request, Map<String, Object>> promptTemplateContextProvider,
41+
@Nullable Consumer<ChatClientRequestSpec> chatClientRequestSpecUpdater,
42+
@Nullable String name,
43+
@Nullable ObservationRegistry observationRegistry) {
44+
super(chatClient, promptTemplate, responseType, promptTemplateContextProvider,
45+
chatClientRequestSpecUpdater, name, observationRegistry);
46+
}
47+
48+
public static <Req, Res> Builder<Req, Res> builder() {
49+
return new Builder<>();
50+
}
51+
52+
public static class Builder<Request, Response> extends
53+
AbstractTaskExecutionAgentBuilder<Request, Response, Builder<Request, Response>> {
54+
55+
private Function<Response, Request> nextRequestPreparer;
56+
private int order;
57+
58+
public Builder<Request, Response> nextRequestPreparer(
59+
Function<Response, Request> nextRequestPreparer) {
60+
this.nextRequestPreparer = nextRequestPreparer;
61+
return this;
62+
}
63+
64+
public Builder<Request, Response> order(int order) {
65+
this.order = order;
66+
return this;
67+
}
68+
69+
@Override
70+
public ChainStepAgent<Request, Response> build() {
71+
Assert.notNull(chatClient, "ChatClient cannot be null");
72+
Assert.hasText(promptTemplate, "Prompt template cannot be empty");
73+
Assert.notNull(nextRequestPreparer, "nextRequestPreparer cannot be null");
74+
return new ChainStepAgent<>(chatClient,
75+
promptTemplate,
76+
responseType,
77+
promptTemplateContextProvider,
78+
chatClientRequestSpecUpdater,
79+
name,
80+
observationRegistry) {
81+
@Override
82+
public Response call(Request request, Map<String, Object> context,
83+
WorkflowChain<Request, Response> chain) {
84+
var response = this.call(request);
85+
return chain.callNext(nextRequestPreparer.apply(response), response);
86+
}
87+
88+
@Override
89+
public int getOrder() {
90+
return order;
91+
}
92+
};
93+
}
94+
}
4295
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.javaaidev.agenticpatterns.chainworkflow;
2+
3+
import com.javaaidev.agenticpatterns.core.AbstractAgenticWorkflow;
4+
import com.javaaidev.agenticpatterns.core.AbstractAgenticWorkflowBuilder;
5+
import io.micrometer.observation.ObservationRegistry;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.jspecify.annotations.Nullable;
9+
import org.springframework.util.Assert;
10+
11+
public class ChainWorkflow<Request, Response> extends AbstractAgenticWorkflow<Request, Response> {
12+
13+
private final List<ChainStep<Request, Response>> steps;
14+
15+
ChainWorkflow(List<ChainStep<Request, Response>> steps,
16+
@Nullable String workflowName,
17+
@Nullable ObservationRegistry observationRegistry) {
18+
super(workflowName, observationRegistry);
19+
this.steps = new ArrayList<>(steps);
20+
}
21+
22+
@Override
23+
protected Response doExecute(@Nullable Request request) {
24+
var chain = new WorkflowChain<>(steps);
25+
return chain.callNext(request, null);
26+
}
27+
28+
public static <Req, Res> Builder<Req, Res> builder() {
29+
return new Builder<>();
30+
}
31+
32+
public static final class Builder<Request, Response> extends
33+
AbstractAgenticWorkflowBuilder<Request, Response, Builder<Request, Response>> {
34+
35+
private final List<ChainStep<Request, Response>> steps = new ArrayList<>();
36+
37+
public Builder<Request, Response> addStep(ChainStep<Request, Response> step) {
38+
Assert.notNull(step, "Chain step cannot be null");
39+
steps.add(step);
40+
return this;
41+
}
42+
43+
public Builder<Request, Response> addStepAgent(
44+
ChainStepAgent<Request, Response> agent) {
45+
Assert.notNull(agent, "Chain step agent cannot be null");
46+
steps.add(agent);
47+
return this;
48+
}
49+
50+
public ChainWorkflow<Request, Response> build() {
51+
return new ChainWorkflow<>(steps, name, observationRegistry);
52+
}
53+
}
54+
}

chain-workflow/src/main/java/com/javaaidev/agenticpatterns/chainworkflow/ChainWorkflowAgent.java

Lines changed: 0 additions & 50 deletions
This file was deleted.

chain-workflow/src/main/java/com/javaaidev/agenticpatterns/chainworkflow/WorkflowChain.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
*/
1919
public class WorkflowChain<Request, Response> {
2020

21-
private final Deque<ChainStepAgent<Request, Response>> agents;
21+
private final Deque<ChainStep<Request, Response>> steps;
2222
private final Map<String, Object> context = new HashMap<>();
2323

2424
private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowChain.class);
2525

26-
public WorkflowChain(List<ChainStepAgent<Request, Response>> agents) {
27-
this.agents = new ArrayDeque<>(agents.stream().sorted(OrderComparator.INSTANCE).toList());
28-
LOGGER.info("Added {} agents to the chain", this.agents.size());
26+
public WorkflowChain(List<ChainStep<Request, Response>> steps) {
27+
this.steps = new ArrayDeque<>(steps.stream().sorted(OrderComparator.INSTANCE).toList());
28+
LOGGER.info("Added {} agents to the chain", this.steps.size());
2929
}
3030

3131
/**
@@ -36,10 +36,10 @@ public WorkflowChain(List<ChainStepAgent<Request, Response>> agents) {
3636
* @return Task output
3737
*/
3838
public Response callNext(Request request, @Nullable Response lastResponse) {
39-
if (agents.isEmpty()) {
39+
if (steps.isEmpty()) {
4040
return lastResponse;
4141
}
42-
var agent = agents.pop();
43-
return agent.call(request, context, this);
42+
var step = steps.pop();
43+
return step.call(request, context, this);
4444
}
4545
}

core/src/main/java/com/javaaidev/agenticpatterns/core/AbstractAgenticWorkflow.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ public abstract class AbstractAgenticWorkflow<Request, Response> implements
1010
AgenticWorkflow<Request, Response> {
1111

1212
@Nullable
13-
protected String workflowName;
13+
protected String name;
1414
@Nullable
1515
protected ObservationRegistry observationRegistry;
1616

17-
protected AbstractAgenticWorkflow(@Nullable String workflowName,
17+
protected AbstractAgenticWorkflow(@Nullable String name,
1818
@Nullable ObservationRegistry observationRegistry) {
19-
this.workflowName = workflowName;
19+
this.name = name;
2020
this.observationRegistry = observationRegistry;
2121
}
2222

2323
@Override
2424
public String getName() {
25-
return this.workflowName != null ? this.workflowName : this.getClass().getSimpleName();
25+
return this.name != null ? this.name : this.getClass().getSimpleName();
2626
}
2727

2828
@Override
@@ -51,4 +51,5 @@ public Response execute(@Nullable Request request) {
5151
}
5252

5353
protected abstract Response doExecute(@Nullable Request request);
54+
5455
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.javaaidev.agenticpatterns.core;
2+
3+
import io.micrometer.observation.ObservationRegistry;
4+
import org.jspecify.annotations.Nullable;
5+
6+
public abstract class AbstractAgenticWorkflowBuilder<Request, Response, T extends AbstractAgenticWorkflowBuilder<Request, Response, T>> implements
7+
AgenticWorkflow.Builder<Request, Response, T> {
8+
9+
@Nullable
10+
protected String name;
11+
@Nullable
12+
protected ObservationRegistry observationRegistry;
13+
14+
@SuppressWarnings("unchecked")
15+
protected T self() {
16+
return (T) this;
17+
}
18+
19+
public T name(String name) {
20+
this.name = name;
21+
return self();
22+
}
23+
24+
public T observationRegistry(
25+
ObservationRegistry observationRegistry) {
26+
this.observationRegistry = observationRegistry;
27+
return self();
28+
}
29+
}

core/src/main/java/com/javaaidev/agenticpatterns/core/AgenticWorkflow.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.javaaidev.agenticpatterns.core;
22

3+
import io.micrometer.observation.ObservationRegistry;
34
import org.jspecify.annotations.Nullable;
45

56
public interface AgenticWorkflow<Request, Response> {
@@ -9,4 +10,21 @@ public interface AgenticWorkflow<Request, Response> {
910
default String getName() {
1011
return this.getClass().getSimpleName();
1112
}
13+
14+
interface Builder<Request, Response, T extends AgenticWorkflow.Builder<Request, Response, T>> {
15+
16+
T name(String name);
17+
18+
T observationRegistry(ObservationRegistry observationRegistry);
19+
20+
AgenticWorkflow<Request, Response> build();
21+
}
22+
23+
static <Request, Response> SingleStepWorkflowBuilder<Request, Response> single() {
24+
return new SingleStepWorkflowBuilder<>();
25+
}
26+
27+
static <Request, Response> CompositeWorkflowBuilder<Request, Response> composite() {
28+
return new CompositeWorkflowBuilder<>();
29+
}
1230
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.javaaidev.agenticpatterns.core;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Deque;
5+
import org.jspecify.annotations.Nullable;
6+
import org.springframework.util.Assert;
7+
8+
public class CompositeWorkflowBuilder<Request, Response> extends
9+
AbstractAgenticWorkflowBuilder<Request, Response, CompositeWorkflowBuilder<Request, Response>> {
10+
11+
private final Deque<AgenticWorkflow> deque = new ArrayDeque<>();
12+
13+
public CompositeWorkflowBuilder<Request, Response> addNext(AgenticWorkflow workflow) {
14+
Assert.notNull(workflow, "Workflow cannot be null");
15+
deque.addLast(workflow);
16+
return this;
17+
}
18+
19+
@Override
20+
public AgenticWorkflow<Request, Response> build() {
21+
Assert.notEmpty(deque, "At least one workflow is required");
22+
return new AbstractAgenticWorkflow<>(name, observationRegistry) {
23+
@Override
24+
@SuppressWarnings("unchecked")
25+
protected Response doExecute(@Nullable Request request) {
26+
Object result = request;
27+
do {
28+
var workflow = deque.pollFirst();
29+
result = workflow.execute(result);
30+
} while (!deque.isEmpty());
31+
return (Response) result;
32+
}
33+
};
34+
}
35+
}

0 commit comments

Comments
 (0)