Skip to content

Commit 54f5a26

Browse files
committed
Bump to LangChain4j 1.9.1
1 parent 17fdf15 commit 54f5a26

File tree

29 files changed

+196
-272
lines changed

29 files changed

+196
-272
lines changed

agentic/deployment/src/main/java/io/quarkiverse/langchain4j/agentic/deployment/AgenticLangChain4jDotNames.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,30 @@
2525
import dev.langchain4j.agentic.declarative.Output;
2626
import dev.langchain4j.agentic.declarative.ParallelAgent;
2727
import dev.langchain4j.agentic.declarative.ParallelExecutor;
28+
import dev.langchain4j.agentic.declarative.PlannerAgent;
2829
import dev.langchain4j.agentic.declarative.RetrievalAugmentorSupplier;
2930
import dev.langchain4j.agentic.declarative.SequenceAgent;
30-
import dev.langchain4j.agentic.declarative.SubAgent;
3131
import dev.langchain4j.agentic.declarative.SupervisorAgent;
3232
import dev.langchain4j.agentic.declarative.ToolProviderSupplier;
3333
import dev.langchain4j.agentic.declarative.ToolsSupplier;
3434
import dev.langchain4j.agentic.scope.AgenticScope;
3535
import dev.langchain4j.agentic.scope.ResultWithAgenticScope;
36+
import dev.langchain4j.service.MemoryId;
3637

3738
public final class AgenticLangChain4jDotNames {
3839

3940
public static final DotName AGENT = DotName.createSimple(Agent.class.getName());
40-
public static final DotName SUB_AGENT = DotName.createSimple(SubAgent.class.getName());
4141
public static final DotName SUPERVISOR_AGENT = DotName.createSimple(SupervisorAgent.class.getName());
4242
public static final DotName SEQUENCE_AGENT = DotName.createSimple(SequenceAgent.class.getName());
4343
public static final DotName PARALLEL_AGENT = DotName.createSimple(ParallelAgent.class.getName());
4444
public static final DotName LOOP_AGENT = DotName.createSimple(LoopAgent.class.getName());
4545
public static final DotName CONDITIONAL_AGENT = DotName.createSimple(ConditionalAgent.class.getName());
46+
public static final DotName PLANNER_AGENT = DotName.createSimple(PlannerAgent.class.getName());
4647

47-
public static final List<DotName> ALL_AGENT_ANNOTATIONS = List.of(AGENT, SUB_AGENT, SUPERVISOR_AGENT, SEQUENCE_AGENT,
48-
PARALLEL_AGENT, LOOP_AGENT, CONDITIONAL_AGENT);
48+
public static final List<DotName> ALL_AGENT_ANNOTATIONS = List.of(AGENT, SUPERVISOR_AGENT, SEQUENCE_AGENT,
49+
PARALLEL_AGENT, LOOP_AGENT, CONDITIONAL_AGENT, PLANNER_AGENT);
4950
public static final List<DotName> AGENT_ANNOTATIONS_WITH_SUB_AGENTS = List.of(SUPERVISOR_AGENT, SEQUENCE_AGENT,
50-
PARALLEL_AGENT, LOOP_AGENT, CONDITIONAL_AGENT);
51+
PARALLEL_AGENT, LOOP_AGENT, CONDITIONAL_AGENT, PLANNER_AGENT);
5152

5253
public static final DotName CHAT_MODEL_SUPPLIER = DotName.createSimple(ChatModelSupplier.class.getName());
5354
public static final DotName AGENTIC_SCOPE = DotName.createSimple(AgenticScope.class);
@@ -87,6 +88,8 @@ public final class AgenticLangChain4jDotNames {
8788
public static final DotName TOOL_SUPPLIER = DotName
8889
.createSimple(ToolsSupplier.class.getName());
8990

91+
public static final DotName MEMORY_ID = DotName.createSimple(MemoryId.class.getName());
92+
9093
private AgenticLangChain4jDotNames() {
9194
}
9295
}

