Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,140 +15,12 @@
*/
package io.serverlessworkflow.fluent.agentic.langchain4j;

import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.scope.AgenticScopeAccess;
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 AgenticScopeAccess {

@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(
Expand Down Expand Up @@ -187,71 +59,4 @@ public interface StyleEditor {
@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 AgenticScopeAccess {

@Agent
String 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<String> 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<String> findMovie(@V("mood") String mood);
}

public record EveningPlan(String movie, String meal) {}

public interface EveningPlannerAgent {

@Agent
List<EveningPlan> plan(@V("mood") String mood);
}

public interface HoroscopeAgent {

@Agent
String invoke(@V("name") String name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,19 @@
*/
package io.serverlessworkflow.fluent.agentic.langchain4j;

import static io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder.workflow;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newAstrologyAgent;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newAudienceEditor;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newCreativeWriter;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newFoodExpert;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newMovieExpert;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newStyleEditor;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newStyleScorer;
import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newSummaryStory;
import static io.serverlessworkflow.fluent.agentic.langchain4j.Agents.*;
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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
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.scope.AgenticScope;
import dev.langchain4j.agentic.workflow.WorkflowAgentsBuilder;
import io.serverlessworkflow.fluent.agentic.AgenticWorkflow;
import io.serverlessworkflow.fluent.agentic.AgentsUtils;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Test;

public class WorkflowAgentsIT {
Expand Down Expand Up @@ -96,123 +77,4 @@ void sequential_agents_tests() {
verify(audienceEditor).editStory(any(), eq("young adults"));
verify(styleEditor).editStory(any(), eq("fantasy"));
}

@Test
public void sequenceHelperTest() {
var creativeWriter = newCreativeWriter();
var audienceEditor = newAudienceEditor();
var styleEditor = newStyleEditor();

AgentsUtils.NovelCreator novelCreator =
AgenticWorkflow.of(AgentsUtils.NovelCreator.class)
.flow(workflow("seqFlow").sequence(creativeWriter, audienceEditor, styleEditor))
.build();

String story = novelCreator.createNovel("dragons and wizards", "young adults", "fantasy");
assertNotNull(story);
}

@Test
public void agentAndSequenceHelperTest() {
var creativeWriter = newCreativeWriter();
var audienceEditor = newAudienceEditor();
var styleEditor = newStyleEditor();

AgentsUtils.NovelCreator novelCreator =
AgenticWorkflow.of(AgentsUtils.NovelCreator.class)
.flow(workflow("seqFlow").agent(creativeWriter).sequence(audienceEditor, styleEditor))
.build();

String story = novelCreator.createNovel("dragons and wizards", "young adults", "fantasy");
assertNotNull(story);
}

@Test
public void agentAndSequenceAndAgentHelperTest() {
var creativeWriter = newCreativeWriter();
var audienceEditor = newAudienceEditor();
var styleEditor = newStyleEditor();
var summaryStory = newSummaryStory();

AgentsUtils.NovelCreator novelCreator =
AgenticWorkflow.of(AgentsUtils.NovelCreator.class)
.flow(
workflow("seqFlow")
.agent(creativeWriter)
.sequence(audienceEditor, styleEditor)
.agent(summaryStory))
.build();

String story = novelCreator.createNovel("dragons and wizards", "young adults", "fantasy");
assertNotNull(story);
}

@Test
public void parallelWorkflow() {
var foodExpert = newFoodExpert();
var movieExpert = newMovieExpert();

Function<AgenticScope, List<EveningPlan>> planEvening =
input -> {
List<String> movies = (List<String>) input.readState("movies");
List<String> meals = (List<String>) input.readState("meals");

int max = Math.min(movies.size(), meals.size());
return IntStream.range(0, max)
.mapToObj(i -> new EveningPlan(movies.get(i), meals.get(i)))
.toList();
};

EveningPlannerAgent eveningPlannerAgent =
AgenticWorkflow.of(EveningPlannerAgent.class)
.flow(workflow("parallelFlow").parallel(foodExpert, movieExpert).outputAs(planEvening))
.build();
List<EveningPlan> result = eveningPlannerAgent.plan("romantic");
assertEquals(3, result.size());
}

@Test
public void loopTest() {
var creativeWriter = newCreativeWriter();
var scorer = newStyleScorer();
var editor = newStyleEditor();

Predicate<AgenticScope> until = s -> s.readState("score", 0.0) >= 0.8;

StyledWriter styledWriter =
AgenticWorkflow.of(StyledWriter.class)
.flow(workflow("loopFlow").agent(creativeWriter).loop(until, scorer, editor))
.build();

String story = styledWriter.writeStoryWithStyle("dragons and wizards", "fantasy");
assertNotNull(story);
}

@Test
public void humanInTheLoop() {
var astrologyAgent = newAstrologyAgent();

var askSign =
new Function<AgenticScope, AgenticScope>() {
@Override
public AgenticScope apply(AgenticScope holder) {
System.out.println("What's your star sign?");
// var sign = System.console().readLine();
holder.writeState("sign", "piscis");
return holder;
}
};

String result =
AgenticWorkflow.of(Agents.HoroscopeAgent.class)
.flow(
workflow("humanInTheLoop")
.inputFrom(askSign)
// .tasks(tasks -> tasks.callFn(fn(askSign))) // TODO should work too
.agent(astrologyAgent))
.build()
.invoke("My name is Mario. What is my horoscope?");

assertNotNull(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static AgentTaskConfigurer loop(Predicate<AgenticScope> exitCondition, Ob
}

public static AgentTaskConfigurer loop(
Predicate<AgenticScope> exitCondition, int maxIterations, Object... agents) {
int maxIterations, Predicate<AgenticScope> exitCondition, Object... agents) {
return list ->
list.loop(
l -> l.subAgents(agents).exitCondition(exitCondition).maxIterations(maxIterations));
Expand Down
Loading