Skip to content

Commit 8f0616e

Browse files
author
jbossorg-bot
committedMar 11, 2025·
Published latest aggregated blog posts
1 parent 4d01942 commit 8f0616e

File tree

5 files changed

+26
-26
lines changed

5 files changed

+26
-26
lines changed
 

‎src/content/posts-aggregator/16.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "Agentic AI with Quarkus - part 1",
3-
"link": "https://quarkus.io/blog/agentic-ai-with-quarkus/",
2+
"title": "The Quarkus Edge: How Real Customers Achieve Speed, Performance, and Agility",
3+
"link": "https://quarkus.io/blog/idc-customer-case-studies/",
44
"author": [
55
{
6-
"name": "Mario Fusco",
6+
"name": "Jeff Beck",
77
"avatar": null
88
}
99
],
10-
"date": "2025-02-18T00:00:00.000Z",
10+
"date": "2025-02-19T00:00:00.000Z",
1111
"feed_title": "Quarkus",
12-
"content": "Although there is no universally agreed definition of an AI agent, several emerging patterns demonstrate how to coordinate and combine the capabilities of multiple AI services to create AI-infused applications that can accomplish more complex tasks. According to a , these Agentic System architectures can be grouped into two main categories: 1. workflows: LLMs and tools are orchestrated through predefined code paths, 2. agents: LLMs dynamically direct their processes and tool usage, maintaining control over how they execute tasks. The goal of this series of articles is to discuss the most common workflow and agentic AI patterns and architectures, with the practical aid of that demonstrates for each of them an example of how they can be implemented through Quarkus and its LangChain4j integration. Of course, a real-world application may use and combine these patterns in multiple ways to implement a complex behavior. This first article focuses on the workflow patterns. A second article will cover the agent patterns. All the demos in that project run the LLMs inference locally through an server. In particular the demos in the workflow section use a llama3.2 model, while the ones relative to the pure agents one employ qwen2.5 since this last model empirically demonstrated of working better when multiple tool callings are required. WORKFLOW PATTERNS AI workflows are patterns in which different LLM-based services (AI services in the Quarkus vocabulary) are coordinated programmatically in a predetermined manner. This article introduces three base patterns, namely: * * * PROMPT CHAINING Prompt chaining is, without a doubt, the simplest yet powerful and effective technique in agentic AI workflows. In this technique, the output of one prompt (the response from an LLM) becomes the input of the next, enabling complex, multi-step reasoning or task execution. It is ideal for situations with a straightforward way to decompose a complex task into smaller and better-delimited steps, thus reducing the possibility of hallucinations or other LLMs misbehaving. Understanding that each coordinated call to LLM may rely on different models and system messages is essential. Thus, each step can be implemented using a more specialized model and system message. A typical use case for applying this technique is content creation, like advertising or novel writing. For instance, this leverages this pattern to implement a creative writing and editing workflow, where the is the following: @RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class) public interface CreativeWriter { @UserMessage(\"\"\" You are a creative writer. Generate a draft of a short novel around the given topic. Return only the novel and nothing else. The topic is {topic}. \"\"\") String generateNovel(String topic); } It generates a story draft on a topic provided by the user. In contrast, two more services, implemented very similarly to this one, subsequently modify the outcome of the first one. In particular, a rewrites the draft to make it more coherent with a determined style, while a executes a final edit to make it a good fit for the required audience. It is also worth to be noted that all these three AI services are intended to be used through one-shot calls in a completely stateless way, so they are configured to not have any . Regardless of this configuration, each AI service has its own chat memory, confined to the single service, and this is why it is necessary to explicitly pass to each of them the output produced by the former LLM in the chain. Figure 1. Prompt chaining pattern In this case, it is pretty straightforward to expose this service through a that invokes these AI services one after the other, making the editors rewrite or refine the content produced by the first creative writer: // The createWriter, styleEditor, and audienceEditor fields are AI services injected by Quarkus, see full code for details @GET @Path(\"topic/{topic}/style/{style}/audience/{audience}\") public String hello(String topic, String style, String audience) { // Call the first AI service: String novel = creativeWriter.generateNovel(topic); // Pass the outcome from the first call to the second AI service: novel = styleEditor.editNovel(novel, style); // Pass the outcome from the second call to the third AI service: novel = audienceEditor.editNovel(novel, audience); // Return the final result: return novel; } The HTTP endpoint allows us to define a topic, style, and audience of the novel to be produced; so, for example, running the project locally, it would be possible to obtain a drama about dogs having kids as the target audience by calling this URL: curl http://localhost:8080/write/topic/dogs/style/drama/audience/kids As an example, it generates a result like . Since this project integrates the observability capabilities provided by Quarkus out-of-the-box, it is also possible to look at the tracing of the flow of invocations performed to fulfill this request, which, of course, puts in evidence of the sequential nature of this pattern. Figure 2. Tracing sequential execution of the prompt chaining pattern PARALLELIZATION This second pattern also orchestrates multiple calls to LLMs. However, unlike the prompt chaining pattern, the calls are independent and do not require the output of one call to be used as the input of another. In these situations, those calls can be performed in parallel, followed by an aggregator that combines their outcomes. To demonstrate how this works, let’s consider this . This code recommends a plan for a lovely evening with a specific mood, combining a movie and a meal that matches that mood. The implements this goal by invoking two different AI services in parallel and then combining their outcome, putting together the three different suggestions of the two different LLM-based experts. import java.time.Duration;@GET @Path(\"mood/{mood}\") public List<EveningPlan> plan(String mood) { var movieSelection = Uni.createFrom().item(() -> movieExpert.findMovie(mood)).runSubscriptionOn(scheduler); var mealSelection = Uni.createFrom().item(() -> foodExpert.findMeal(mood)).runSubscriptionOn(scheduler); return Uni.combine().all() .unis(movieSelection, mealSelection) // This invokes the two LLMs in parallel .with((movies, meals) -> { // Both calls have completed, let's combine the results List<EveningPlan> moviesAndMeals = new ArrayList<>(); for (int i = 0; i < 3; i++) { moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i))); } return moviesAndMeals; }) .await().atMost(Duration.ofSeconds(60)); } The is an AI service asked to provide three titles of movies matching the given mood. @RegisterAiService 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. \"\"\") List<String> findMovie(String mood); } The , with a very similar implementation is asked to provide three meals. When these LLM calls are complete, the results (3 lists of 3 items each) are aggregated to create a list of 3 fantastic evening plans with a suggested movie and meal each. Figure 3. The parallelization pattern For instance asking that endpoint to provide evening plans for a romantic mood: curl http://localhost:8080/evening/mood/romantic The outcome is something like: [ EveningPlan[movie=1. The Notebook, meal=1. Candlelit Chicken Piccata], EveningPlan[movie=2. La La Land, meal=2. Rose Petal Risotto], EveningPlan[movie=3. Crazy, Stupid, Love., meal=3. Sunset Seared Scallops] ] In this case, the tracing of the flow of invocations performed to fulfill this request shows, as expected, that the two LLM invocations are performed in parallel. Figure 4. Tracing parallel LLMs invocation ROUTING Another common situation is the need to direct tasks requiring specific handling to specialized models, tools, or processes based on determined criteria. In these cases, the routing workflow allows the dynamic allocation of tasks to the most suitable AI service. shows how this pattern can be applied in a simple scenario where a user asks a question that has to be redirected to a , or expert to be answered most accurately, where any of these experts are an AI service implemented for instance as follows: @RegisterAiService 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}. \"\"\") String chat(String request); } The categorization of the user’s request is performed by @RegisterAiService public interface CategoryRouter { @UserMessage(\"\"\" Analyze the following user request and categorize it as 'legal', 'medical' or 'technical'. Reply with only one of those words and nothing else. The user request is {request}. \"\"\") RequestCategory classify(String request); } that returns one of the possible categories of the user’s request, encoded in this enumeration: public enum RequestCategory { LEGAL, MEDICAL, TECHNICAL, UNKNOWN } Thus, the can send the question to the right expert. public UnaryOperator<String> findExpert(String request) { var category = RequestType.decode(categoryRouter.classify(request)); return switch (category) { case LEGAL -> legalExpert::chat; case MEDICAL -> medicalExpert::chat; case TECHNICAL -> technicalExpert::chat; default -> ignore -> \"I cannot find an appropriate category for this request.\"; }; } Figure 5. Routing pattern In this way, when the user calls the , exposing this service writing something like: \"I broke my leg what should I do\": curl http://localhost:8080/expert/request/I%20broke%20my%20leg%20what%20should%20I%20do The first LLM categorizes this request as a medical one, and the router forwards it to the medical expert LLM, thus generating a result like . CONCLUSION This article demonstrated how you can implement workflow patterns with Quarkus Langchain4J. Quarkus Langchain4J provides a powerful and flexible way to implement these patterns, allowing you to orchestrate multiple AI services in a way that is both efficient and easy to understand. The next article will cover the agent patterns. So, stay tuned!"
12+
"content": "If you’re part of the Quarkus community, you already know it’s fast, lightweight, and designed for cloud-native workloads. But have you ever wondered how other organizations are using Quarkus in production? A new IDC whitepaper, The Quarkus Edge: How Real Customers Achieve Speed, Performance, and Agility, showcases how three organizations have transformed their developer productivity, infrastructure efficiency, and business agility with Quarkus. These case studies highlight why organizations are choosing Quarkus and how it’s driving measurable results in industries like telecommunications, transportation, and banking. If you’re looking for compelling, real-world stories to share with your peers, leadership, or decision-makers, this report is exactly what you need. Let’s dive into the highlights. WHY ORGANIZATIONS ARE CHOOSING QUARKUS The report highlights several common drivers behind Quarkus adoption: ✅ Massive Performance Gains: up to 10x CPU cost reduction and 85% faster start-up times. ✅ Cloud-Native & Kubernetes-Ready: built for containerization and event-driven architectures. ✅ Developer Productivity Boosts: live coding, lower memory footprint, and seamless integration w/ DevOps workflows. ✅ Lower Costs: significant reduction in cloud infrastructure spend, especially for high-throughput, low-latency applications. CASE STUDY #1 (TELCO): POWERING EDGE & IOT WITH QUARKUS A major telecommunications company needed to meet 5G and edge computing demands. After benchmarking against alternatives like Spring WebFlux and Vert.x, they selected Quarkus for its CPU efficiency and latency performance. 🔹 10x reduction in CPU costs, crucial for high-throughput workloads. 🔹 Lower latency, enabling real-time IoT and edge applications. 🔹 Seamless integration with Kafka, RabbitMQ, and event-driven architectures. CASE STUDY #2 (TRANSPORTATION): FASTER EVENT-DRIVEN SERVICES A global transportation services provider turned to Quarkus for its event-driven architecture (EDA). The result was a dramatic reduction in infrastructure footprint and start-up times, helping them move toward a cloud-native future. 🔹 Start-up time reduced from 5-10 minutes to seconds. 🔹 Significantly lower memory and CPU usage, cutting operational costs. 🔹 Seamless event-driven integration with Kafka and reactive messaging. CASE STUDY #3 (BANKING): MODERNIZING FINANCIAL SERVICES A leading European bank began migrating its core banking platform to Quarkus, driven by support discontinuation for legacy systems. They needed a solution that was secure, scalable, and cost-effective. 🔹 85% faster start-up times, improving deployment and system resilience. 🔹 30% more applications hosted on existing infrastructure. 🔹 Increased developer productivity, reducing time-to-market for new financial products. USE THIS REPORT TO ADVOCATE FOR QUARKUS If you’re already using or evaluating Quarkus, this report is a powerful tool to help spread the word within your organization. 🔹 developers: share it with your peers to highlight how Quarkus improves developer experience and reduces friction in cloud-native development. 🔹 architects & decision-makers: use the case studies to showcase Quarkus’ proven success in real-world deployments. 🔹 executives: the whitepaper quantifies cost savings, performance gains, and business agility, making the case for standardizing on Quarkus. WANT TO SHARE YOUR STORY Tell us how you are using Quarkus by writing your own user story on Quarkus.io. "
1313
}

