Module 3: Building intelligent applications

In Module 1 you saw that the Parasol Insurance inbox uses simple keyword matching to classify emails. Ambiguous emails that the keywords cannot confidently classify are flagged as REVIEW REQUIRED, requiring manual triage by a claims administrator. In this module you will replace that keyword matching with a Large Language Model (LLM)-powered classification service that intelligently routes every email, promote it to production, and observe how OpenShift automatically scales the application under load.

OpenShift provides multiple autoscaling capabilities. The Horizontal Pod Autoscaler (HPA) scales workloads based on CPU or memory utilization, which you will use in this module. For event-driven or scale-to-zero workloads, OpenShift Serverless (based on Knative) offers more advanced scaling options, though it is not used in this lab.

Learning objectives

By the end of this module, you will be able to:

  • Integrate an LLM-powered classification service into an existing Quarkus application using Langchain4j

  • Explain how platform-managed credentials (LLM_BASE_URL, LLM_API_KEY) flow from Vault through ExternalSecrets into both Dev Spaces and deployed environments

  • Test LLM-powered features locally using Quarkus dev mode

  • Promote a feature to production using a tag-based GitOps workflow through GitLab

  • Inspect container images in Quay after a pipeline build

  • Verify a production deployment using Argo CD

  • Observe Horizontal Pod Autoscaler (HPA) behavior under load

Exercise 1: Add LLM classification to the application

To save time and reduce errors, the LLM classification code has been prepared on a branch in your repository. You will overlay it onto your current working branch, review the key changes, and test the new classification locally.

Overlay the LLM classification code

  1. Return to your Dev Spaces workspace (reopen it from the Topology view if needed)

  2. If Quarkus dev mode is still running, stop it by pressing Ctrl+C in the terminal where it is running

  3. Open a new terminal (top-left menu Terminal > New Terminal) and run the following command to overlay the LLM classification code:

    git checkout origin/llm-classification -- .

    This pulls the LLM classification files onto your current branch without switching branches. The changed files can be seen in the Source Control view on the left.

    Changes pulled in from the llm-classification branch
    Figure 1. Source Control showing the overlaid LLM classification files

Review the changes

Take a few minutes to review the key files that were added or modified:

  1. Open src/main/java/com/parasol/EmailClassifier.java

    This is the LLM classification service. Notice the @RegisterAiService annotation from Langchain4j and the @SystemMessage that defines the classification rules. The system message tells the LLM how to categorize each email, including what qualifies as a claim, a follow-up, a new customer inquiry, and so on.

  2. Open src/main/java/com/parasol/EmailRouter.java

    Previously this file contained the keyword matching logic. Now it delegates classification to the EmailClassifier. The router sends each email’s content to the LLM and uses the response to route the email to the appropriate category.

  3. Open src/main/resources/application.properties

    Look for the LLM configuration properties:

    %dev.quarkus.langchain4j.openai.base-url=${LLM_BASE_URL}
    %dev.quarkus.langchain4j.openai.api-key=${LLM_API_KEY}
    %dev.quarkus.langchain4j.openai.chat-model.model-name=qwen3-14b

    The %dev prefix means these properties apply when running in Quarkus dev mode. The LLM_BASE_URL, LLM_API_KEY, and qwen3-14b model are variables that the platform administrator configured specifically for adding agentic features to applications. These credentials are pre-loaded into your Dev Spaces workspace so that developers can test LLM-powered features locally without any manual setup.

    In the deployed environments (dev and prod namespaces), valid vLLM credentials are injected by the ExternalSecrets you saw in Module 1. The platform engineer stored the LLM endpoint and API key in Vault, and the ExternalSecrets Operator syncs them into your namespaces as Kubernetes Secrets. This means credentials flow seamlessly from local development in Dev Spaces to deployed workloads on the cluster, all managed by the platform team.

Test the LLM classification

  1. Start the application in dev mode again using Terminal > Run Task…​ > devfile > Start Development mode

  2. Wait for Quarkus to start and open the application preview

  3. Navigate to the Inbox page

  4. Notice the difference: there are no REVIEW REQUIRED emails. Every email has been intelligently classified with a specific category and a detailed routing reason from the LLM.

    Compare this to what you saw in Module 1 where several emails were flagged for manual review. The LLM understands the content and context of each email, making nuanced classification decisions that keyword matching cannot.

    All emails are now routed successfully
    Figure 2. Inbox with LLM-classified emails and no REVIEW REQUIRED entries
  5. Return to OpenShift Dev Spaces and view the logs printed by the Quarkus application. You’ll notice the LLM’s reasoning is printed in the logs.

    Quarkus logs showing the LLM classification reasoning
    Figure 3. Quarkus dev mode logs with LLM classification reasoning
  6. Return to the application preview and click an email in the Inbox. The LLM’s reasoning is displayed.

    Reasoning shown in the Quarkus application
    Figure 4. Email detail view showing the LLM classification reasoning

Verify

