Intro Link to heading
A couple of weeks ago I came across Roberto Carratalla’s blog post on Function calling on OpenShift AI. At the time I was preparing for RedHat Summit Connect Zurich 2025 where I was meant to run a workshop on Quarkus and Langchain4j with Kyra Goud, Dimitris Andreadis and Codrin Bucur. We had an issue however, related to enabling functions on OpenShift AI.
Roberto pointed us to the blog post, but I couldn’t spot what I was doing wrong. So, I decided to port the examples in the blog post to Java to make sure that I was comparing apples to apples.
This post is a step by step guide on how to port the DuckDuckGo example from Python to Java with Quarkus and Langchain4j.
A video of me porting the example to Quarkus and Langchain4j can be found at:
Porting the DuckDuckGo example from Python to Java with Quarkus and Langchain4j.The original example Link to heading
The original example is written in Python and it’s pretty straightforward.
Create the chat model Link to heading
It creates and configures an instance of a chat-based language model.
|
|
Connect tools Link to heading
It then creates a tool that delegates to the duckduckgo search library.
|
|
Call the LLM Link to heading
Next stop is to actually prompt the user for a query and send it over to the LLM, letting it know what the available tools are.
|
|
Perform the actual calls Link to heading
Once we get the response from the LLM, we can perform the actual calls to the tools.
|
|
Pass the tool response back to the LLM Link to heading
Finally, we pass the tool response back to the LLM.
|
|
Porting the example to Quarkus and Langchain4j Link to heading
Create a client for DuckDuckGo Link to heading
The python example used the duckduckgo search tool from the langchain_community library. Not sure if there is a Java equivalent, I decided to create a client for DuckDuckGo using Rest Client Jackson. I’ll just need an `interface` that defines a search method that corresponds to an http get request to `/q={query}&format=json`.
The `@RegisterRestClient` annotation is used to register the client with the Quarkus runtime. The `configKey` attribute is used to specify the configuration key that will be used to configure the client. I can the set the URL of the DuckDuckGo API in the `application.properties` file, or alternatively pass it directly through the `@RegisterRestClient` annotation (via the `baseUri` attribute).
|
|
(Optional) Create a service that uses the client Link to heading
I will wrap this in a service that will be used by the Langchain4j tool. I am doing this just to have a place to add logging or other control logic if needed. I could have used the client directly in the tool.
|
|
Define the AI Service Link to heading
|
|
Call the Service from the CLI Link to heading
Last step is to create a CLI command for that reads the query from the user and calls the service.
|
|
Thoughts Link to heading
Imperative vs Declarative Link to heading
Despite the fact that Python is generally considered less verbose than Java, I find the Java version of the example to be more readable and easier to follow. Of course, this is a matter of personal preference, but I find the declarative nature of the Quarkus example to be more appealing.
The Quarkus Langchain4j focuses more on that `what` rather than the `how`.
The biggest selling point in my opinion is that it makes the integration of tool completely transparent to the user. In other words, the user doesn’t need to know how to deal with tool call requests, how to pass the response back to the LLM etc. All of this is handled by the Quarkus Langchain4j framework.
Rest Client with Langchain4j is a perfect match Link to heading
At first I went with the rest client out of necessity. When the example was complete, I relized that the result was almost magical. I didn’t have to implement client code for the DuckDuckGo API. I didn’t have to define domain objects for the response. I definitely didn’t have to parse the response. And finally, I didn’t have to worry how to pass the response back to the LLM.
All I needed to do was to define the `contract` for the client. Parsing the response, isolating the relevant parts and creating a human readable response was handled by LLM.
The best part is that the structure of the response can change at any time without breaking the implementation (provided of course that the path remains the same).
Conclusion Link to heading
Python is an amazing language for data science and machine learning. However, nowadays with the rise of the LLMs, crafting smart applications is easily accessible to other languages too. I often like to say that nowadays, building intelligent applications is an integration problem. Quarkus and Langchain4j make this integration problem a breeze, as they allow you to focus on the `what` rather than the `how`. Combined with the efficiency and performance characteristics of Quarkus, you get a winning combination.