‎src/content/posts-aggregator/17.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "Using LangChain4j to analyze PDF documents",
3-
"link": "https://quarkus.io/blog/using-langchain4j-to-analyze-pdf-documents/",
2+
"title": "Agentic AI with Quarkus - part 1",
3+
"link": "https://quarkus.io/blog/agentic-ai-with-quarkus/",
44
"author": [
55
{
6-
"name": "Emil Lefkof",
6+
"name": "Mario Fusco",
77
"avatar": null
88
}
99
],
10-
"date": "2025-02-17T00:00:00.000Z",
10+
"date": "2025-02-18T00:00:00.000Z",
1111
"feed_title": "Quarkus",
12-
"content": "In my consulting work, clients frequently present us with challenging problems that require innovative solutions. Recently, we were tasked with extracting structured metadata from PDF documents through automated analysis. Below, I’ll share a simplified version of this real-world challenge and how we approached it. USE CASE Our client receives compressed archives (.zip files) containing up to hundreds of portable document format (PDF) lease documents that need review. Each document contains property lease details that must be validated for accuracy. The review process involves checking various business rules - for example, identifying leases with terms shorter than 2 years. Currently, this document validation is done manually, which is time-consuming. The client wants to automate and streamline this review workflow to improve efficiency. Some complications with these lease documents are: * The documents are not in a standard format so each lease may be written in a different way by a different property manager. * The documents may be scanned, so the text is sometimes human writing and not typewritten. * The documents may contain multiple pages, which are not always in the same order. * The lease terms may not be an actual date but written as \"Expires five years from the start date\" or \"Expires on the anniversary of the start date\". * Metadata such as acreage and tax parcel information is needed by our client to validate the lease details. You can understand why this is time consuming for a human to review and validate the documents. OUR SOLUTION After consulting with and collaborating with the Quarkus team, we implemented a solution using LangChain4j for document metadata extraction. We chose as our Large Language Model (LLM) since it excels at PDF analysis through its built-in Optical Character Recognition (OCR) capabilities, enabling accurate text extraction from both digital and scanned documents. TECHNICAL DETAILS The application is built using: * Quarkus - A Kubernetes-native Java framework * LangChain4j - Java bindings for LangChain to interact with LLMs * Google Gemini AI - For PDF document analysis and information extraction * Quarkus REST - For handling multipart file uploads * HTML/JavaScript frontend - Simple UI for file upload and results display The backend processes the PDF through these steps: 1. Accepts PDF upload via multipart form data 2. Converts PDF content to base64 encoding 3. Sends to Gemini AI with a structured JSON schema for response formatting 4. Returns parsed lease information in a standardized format 5. Displays results in a tabular format on the web interface The main components are: * LeaseAnalyzerResource - REST endpoint for PDF analysis * LeaseReport - Data structure for lease information * Web interface for file upload and results display HOW IT WORKS First we need a Google Gemini API key. You can get one for free, see more details here: . export QUARKUS_LANGCHAIN4J_AI_GEMINI_API_KEY=<your-google-ai-gemini-api-key> Next we need to install the LangChain4j dependencies: <dependency> <groupId>io.quarkiverse.langchain4j</groupId> <artifactId>quarkus-langchain4j-ai-gemini</artifactId> <version>0.25.0</version> </dependency> CONFIGURE GEMINI LLM Next we need to wire up the Gemini LLM to the application (using your Google AI Gemini API key). quarkus.langchain4j.ai.gemini.chat-model.model-id=gemini-2.0-flash quarkus.langchain4j.log-requests=true quarkus.langchain4j.log-responses=true Logging the request and response is optional but can be helpful for debugging. REGISTER THE AI SERVICE We must register the AI service to use the LeaseAnalyzer interface. import dev.langchain4j.data.pdf.PdfFile; import dev.langchain4j.service.UserMessage; import io.quarkiverse.langchain4j.PdfUrl; import io.quarkiverse.langchain4j.RegisterAiService; @RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class) public interface LeaseAnalyzer { @UserMessage(\"Analyze the given document\") LeaseReport analyze(@PdfUrl PdfFile pdfFile); } DEFINE YOUR DATA STRUCTURE Now we need to model the data structure for the lease information that we want the LLM to extract from any lease document. You can customize these fields based on the information you need from the PDF documents but in our use case below we are extracting the following information: public record LeaseReport( LocalDate agreementDate, LocalDate termStartDate, LocalDate termEndDate, LocalDate developmentTermEndDate, String landlordName, String tenantName, String taxParcelId, BigDecimal acres, Boolean exclusiveRights) { } CREATE THE REST ENDPOINT Lastly, we need to create a LeaseAnalyzerResource class that will use the LLM to extract the lease information from the PDF document. @Inject LeaseAnalyzer analyzer; @PUT @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public String upload(@RestForm(\"file\") FileUpload fileUploadRequest) { final String fileName = fileUploadRequest.fileName(); log.infof(\"Uploading file: %s\", fileName); try { // Convert input stream to byte array for processing byte[] fileBytes = Files.readAllBytes(fileUploadRequest.filePath()); // Encode PDF content to base64 for transmission String documentEncoded = Base64.getEncoder().encodeToString(fileBytes); log.info(\"Google Gemini analyzing....\"); long startTime = System.nanoTime(); LeaseReport result = analyzer.analyze(PdfFile.builder().base64Data(documentEncoded).build()); long endTime = System.nanoTime(); log.infof(\"Google Gemini analyzed in %.2f seconds: %s\", (endTime - startTime) / 1_000_000_000.0, result); return result; } catch (IOException e) { throw new RuntimeException(e); } } There is a simple HTML/JavaScript frontend that allows you to upload a PDF document and view the results. In the example below 3 different lease documents were uploaded and analyzed. Figure 1. Lease Analyzer Results You can find the complete example code on . CONCLUSION This article demonstrated how LangChain4j and AI can be leveraged to automatically extract structured metadata from PDF documents. By implementing this solution, our client will significantly reduce manual document processing time, potentially saving thousands of work hours annually. The combination of LangChain4j and Google Gemini AI proves to be a powerful approach for automating document analysis workflows."
12+
"content": "Although there is no universally agreed definition of an AI agent, several emerging patterns demonstrate how to coordinate and combine the capabilities of multiple AI services to create AI-infused applications that can accomplish more complex tasks. According to a , these Agentic System architectures can be grouped into two main categories: 1. workflows: LLMs and tools are orchestrated through predefined code paths, 2. agents: LLMs dynamically direct their processes and tool usage, maintaining control over how they execute tasks. The goal of this series of articles is to discuss the most common workflow and agentic AI patterns and architectures, with the practical aid of that demonstrates for each of them an example of how they can be implemented through Quarkus and its LangChain4j integration. Of course, a real-world application may use and combine these patterns in multiple ways to implement a complex behavior. This first article focuses on the workflow patterns. A second article will cover the agent patterns. All the demos in that project run the LLMs inference locally through an server. In particular the demos in the workflow section use a llama3.2 model, while the ones relative to the pure agents one employ qwen2.5 since this last model empirically demonstrated of working better when multiple tool callings are required. WORKFLOW PATTERNS AI workflows are patterns in which different LLM-based services (AI services in the Quarkus vocabulary) are coordinated programmatically in a predetermined manner. This article introduces three base patterns, namely: * * * PROMPT CHAINING Prompt chaining is, without a doubt, the simplest yet powerful and effective technique in agentic AI workflows. In this technique, the output of one prompt (the response from an LLM) becomes the input of the next, enabling complex, multi-step reasoning or task execution. It is ideal for situations with a straightforward way to decompose a complex task into smaller and better-delimited steps, thus reducing the possibility of hallucinations or other LLMs misbehaving. Understanding that each coordinated call to LLM may rely on different models and system messages is essential. Thus, each step can be implemented using a more specialized model and system message. A typical use case for applying this technique is content creation, like advertising or novel writing. For instance, this leverages this pattern to implement a creative writing and editing workflow, where the is the following: @RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class) public interface CreativeWriter { @UserMessage(\"\"\" You are a creative writer. Generate a draft of a short novel around the given topic. Return only the novel and nothing else. The topic is {topic}. \"\"\") String generateNovel(String topic); } It generates a story draft on a topic provided by the user. In contrast, two more services, implemented very similarly to this one, subsequently modify the outcome of the first one. In particular, a rewrites the draft to make it more coherent with a determined style, while a executes a final edit to make it a good fit for the required audience. It is also worth to be noted that all these three AI services are intended to be used through one-shot calls in a completely stateless way, so they are configured to not have any . Regardless of this configuration, each AI service has its own chat memory, confined to the single service, and this is why it is necessary to explicitly pass to each of them the output produced by the former LLM in the chain. Figure 1. Prompt chaining pattern In this case, it is pretty straightforward to expose this service through a that invokes these AI services one after the other, making the editors rewrite or refine the content produced by the first creative writer: // The createWriter, styleEditor, and audienceEditor fields are AI services injected by Quarkus, see full code for details @GET @Path(\"topic/{topic}/style/{style}/audience/{audience}\") public String hello(String topic, String style, String audience) { // Call the first AI service: String novel = creativeWriter.generateNovel(topic); // Pass the outcome from the first call to the second AI service: novel = styleEditor.editNovel(novel, style); // Pass the outcome from the second call to the third AI service: novel = audienceEditor.editNovel(novel, audience); // Return the final result: return novel; } The HTTP endpoint allows us to define a topic, style, and audience of the novel to be produced; so, for example, running the project locally, it would be possible to obtain a drama about dogs having kids as the target audience by calling this URL: curl http://localhost:8080/write/topic/dogs/style/drama/audience/kids As an example, it generates a result like . Since this project integrates the observability capabilities provided by Quarkus out-of-the-box, it is also possible to look at the tracing of the flow of invocations performed to fulfill this request, which, of course, puts in evidence of the sequential nature of this pattern. Figure 2. Tracing sequential execution of the prompt chaining pattern PARALLELIZATION This second pattern also orchestrates multiple calls to LLMs. However, unlike the prompt chaining pattern, the calls are independent and do not require the output of one call to be used as the input of another. In these situations, those calls can be performed in parallel, followed by an aggregator that combines their outcomes. To demonstrate how this works, let’s consider this . This code recommends a plan for a lovely evening with a specific mood, combining a movie and a meal that matches that mood. The implements this goal by invoking two different AI services in parallel and then combining their outcome, putting together the three different suggestions of the two different LLM-based experts. import java.time.Duration;@GET @Path(\"mood/{mood}\") public List<EveningPlan> plan(String mood) { var movieSelection = Uni.createFrom().item(() -> movieExpert.findMovie(mood)).runSubscriptionOn(scheduler); var mealSelection = Uni.createFrom().item(() -> foodExpert.findMeal(mood)).runSubscriptionOn(scheduler); return Uni.combine().all() .unis(movieSelection, mealSelection) // This invokes the two LLMs in parallel .with((movies, meals) -> { // Both calls have completed, let's combine the results List<EveningPlan> moviesAndMeals = new ArrayList<>(); for (int i = 0; i < 3; i++) { moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i))); } return moviesAndMeals; }) .await().atMost(Duration.ofSeconds(60)); } The is an AI service asked to provide three titles of movies matching the given mood. @RegisterAiService 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. \"\"\") List<String> findMovie(String mood); } The , with a very similar implementation is asked to provide three meals. When these LLM calls are complete, the results (3 lists of 3 items each) are aggregated to create a list of 3 fantastic evening plans with a suggested movie and meal each. Figure 3. The parallelization pattern For instance asking that endpoint to provide evening plans for a romantic mood: curl http://localhost:8080/evening/mood/romantic The outcome is something like: [ EveningPlan[movie=1. The Notebook, meal=1. Candlelit Chicken Piccata], EveningPlan[movie=2. La La Land, meal=2. Rose Petal Risotto], EveningPlan[movie=3. Crazy, Stupid, Love., meal=3. Sunset Seared Scallops] ] In this case, the tracing of the flow of invocations performed to fulfill this request shows, as expected, that the two LLM invocations are performed in parallel. Figure 4. Tracing parallel LLMs invocation ROUTING Another common situation is the need to direct tasks requiring specific handling to specialized models, tools, or processes based on determined criteria. In these cases, the routing workflow allows the dynamic allocation of tasks to the most suitable AI service. shows how this pattern can be applied in a simple scenario where a user asks a question that has to be redirected to a , or expert to be answered most accurately, where any of these experts are an AI service implemented for instance as follows: @RegisterAiService 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}. \"\"\") String chat(String request); } The categorization of the user’s request is performed by @RegisterAiService public interface CategoryRouter { @UserMessage(\"\"\" Analyze the following user request and categorize it as 'legal', 'medical' or 'technical'. Reply with only one of those words and nothing else. The user request is {request}. \"\"\") RequestCategory classify(String request); } that returns one of the possible categories of the user’s request, encoded in this enumeration: public enum RequestCategory { LEGAL, MEDICAL, TECHNICAL, UNKNOWN } Thus, the can send the question to the right expert. public UnaryOperator<String> findExpert(String request) { var category = RequestType.decode(categoryRouter.classify(request)); return switch (category) { case LEGAL -> legalExpert::chat; case MEDICAL -> medicalExpert::chat; case TECHNICAL -> technicalExpert::chat; default -> ignore -> \"I cannot find an appropriate category for this request.\"; }; } Figure 5. Routing pattern In this way, when the user calls the , exposing this service writing something like: \"I broke my leg what should I do\": curl http://localhost:8080/expert/request/I%20broke%20my%20leg%20what%20should%20I%20do The first LLM categorizes this request as a medical one, and the router forwards it to the medical expert LLM, thus generating a result like . CONCLUSION This article demonstrated how you can implement workflow patterns with Quarkus Langchain4J. Quarkus Langchain4J provides a powerful and flexible way to implement these patterns, allowing you to orchestrate multiple AI services in a way that is both efficient and easy to understand. The next article will cover the agent patterns. So, stay tuned!"
1313
}

