|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: 'Agentic AI Patterns: one size does not fit all' |
| 4 | +date: 2025-11-12T00:00:00Z |
| 5 | +tags: ai llm agents |
| 6 | +synopsis: 'Agentic AI Patterns: one size does not fit all' |
| 7 | +author: mariofusco |
| 8 | +--- |
| 9 | +:imagesdir: /assets/images/posts/agentic |
| 10 | + |
| 11 | +At the beginning of 2025 we started doing some experiments with agentic AI, using Quarkus and its LangChain4j extension, that culminated with the publication of a three-part blog post series on this topic, that first https://quarkus.io/blog/agentic-ai-with-quarkus/[introduced agentic AI and workflow patterns], then explored https://quarkus.io/blog/agentic-ai-with-quarkus-p2/[purely AI orchestrated agentic patterns], and finally examined the https://quarkus.io/blog/agentic-ai-with-quarkus-p3/[differences between these two approaches] with a practical example, trying to put in evidence their pros and cons. |
| 12 | + |
| 13 | +Only a handful of months have passed since those articles, but in the meantime the AI ecosystem, and the possibilities it enables, have evolved at an incredible speed, making it increasingly harder to keep up with all the novelties and changes. In fact the initial experiments discussed in that series seem now to come from a different geological era, even though they served as an important starting point to explore the agentic AI landscape. |
| 14 | + |
| 15 | +These experiments and explorations lead to the implementation of a https://docs.langchain4j.dev/tutorials/agents/[new agentic module] in LangChain4j intended to provide a comprehensive framework for building agentic AI systems. This module also includes a set of predefined agentic patterns that can be used as building blocks to coordinate agents in different ways. Unfortunately, continuing with those experiments, and most importantly discussing them with the LangChain4j community, made it clear that the belief that this set of patterns could be quite exhaustive and cover all the possible use cases was naive and the reality is more complex and nuanced. |
| 16 | + |
| 17 | +== Agentic AI at Devoxx Belgium 2025 |
| 18 | + |
| 19 | +Agentic AI was, by far, the most discussed topic at Devoxx Belgium 2025 and we had the opportunity to present there the agentic AI patterns implemented in LangChain4j. In fact, we both gave a https://www.youtube.com/watch?v=X9baI7RBhqk[talk] where we simply exposed with practical examples how those patterns work, and how to use them, and another maybe even more interesting https://www.youtube.com/watch?v=mtWHfYTLeKE[session] where we discussed the steps that brought us to implement the agentic framework as it was together with the mistakes we made and the decision that we had to take during that journey. |
| 20 | + |
| 21 | +As usual at Devoxx, even more important than attending the uncountable talks on agentic AI, was the opportunity to meet so many people working in this field, exchanging ideas and sharing experiences, challenges, and solutions. |
| 22 | + |
| 23 | +We learned a lot from these interactions, and returned to home with only one clear thought: we will never find an ultimate agent orchestrator pattern, not only because this is not our domain of expertise, but mostly because such a thing does not exist. The variety and complexity of tasks that can be required to an agentic system implies that there cannot be a pattern that works in all situations, a one-size-fits-all solution for agentic AI orchestration. |
| 24 | + |
| 25 | +In fact, there is a large spectrum of agentic patterns, ranging from reliable but rigid workflows, where agents control flow follows predetermined code paths, to flexible but unpredictable pure agentic orchestration, where LLMs autonomously decide the sequence of actions to be taken, maintaining control over how they execute tasks, and everything in between. In particular this "in between" space could represent the most interesting one, as it allows to balance reliability and flexibility, and considering the difficulties in better identifying it, why at least not giving to users the possibility to shape it in the way they need? |
| 26 | + |
| 27 | +== A generic architecture for agentic AI patterns |
| 28 | + |
| 29 | +The natural consequence of these considerations was deciding to design a generic architecture for agentic AI patterns that would be customizable enough to allow the widest possible variety of patterns, keeping the possibility of using those patterns as combinable building block that could be seamlessly used together and complement each other. |
| 30 | + |
| 31 | +To keep this as flexible as possible, it was necessary to identify the minimal and simplest abstraction that could do the work, thus realizing that an agentic pattern is simply the specification of an execution plan for the subagents that it coordinates. This plan can be defined by implementing the following `Planner` interface: |
| 32 | + |
| 33 | +[source,java] |
| 34 | +---- |
| 35 | +public interface Planner { |
| 36 | +
|
| 37 | + default void init(InitPlanningContext initPlanningContext) { } |
| 38 | +
|
| 39 | + default Action firstAction(PlanningContext planningContext) { |
| 40 | + return nextAction(planningContext); |
| 41 | + } |
| 42 | +
|
| 43 | + Action nextAction(PlanningContext planningContext); |
| 44 | +} |
| 45 | +---- |
| 46 | + |
| 47 | +This interface defines a method to initialize the planner, plus two other methods to plan the first action to be executed and all the subsequent ones. The method returning the first action is optional and by default it simply delegates to the method returning the next action, which is the only one that must be implemented. The `Action` class returned by these two methods represents the next step to be taken by the agentic pattern, and can be one either a list of one or more subagents to be called next, or a signal that the execution has been completed. |
| 48 | + |
| 49 | +All the built-in agentic patterns offered out-of-the-box by the `langchain4j-agentic` module have been rewritten in terms of this `Planner` abstraction. The pull requests implementing this new architecture has been already merged, and this article won't go in many details in how it works here, since you can find all the information in https://github.com/langchain4j/langchain4j/pull/3929[its description]. |
| 50 | + |
| 51 | +The other important point that deserves to be considered is that not only this new architecture gives a chance to users to implement their own agentic patterns, but it also allows to easily combine them with any other pattern, either offered by LangChain4j or implemented by the users themselves. In fact, since all the patterns are now defined in terms of a `Planner`, it is possible to use any pattern as a subagent of any other pattern, thus opening the door to an infinite variety of combinations, as the next example is intended to demonstrate. |
| 52 | + |
| 53 | +== Mixing multiple agentic patterns |
| 54 | + |
| 55 | +One of the examples used in the pull request mentioned above to illustrate this new architecture and how to create your own custom agentic pattern through it, is a goal oriented strategy determining the sequence of agents to be invoked in order to perform a specific complex task. |
| 56 | + |
| 57 | +More in detail, in order to put this approach in practice, not only the whole agentic system needs to define a final goal, but also each subagent needs to declare its own preconditions, required to be able to perform its task, and postconditions, that is the outcomes that it guarantees once its execution is completed. However, in agentic LangChain4j, all this information are implicitly already present in the agentic system, as those pre and postconditions are nothing else than the required inputs and produced outputs of each agent, and the final goal is simply the desired outputs of the whole agentic system. |
| 58 | + |
| 59 | +Following this idea, it is possible to calculate a dependency graph of all the subagents participating in the agentic system, and then to implement a `Planner` that is capable of analyzing the initial state of the `AgenticScope`, comparing it with the desired goal, and then using that graph to determine the sequence of agent invocations that can lead to the achievement of that goal. |
| 60 | + |
| 61 | +[source,java] |
| 62 | +---- |
| 63 | +public class GoalOrientedPlanner implements Planner { |
| 64 | +
|
| 65 | + private String goal; |
| 66 | +
|
| 67 | + private GoalOrientedSearchGraph graph; |
| 68 | + private List<AgentInstance> path; |
| 69 | +
|
| 70 | + private int agentCursor = 0; |
| 71 | +
|
| 72 | + @Override |
| 73 | + public void init(InitPlanningContext initPlanningContext) { |
| 74 | + this.goal = initPlanningContext.plannerAgent().outputKey(); |
| 75 | + this.graph = new GoalOrientedSearchGraph(initPlanningContext.subagents()); |
| 76 | + } |
| 77 | +
|
| 78 | + @Override |
| 79 | + public Action firstAction(PlanningContext planningContext) { |
| 80 | + path = graph.search(planningContext.agenticScope().state().keySet(), goal); |
| 81 | + if (path.isEmpty()) { |
| 82 | + throw new IllegalStateException("No path found for goal: " + goal); |
| 83 | + } |
| 84 | + return call(path.get(agentCursor++)); |
| 85 | + } |
| 86 | +
|
| 87 | + @Override |
| 88 | + public Action nextAction(PlanningContext planningContext) { |
| 89 | + return agentCursor >= path.size() ? done() : call(path.get(agentCursor++)); |
| 90 | + } |
| 91 | +} |
| 92 | +---- |
| 93 | + |
| 94 | +As anticipated, here the goal coincides with the final output of the planner-based agentic pattern itself, while the path from the initial state to the goal is calculated using a graph, that is built analyzing the input and output keys of all subagents. The sequence of agents to be invoked is then calculated as the shortest path on that graph from the current state to the desired goal. |
| 95 | + |
| 96 | +The example discussed in that pull request to demonstrate this pattern at work is an agentic system that generates horoscope-based writeups. The complete source code of this example is available https://github.com/langchain4j/langchain4j/tree/main/langchain4j-agentic-patterns/src/test/java/dev/langchain4j/agentic/patterns/goap/horoscope[here]. This system is composed of multiple subagents, each responsible for a specific task, such as extracting the user's data from the prompt, fetching horoscope data from an external source, generating content based on the horoscope, and formatting the final writeup. In this situation the graph of agents dependencies, also determining the sequence of their activations, is the following: |
| 97 | + |
| 98 | +[.text-center] |
| 99 | +.The GOAP-determined sequence of agents invocation to generate an horoscope-based writeup |
| 100 | +image::goap.png[width=50%, align="center", alt="The GOAP-determined sequence of agents invocation to generate an horoscope-based writeup"] |
| 101 | + |
| 102 | +The main advantage of this goal oriented strategy is that it deterministically determines the shortest sequence of steps to be performed from the initial state to the final goal. Nevertheless, in some cases, this sequence of agents invocations purely calculated following the optimal path on that dependency graph, could represent a limitation. For example, a shortest path by definition doesn't contain loops, but sometimes a loop can be necessary to force an agent to reflect on its own work and iteratively refine its outcome. |
| 103 | + |
| 104 | +As anticipated, the LangChain4j agentic framework allows to overcome this limitation making it possible to seamlessly combine this goal oriented agentic system with any other agentic pattern either provided by the framework or custom made. For instance the final agent generating the writeup could be complemented with a reflection loop, using another agent that provides a score for the generated writeup. |
| 105 | + |
| 106 | +[source,java] |
| 107 | +---- |
| 108 | +public interface Writer { |
| 109 | + @UserMessage(""" |
| 110 | + Create an amusing writeup for {{person}} based on the following: |
| 111 | + - their horoscope: {{horoscope}} |
| 112 | + - a current news story: {{story}} |
| 113 | + """) |
| 114 | + @Agent(""" |
| 115 | + Create an amusing writeup for the target person based |
| 116 | + on their horoscope and current news stories |
| 117 | + """) |
| 118 | + String write(@V("person") Person person, |
| 119 | + @V("horoscope") String horoscope, |
| 120 | + @V("story") String story); |
| 121 | +} |
| 122 | +
|
| 123 | +public interface WriteupScorer { |
| 124 | +
|
| 125 | + @UserMessage(""" |
| 126 | + You are a critical reviewer. Give a review score between 0.0 and 1.0 |
| 127 | + for the following writeup |
| 128 | + based on how well it aligns with the spirit of the given zodiac sign. |
| 129 | + Return only the score and nothing else. |
| 130 | +
|
| 131 | + The writeup is: "{{writeup}}" |
| 132 | + The sign is: "{{sign}}" |
| 133 | + """) |
| 134 | + @Agent("Scores a story based on how well it aligns with a given style") |
| 135 | + double scoreWriteup(@V("writeup") String writeup, @V("sign") Sign sign); |
| 136 | +} |
| 137 | +
|
| 138 | +public interface WriteupAndReviewLoop { |
| 139 | + @Agent |
| 140 | + String write(@V("person") Person person, |
| 141 | + @V("horoscope") String horoscope, |
| 142 | + @V("story") String story); |
| 143 | +} |
| 144 | +---- |
| 145 | + |
| 146 | +In this way, the single `Writer` agent can be replaced with this loop, so that the final agentic system could overcome one of the typical limitation of the goal oriented strategy and be defined as follows: |
| 147 | + |
| 148 | +[source,java] |
| 149 | +---- |
| 150 | +Writer writer = AgenticServices.agentBuilder(Writer.class) |
| 151 | + .chatModel(baseModel()) |
| 152 | + .outputKey("writeup") |
| 153 | + .build(); |
| 154 | +
|
| 155 | +WriteupScorer scorer = AgenticServices.agentBuilder(WriteupScorer.class) |
| 156 | + .chatModel(baseModel()) |
| 157 | + .outputKey("score") |
| 158 | + .build(); |
| 159 | +
|
| 160 | +WriteupAndReviewLoop writeAndReviewLoop = AgenticServices |
| 161 | + .loopBuilder(WriteupAndReviewLoop.class) |
| 162 | + .subAgents(writer, scorer) |
| 163 | + .outputKey("writeup") |
| 164 | + .exitCondition( agenticScope -> agenticScope.readState("score", 0.0) >= 0.8) |
| 165 | + .maxIterations(5) |
| 166 | + .build(); |
| 167 | +
|
| 168 | +UntypedAgent horoscopeAgent = AgenticServices.plannerBuilder() |
| 169 | + .subAgents(horoscopeGenerator, personExtractor, |
| 170 | + signExtractor, writeAndReviewLoop, storyFinder) |
| 171 | + .outputKey("writeup") |
| 172 | + .planner(GoalOrientedPlanner::new) |
| 173 | + .build(); |
| 174 | +---- |
| 175 | + |
| 176 | +== Conclusions |
| 177 | + |
| 178 | +An agentic system made of multiple small but specialized agents can often outperform a single AI services based on a huge LLM at a fraction of the cost. The use of many interoperating agents poses the challenge of coordinating them effectively to achieve complex tasks. Different agentic patterns offer various trade-offs between reliability and flexibility, and no single pattern is suitable for all scenarios. Therefore, a customizable architecture that allows users to define and combine different agentic patterns is essential. |
| 179 | + |
| 180 | +By implementing a generic `Planner` interface, LangChain4j enables users to create their own agentic patterns and seamlessly integrate them with existing ones. This flexibility allows for the development of sophisticated agentic systems tailored to specific needs, balancing the strengths of various orchestration strategies. |
| 181 | + |
| 182 | +This also opens the door to users to help enriching the LangChain4j agentic ecosystem, contributing with their own agentic patterns implementations of the `Planner` interface. To this purpose, a https://github.com/langchain4j/langchain4j/tree/main/langchain4j-agentic-patterns[new dedicated module], named `langchain4j-agentic-patterns`, has been added to the LangChain4j project, with the goal of collecting and maintaining a growing set of useful agentic patterns that can be used as building blocks to create complex agentic systems. |
0 commit comments