Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
more examples
Signed-off-by: Dmitrii Tikhomirov <chani.liet@gmail.com>
  • Loading branch information
treblereel committed Sep 24, 2025
commit 32d65d8f4a714a644ed59b4be87991ca64dc3c00
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.serverlessworkflow.fluent.agentic;

import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.internal.AgentSpecification;
import dev.langchain4j.service.SystemMessage;
Expand Down Expand Up @@ -318,4 +319,60 @@ interface AstrologyAgent {
@Agent("An astrologist that generates horoscopes based on the user's name and zodiac sign.")
String horoscope(@V("name") String name, @V("sign") String sign);
}

enum RequestCategory {
LEGAL,
MEDICAL,
TECHNICAL,
UNKNOWN
}

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("Categorizes a user request")
RequestCategory classify(@V("request") String request);
}

interface MedicalExpert {

@dev.langchain4j.service.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 {{it}}.
""")
@Tool("A medical expert")
String medicalRequest(String request);
}

interface LegalExpert {

@dev.langchain4j.service.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 {{it}}.
""")
@Tool("A legal expert")
String legalRequest(String request);
}

interface TechnicalExpert {

@dev.langchain4j.service.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 {{it}}.
""")
@Tool("A technical expert")
String technicalRequest(String request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,36 @@ public static Agents.AstrologyAgent newAstrologyAgent() {
.outputName("horoscope")
.build());
}

public static Agents.CategoryRouter newCategoryRouter() {
return spy(
AgenticServices.agentBuilder(Agents.CategoryRouter.class)
.chatModel(BASE_MODEL)
.outputName("category")
.build());
}

public static Agents.MedicalExpert newMedicalExpert() {
return spy(
AgenticServices.agentBuilder(Agents.MedicalExpert.class)
.chatModel(BASE_MODEL)
.outputName("response")
.build());
}

public static Agents.TechnicalExpert newTechnicalExpert() {
return spy(
AgenticServices.agentBuilder(Agents.TechnicalExpert.class)
.chatModel(BASE_MODEL)
.outputName("response")
.build());
}

public static Agents.LegalExpert newLegalExpert() {
return spy(
AgenticServices.agentBuilder(Agents.LegalExpert.class)
.chatModel(BASE_MODEL)
.outputName("response")
.build());
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should be disable by default ( I mean, not run every time we do mvn clean install over the SDK)
Im not sure thats the case, hence this comment
@ricardozanini

Copy link
Member

@ricardozanini ricardozanini Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not. All the IT tests only run when we do -Pintegration-tests. See #732

Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void sequentialWorkflow() {
}

@Test
@DisplayName("Looping agents via DSL.loop(...)") // TODO maxIterations(5)
@DisplayName("Looping agents via DSL.loop(...)")
public void loopWorkflow() {

var scorer = AgentsUtils.newStyleScorer();
Expand Down Expand Up @@ -103,6 +103,48 @@ public void loopWorkflow() {
assertThat(result).containsKey("story");
}

@Test
@DisplayName("Looping agents via DSL.loop(...)")
public void loopWorkflowWithMaxIterations() {
var scorer = AgentsUtils.newStyleScorer();
var editor = AgentsUtils.newStyleEditor();

Workflow wf =
AgentWorkflowBuilder.workflow("maxFlow")
.tasks(
d ->
d.loop(
"limit",
l ->
l.maxIterations(5)
.exitCondition(c -> c.readState("score", 0).doubleValue() >= 0.8)
.subAgents("sub", scorer, editor)))
.build();

List<TaskItem> items = wf.getDo();
assertThat(items).hasSize(1);

var fn = (ForTaskFunction) items.get(0).getTask().getForTask();
assertThat(fn.getDo()).isNotNull();
assertThat(fn.getDo()).hasSize(2);
fn.getDo()
.forEach(si -> assertThat(si.getTask().getCallTask()).isInstanceOf(CallTaskJava.class));

Map<String, Object> input =
Map.of(
"story", "dragons and wizards",
"style", "comedy");

Map<String, Object> result;
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
result = app.workflowDefinition(wf).instance(input).start().get().asMap().orElseThrow();
} catch (Exception e) {
throw new RuntimeException("Workflow execution failed", e);
}

assertThat(result).containsKey("story");
}

@Test
@DisplayName("Parallel agents via DSL.parallel(...)")
public void parallelWorkflow() {
Expand All @@ -115,7 +157,9 @@ public void parallelWorkflow() {
assertThat(items).hasSize(1);

var fork = items.get(0).getTask().getForkTask();
// two branches created
assertThat(fork.getFork().getBranches()).hasSize(2);
// branch names follow "branch-{index}-{name}"
assertThat(fork.getFork().getBranches().get(0).getName()).isEqualTo("branch-0-fanout");
assertThat(fork.getFork().getBranches().get(1).getName()).isEqualTo("branch-1-fanout");

Expand All @@ -132,6 +176,50 @@ public void parallelWorkflow() {
assertEquals("Fake conflict response", result.get("movies"));
}

// TODO
@Test
@DisplayName("Conditional agents via choice(...)")
public void conditionalWorkflow() {

var category = AgentsUtils.newCategoryRouter();
var a1 = AgentsUtils.newMedicalExpert();
var a2 = AgentsUtils.newTechnicalExpert();
var a3 = AgentsUtils.newLegalExpert();
}

// TODO
@Test
@DisplayName("Error handling with agents")
public void errorHandling() {
var a1 = AgentsUtils.newCreativeWriter();
var a2 = AgentsUtils.newAudienceEditor();
var a3 = AgentsUtils.newStyleEditor();

Workflow wf = workflow("seqFlow").tasks(tasks -> tasks.sequence("process", a1, a2, a3)).build();

List<TaskItem> items = wf.getDo();
assertThat(items).hasSize(3);

assertThat(items.get(0).getName()).isEqualTo("process-0");
assertThat(items.get(1).getName()).isEqualTo("process-1");
assertThat(items.get(2).getName()).isEqualTo("process-2");
items.forEach(it -> assertThat(it.getTask().getCallTask()).isInstanceOf(CallTaskJava.class));

Map<String, Object> input =
Map.of(
"style", "fantasy",
"audience", "young adults");

Map<String, Object> result;
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
result = app.workflowDefinition(wf).instance(input).start().get().asMap().orElseThrow();
} catch (Exception e) {
throw new RuntimeException("Workflow execution failed", e);
}

assertThat(result).containsKey("story");
}

@Test
@DisplayName("Human in the loop")
public void humanInTheLoop() {
Expand All @@ -149,8 +237,7 @@ public void humanInTheLoop() {

var a1 = AgentsUtils.newAstrologyAgent();

Workflow wf =
workflow("seqFlow").tasks(tasks -> tasks.sequence("process", a1, humanInTheLoop)).build();
Workflow wf = workflow("seqFlow").sequence("process", a1, humanInTheLoop).build();

assertThat(wf.getDo()).hasSize(2);

Expand Down