‎src/content/posts-aggregator/18.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "resteasy-grpc: Handling Collections",
3-
"link": "https://resteasy.dev/2025/02/14/resteasy-grpc-collections/",
2+
"title": "Using LangChain4j to analyze PDF documents",
3+
"link": "https://quarkus.io/blog/using-langchain4j-to-analyze-pdf-documents/",
44
"author": [
55
{
6-
"name": null,
6+
"name": "Emil Lefkof",
77
"avatar": null
88
}
99
],
10-
"date": "2025-02-14T00:00:00.000Z",
11-
"feed_title": "RESTEasy",
12-
"content": ""
10+
"date": "2025-02-17T00:00:00.000Z",
11+
"feed_title": "Quarkus",
12+
"content": "In my consulting work, clients frequently present us with challenging problems that require innovative solutions. Recently, we were tasked with extracting structured metadata from PDF documents through automated analysis. Below, I’ll share a simplified version of this real-world challenge and how we approached it. USE CASE Our client receives compressed archives (.zip files) containing up to hundreds of portable document format (PDF) lease documents that need review. Each document contains property lease details that must be validated for accuracy. The review process involves checking various business rules - for example, identifying leases with terms shorter than 2 years. Currently, this document validation is done manually, which is time-consuming. The client wants to automate and streamline this review workflow to improve efficiency. Some complications with these lease documents are: * The documents are not in a standard format so each lease may be written in a different way by a different property manager. * The documents may be scanned, so the text is sometimes human writing and not typewritten. * The documents may contain multiple pages, which are not always in the same order. * The lease terms may not be an actual date but written as \"Expires five years from the start date\" or \"Expires on the anniversary of the start date\". * Metadata such as acreage and tax parcel information is needed by our client to validate the lease details. You can understand why this is time consuming for a human to review and validate the documents. OUR SOLUTION After consulting with and collaborating with the Quarkus team, we implemented a solution using LangChain4j for document metadata extraction. We chose as our Large Language Model (LLM) since it excels at PDF analysis through its built-in Optical Character Recognition (OCR) capabilities, enabling accurate text extraction from both digital and scanned documents. TECHNICAL DETAILS The application is built using: * Quarkus - A Kubernetes-native Java framework * LangChain4j - Java bindings for LangChain to interact with LLMs * Google Gemini AI - For PDF document analysis and information extraction * Quarkus REST - For handling multipart file uploads * HTML/JavaScript frontend - Simple UI for file upload and results display The backend processes the PDF through these steps: 1. Accepts PDF upload via multipart form data 2. Converts PDF content to base64 encoding 3. Sends to Gemini AI with a structured JSON schema for response formatting 4. Returns parsed lease information in a standardized format 5. Displays results in a tabular format on the web interface The main components are: * LeaseAnalyzerResource - REST endpoint for PDF analysis * LeaseReport - Data structure for lease information * Web interface for file upload and results display HOW IT WORKS First we need a Google Gemini API key. You can get one for free, see more details here: . export QUARKUS_LANGCHAIN4J_AI_GEMINI_API_KEY=<your-google-ai-gemini-api-key> Next we need to install the LangChain4j dependencies: <dependency> <groupId>io.quarkiverse.langchain4j</groupId> <artifactId>quarkus-langchain4j-ai-gemini</artifactId> <version>0.25.0</version> </dependency> CONFIGURE GEMINI LLM Next we need to wire up the Gemini LLM to the application (using your Google AI Gemini API key). quarkus.langchain4j.ai.gemini.chat-model.model-id=gemini-2.0-flash quarkus.langchain4j.log-requests=true quarkus.langchain4j.log-responses=true Logging the request and response is optional but can be helpful for debugging. REGISTER THE AI SERVICE We must register the AI service to use the LeaseAnalyzer interface. import dev.langchain4j.data.pdf.PdfFile; import dev.langchain4j.service.UserMessage; import io.quarkiverse.langchain4j.PdfUrl; import io.quarkiverse.langchain4j.RegisterAiService; @RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.NoChatMemoryProviderSupplier.class) public interface LeaseAnalyzer { @UserMessage(\"Analyze the given document\") LeaseReport analyze(@PdfUrl PdfFile pdfFile); } DEFINE YOUR DATA STRUCTURE Now we need to model the data structure for the lease information that we want the LLM to extract from any lease document. You can customize these fields based on the information you need from the PDF documents but in our use case below we are extracting the following information: public record LeaseReport( LocalDate agreementDate, LocalDate termStartDate, LocalDate termEndDate, LocalDate developmentTermEndDate, String landlordName, String tenantName, String taxParcelId, BigDecimal acres, Boolean exclusiveRights) { } CREATE THE REST ENDPOINT Lastly, we need to create a LeaseAnalyzerResource class that will use the LLM to extract the lease information from the PDF document. @Inject LeaseAnalyzer analyzer; @PUT @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public String upload(@RestForm(\"file\") FileUpload fileUploadRequest) { final String fileName = fileUploadRequest.fileName(); log.infof(\"Uploading file: %s\", fileName); try { // Convert input stream to byte array for processing byte[] fileBytes = Files.readAllBytes(fileUploadRequest.filePath()); // Encode PDF content to base64 for transmission String documentEncoded = Base64.getEncoder().encodeToString(fileBytes); log.info(\"Google Gemini analyzing....\"); long startTime = System.nanoTime(); LeaseReport result = analyzer.analyze(PdfFile.builder().base64Data(documentEncoded).build()); long endTime = System.nanoTime(); log.infof(\"Google Gemini analyzed in %.2f seconds: %s\", (endTime - startTime) / 1_000_000_000.0, result); return result; } catch (IOException e) { throw new RuntimeException(e); } } There is a simple HTML/JavaScript frontend that allows you to upload a PDF document and view the results. In the example below 3 different lease documents were uploaded and analyzed. Figure 1. Lease Analyzer Results You can find the complete example code on . CONCLUSION This article demonstrated how LangChain4j and AI can be leveraged to automatically extract structured metadata from PDF documents. By implementing this solution, our client will significantly reduce manual document processing time, potentially saving thousands of work hours annually. The combination of LangChain4j and Google Gemini AI proves to be a powerful approach for automating document analysis workflows."
1313
}