Confirm the following before moving on:

  • The EmailClassifier.java file contains the @RegisterAiService annotation and a @SystemMessage

  • The application.properties file references LLM_BASE_URL, LLM_API_KEY, and the qwen3-14b model

  • The Inbox page shows every email classified with a category and reason, with no REVIEW REQUIRED entries

Exercise 2: Deploy to development and check the container image

Now that the LLM classification works locally, push the changes through the same CI/CD pipeline you used in Module 2.

Commit and push

  1. Stop Quarkus dev mode by pressing Ctrl+C in the terminal

  2. In the Dev Spaces left sidebar, click the Source Control icon (the branch icon)

  3. Review the changed files in the Changes section

  4. Enter a commit message:

    feat: replace keyword matching with LLM classification
  5. Click the down arrow on the Commit button and click Commit & Push

  6. Switch to the OpenShift Console and navigate to the {user_name}-build namespace

  7. Select Pipelines > Pipelines from the left menu. Watch the new push PipelineRun in progress. The same pipeline Tasks run as before: clone, maven-build, sonar-scan, build-and-push, rollout-restart.

  8. Wait for the pipeline to complete.

Check the container image in Quay

The pipeline built a new container image and pushed it to the Quay registry. Let’s verify it.

  1. Open the Quay tab on the right side of this page

  2. Log in with your credentials:

    • Username: {user_name}

    • Password: {user_password}

  3. You should see the parasol-insurance repository.

    Parasol Insurance repository in the Quay image registry
    Figure 5. Parasol Insurance repository in Quay
  4. Click and check that it has a latest container image that was just built by the pipeline. Note the latest tag and the timestamp confirming it was pushed moments ago.

    Parasol Insurance repository showing the latest image tag
    Figure 6. Container image with latest tag in Quay

Verify

Confirm the following before moving on:

  • The PipelineRun completed successfully with all 5 stages green

  • The Quay registry contains a parasol-insurance image with a recent latest tag

Exercise 3: Promote to production

So far, all your changes have deployed to the dev environment. Now you will promote the LLM classification feature to production using a tag-based GitOps workflow.

Create a release tag in GitLab

  1. Open the GitLab tab on the right side of this page, or navigate to https://gitlab-gitlab.{openshift_cluster_ingress_domain}/

  2. Navigate to your parasol-insurance repository

  3. In the left sidebar, click Code > Tags

  4. Click New tag

  5. Enter v1.0 as the tag name

    Create a new tag in GitLab named v1.0
    Figure 7. Creating the v1.0 tag in GitLab
  6. Click Create tag

Watch the tag-promote pipeline

  1. Switch to the OpenShift Console and navigate to the {user_name}-build namespace

  2. Select Pipelines > Pipelines from the left menu

  3. A new PipelineRun starts for the tag-promote pipeline. Click on it to watch the stages.

    A new tag-promote pipeline is shown in the OpenShift Pipelines view
    Figure 8. The tag-promote PipelineRun in progress
  4. Note the open-prod-pr task. This task automatically creates a merge request in your parasol-insurance-manifests repository to update values-prod.yaml with the v1.0 image tag.

    The open-prod-pr Task is the last in the PipelineRun
    Figure 9. Tag-promote pipeline stages with the open-prod-pr task
  5. Wait for the tag-promote pipeline to complete.

Merge the production promotion

  1. Return to GitLab and navigate to your parasol-insurance-manifests repository

  2. Click Merge Requests in the left sidebar

  3. Open the merge request created by the tag-promote pipeline. Review the Changes tab. It updates image.tag to v1.0 in values-prod.yaml.

    The Merge Request that bumps our production image to v1.0
    Figure 10. Merge request updating the production image tag to v1.0
  4. Click Merge to merge the changes

  5. Open the Argo CD tab on the right side of this page, or navigate to https://openshift-gitops-server-openshift-gitops.{openshift_cluster_ingress_domain}/

  6. Find the {user_name}-prod Application and click Refresh. You should briefly see the application flash OutOfSync as Argo CD detects the manifest change, then transition to Syncing as it reconciles the new image tag.

    OpenShift GitOps will briefly show that the Application is out of sync after the refresh
    Figure 11. Argo CD showing OutOfSync status before reconciling

    Depending on your timing, the application may already be syncing or synced. Argo CD polls for changes every 3 minutes by default, so if you merged a moment ago the sync may have started automatically before you clicked Refresh.

  7. Once the Application shows Synced, note the commit SHA and message. They match the merge you just performed in GitLab. This is GitOps in action: the cluster state is driven by what is in Git.

Verify

Confirm the following before moving on:

  • A v1.0 tag exists in your parasol-insurance repository in GitLab

  • The tag-promote PipelineRun completed successfully, including the open-prod-pr task

  • The merge request in parasol-insurance-manifests has been merged

  • The Argo CD {user_name}-prod Application shows Synced status and the commit SHA matches the merge

Exercise 4: Verify the production deployment