agentic/deployment/src/main/java/io/quarkiverse/langchain4j/agentic/deployment/AgenticProcessor.java

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.HashSet;
1414
import java.util.List;
1515
import java.util.Map;
16+
import java.util.Objects;
1617
import java.util.Optional;
1718
import java.util.Set;
1819
import java.util.function.Predicate;
@@ -535,24 +536,25 @@ public OutputKeyBuildItem outputKeyItems(DetectedAiAgentAsMapBuildItem detectedA
535536
if (subAgentsValue == null) {
536537
continue;
537538
}
538-
for (AnnotationInstance subAgentInstance : subAgentsValue.asNestedArray()) {
539-
AnnotationValue outputKeyValue = subAgentInstance.value("outputKey");
540-
AnnotationValue typeValue = subAgentInstance.value("type");
541-
if ((outputKeyValue != null) && (typeValue != null)) {
542-
String outputKeyString = outputKeyValue.asString();
543-
DotName subAgentType = typeValue.asClass().name();
544-
545-
// in order to determine the type associated with the outputKey, we need to look up
546-
// the interface and check the return type of (the single agent-related) method
547-
548-
List<MethodInfo> subAgentMethods = ifaceToAgentMethodsMap.get(subAgentType);
549-
if (subAgentMethods.size() != 1) {
550-
log.warn("Unable to determine type of outputKey '%s' for subagent with type '%s'"
551-
.formatted(outputKeyString, subAgentType));
552-
} else {
553-
builder.addKeyType(outputKeyString,
554-
determineAgentMethodReturnType(subAgentMethods.get(0).returnType()));
555-
}
539+
for (Type subAgentType : subAgentsValue.asClassArray()) {
540+
// in order to determine the type associated with the outputKey, we need to look up
541+
// the interface and check the return key and type of (the single agent-related) method
542+
543+
List<MethodInfo> subAgentMethods = ifaceToAgentMethodsMap.get(subAgentType.name());
544+
if (subAgentMethods.size() != 1) {
545+
log.warn("Unable to determine type of outputKey for subagent with type '%s'"
546+
.formatted(subAgentType));
547+
} else {
548+
MethodInfo subAgentMethod = subAgentMethods.get(0);
549+
AgenticLangChain4jDotNames.ALL_AGENT_ANNOTATIONS.stream()
550+
.map(ann -> subAgentMethod.annotation(ann))
551+
.filter(Objects::nonNull)
552+
.map(ann -> ann.value("outputKey"))
553+
.filter(Objects::nonNull)
554+
.map(key -> key.asString())
555+
.findFirst()
556+
.ifPresent(outputKeyString -> builder.addKeyType(outputKeyString,
557+
determineAgentMethodReturnType(subAgentMethod.returnType())));
556558
}
557559
}
558560
}
@@ -575,12 +577,8 @@ public OutputKeyBuildItem outputKeyItems(DetectedAiAgentAsMapBuildItem detectedA
575577
String parameterName = determineParameterName(pi);
576578
builder.addSupervisedKeys(parameterName);
577579
});
578-
for (AnnotationInstance subAgentInstance : subAgentsValue.asNestedArray()) {
579-
AnnotationValue typeValue = subAgentInstance.value("type");
580-
if (typeValue == null) {
581-
continue;
582-
}
583-
ifaceToAgentMethodsMap.getOrDefault(typeValue.asClass().name(), Collections.emptyList()).forEach(mi -> {
580+
for (Type subAgentType : subAgentsValue.asClassArray()) {
581+
ifaceToAgentMethodsMap.getOrDefault(subAgentType.name(), Collections.emptyList()).forEach(mi -> {
584582
List<MethodParameterInfo> parameters = mi.parameters();
585583
parameters.forEach(pi -> {
586584
String parameterName = determineParameterName(pi);
@@ -651,6 +649,9 @@ public void validateAgenticParameterTypes(CombinedIndexBuildItem indexBuildItem,
651649
if (AgenticLangChain4jDotNames.AGENTIC_SCOPE.equals(parameterType.name())) {
652650
continue;
653651
}
652+
if (parameter.annotation(AgenticLangChain4jDotNames.MEMORY_ID) != null) {
653+
continue;
654+
}
654655
DotName expectedParameterTypeName = outputKeyBuildItem.getKeyToTypeMap().get(parameterName);
655656
if (expectedParameterTypeName == null) {
656657
boolean doesNotNeedResolution = outputKeyBuildItem.getUserProvidedKeys().contains(parameterName)

agentic/deployment/src/test/java/io/quarkiverse/langchain4j/agentic/deployment/AgentBeanSmokeTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class AgentBeanSmokeTest extends OpenAiBaseTest {
1919
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
2020
.setArchiveProducer(
2121
() -> ShrinkWrap.create(JavaArchive.class)
22-
.addClasses(Agents.class))
22+
.addClasses(Agents.class, ParallelAgents.class))
2323
.overrideRuntimeConfigKey("quarkus.langchain4j.openai.api-key", "whatever")
2424
.overrideRuntimeConfigKey("quarkus.langchain4j.openai.base-url",
2525
WiremockAware.wiremockUrlForConfig("/v1"));
@@ -34,7 +34,7 @@ public class AgentBeanSmokeTest extends OpenAiBaseTest {
3434
Agents.SupervisorStoryCreator supervisorStoryCreator;
3535

3636
@Inject
37-
Agents.EveningPlannerAgent eveningPlannerAgent;
37+
ParallelAgents.EveningPlannerAgent eveningPlannerAgent;
3838

3939
@Inject
4040
Agents.MedicalExpertWithMemory medicalExpertWithMemory;

agentic/deployment/src/test/java/io/quarkiverse/langchain4j/agentic/deployment/Agents.java

Lines changed: 14 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.quarkiverse.langchain4j.agentic.deployment;
22

3-
import java.util.List;
4-
53
import jakarta.enterprise.context.ApplicationScoped;
64
import jakarta.inject.Inject;
75

@@ -13,7 +11,6 @@
1311
import dev.langchain4j.agentic.declarative.ExitCondition;
1412
import dev.langchain4j.agentic.declarative.LoopAgent;
1513
import dev.langchain4j.agentic.declarative.SequenceAgent;
16-
import dev.langchain4j.agentic.declarative.SubAgent;
1714
import dev.langchain4j.agentic.declarative.SupervisorAgent;
1815
import dev.langchain4j.agentic.declarative.SupervisorRequest;
1916
import dev.langchain4j.agentic.scope.AgenticScopeAccess;
@@ -63,7 +60,7 @@ public interface CategoryRouter {
6360
Reply with only one of those words and nothing else.
6461
The user request is: '{{request}}'.
6562
""")
66-
@Agent("Categorize a user request")
63+
@Agent(description = "Categorize a user request", outputKey = "category")
6764
RequestCategory classify(@V("request") String request);
6865

6966
@ChatModelSupplier
@@ -100,7 +97,7 @@ public interface MedicalExpert {
10097
The user request is {{request}}.
10198
""")
10299
@Tool("A medical expert")
103-
@Agent("A medical expert")
100+
@Agent(description = "A medical expert", outputKey = "response")
104101
String medical(@V("request") String request);
105102

106103
@ChatModelSupplier
@@ -118,7 +115,7 @@ public interface MedicalExpertWithMemory {
118115
The user request is {{request}}.
119116
""")
120117
@Tool("A medical expert")
121-
@Agent("A medical expert")
118+
@Agent(description = "A medical expert", outputKey = "response")
122119
String medical(@MemoryId String memoryId, @V("request") String request);
123120
}
124121

@@ -130,7 +127,7 @@ public interface LegalExpert {
130127
The user request is {{request}}.
131128
""")
132129
@Tool("A legal expert")
133-
@Agent("A legal expert")
130+
@Agent(description = "A legal expert", outputKey = "response")
134131
String legal(@V("request") String request);
135132
}
136133

@@ -142,7 +139,7 @@ public interface LegalExpertWithMemory {
142139
The user request is {{request}}.
143140
""")
144141
@Tool("A legal expert")
145-
@Agent("A legal expert")
142+
@Agent(description = "A legal expert", outputKey = "response")
146143
String legal(@MemoryId String memoryId, @V("request") String request);
147144
}
148145

@@ -154,7 +151,7 @@ public interface TechnicalExpert {
154151
The user request is {{request}}.
155152
""")
156153
@Tool("A technical expert")
157-
@Agent("A technical expert")
154+
@Agent(description = "A technical expert", outputKey = "response")
158155
String technical(@V("request") String request);
159156
}
160157

@@ -166,7 +163,7 @@ public interface TechnicalExpertWithMemory {
166163
The user request is {{request}}.
167164
""")
168165
@Tool("A technical expert")
169-
@Agent("A technical expert")
166+
@Agent(description = "A technical expert", outputKey = "response")
170167
String technical(@MemoryId String memoryId, @V("request") String request);
171168
}
172169

@@ -178,7 +175,7 @@ public interface CreativeWriter {
178175
Return only the story and nothing else.
179176
The topic is {{topic}}.
180177
""")
181-
@Agent("Generate a story based on the given topic")
178+
@Agent(description = "Generate a story based on the given topic", outputKey = "story")
182179
String generateStory(@V("topic") String topic);
183180

184181
@ChatModelSupplier
@@ -200,7 +197,7 @@ public interface AudienceEditor {
200197
Return only the story and nothing else.
201198
The story is "{{story}}".
202199
""")
203-
@Agent("Edit a story to better fit a given audience")
200+
@Agent(description = "Edit a story to better fit a given audience", outputKey = "story")
204201
String editStory(@V("story") String story, @V("audience") String audience);
205202

206203
@ChatModelSupplier
@@ -224,7 +221,7 @@ public interface StyleEditor {
224221
Return only the story and nothing else.
225222
The story is "{{story}}".
226223
""")
227-
@Agent("Edit a story to better fit a given style")
224+
@Agent(description = "Edit a story to better fit a given style", outputKey = "story")
228225
String editStory(@V("story") String story, @V("style") String style);
229226

230227
@ChatModelSupplier
@@ -256,7 +253,7 @@ public interface StyleScorer {
256253
257254
The story is: "{{story}}"
258255
""")
259-
@Agent("Score a story based on how well it aligns with a given style")
256+
@Agent(description = "Score a story based on how well it aligns with a given style", outputKey = "score")
260257
double scoreStyle(@V("story") String story, @V("style") String style);
261258

262259
@ChatModelSupplier
@@ -277,40 +274,6 @@ public interface StyledWriter extends AgenticScopeAccess {
277274
ResultWithAgenticScope<String> writeStoryWithStyle(@V("topic") String topic, @V("style") String style);
278275
}
279276

280-
public interface FoodExpert {
281-
282-
@UserMessage("""
283-
You are a great evening planner.
284-
Propose a list of 3 meals matching the given mood.
285-
The mood is {{mood}}.
286-
For each meal, just give the name of the meal.
287-
Provide a list with the 3 items and nothing else.
288-
""")
289-
@Agent
290-
List<String> findMeal(@V("mood") String mood);
291-
}
292-
293-
public interface MovieExpert {
294-
295-
@UserMessage("""
296-
You are a great evening planner.
297-
Propose a list of 3 movies matching the given mood.
298-
The mood is {{mood}}.
299-
Provide a list with the 3 items and nothing else.
300-
""")
301-
@Agent
302-
List<String> findMovie(@V("mood") String mood);
303-
}
304-
305-
public record EveningPlan(String movie, String meal) {
306-
}
307-
308-
public interface EveningPlannerAgent {
309-
310-
@Agent
311-
List<EveningPlan> plan(@V("mood") String mood);
312-
}
313-
314277
@ApplicationScoped
315278
public static class NoneAiAgent {
316279

@@ -328,20 +291,14 @@ public String goodBye() {
328291

329292
public interface StoryCreator {
330293

331-
@SequenceAgent(outputKey = "story", subAgents = {
332-
@SubAgent(type = CreativeWriter.class, outputKey = "story"),
333-
@SubAgent(type = AudienceEditor.class, outputKey = "story"),
334-
@SubAgent(type = StyleEditor.class, outputKey = "story")
335-
})
294+
@SequenceAgent(outputKey = "story", subAgents = { CreativeWriter.class, AudienceEditor.class, StyleEditor.class })
336295
String write(@V("topic") String topic, @V("style") String style, @V("audience") String audience);
337296
}
338297

339298
public interface StyleReviewLoopAgent {
340299

341300
@LoopAgent(description = "Review the given story to ensure it aligns with the specified style", outputKey = "story", maxIterations = 5, subAgents = {
342-
@SubAgent(type = StyleScorer.class, outputKey = "score"),
343-
@SubAgent(type = StyleEditor.class, outputKey = "story")
344-
})
301+
StyleScorer.class, StyleEditor.class })
345302
String write(@V("story") String story);
346303

347304
@ExitCondition
@@ -353,9 +310,7 @@ static boolean exit(@V("score") double score) {
353310
public interface SupervisorStoryCreator {
354311

355312
@SupervisorAgent(outputKey = "story", responseStrategy = SupervisorResponseStrategy.LAST, subAgents = {
356-
@SubAgent(type = CreativeWriter.class, outputKey = "story"),
357-
@SubAgent(type = StyleReviewLoopAgent.class, outputKey = "story")
358-
})
313+
CreativeWriter.class, StyleReviewLoopAgent.class })
359314
ResultWithAgenticScope<String> write(@V("topic") String topic, @V("style") String style);
360315

361316
@SupervisorRequest
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.quarkiverse.langchain4j.agentic.deployment;
2+
3+
import java.util.List;
4+
5+
import dev.langchain4j.agentic.Agent;
6+
import dev.langchain4j.agentic.declarative.ParallelAgent;
7+
import dev.langchain4j.service.UserMessage;
8+
import dev.langchain4j.service.V;
9+
10+
public class ParallelAgents {
11+
public interface FoodExpert {
12+
13+
@UserMessage("""
14+
You are a great evening planner.
15+
Propose a list of 3 meals matching the given mood.
16+
The mood is {{mood}}.
17+
For each meal, just give the name of the meal.
18+
Provide a list with the 3 items and nothing else.
19+
""")
20+
@Agent(outputKey = "meals")
21+
List<String> findMeal(@V("mood") String mood);
22+
}
23+
24+
public interface MovieExpert {
25+
26+
@UserMessage("""
27+
You are a great evening planner.
28+
Propose a list of 3 movies matching the given mood.
29+
The mood is {{mood}}.
30+
Provide a list with the 3 items and nothing else.
31+
""")
32+
@Agent(outputKey = "movies")
33+
List<String> findMovie(@V("mood") String mood);
34+
}
35+
36+
public record EveningPlan(String movie, String meal) {
37+
}
38+
39+
public interface EveningPlannerAgent {
40+
41+
@ParallelAgent(outputKey = "plans", subAgents = { FoodExpert.class, MovieExpert.class })
42+
List<EveningPlan> plan(@V("mood") String mood);
43+
}
44+
}

0 commit comments

Comments
 (0)