‎src/content/posts-aggregator/19.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "Eclipse Vert.x 5 candidate 5 released!",
3-
"link": "https://vertx.io/blog/eclipse-vert-x-5-candidate-5-released",
2+
"title": "resteasy-grpc: Handling Collections",
3+
"link": "https://resteasy.dev/2025/02/14/resteasy-grpc-collections/",
44
"author": [
55
{
6-
"name": "Julien Viet",
6+
"name": null,
77
"avatar": null
88
}
99
],
10-
"date": "2025-02-13T00:00:00.000Z",
11-
"feed_title": "Vert.x",
10+
"date": "2025-02-14T00:00:00.000Z",
11+
"feed_title": "RESTEasy",
1212
"content": ""
1313
}

‎src/content/posts-aggregator/20.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"title": "Vlog: Admin your WildFly with LLM and MCP",
3-
"link": "https://www.youtube.com/watch?v=wg1hAdOoe2w",
2+
"title": "Eclipse Vert.x 5 candidate 5 released!",
3+
"link": "https://vertx.io/blog/eclipse-vert-x-5-candidate-5-released",
44
"author": [
55
{
6-
"name": "smaesti",
6+
"name": "Julien Viet",
77
"avatar": null
88
}
99
],
1010
"date": "2025-02-13T00:00:00.000Z",
11-
"feed_title": "WildFly",
12-
"content": "This video demonstrates using an MCP server to administer a WildFly installation with AI."
11+
"feed_title": "Vert.x",
12+
"content": ""
1313
}

0 commit comments

Comments
 (0)
Please sign in to comment.