Argo CD has synced your production namespace with the updated manifests. Let’s confirm the application is running with LLM classification.

  1. In the OpenShift Console, navigate to the {user_name}-prod namespace

  2. Navigate to Workloads > Pods

  3. The parasol-insurance Pod should have updated with the new production image. This is seen as a new Pod with a recent Created time.

    Newly created Parasol Insurance application Pod
    Figure 12. New production Pod after Argo CD sync
  4. Once the rollout completes, click Networking > Routes from the left menu

  5. Click the URL in the Location column to view the production Parasol Insurance application

  6. Navigate to the Inbox page and confirm the LLM classification is working. Every email should be classified with a category and reason, with no REVIEW REQUIRED entries.

Verify

Confirm the following before moving on:

  • The production Parasol Insurance application is running with LLM-powered email classification

  • Every email in the Inbox is classified with a category and reason, with no REVIEW REQUIRED entries

Exercise 5: Autoscaling under load

The production deployment includes a Horizontal Pod Autoscaler (HPA) that automatically scales the Parasol Insurance application based on CPU utilization. Let’s generate some load and watch it scale.

Take a look in your app/values/values-prod.yaml and app/templates/hpa.yaml files in the parasol-insurance-manifests repository to view the HPA configuration. The Pod CPU utilization target is set low, at just 10%. This is deliberate so we can demonstrate autoscaling.

Generate load

  1. Return to your Dev Spaces workspace

  2. Open a new terminal and run the following command to start a load generator Pod in your production namespace:

    oc run load-generator --image=quay.io/curl/curl:latest -n {prod_ns} \
    --restart=Never \
    -- /bin/sh -c "while true; do curl -s -o /dev/null http://parasol-insurance.{prod_ns}.svc:8080/api/claims; done"

    This creates a Pod that continuously sends requests to the Parasol Insurance claims API. These are load balanced across all available Pods. If any Pod reports CPU utilization above 10%, the HPA creates a new Pod assuming it has not reached the configured max Pods number.

Watch the HPA scale

  1. In the OpenShift Console, navigate to the {user_name}-prod namespace

  2. Select Workloads > HorizontalPodAutoscalers from the left menu. Your HPA will be listed, showing min and max replica counts

    A list of HorizontalPodAutoscalers in the production namespace
    Figure 13. HorizontalPodAutoscaler in the production namespace
  3. Click on the parasol-insurance-hpa HPA. Note the HPA’s current CPU utilization and replica count. As the load increases, the HPA will scale the Deployment beyond the minimum of 2 replicas.

  4. Click the Events tab. You should notice that scaling events are reported

    The HorizontalPodAutoscaler reports events related to scaling and metrics
    Figure 14. HPA scaling events
  5. Select Workloads > Pods and observe new Pods being created as the HPA scales up.

    Newly created Pods to ensure our application remains responsive
    Figure 15. Additional Pods created by the HPA

Clean up the load generator

  1. Once you have observed the scaling behavior, return to the Dev Spaces terminal and delete the load generator:

    oc delete pod load-generator -n {prod_ns}
  2. Return to Workloads > HorizontalPodAutoscalers in the OpenShift Console. After a few minutes, the CPU utilization will drop and the HPA will scale the Deployment back down to the minimum replica count.

Verify

Confirm the following before moving on:

  • The HPA scaled the Parasol Insurance Deployment beyond 2 replicas under load

  • After deleting the load generator, the HPA begins scaling back down

Learning outcomes

By completing this module, you should now understand:

  • How Langchain4j and Quarkus make it straightforward to integrate LLM-powered features into existing Java applications

  • How platform-managed credentials (LLM_BASE_URL, LLM_API_KEY) flow from Vault through ExternalSecrets into both Dev Spaces workspaces and deployed environments, without developers managing secrets

  • How the same CI/CD pipeline and security controls apply whether you are changing a date format or integrating an LLM

  • How a tag-based GitOps workflow enables controlled production promotions through merge requests

  • How Argo CD continuously reconciles the cluster state with Git, ensuring production matches the approved manifests

  • How the Horizontal Pod Autoscaler automatically scales applications based on CPU utilization

Module summary

You replaced a naive keyword classifier with an LLM-powered classification service, deployed it through the same trusted CI/CD pipeline, and promoted it to production using a tag-based GitOps workflow.

What you accomplished:

  • Overlaid the LLM classification code from a prepared branch

  • Reviewed the EmailClassifier service and understood how LLM_BASE_URL, LLM_API_KEY, and the qwen3-14b model are configured by the platform administrator

  • Tested the LLM classification locally in Quarkus dev mode

  • Pushed the changes through the automated CI/CD pipeline

  • Inspected the container image in Quay

  • Created a v1.0 release tag in GitLab to trigger the tag-promote pipeline

  • Merged the production promotion merge request

  • Verified the production deployment and confirmed GitOps reconciliation in Argo CD

  • Generated load and observed the HPA automatically scaling the production application

The same pipeline, the same security controls, the same GitOps delivery. Whether you are changing a date format or integrating an LLM, the platform handles the complexity so you can focus on the code.