This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch camel-quarkus-main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
The following commit(s) were added to refs/heads/camel-quarkus-main by this
push:
new d919f70f Add the langchain4j-ingest-rag example: RAG document Q&A over
an ingested folder
d919f70f is described below
commit d919f70f866bff0fba9d4dd2271a2a01a60bd31a
Author: Jiří Ondrušek <[email protected]>
AuthorDate: Fri Sep 25 15:31:58 2026 +0200
Add the langchain4j-ingest-rag example: RAG document Q&A over an ingested
folder
A folder of documents becomes a knowledge base through the
camel-quarkus-langchain4j-ingest extension, declared in
application.properties
only: the files are split, embedded in-process and stored in pgvector,
started
by Dev Services. A Quarkus LangChain4j AI service answers questions
grounded in
the retrieved segments through the RetrievalAugmentor the
camel-quarkus-langchain4j-embeddingstore extension produces, so the example
carries no RAG wiring; the chat model is served by Ollama. The /search,
/chat/preview and /chat endpoints show each step. Tests run in JVM and
native
mode with a WireMock stand-in for Ollama.
Requires the RAG augmentor fix of apache/camel-quarkus#9229.
Co-authored-by: Claude Fable 5.1 <[email protected]>
---
docs/modules/ROOT/attachments/examples.json | 5 +
langchain4j-ingest-rag/README.adoc | 139 +++++++++
langchain4j-ingest-rag/documents/company-policy.md | 44 +++
langchain4j-ingest-rag/documents/product-guide.md | 67 ++++
langchain4j-ingest-rag/documents/support-faq.md | 61 ++++
.../eclipse-formatter-config.xml | 276 +++++++++++++++++
langchain4j-ingest-rag/pom.xml | 341 +++++++++++++++++++++
.../acme/langchain4j/ingest/rag/ChatResource.java | 59 ++++
.../langchain4j/ingest/rag/DocumentAssistant.java | 38 +++
.../langchain4j/ingest/rag/SearchResource.java | 58 ++++
.../src/main/resources/application.properties | 52 ++++
.../ingest/rag/Langchain4jIngestRagIT.java | 24 ++
.../ingest/rag/Langchain4jIngestRagTest.java | 80 +++++
.../langchain4j/ingest/rag/OllamaTestResource.java | 90 ++++++
pom.xml | 1 +
15 files changed, 1335 insertions(+)
diff --git a/docs/modules/ROOT/attachments/examples.json
b/docs/modules/ROOT/attachments/examples.json
index de2599c5..e4b8ee09 100644
--- a/docs/modules/ROOT/attachments/examples.json
+++ b/docs/modules/ROOT/attachments/examples.json
@@ -94,6 +94,11 @@
"description": "Shows how you can build a simple Kamelet and use with your
Camel applications.",
"link":
"https://github.com/apache/camel-quarkus-examples/tree/main/kamelet-chucknorris"
},
+ {
+ "title": "LangChain4j Ingest RAG",
+ "description": "Shows how to build a RAG document Q\u0026A where a folder
of documents is ingested into a vector store by the langchain4j-ingest
extension and an AI service answers questions grounded in them",
+ "link":
"https://github.com/apache/camel-quarkus-examples/tree/main/langchain4j-ingest-rag"
+ },
{
"title": "Leader election in Kubernetes: A Camel Quarkus Master example",
"description": "Shows how to use Camel master component.",
diff --git a/langchain4j-ingest-rag/README.adoc
b/langchain4j-ingest-rag/README.adoc
new file mode 100644
index 00000000..3991be2d
--- /dev/null
+++ b/langchain4j-ingest-rag/README.adoc
@@ -0,0 +1,139 @@
+= LangChain4j Ingest RAG: A Camel Quarkus example
+:cq-example-description: An example that shows how to build a RAG document Q&A
where a folder of documents is ingested into a vector store by the
langchain4j-ingest extension and an AI service answers questions grounded in
them
+
+{cq-description}
+
+TIP: Check the
https://camel.apache.org/camel-quarkus/latest/first-steps.html[Camel Quarkus
User guide] for prerequisites
+and other general information.
+
+== What the example does
+
+The goal is to answer questions using only the content of your own documents,
the
+https://en.wikipedia.org/wiki/Retrieval-augmented_generation[RAG] pattern.
+
+* The `documents/` folder is the knowledge base. The
`camel-quarkus-langchain4j-ingest` extension reads every file in it, splits the
text into overlapping segments, embeds them and writes them to a
https://github.com/pgvector/pgvector[pgvector] store. The whole pipeline is
declared in `application.properties`; there is no route and no ingestion code.
+* `DocumentAssistant` is a plain Quarkus LangChain4j `@RegisterAiService`
interface. The `camel-quarkus-langchain4j-embeddingstore` extension, which the
ingest extension depends on, produces a `RetrievalAugmentor` from the
application's embedding store and model, and Quarkus LangChain4j picks it up,
so every question is augmented with the retrieved segments. Again, no RAG code.
+* The embedding model runs in-process (`all-MiniLM-L6-v2`), registered by
Quarkus LangChain4j from the `langchain4j-embeddings-all-minilm-l6-v2`
dependency, so ingestion and retrieval need no API key. Only the chat model is
served by https://ollama.com/[Ollama].
+
+Three endpoints show the steps:
+
+* `GET /search?q=...` returns the segments closest to the question, with the
document they came from.
+* `GET /chat/preview?q=...` returns the prompt the assistant would send, the
question plus the retrieved context, without calling a model.
+* `GET /chat?q=...` asks the assistant.
+
+== Prerequisites
+
+* Docker, for the pgvector database that Quarkus Dev Services start in dev and
test mode.
+* Ollama serving the chat model. Install it from
https://ollama.com/[ollama.com] and pull the model once:
++
+[source,shell]
+----
+ollama pull llama3.2
+----
++
+Or run it in a container:
++
+[source,shell]
+----
+docker run --rm -d -v cqex-ingest-ollama:/root/.ollama -p 11434:11434 --name
cqex-ingest-ollama docker.io/ollama/ollama:0.24.0
+docker exec -it cqex-ingest-ollama ollama pull llama3.2
+----
++
+An Ollama running elsewhere, or another model, is selected with environment
variables:
++
+[source,shell]
+----
+export OLLAMA_BASE_URL=http://ollama.example.com:11434
+export OLLAMA_MODEL=llama3.2
+----
+
+== Start in the Development mode
+
+[source,shell]
+----
+mvn clean compile quarkus:dev
+----
+
+The above command compiles the project, starts the application and lets the
Quarkus tooling watch for changes in your
+workspace. Any modifications in your project will automatically take effect in
the running application.
+
+TIP: Please refer to the Development mode section of
+https://camel.apache.org/camel-quarkus/latest/first-steps.html#_development_mode[Camel
Quarkus User guide] for more details.
+
+A pgvector database is started for you, and the log shows the folder being
ingested:
+
+[source,shell]
+----
+INFO [org.apa.cam.qua.com.lan.ing.IngestRoutes] (main) Ingestion pipeline
'docs': source=file:documents
+----
+
+Then ask questions:
+
+[source,shell]
+----
+curl "http://localhost:8080/chat?q=What%20is%20the%20return%20policy%3F"
+curl "http://localhost:8080/chat?q=How%20do%20I%20reset%20my%20password%3F"
+curl
"http://localhost:8080/chat?q=Our%20users%20can%20log%20in%20but%20synchronization%20is%20very%20slow%20with%20high%20CPU%20usage%20and%20no%20error%20codes.%20What%20network%20configuration%20might%20cause%20this%3F"
+----
+
+To see what retrieval found, and what the model was given, without calling the
model:
+
+[source,shell]
+----
+curl "http://localhost:8080/search?q=What%20is%20the%20return%20policy%3F"
+curl
"http://localhost:8080/chat/preview?q=What%20is%20the%20return%20policy%3F"
+----
+
+Drop your own `.md` or `.txt` files into `documents/`: a new or edited file is
picked up on the next poll of the running application, and the whole folder is
indexed again on every start.
+
+=== Package and run the application
+
+Once you are done with developing you may want to package and run the
application.
+
+TIP: Find more details about the JVM mode and Native mode in the Package and
run section of
+https://camel.apache.org/camel-quarkus/latest/first-steps.html#_package_and_run_the_application[Camel
Quarkus User guide]
+
+Outside of dev mode there are no Dev Services, so start a pgvector database
first. The connection details below are the ones configured for the `prod`
profile in `application.properties`:
+
+[source,shell]
+----
+docker run --rm -d --name pgvector -p 5432:5432 -e POSTGRES_USER=postgres -e
POSTGRES_PASSWORD=postgres -e POSTGRES_DB=documents
docker.io/pgvector/pgvector:pg17
+----
+
+==== JVM mode
+
+[source,shell]
+----
+mvn clean package -DskipTests
+java -jar target/quarkus-app/quarkus-run.jar
+----
+
+==== Native mode
+
+IMPORTANT: Native mode requires having GraalVM and other tools installed.
Please check the Prerequisites section
+of
https://camel.apache.org/camel-quarkus/latest/first-steps.html#_prerequisites[Camel
Quarkus User guide].
+
+To prepare a native executable using GraalVM, run the following command:
+
+[source,shell]
+----
+mvn clean package -DskipTests -Dnative
+./target/*-runner
+----
+
+== Testing
+
+The tests start pgvector through Dev Services and ingest the `documents/`
folder. The chat model is played by a WireMock server answering the Ollama API
with canned replies, so no model is needed. The fake model knows the answer
only when the retrieved document text is part of the request, so a correct
answer proves that retrieval grounded the prompt. To run them against a real
Ollama instead, export `OLLAMA_BASE_URL` (and `OLLAMA_MODEL`) as shown above.
+
+[source,shell]
+----
+# JVM mode
+mvn clean test
+
+# native mode
+mvn clean verify -Dnative
+----
+
+== Feedback
+
+Please report bugs and propose improvements via
https://github.com/apache/camel-quarkus/issues[GitHub issues of Camel Quarkus]
project.
diff --git a/langchain4j-ingest-rag/documents/company-policy.md
b/langchain4j-ingest-rag/documents/company-policy.md
new file mode 100644
index 00000000..4e60b6d8
--- /dev/null
+++ b/langchain4j-ingest-rag/documents/company-policy.md
@@ -0,0 +1,44 @@
+# Company Policy: Returns, Refunds and Exchanges
+
+This policy applies to every order placed in the Acme web store. It explains
how to return a
+product, when a refund is issued and how exchanges work.
+
+## Standard returns
+
+Products can be returned within 30 days of delivery. To be accepted, a
returned item must be
+unused, in its original packaging and accompanied by the order number. Returns
requested after
+the 30-day window are declined, except for defective items covered by the
warranty section below.
+
+To start a return, sign in to your account, open the order and choose "Return
items". A prepaid
+shipping label is generated for orders within the European Union and the
United Kingdom. For
+other destinations the return shipping cost is deducted from the refund.
+
+## Refunds
+
+Refunds are issued to the original payment method within 5 to 7 business days
after the returned
+item reaches our warehouse. Shipping fees paid on the original order are not
refunded unless the
+return is caused by our mistake. If a refund has not arrived after 10 business
days, contact
+support with the order number and the return tracking number.
+
+## Exchanges
+
+An item can be exchanged for a different size or colour of the same product
within the same
+30-day window. Exchanges ship free of charge once the original item has been
received. To
+exchange a product for a different model, return it for a refund and place a
new order.
+
+## Defective, damaged or incorrect items
+
+Report a defective, damaged or incorrect item within 14 days of delivery
through the order page.
+We cover the return shipping and send a replacement, or issue a full refund
including the original
+shipping fees. Hardware products carry a 24-month warranty against
manufacturing defects; a
+warranty claim is handled by support and does not require the original
packaging.
+
+## Items that cannot be returned
+
+Gift cards, downloadable software licences, opened consumables and
personalised products cannot be
+returned. Items marked "final sale" on the product page are sold without a
right of return.
+
+## Gift returns
+
+A product received as a gift can be returned within the standard 30 days. The
refund is issued as
+store credit to the person returning the item, so the buyer is not notified.
diff --git a/langchain4j-ingest-rag/documents/product-guide.md
b/langchain4j-ingest-rag/documents/product-guide.md
new file mode 100644
index 00000000..e4930275
--- /dev/null
+++ b/langchain4j-ingest-rag/documents/product-guide.md
@@ -0,0 +1,67 @@
+# Acme Sync Product Guide
+
+Acme Sync is the desktop client that keeps a local folder synchronised with
your Acme cloud
+workspace. This guide covers installation, activation, the first
synchronisation and the most
+common problems.
+
+## System requirements
+
+Acme Sync runs on Windows 10 and later, macOS 13 and later, and Ubuntu 22.04
and later. The client
+needs 4 GB of memory, 500 MB of disk space for the application and outbound
HTTPS access to
+sync.acme.example on port 443. No inbound connection is required.
+
+## Installation
+
+On Windows, run the downloaded installer and follow the wizard; administrator
rights are required
+once, for the installation itself. On macOS, open the disk image and drag Acme
Sync to the
+Applications folder. On Ubuntu, install the .deb package with `sudo apt
install ./acme-sync.deb`.
+The client starts automatically at login after the installation.
+
+## Licensing and activation
+
+Acme Sync is included in every Team and Enterprise subscription. On first
start, sign in with your
+Acme account: the licence is checked online and cached for 30 days, so the
client keeps working
+while you are offline. A licence can be active on three devices at the same
time; a fourth device
+signs out the oldest one.
+
+## The initial synchronisation
+
+The first synchronisation downloads the whole workspace, which can take a
while for large
+workspaces. Progress is shown in the tray icon menu. Choose "Selective sync"
in the preferences to
+download only some folders; folders left out remain visible online. Files are
checked every
+30 seconds, and a change made on another device usually appears within a
minute.
+
+## Troubleshooting
+
+### Slow synchronisation and high CPU usage without error codes
+
+When users can sign in but synchronisation is extremely slow and the client
consumes a lot of CPU,
+with no error codes in the log, the cause is nearly always the network path
rather than the client.
+The two configurations to check are:
+
+- A proxy or security appliance that inspects TLS traffic on port 443. Acme
Sync detects the
+ replaced certificate and falls back to a per-file verification mode, which
is slow and CPU
+ intensive. Add sync.acme.example to the proxy bypass list so the traffic is
not inspected.
+- A VPN link with a reduced MTU. Fragmented packets make the client retransmit
large chunks
+ repeatedly. Set the MTU on the VPN adapter to 1400 or lower, or enable
"Small chunks" in the
+ advanced preferences of the client.
+
+After changing either setting, restart the client. Synchronisation returns to
normal speed and the
+CPU usage drops within a few minutes.
+
+### The client cannot reach the server
+
+If the tray icon shows "Offline" although the network works, verify that
outbound HTTPS to
+sync.acme.example on port 443 is allowed by the firewall. The client does not
support SOCKS proxies;
+use an HTTP proxy configured in the preferences.
+
+### Log files
+
+Logs are written to the "logs" folder inside the application data directory.
Choose "Collect
+diagnostics" in the tray icon menu to create an archive with the logs and the
current configuration,
+and attach it when contacting support.
+
+## Updates
+
+Updates are downloaded automatically and installed at the next start of the
client. Enterprise
+administrators can pin a version and roll out updates through the admin
console.
diff --git a/langchain4j-ingest-rag/documents/support-faq.md
b/langchain4j-ingest-rag/documents/support-faq.md
new file mode 100644
index 00000000..e2d43ce2
--- /dev/null
+++ b/langchain4j-ingest-rag/documents/support-faq.md
@@ -0,0 +1,61 @@
+# Support FAQ
+
+## Account management
+
+### How do I reset my password?
+
+Open the sign-in page and choose "Forgot password". Enter the email address
registered on the
+account; a reset link is sent to it within a few minutes. The link is valid
for 15 minutes and can
+be used once. If no email arrives, check the spam folder and make sure the
address is the one
+registered on the account. Accounts protected by two-factor authentication ask
for the second
+factor after the new password has been set.
+
+### How do I change my registered email address?
+
+Sign in, open "Account settings" and choose "Change email". A confirmation
link is sent to the new
+address; the change takes effect once the link is opened. Invoices and
notifications are sent to
+the new address from then on.
+
+### How do I enable two-factor authentication?
+
+In "Account settings", open "Security" and choose "Enable two-factor
authentication". Scan the QR
+code with an authenticator application and enter the generated code to
confirm. Store the recovery
+codes shown at the end in a safe place: they are the only way back into the
account if the
+authenticator device is lost.
+
+### How do I delete my account?
+
+Account deletion is requested in "Account settings" under "Delete account".
The account is
+deactivated immediately and permanently deleted after 30 days; signing in
during that period
+cancels the deletion. Data in shared workspaces owned by other members is not
affected.
+
+## Billing and subscriptions
+
+### Which payment methods are accepted?
+
+We accept Visa, Mastercard and American Express cards, as well as SEPA direct
debit for customers
+in the European Union. Enterprise subscriptions can be paid by invoice with
30-day payment terms.
+
+### Where can I find my invoices?
+
+Invoices are available in "Billing" under "Invoices" and are also emailed to
the billing contact
+on the day they are issued. Each invoice can be downloaded as a PDF.
+
+### What happens if my payment fails?
+
+A failed payment is retried three times over 10 days. During that period the
subscription keeps
+working and the billing contact is notified after each attempt. If every
attempt fails, the
+subscription is downgraded to the free plan; data is kept for 90 days.
+
+## Technical support
+
+### How do I contact support?
+
+Support is reached through the "Help" menu in the application or by email at
[email protected]. Include the account email address and, for technical
problems, the
+diagnostics archive created by the client.
+
+### What is the response time?
+
+Free plan requests are answered within 2 business days. Team subscriptions get
a first response
+within 8 business hours, Enterprise subscriptions within 1 hour around the
clock.
diff --git a/langchain4j-ingest-rag/eclipse-formatter-config.xml
b/langchain4j-ingest-rag/eclipse-formatter-config.xml
new file mode 100644
index 00000000..2248b2b8
--- /dev/null
+++ b/langchain4j-ingest-rag/eclipse-formatter-config.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<profiles version="8">
+ <profile name="Camel Java Conventions" version="8"
kind="CodeFormatterProfile">
+ <setting
id="org.eclipse.jdt.core.formatter.align_type_members_on_columns"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+ <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration"
value="16"/>
+ <setting
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration"
value="16"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports"
value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package"
value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+ <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer"
value="end_of_line"/>
+ <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration"
value="end_of_line"/>
+ <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration"
value="end_of_line"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_block_comments"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_comments"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_header"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.format_html"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.indent_return_description"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter"
value="do not insert"/>
+ <setting id="org.eclipse.jdt.core.formatter.comment.line_length"
value="120"/>
+ <setting id="org.eclipse.jdt.core.formatter.compact_else_if"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.continuation_indentation"
value="2"/>
+ <setting
id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer"
value="2"/>
+ <setting
id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases"
value="true"/>
+ <setting
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.indentation.size"
value="8"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do
not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do
not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not
insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional"
value="insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not
insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration"
value="do not insert"/>
+ <setting
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation"
value="do not insert"/>
+ <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line"
value="false"/>
+ <setting
id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
+ <setting
id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body"
value="0"/>
+ <setting
id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve"
value="1"/>
+ <setting
id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.tabulation.char"
value="space"/>
+ <setting id="org.eclipse.jdt.core.formatter.tabulation.size"
value="4"/>
+ <setting
id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations"
value="false"/>
+ <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags"
value="true"/>
+ <setting id="org.eclipse.jdt.core.formatter.disabling_tag"
value="CHECKSTYLE:OFF"/>
+ <setting id="org.eclipse.jdt.core.formatter.enabling_tag"
value="CHECKSTYLE:ON"/>
+ </profile>
+</profiles>
diff --git a/langchain4j-ingest-rag/pom.xml b/langchain4j-ingest-rag/pom.xml
new file mode 100644
index 00000000..4848b802
--- /dev/null
+++ b/langchain4j-ingest-rag/pom.xml
@@ -0,0 +1,341 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>camel-quarkus-examples-langchain4j-ingest-rag</artifactId>
+ <groupId>org.apache.camel.quarkus.examples</groupId>
+ <version>3.40.0-SNAPSHOT</version>
+
+ <name>Camel Quarkus :: Examples :: LangChain4j Ingest RAG</name>
+ <description>Camel Quarkus Example :: RAG document Q&A over a folder
ingested by the langchain4j-ingest extension</description>
+
+ <properties>
+ <quarkus.platform.version>3.40.0</quarkus.platform.version>
+
<camel-quarkus.platform.version>3.40.0-SNAPSHOT</camel-quarkus.platform.version>
+
+ <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+
<camel-quarkus.platform.group-id>org.apache.camel.quarkus</camel-quarkus.platform.group-id>
+
<camel-quarkus.platform.artifact-id>camel-quarkus-bom</camel-quarkus.platform.artifact-id>
+
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <maven.compiler.release>17</maven.compiler.release>
+
+ <formatter-maven-plugin.version>2.29.0</formatter-maven-plugin.version>
+ <impsort-maven-plugin.version>1.13.0</impsort-maven-plugin.version>
+ <license-maven-plugin.version>5.1.2</license-maven-plugin.version>
+ <maven-compiler-plugin.version>3.16.0</maven-compiler-plugin.version>
+ <maven-jar-plugin.version>3.5.1</maven-jar-plugin.version>
+ <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
+ <maven-surefire-plugin.version>3.5.6</maven-surefire-plugin.version>
+
+ <!-- the Quarkus LangChain4j release Camel Quarkus is built against -->
+
<quarkiverse-langchain4j.version>1.13.3</quarkiverse-langchain4j.version>
+ <wiremock.version>3.13.2</wiremock.version>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- Import BOM -->
+ <dependency>
+ <groupId>io.quarkiverse.langchain4j</groupId>
+ <artifactId>quarkus-langchain4j-bom</artifactId>
+ <version>${quarkiverse-langchain4j.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>${quarkus.platform.artifact-id}</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>${camel-quarkus.platform.group-id}</groupId>
+ <artifactId>${camel-quarkus.platform.artifact-id}</artifactId>
+ <version>${camel-quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- the ingestion pipeline: a folder becomes a knowledge base,
declared in application.properties -->
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-langchain4j-ingest</artifactId>
+ </dependency>
+ <!-- the vector store: pgvector, started by Dev Services in dev and
test mode -->
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-jdbc-postgresql</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.quarkiverse.langchain4j</groupId>
+ <artifactId>quarkus-langchain4j-pgvector</artifactId>
+ </dependency>
+ <!-- the chat model behind the @RegisterAiService assistant: a model
served by Ollama -->
+ <dependency>
+ <groupId>io.quarkiverse.langchain4j</groupId>
+ <artifactId>quarkus-langchain4j-ollama</artifactId>
+ </dependency>
+ <!-- the embedding model: runs in-process, no API key needed -->
+ <dependency>
+ <groupId>dev.langchain4j</groupId>
+ <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-rest</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-rest-jackson</artifactId>
+ </dependency>
+
+ <!-- Test -->
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.rest-assured</groupId>
+ <artifactId>rest-assured</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wiremock</groupId>
+ <artifactId>wiremock-standalone</artifactId>
+ <version>${wiremock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>net.revelc.code.formatter</groupId>
+ <artifactId>formatter-maven-plugin</artifactId>
+ <version>${formatter-maven-plugin.version}</version>
+ <configuration>
+
<configFile>${maven.multiModuleProjectDirectory}/eclipse-formatter-config.xml</configFile>
+ <lineEnding>LF</lineEnding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code</groupId>
+ <artifactId>impsort-maven-plugin</artifactId>
+ <version>${impsort-maven-plugin.version}</version>
+ <configuration>
+ <groups>java.,javax.,org.w3c.,org.xml.,junit.</groups>
+ <removeUnused>true</removeUnused>
+ <staticAfter>true</staticAfter>
+
<staticGroups>java.,javax.,org.w3c.,org.xml.,junit.</staticGroups>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>${maven-compiler-plugin.version}</version>
+ <configuration>
+ <showDeprecation>true</showDeprecation>
+ <showWarnings>true</showWarnings>
+ <compilerArgs>
+ <arg>-Xlint:unchecked</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ <configuration>
+ <failIfNoTests>false</failIfNoTests>
+ <systemPropertyVariables>
+
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>quarkus-maven-plugin</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <extensions>true</extensions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>${maven-surefire-plugin.version}</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>${maven-jar-plugin.version}</version>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <version>${license-maven-plugin.version}</version>
+ <configuration>
+ <failIfUnknown>true</failIfUnknown>
+
<header>${maven.multiModuleProjectDirectory}/header.txt</header>
+ <excludes>
+ <exclude>**/*.adoc</exclude>
+ <exclude>**/*.md</exclude>
+ <exclude>**/*.txt</exclude>
+ <exclude>**/LICENSE.txt</exclude>
+ <exclude>**/LICENSE</exclude>
+ <exclude>**/NOTICE.txt</exclude>
+ <exclude>**/NOTICE</exclude>
+ <exclude>**/README</exclude>
+ <exclude>**/pom.xml.versionsBackup</exclude>
+ <exclude>**/quarkus.log*</exclude>
+ </excludes>
+ <mapping>
+ <java>SLASHSTAR_STYLE</java>
+ <properties>CAMEL_PROPERTIES_STYLE</properties>
+ <kt>SLASHSTAR_STYLE</kt>
+ </mapping>
+ <headerDefinitions>
+
<headerDefinition>${maven.multiModuleProjectDirectory}/license-properties-headerdefinition.xml</headerDefinition>
+ </headerDefinitions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ <plugins>
+ <plugin>
+ <groupId>${quarkus.platform.group-id}</groupId>
+ <artifactId>quarkus-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>build</id>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code.formatter</groupId>
+ <artifactId>formatter-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>format</id>
+ <goals>
+ <goal>format</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>net.revelc.code</groupId>
+ <artifactId>impsort-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>sort-imports</id>
+ <goals>
+ <goal>sort</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>com.mycila</groupId>
+ <artifactId>license-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>license-format</id>
+ <goals>
+ <goal>format</goal>
+ </goals>
+ <phase>process-sources</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>native</id>
+ <activation>
+ <property>
+ <name>native</name>
+ </property>
+ </activation>
+ <properties>
+ <quarkus.native.enabled>true</quarkus.native.enabled>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>skip-testcontainers-tests</id>
+ <activation>
+ <property>
+ <name>skip-testcontainers-tests</name>
+ </property>
+ </activation>
+ <properties>
+ <skipTests>true</skipTests>
+ </properties>
+ </profile>
+ </profiles>
+
+</project>
diff --git
a/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/ChatResource.java
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/ChatResource.java
new file mode 100644
index 00000000..5b00ad85
--- /dev/null
+++
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/ChatResource.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import java.util.List;
+
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.rag.AugmentationRequest;
+import dev.langchain4j.rag.AugmentationResult;
+import dev.langchain4j.rag.RetrievalAugmentor;
+import dev.langchain4j.rag.query.Metadata;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/chat")
+public class ChatResource {
+
+ @Inject
+ DocumentAssistant assistant;
+
+ @Inject
+ RetrievalAugmentor augmentor;
+
+ /** Asks the assistant; the answer is grounded in the retrieved documents.
*/
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String chat(@QueryParam("q") String question) {
+ return assistant.answer(question);
+ }
+
+ /** The prompt the assistant would send: the question plus the retrieved
context. No LLM is called. */
+ @GET
+ @Path("/preview")
+ @Produces(MediaType.TEXT_PLAIN)
+ public String preview(@QueryParam("q") String question) {
+ UserMessage message = UserMessage.from(question);
+ AugmentationResult result = augmentor.augment(
+ new AugmentationRequest(message, Metadata.from(message,
"preview", List.of())));
+ return ((UserMessage) result.chatMessage()).singleText();
+ }
+}
diff --git
a/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/DocumentAssistant.java
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/DocumentAssistant.java
new file mode 100644
index 00000000..7fdf6469
--- /dev/null
+++
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/DocumentAssistant.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import dev.langchain4j.service.SystemMessage;
+import dev.langchain4j.service.UserMessage;
+import io.quarkiverse.langchain4j.RegisterAiService;
+
+/**
+ * The assistant. There is no RAG code here: the langchain4j-embeddingstore
extension, which the
+ * ingest extension depends on, produces a RetrievalAugmentor from the
application's embedding
+ * store and model, and Quarkus LangChain4j picks it up as the default, so
every question is
+ * augmented with the segments the pipeline ingested.
+ */
+@RegisterAiService
+public interface DocumentAssistant {
+
+ @SystemMessage("""
+ You answer questions about the company's documents.
+ Use ONLY the information provided together with the question.
+ If it does not contain the answer, reply: I don't have complete
information on that.
+ Be concise.""")
+ String answer(@UserMessage String question);
+}
diff --git
a/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/SearchResource.java
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/SearchResource.java
new file mode 100644
index 00000000..c09dbc92
--- /dev/null
+++
b/langchain4j-ingest-rag/src/main/java/org/acme/langchain4j/ingest/rag/SearchResource.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import java.util.List;
+import java.util.Map;
+
+import dev.langchain4j.data.segment.TextSegment;
+import dev.langchain4j.model.embedding.EmbeddingModel;
+import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
+import dev.langchain4j.store.embedding.EmbeddingStore;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+import
org.apache.camel.quarkus.component.langchain4j.ingest.core.IngestService;
+
+@Path("/search")
+public class SearchResource {
+
+ @Inject
+ EmbeddingStore<TextSegment> store;
+
+ @Inject
+ EmbeddingModel model;
+
+ /** The raw retrieval step: the segments closest to the question, with
their source document. */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<Map<String, Object>> search(@QueryParam("q") String question) {
+ return store.search(EmbeddingSearchRequest.builder()
+ .queryEmbedding(model.embed(question).content())
+ .maxResults(3)
+ .build())
+ .matches().stream()
+ .map(match -> Map.<String, Object> of(
+ "score", match.score(),
+ "document",
String.valueOf(match.embedded().metadata().getString(IngestService.METADATA_DOCUMENT_ID)),
+ "text", match.embedded().text()))
+ .toList();
+ }
+}
diff --git a/langchain4j-ingest-rag/src/main/resources/application.properties
b/langchain4j-ingest-rag/src/main/resources/application.properties
new file mode 100644
index 00000000..36d990db
--- /dev/null
+++ b/langchain4j-ingest-rag/src/main/resources/application.properties
@@ -0,0 +1,52 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+# The knowledge base. Every file in the folder is ingested at startup, and a
file added or
+# edited while the application runs is picked up on the next poll. No route,
no ingestion
+# code: the langchain4j-ingest extension reads, splits, embeds and stores. The
pipeline
+# writes to the application's only embedding store, the pgvector store below.
+documents.directory=documents
+quarkus.camel.langchain4j.ingest.docs.source.directory=${documents.directory}
+quarkus.camel.langchain4j.ingest.docs.max-segment-size=500
+quarkus.camel.langchain4j.ingest.docs.max-overlap-size=50
+
+# The vector store: pgvector through Quarkus LangChain4j. Dev Services start a
pgvector
+# enabled PostgreSQL in dev and test mode; in prod mode point the datasource
at your own.
+quarkus.datasource.db-kind=postgresql
+quarkus.datasource.devservices.image-name=docker.io/pgvector/pgvector:pg17
+%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/documents
+%prod.quarkus.datasource.username=postgres
+%prod.quarkus.datasource.password=postgres
+# all-MiniLM-L6-v2 produces 384-dimensional vectors
+quarkus.langchain4j.pgvector.dimension=384
+# the vector extension is created on start (the default only does so in dev
and test mode)
+quarkus.langchain4j.pgvector.register-vector-pg-extension=true
+# the folder is indexed again on every start, so start from an empty table
+quarkus.langchain4j.pgvector.drop-table-first=true
+
+# The chat model behind the assistant: a model served by Ollama,
https://ollama.com. Pull the
+# model once with `ollama pull llama3.2`; point OLLAMA_BASE_URL at an Ollama
running elsewhere.
+quarkus.langchain4j.ollama.base-url=${OLLAMA_BASE_URL:http://localhost:11434}
+quarkus.langchain4j.ollama.chat-model.model-id=${OLLAMA_MODEL:llama3.2}
+quarkus.langchain4j.ollama.chat-model.temperature=0.1
+quarkus.langchain4j.ollama.timeout=120s
+# Ollama is run by you rather than by Dev Services: pulling a model is a
multi-gigabyte download
+quarkus.langchain4j.ollama.devservices.enabled=false
+# Embeddings come from the in-process all-MiniLM-L6-v2 model, which Quarkus
LangChain4j registers
+# as the EmbeddingModel bean from the langchain4j-embeddings-all-minilm-l6-v2
dependency. The
+# Ollama embedding model is switched off, so ingestion and retrieval share
that one bean.
+quarkus.langchain4j.ollama.embedding-model.enabled=false
diff --git
a/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagIT.java
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagIT.java
new file mode 100644
index 00000000..43fff920
--- /dev/null
+++
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagIT.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class Langchain4jIngestRagIT extends Langchain4jIngestRagTest {
+
+}
diff --git
a/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagTest.java
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagTest.java
new file mode 100644
index 00000000..8d9c80d8
--- /dev/null
+++
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/Langchain4jIngestRagTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@QuarkusTestResource(OllamaTestResource.class)
+@QuarkusTest
+class Langchain4jIngestRagTest {
+
+ private static final String QUESTION = "What is the return policy?";
+
+ @Test
+ void documentsAreIngestedAndSearchable() {
+ List<Map<String, Object>> hits = awaitReturnPolicy();
+ assertTrue(hits.stream().anyMatch(hit ->
"company-policy.md".equals(hit.get("document"))),
+ "the hit must cite the document it came from, got: " + hits);
+ }
+
+ @Test
+ void assistantAnswersFromTheDocuments() {
+ awaitReturnPolicy();
+
+ // the retrieved context reaches the prompt without any RAG code; no
LLM involved here
+ String prompt = RestAssured.given()
+ .queryParam("q", QUESTION)
+ .get("/chat/preview")
+ .then().statusCode(200)
+ .extract().asString();
+ assertTrue(prompt.contains("30 days"), "the prompt must contain the
retrieved context, got: " + prompt);
+
+ String answer = RestAssured.given()
+ .queryParam("q", QUESTION)
+ .get("/chat")
+ .then().statusCode(200)
+ .extract().asString();
+ // the fake model answers correctly only when the retrieved document
text was in the request
+ assertTrue(answer.contains("30"), "the answer must come from the
documents, got: " + answer);
+
+ }
+
+ /** Ingestion runs in the background after startup: wait until the return
policy is searchable. */
+ static List<Map<String, Object>> awaitReturnPolicy() {
+ return await().atMost(3, TimeUnit.MINUTES).pollInterval(1,
TimeUnit.SECONDS)
+ .until(() -> search("How long do I have to return a product?"),
+ hits -> hits.stream().anyMatch(hit ->
String.valueOf(hit.get("text")).contains("30 days")));
+ }
+
+ static List<Map<String, Object>> search(String question) {
+ return RestAssured.given()
+ .queryParam("q", question)
+ .get("/search")
+ .then().statusCode(200)
+ .extract().jsonPath().getList("$");
+ }
+}
diff --git
a/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/OllamaTestResource.java
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/OllamaTestResource.java
new file mode 100644
index 00000000..fe71bea3
--- /dev/null
+++
b/langchain4j-ingest-rag/src/test/java/org/acme/langchain4j/ingest/rag/OllamaTestResource.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.acme.langchain4j.ingest.rag;
+
+import java.util.Map;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.containing;
+import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static
com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+
+/**
+ * The chat model of the tests. By default a WireMock server plays the Ollama
API with canned
+ * answers, so the tests need no model. With {@code OLLAMA_BASE_URL} set, that
real Ollama is
+ * used instead, with the model named by {@code OLLAMA_MODEL}.
+ */
+public class OllamaTestResource implements QuarkusTestResourceLifecycleManager
{
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OllamaTestResource.class);
+ private static final String CHAT = "/api/chat";
+
+ private WireMockServer server;
+
+ @Override
+ public Map<String, String> start() {
+ String realBaseUrl = System.getenv("OLLAMA_BASE_URL");
+ if (realBaseUrl != null && !realBaseUrl.isBlank()) {
+ LOG.info("Using the Ollama server at {}", realBaseUrl);
+ return Map.of();
+ }
+
+ LOG.info("Starting a fake Ollama server backed by WireMock");
+ server = new WireMockServer(options().dynamicPort());
+ server.start();
+ // the "model" knows the answer only when the retrieved document text
is part of the request,
+ // as it would be after augmentation; a bare question gets the
fallback below, so a correct
+ // answer in the tests proves that retrieval grounded the prompt
+ server.stubFor(post(urlPathEqualTo(CHAT))
+ .withRequestBody(containing("within 30 days of delivery"))
+ .willReturn(okJson(answer(
+ "Products can be returned within 30 days of delivery,
unused and in their original packaging."))));
+ server.stubFor(post(urlPathEqualTo(CHAT))
+ .atPriority(10)
+ .willReturn(okJson(answer("I don't have complete information
on that."))));
+
+ return Map.of("quarkus.langchain4j.ollama.base-url", server.baseUrl());
+ }
+
+ private static String answer(String content) {
+ return """
+ {
+ "model": "llama3.2",
+ "created_at": "2026-01-01T00:00:00Z",
+ "message": { "role": "assistant", "content": "%s" },
+ "done": true,
+ "done_reason": "stop",
+ "prompt_eval_count": 1,
+ "eval_count": 1
+ }
+ """.formatted(content);
+ }
+
+ @Override
+ public void stop() {
+ if (server != null) {
+ server.stop();
+ server = null;
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index abbb4a40..ca094530 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,7 @@
<module>jta-jpa</module>
<module>kafka</module>
<module>kamelet-chucknorris</module>
+ <module>langchain4j-ingest-rag</module>
<module>message-bridge</module>
<module>netty-custom-correlation</module>
<module>observability</module>