Skip to content

Commit d9f5bcf

Browse files
authored
Merge pull request #2241 from mariofusco/agentic2
Blog post on agentic AI with Quarkus - part 2
2 parents bc02cb3 + 684182f commit d9f5bcf

File tree

4 files changed

+393
-0
lines changed

4 files changed

+393
-0
lines changed
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
---
2+
layout: post
3+
title: 'Agentic AI with Quarkus - part 2'
4+
date: 2025-02-20T00:00:00Z
5+
tags: ai llm agents
6+
synopsis: 'Agentic AI with Quarkus - part 2'
7+
author: mariofusco
8+
---
9+
:imagesdir: /assets/images/posts/agentic
10+
11+
The https://quarkus.io/blog/agentic-ai-with-quarkus/[first part] of this blog post series briefly introduced agentic AI and discussed workflow patterns.
12+
This post will explore another kind of pattern: _agents_.
13+
The main difference between the two is that workflow patterns are defined programmatically, while agents are more flexible and can handle a broader range of tasks.
14+
With agents, the LLM orchestrates the sequence of steps instead of being externally orchestrated programmatically, thus reaching a higher level of autonomy and flexibility.
15+
16+
== Agents
17+
18+
Agents differ from the workflow patterns because the control flow is entirely delegated to LLMs instead of being implemented programmatically.
19+
To successfully implement agents, the LLM must be able to reason and have access to a set of tools (_toolbox_).
20+
The LLM orchestrates the sequence of steps and decides which tools to call with which parameters.
21+
From an external point of view, invoking an agent can be seen as invoking a function that opportunistically invokes tools to complete determinate subtasks.
22+
23+
The agent's toolbox can be composed of:
24+
25+
- External services (like HTTP endpoints)
26+
- Other LLM / agents
27+
- Methods providing data from a data store
28+
- Methods provided by the application code itself
29+
30+
[.text-center]
31+
.Agents can invoke tools
32+
image::agent.png[width=50%, align="center", alt="Agents can invoke tools"]
33+
34+
In Quarkus, agents are represented by interfaces annotated with `@RegisterAiService.`
35+
They are called _AI services_.
36+
In that aspect, they are not different from the _workflow patterns_.
37+
The main difference is that the methods of an agent interface are annotated with `@ToolBox` to declare the tools that the LLM can use to complete the task:
38+
39+
[source,java]
40+
----
41+
@RegisterAiService(modelName = "my-model") // <-- The model used by this agent must be able to reason and decide which tools to call
42+
@SystemMessage("...")
43+
public interface RestaurantAgent {
44+
45+
@UserMessage("...")
46+
@ToolBox({BookingService.class, WeatherService.class}) // <-- The tools that the LLM can use
47+
String handleRequest(String request);
48+
}
49+
----
50+
51+
Alternatively, the `@RegisterAiService` annotation can receive the set of tools in its `tools` parameter.
52+
53+
Let's look at some examples of agents to understand better how they work and what they can achieve.
54+
55+
=== The weather forecast agent
56+
57+
This https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool[first example] of agentic AI implements the following https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool/WeatherForecastAgent.java[weather forecast agent].
58+
The agent receives a user prompt and must answer questions about the weather using at most three lines.
59+
To achieve this goal, the agent has a toolbox containing:
60+
61+
1. An AI service specialized in extracting the name of a city from the user's prompt - which can be another _agent_;
62+
2. A web service returning the geographic coordinates of a given city - this is a remote call;
63+
3. A second web service providing the weather forecast for the given latitude and longitude - another remote call.
64+
65+
[.text-center]
66+
.Weather agent architecture
67+
image::weather-agent.png[width=80%, align="center", alt="Weather agent architecture"]
68+
69+
We do not indicate when and how these tools are used; we just add them to the toolbox.
70+
The LLM decides when to call them and with which parameters.
71+
72+
[source,java]
73+
----
74+
@RegisterAiService(modelName = "tool-use")
75+
public interface WeatherForecastAgent {
76+
77+
@SystemMessage("""
78+
You are a meteorologist, and you need to answer questions asked by the user about weather using at most 3 lines.
79+
80+
The weather information is a JSON object and has the following fields:
81+
82+
maxTemperature is the maximum temperature of the day in Celsius degrees
83+
minTemperature is the minimum temperature of the day in Celsius degrees
84+
precipitation is the amount of water in mm
85+
windSpeed is the speed of wind in kilometers per hour
86+
weather is the overall weather.
87+
""")
88+
@ToolBox({CityExtractorAgent.class, WeatherForecastService.class, GeoCodingService.class})
89+
String chat(String query);
90+
}
91+
----
92+
93+
Let's see how are defined the tools used by this agent:
94+
95+
1 . https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool/CityExtractorAgent.java[Another AI Service] specialized in extracting the name of a city from the user's prompt (thus also demonstrating how easily an _agent_ can be configured to become a tool for another AI service/agent).
96+
97+
[source,java]
98+
----
99+
@ApplicationScoped
100+
@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class)
101+
public interface CityExtractorAgent {
102+
103+
@UserMessage("""
104+
You are given one question and you have to extract city name from it
105+
Only reply the city name if it exists or reply 'unknown_city' if there is no city name in question
106+
107+
Here is the question: {question}
108+
""")
109+
@Tool("Extracts the city from a question") // <-- The tool description, the LLM can use it to decide when to call this tool
110+
String extractCity(String question); // <-- The method signature, the LLM use it to know how to call this tool
111+
}
112+
----
113+
114+
2 . A https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool/geo/GeoCodingService.java[web service] returning the geographic coordinates of a given city.
115+
It's a simple Quarkus REST client interface, meaning that Quarkus automatically generates the actual implementation.
116+
It can be combined with fault tolerance, metrics, and other Quarkus features.
117+
118+
[source,java]
119+
----
120+
@RegisterRestClient(configKey = "geocoding")
121+
@Path("/v1")
122+
public interface GeoCodingService {
123+
124+
@GET
125+
@Path("/search")
126+
@ClientQueryParam(name = "count", value = "1") // Limit the number of results to 1 (HTTP query parameter)
127+
@Tool("Finds the latitude and longitude of a given city")
128+
GeoResults findCity(@RestQuery String name);
129+
}
130+
----
131+
132+
3 . Another https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool/weather/WeatherForecastService.java[web service] providing the weather forecast for the given latitude and longitude.
133+
134+
[source,java]
135+
----
136+
@RegisterRestClient(configKey = "openmeteo")
137+
@Path("/v1")
138+
public interface WeatherForecastService {
139+
140+
@GET
141+
@Path("/forecast")
142+
@ClientQueryParam(name = "forecast_days", value = "7")
143+
@ClientQueryParam(name = "daily", value = {
144+
"temperature_2m_max",
145+
"temperature_2m_min",
146+
"precipitation_sum",
147+
"wind_speed_10m_max",
148+
"weather_code"
149+
})
150+
@Tool("Forecasts the weather for the given latitude and longitude")
151+
WeatherForecast forecast(@RestQuery double latitude, @RestQuery double longitude);
152+
}
153+
----
154+
155+
It's possible to invoke the https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/aiastool/WeatherResource.java[HTTP endpoint] exposing this agent-based weather service:
156+
157+
[source,shell]
158+
----
159+
curl http://localhost:8080/weather/city/Rome
160+
----
161+
162+
The response will be something like:
163+
164+
----
165+
The weather in Rome today will have a maximum temperature of 14.3°C, minimum temperature of 2.0°C.
166+
No precipitation expected, and the wind speed will be up to 5.6 km/h.
167+
The overall weather condition is expected to be cloudy.
168+
----
169+
170+
In essence, this control flow is quite similar to the prompt chaining workflow (introduced in the https://quarkus.io/blog/agentic-ai-with-quarkus/#prompt-chaining[previous post]), where the user input is sequentially transformed in steps (in this case, going from the prompt to the name of the city contained in that prompt to the geographical coordinates of that city, to the weather forecasts at those coordinates).
171+
The significant difference is that the LLM directly orchestrates the sequence of steps instead of being externally orchestrated programmatically.
172+
173+
The observability automatically provided by Quarkus (in the GitHub project, observability is disabled by default, but it can be turned on with the `-Dobservability` flag) allows one to visually trace the sequence of tasks accomplished by the agent in order to execute its task.
174+
175+
[.text-center]
176+
.Tracing weather agent execution
177+
image::weather-trace.png[align=center,alt="Tracing sequential execution of the prompt chaining pattern"]
178+
179+
=== A more general-purpose AI agent
180+
181+
In the previous example, the agent has access to very specific tools.
182+
It's possible to provide more general tools that help the agent perform a wider range of tasks.
183+
Typically, a web search tool can be handy for information retrieval tasks.
184+
That's the purpose of https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/searchastool[this second example].
185+
It extends the agent's capabilities by allowing the LLM to search online for information not part of its original training set.
186+
187+
In general, these scenarios require a bigger model, so this example has been configured to use `qwen2.5-14b` and a longer timeout to give it a chance to complete its task:
188+
189+
[source,properties]
190+
----
191+
quarkus.langchain4j.ollama.big-model.chat-model.model-id=qwen2.5:14b
192+
quarkus.langchain4j.ollama.big-model.chat-model.temperature=0
193+
quarkus.langchain4j.ollama.big-model.timeout=600s
194+
----
195+
196+
The https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/searchastool/IntelligentAgent.java[intelligent agent] of this example can be configured to use this bigger model passing its name to the `@RegisterAiService` annotation.
197+
198+
[source,java]
199+
----
200+
@RegisterAiService(modelName = "big-model")
201+
public interface IntelligentAgent {
202+
203+
@SystemMessage("""
204+
You are a chatbot, and you need to answer questions asked by the user.
205+
Perform a web search for information that you don't know and use the result to answer to the initial user's question.
206+
""")
207+
@ToolBox({WebSearchService.class}) // <-- the web search tool
208+
String chat(String question);
209+
}
210+
----
211+
212+
The https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/searchastool/WebSearchService.java[tool] can perform a web search on _DuckDuckGo_ and returns the result in plain text:
213+
214+
[source,java]
215+
----
216+
@ApplicationScoped
217+
public class WebSearchService {
218+
219+
@Tool("Perform a web search to retrieve information online")
220+
String webSearch(String q) throws IOException {
221+
String webUrl = "https://html.duckduckgo.com/html/?q=" + q;
222+
return Jsoup.connect(webUrl).get().text();
223+
}
224+
}
225+
----
226+
227+
It is possible to use more advanced search engines or APIs, like https://docs.quarkiverse.io/quarkus-langchain4j/dev/web-search.html[Tavily].
228+
229+
The AI service uses this tool to retrieve online information for everything it ignores and arrange that data together to provide an answer to a generic user question.
230+
231+
For instance, consider the following question: _How many seconds would it take for a leopard at full speed to run through the Pont des Arts?_
232+
Using this https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/searchastool/AgenticChatbotResource.java[HTTP endpoint], it would be executed using:
233+
234+
[source,shell]
235+
----
236+
curl http://localhost:8080/ask/how%20many%20seconds%20would%20it%20take%20for%20a%20leopard%20at%20full%20speed%20to%20run%20through%20Pont%20des%20Arts
237+
----
238+
239+
To reply to this question, the agent invokes the web search tool twice: once to find the length of Pont des Arts and once to retrieve a leopard's speed.
240+
241+
[.text-center]
242+
.An agent using an external web search tool
243+
image::ai-agent.png[width=80%, align="center", alt="An agent using an external web search tool"]
244+
245+
Then, the agent puts this information together and generates an output like:
246+
247+
----
248+
The length of Pont des Arts is approximately 155 meters. A leopard can run at speeds up to about 58 kilometers per hour (36 miles per hour). To calculate how many seconds it would take for a leopard running at full speed to cross the bridge, we need to convert its speed into meters per second and then divide the length of the bridge by this speed.
249+
250+
1 kilometer = 1000 meters
251+
58 kilometers/hour = 58 * 1000 / 3600 ≈ 16.11 meters/second
252+
253+
Now, we can calculate the time it would take for a leopard to run through Pont des Arts:
254+
255+
Time (seconds) = Distance (meters) / Speed (m/s)
256+
= 155 / 16.11
257+
≈ 9.62 seconds
258+
259+
So, it would take approximately 9.62 seconds for a leopard running at full speed to run through Pont des Arts.
260+
----
261+
262+
This example illustrates how an agent can use _tools_ to retrieve data.
263+
While we use a search engine here, you can easily implement a tool that queries a database or another service to retrieve the needed information.
264+
You can check https://github.com/quarkusio/quarkus-workshop-langchain4j/blob/main/step-08/src/main/java/dev/langchain4j/quarkus/workshop/BookingRepository.java[this example] to see how to implement a tool that queries a database using a Quarkus Panache repository.
265+
266+
=== Agents and Conversational AI
267+
268+
The flexibility of AI agents can become even more relevant when used in services that are not intended to fulfill a single request but need to have a more extended conversation with the user to achieve their goal.
269+
For instance, agents can function as chatbots, enabling them to handle multiple users in parallel, each with independent conversations.
270+
It requires managing the state of each conversation, often referred to as memories (the set of messages already exchanged with the LLM).
271+
272+
A https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/restaurant/RestaurantAgent.java[chatbot of a restaurant booking system], designed to chat with customers and collect their data and requirements, represents an interesting practical application of this pattern.
273+
274+
[source,java]
275+
----
276+
@RegisterAiService(modelName = "tool-use")
277+
@SystemMessage("""
278+
You are an AI dealing with the booking for a restaurant.
279+
Do not invent the customer name or party size, but explicitly ask for them if not provided.
280+
If the user specifies a preference (indoor/outdoor), you should book the table with the preference. However, please check the weather forecast before booking the table.
281+
""")
282+
@SessionScoped
283+
public interface RestaurantAgent {
284+
285+
@UserMessage("""
286+
You receive request from customer and need to book their table in the restaurant.
287+
Please be polite and try to handle the user request.
288+
289+
Before booking the table, makes sure to have valid date for the reservation, and that the user explicitly provided his name and party size.
290+
If the booking is successful just notify the user.
291+
292+
Today is: {current_date}.
293+
Request: {request}
294+
""")
295+
@ToolBox({BookingService.class, WeatherService.class})
296+
String handleRequest(String request);
297+
}
298+
----
299+
300+
[TIP]
301+
====
302+
Note that the user message conveys not only the customer's request but also includes the current date.
303+
This allows the LLM to understand relative dates, such as "tomorrow" or "in three days," which are often used by humans.
304+
Initially, we included the current date in the system message, but doing so often caused the LLM to forget it and hallucinate using a different date.
305+
Moving it to the user message empirically proved to work much better, possibly because this way, it is passed not only once but in every message in the chat memory.
306+
====
307+
308+
When the agent completes that information-gathering process, the chatbot uses a https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/restaurant/booking/BookingService.java[tool accessing the database] of existing reservations to both check if there is still a table available for the customer's needs and to book that table if so.
309+
310+
[source,java]
311+
----
312+
@ApplicationScoped
313+
public class BookingService {
314+
315+
private final int capacity;
316+
317+
public BookingService(@ConfigProperty(name = "restaurant.capacity") int capacity) {
318+
this.capacity = capacity;
319+
}
320+
321+
public boolean hasCapacity(LocalDate date, int partySize) {
322+
int sum = Booking.find("date", date).list().stream().map(b -> (Booking) b)
323+
.mapToInt(b -> b.partySize)
324+
.sum();
325+
return sum + partySize <= capacity;
326+
}
327+
328+
@Transactional
329+
@Tool("Books a table for a given name, date (passed as day of the month, month and year), party size and preference (indoor/outdoor). If the restaurant is full, an exception is thrown. If preference is not specified, `UNSET` is used.")
330+
public String book(String name, int day, int month, int year, int partySize, Booking.Preference preference) {
331+
var date = LocalDate.of(year, month, day);
332+
if (hasCapacity(date, partySize)) {
333+
Booking booking = new Booking();
334+
booking.date = date;
335+
booking.name = name;
336+
booking.partySize = partySize;
337+
if (preference == null) {
338+
preference = Booking.Preference.UNSET;
339+
}
340+
booking.preference = preference;
341+
booking.persist();
342+
String result = String.format("%s successfully booked a %s table for %d persons on %s", name, preference, partySize, date);
343+
Log.info(result);
344+
return result;
345+
}
346+
return "The restaurant is full for that day";
347+
}
348+
}
349+
----
350+
351+
To assist the customer in deciding whether to eat outside, the agent can also reuse, as a second tool, the https://github.com/mariofusco/quarkus-agentic-ai/blob/main/src/main/java/org/agenticai/restaurant/weather/WeatherService.java[weather forecast service] implemented in one of the former examples, passing to it the geographic coordinates of the restaurant.
352+
353+
[source,properties]
354+
----
355+
restaurant.location.latitude=45
356+
restaurant.location.longitude=5
357+
----
358+
359+
The final architectural design of the chatbot is the following:
360+
361+
[.text-center]
362+
.The restaurant chatbot agent
363+
image::restaurant-agent.png[width=80%, align="center", alt="The restaurant chatbot agent"]
364+
365+
Once the customer provides all necessary details, the chatbot confirms the booking and presents a reservation summary.
366+
The final booking is then stored in the database.
367+
It is possible to give this a try by accessing the URL:
368+
369+
http://localhost:8080/restaurant.html
370+
371+
A typical example of user interaction could be something like this:
372+
373+
[.text-center]
374+
.An example of interaction with the restaurant chatbot agent
375+
image::restaurant.png[width=80%, align="center", alt="An example of interaction with the restaurant chatbot agent"]
376+
377+
== Conclusions and next steps
378+
379+
This blog post series illustrates how you can use agentic patterns to implement AI-infused applications with Quarkus and its LangChain4j extension.
380+
381+
We have covered _workflow patterns in a previous post_ and _agents in this post_.
382+
Both sets of patterns are based on the same underlying principles but differ in how the control flow is managed.
383+
The workflow patterns are more suitable for tasks that can be easily defined programmatically, while agents are more flexible and can handle a broader range of tasks.
384+
385+
Nevertheless, the examples discussed in this series can be improved and further generalized with other techniques that will be introduced in future works, such as:
386+
387+
- Memory management across LLM calls
388+
- State management for long-running processes
389+
- Improved observability
390+
- Dynamic tools and tool discovery
391+
- The relation with the MCP protocol and how agentic architecture can be implemented with MCP clients and servers
392+
- How can the RAG pattern be revisited in light of the agentic architecture, both with workflow patterns and agents?
393+
42.3 KB
Loading
2.42 KB
Loading
98.3 KB
Loading

0 commit comments

Comments
 (0)