This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push:
new b07cce5ef3 Move the RAG augmentor bridge into the
langchain4j-embeddingstore extension
b07cce5ef3 is described below
commit b07cce5ef36f510703864b20c12ae85e91e93f58
Author: James Netherton <[email protected]>
AuthorDate: Fri Sep 11 15:43:28 2026 +0100
Move the RAG augmentor bridge into the langchain4j-embeddingstore extension
* Move the RAG augmentor bridge into the langchain4j-embeddingstore
extension
The bridge producing RetrievalAugmentor beans and binding
@EmbeddingStoreName CDI
beans into the Camel registry lived in camel-quarkus-support-langchain4j,
so it was
active for all six extensions depending on that artifact. Neither half has
a consumer
in the other five: embeddingStore is a URI option on
camel-langchain4j-embeddingstore
alone, and no Camel component consumes a RetrievalAugmentor. It is now
registered only
when camel-quarkus-langchain4j-embeddingstore is a dependency.
* Move RagRetrievalFilterSupplier, RagAugmentorName, RagBridgeConfig and
DefaultRetrievalAugmentorSupplier to the extension runtime, and split the
RAG half
of QuarkusLangchain4jRecorder into Langchain4jEmbeddingstoreRecorder
* Move the six RAG build steps, BeanCensus and AugmentorDefinition into
Langchain4jEmbeddingstoreRagProcessor, along with EasyRagPresent and its
unit test.
SupportQuarkusLangchain4jProcessor drops from 435 to 152 lines
* Document the narrowed activation and the RagRetrievalFilterSupplier
package change
in the 3.40.0 migration guide
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
* Keep the RAG augmentor bridge for langchain4j-ingest and document the
opt-out
Ingestion pipelines fill stores through CDI beans without the embeddingstore
component, and the ingest docs promise the bridge. With it now registered
only
by camel-quarkus-langchain4j-embeddingstore, ingest applications would have
lost
it on upgrade.
* Make camel-quarkus-langchain4j-ingest depend on
camel-quarkus-langchain4j-embeddingstore, so ingest applications keep the
bridge as in 3.39.0
* Document the per-service opt-out,
RegisterAiService.NoRetrievalAugmentorSupplier
* Name the applications the move affects in the migration guide: configured
augmentors, the default augmentor and AgentConfiguration users
* Link the ingest docs to the bridge documentation
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../modules/ROOT/pages/migration-guide/3.40.0.adoc | 35 +++
.../extensions/langchain4j-embeddingstore.adoc | 38 ++-
.../reference/extensions/langchain4j-ingest.adoc | 2 +-
extensions-support/langchain4j/deployment/pom.xml | 5 -
.../SupportQuarkusLangchain4jProcessor.java | 283 ---------------------
.../langchain4j/QuarkusLangchain4jRecorder.java | 60 -----
.../langchain4j-embeddingstore/deployment/pom.xml | 5 +
.../embeddingstore}/deployment/EasyRagPresent.java | 2 +-
.../Langchain4jEmbeddingstoreRagProcessor.java | 134 +---------
.../deployment/QuarkusLangchain4jPresent.java | 8 +-
.../RagAugmentorDefaultResolutionTest.java | 6 +-
.../langchain4j-embeddingstore/runtime/pom.xml | 13 +
.../runtime/src/main/doc/usage.adoc | 2 +-
.../DefaultRetrievalAugmentorSupplier.java | 2 +-
.../Langchain4jEmbeddingstoreRecorder.java | 19 +-
.../embeddingstore}/RagAugmentorName.java | 4 +-
.../embeddingstore}/RagBridgeConfig.java | 18 +-
.../RagRetrievalFilterSupplier.java | 2 +-
.../main/resources/META-INF/quarkus-extension.yaml | 2 +
extensions/langchain4j-ingest/deployment/pom.xml | 4 +
extensions/langchain4j-ingest/runtime/pom.xml | 4 +
.../runtime/src/main/doc/quarkus-langchain4j.adoc | 2 +-
.../ragbridge/it/TestTenantFilterSupplier.java | 2 +-
.../ragbridge/it/MultiAugmentorProfile.java | 2 +-
24 files changed, 145 insertions(+), 509 deletions(-)
diff --git a/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
b/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
index b9d5283362..e62955d7a0 100644
--- a/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
+++ b/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
@@ -49,6 +49,41 @@ Nothing else about the metadata changed: the values and the
segment text are wri
Newly ingested segments carry the new keys, while segments already in a store
keep the old ones — retrieval that filters or cites by these keys sees the
mismatch.
Either re-ingest the affected documents (with the default in-memory register a
restart re-ingests the directory; clear a persistent register first) so
everything carries the new keys, or make retrieval read both key pairs during
the transition.
+== LangChain4j EmbeddingStore extension changes
+
+=== The RAG augmentor bridge moved to the EmbeddingStore extension
+
+The bridge that produces `RetrievalAugmentor` beans from Camel ingestion
routes, and binds
+`@EmbeddingStoreName`-qualified CDI beans into the Camel registry, was part of
+`camel-quarkus-support-langchain4j`. It is now part of
`camel-quarkus-langchain4j-embeddingstore`.
+
+It was previously active for any application depending on
`camel-quarkus-langchain4j-agent`,
+`-chat`, `-embeddings`, `-ingest`, `-tools` or `-web-search`. It is now active
when
+`camel-quarkus-langchain4j-embeddingstore` is a dependency, which
`camel-quarkus-langchain4j-ingest`
+depends on. Applications using the other extensions that relied on the bridge
must add it.
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-langchain4j-embeddingstore</artifactId>
+</dependency>
+----
+
+This includes applications configuring
`quarkus.camel.langchain4j.rag.augmentors.*`, whose
+properties are otherwise not recognised, applications relying on the default
augmentor produced
+from an `EmbeddingStore` and an `EmbeddingModel` bean, and applications
passing a produced
+augmentor to `AgentConfiguration.withRetrievalAugmentor`. Without the
dependency no augmentor is
+produced.
+
+The `RagRetrievalFilterSupplier` interface moved with it. Update the import.
+
+[source,java]
+----
+// Was: import
org.apache.camel.quarkus.component.support.langchain4j.RagRetrievalFilterSupplier;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagRetrievalFilterSupplier;
+----
+
== JTA extension changes
=== Rollback and resume failures are no longer swallowed
diff --git
a/docs/modules/ROOT/pages/reference/extensions/langchain4j-embeddingstore.adoc
b/docs/modules/ROOT/pages/reference/extensions/langchain4j-embeddingstore.adoc
index b1e1e1b927..0c8d6bd03d 100644
---
a/docs/modules/ROOT/pages/reference/extensions/langchain4j-embeddingstore.adoc
+++
b/docs/modules/ROOT/pages/reference/extensions/langchain4j-embeddingstore.adoc
@@ -65,7 +65,7 @@ A store only referenced from routes does not need to be
injected anywhere in Jav
[id="extensions-langchain4j-embeddingstore-usage-retrieval-augmentors-for-registeraiservice"]
=== Retrieval augmentors for `@RegisterAiService`
-With Quarkus LangChain4j present, a `RetrievalAugmentor` is produced
automatically from the `@Default` `EmbeddingStore` and `EmbeddingModel` beans,
so an `@RegisterAiService` interface answers from the same store a Camel route
ingests into. Nothing is produced when the application declares its own
`RetrievalAugmentor`.
+With Quarkus LangChain4j present, a `RetrievalAugmentor` is produced
automatically from the `@Default` `EmbeddingStore` and `EmbeddingModel` beans,
so an `@RegisterAiService` interface answers from the same store a Camel route
ingests into. Nothing is produced when the application declares its own
`RetrievalAugmentor`. An AI service that should not answer from the store opts
out with `@RegisterAiService(retrievalAugmentor =
RegisterAiService.NoRetrievalAugmentorSupplier.class)`.
Augmentors can also be declared explicitly, one per store:
@@ -165,3 +165,39 @@ As such, one might need to import the `langchain4j-bom`
before other related Cam
WARNING: At present, this extension is neither tested with nor intended to be
used in conjunction with any Quarkus LangChain4j extensions.
Consequently, both JVM and native modes may exhibit unexpected behaviour or
fail to function correctly in such configurations.
+
+[id="extensions-langchain4j-embeddingstore-additional-camel-quarkus-configuration"]
+== Additional Camel Quarkus configuration
+
+[width="100%",cols="80,5,15",options="header"]
+|===
+| Configuration property | Type | Default
+
+
+a|icon:lock[title=Fixed at build time]
[[quarkus-camel-langchain4j-rag-augmentors-augmentors-embedding-store-name]]`link:#quarkus-camel-langchain4j-rag-augmentors-augmentors-embedding-store-name[quarkus.camel.langchain4j.rag.augmentors."augmentors".embedding-store-name]`
+
+CDI bean name of the `EmbeddingStore` to use.
+Matches beans annotated with `@Named("name")` or `@EmbeddingStoreName("name")`.
+| `string`
+| required icon:exclamation-circle[title=Configuration property is required]
+
+a|icon:lock[title=Fixed at build time]
[[quarkus-camel-langchain4j-rag-augmentors-augmentors-embedding-model-name]]`link:#quarkus-camel-langchain4j-rag-augmentors-augmentors-embedding-model-name[quarkus.camel.langchain4j.rag.augmentors."augmentors".embedding-model-name]`
+
+CDI bean name of the `EmbeddingModel` to use.
+When not set, the default (unnamed) `EmbeddingModel` is used.
+| `string`
+|
+
+a|icon:lock[title=Fixed at build time]
[[quarkus-camel-langchain4j-rag-augmentors-augmentors-default]]`link:#quarkus-camel-langchain4j-rag-augmentors-augmentors-default[quarkus.camel.langchain4j.rag.augmentors."augmentors".default]`
+
+Marks this augmentor as the one `@RegisterAiService` AI services use when they
+do not select an augmentor explicitly. Exactly one augmentor must be marked
when more
+than one is configured — otherwise the build fails, because an unmarked
ambiguity
+would silently disable RAG for every AI service in the application.
+| `boolean`
+| `false`
+|===
+
+[.configuration-legend]
+{doc-link-icon-lock}[title=Fixed at build time] Configuration property fixed
at build time. All other configuration properties are overridable at runtime.
+
diff --git
a/docs/modules/ROOT/pages/reference/extensions/langchain4j-ingest.adoc
b/docs/modules/ROOT/pages/reference/extensions/langchain4j-ingest.adoc
index 2dfb343acd..d481cc2adf 100644
--- a/docs/modules/ROOT/pages/reference/extensions/langchain4j-ingest.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/langchain4j-ingest.adoc
@@ -172,7 +172,7 @@ As such, one might need to import the `langchain4j-bom`
before other related Cam
=== Quarkus LangChain4j support
-This extension is designed and tested to work together with the
https://docs.quarkiverse.io/quarkus-langchain4j/dev/index.html[Quarkus
LangChain4j] extensions. The `EmbeddingStore` and `EmbeddingModel` beans a
pipeline writes through are ordinary CDI beans shared by both stacks —
including stores declared through Quarkus LangChain4j configuration — and with
the RAG augmentor bridge an `@RegisterAiService` interface answers from the
store a pipeline filled, without glue code. The integrat [...]
+This extension is designed and tested to work together with the
https://docs.quarkiverse.io/quarkus-langchain4j/dev/index.html[Quarkus
LangChain4j] extensions. The `EmbeddingStore` and `EmbeddingModel` beans a
pipeline writes through are ordinary CDI beans shared by both stacks —
including stores declared through Quarkus LangChain4j configuration — and with
the
xref:reference/extensions/langchain4j-embeddingstore.adoc#extensions-langchain4j-embeddingstore-usage-retrieval-augmentors-for-r
[...]
[id="extensions-langchain4j-ingest-additional-camel-quarkus-configuration"]
diff --git a/extensions-support/langchain4j/deployment/pom.xml
b/extensions-support/langchain4j/deployment/pom.xml
index 0ca00589e8..0cbcbd9578 100644
--- a/extensions-support/langchain4j/deployment/pom.xml
+++ b/extensions-support/langchain4j/deployment/pom.xml
@@ -42,11 +42,6 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-langchain4j</artifactId>
</dependency>
- <dependency>
- <groupId>io.quarkus</groupId>
- <artifactId>quarkus-junit-internal</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
b/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
index 25cadd2a55..275eb805cc 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
+++
b/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
@@ -16,54 +16,31 @@
*/
package org.apache.camel.quarkus.component.support.langchain4j.deployment;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
import java.util.stream.Collectors;
import dev.langchain4j.guardrail.Guardrail;
import dev.langchain4j.guardrail.InputGuardrail;
import dev.langchain4j.guardrail.OutputGuardrail;
-import dev.langchain4j.model.embedding.EmbeddingModel;
-import dev.langchain4j.rag.RetrievalAugmentor;
-import dev.langchain4j.store.embedding.EmbeddingStore;
-import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
-import io.quarkus.arc.deployment.QualifierRegistrarBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
-import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
-import
io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
-import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
-import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
-import io.quarkus.runtime.configuration.ConfigurationException;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.enterprise.context.Dependent;
-import jakarta.inject.Named;
import jakarta.inject.Singleton;
import
org.apache.camel.quarkus.component.support.langchain4j.QuarkusLangchain4jRecorder;
-import org.apache.camel.quarkus.component.support.langchain4j.RagAugmentorName;
-import org.apache.camel.quarkus.component.support.langchain4j.RagBridgeConfig;
-import
org.apache.camel.quarkus.component.support.langchain4j.RagBridgeConfig.AugmentorConfig;
-import
org.apache.camel.quarkus.component.support.langchain4j.RagRetrievalFilterSupplier;
-import org.apache.camel.quarkus.core.deployment.spi.CamelRegistryBuildItem;
-import org.apache.camel.quarkus.core.deployment.spi.CamelRuntimeTaskBuildItem;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
-import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import static
io.quarkus.arc.deployment.UnremovableBeanBuildItem.beanClassNames;
@@ -76,11 +53,6 @@ class SupportQuarkusLangchain4jProcessor {
public static final DotName REGISTER_AI_SERVICES_DOTNAME = DotName
.createSimple("io.quarkiverse.langchain4j.RegisterAiService");
- private static final DotName EMBEDDING_STORE_NAME_DOTNAME = DotName
- .createSimple("io.quarkiverse.langchain4j.EmbeddingStoreName");
- private static final DotName RAG_RETRIEVAL_FILTER_SUPPLIER_DOTNAME =
DotName
- .createSimple(RagRetrievalFilterSupplier.class.getName());
- private static final DotName DEPENDENT_DOTNAME =
DotName.createSimple(Dependent.class.getName());
private static final Logger LOG =
Logger.getLogger(SupportQuarkusLangchain4jProcessor.class);
@@ -160,78 +132,6 @@ class SupportQuarkusLangchain4jProcessor {
.build());
}
- // The retrieval filter supplier is only resolved programmatically at
augmentor creation
- // time, so ArC would remove a user's implementation as unused.
- @BuildStep
- UnremovableBeanBuildItem retainRagRetrievalFilterSuppliers() {
- return
UnremovableBeanBuildItem.beanTypes(RAG_RETRIEVAL_FILTER_SUPPLIER_DOTNAME);
- }
-
- /**
- * The retrieval filter is an access control, so every way of disabling it
silently is a build
- * failure or a warning: two implementations make the lookup ambiguous, a
{@code @Dependent}
- * one is resolved once and never destroyed, and a filter no produced
augmentor will ever
- * consult isolates nothing.
- */
- @BuildStep
- void validateRagRetrievalFilterSupplier(BeanDiscoveryFinishedBuildItem
beanDiscovery,
- RagBridgeConfig ragBridgeConfig,
- BuildProducer<ValidationErrorBuildItem> validationErrors) {
- BeanCensus census = BeanCensus.of(beanDiscovery);
- if (census.filterSuppliers().isEmpty()) {
- return;
- }
-
- if (census.filterSuppliers().size() > 1) {
- validationErrors.produce(new ValidationErrorBuildItem(new
ConfigurationException(
- "Found " + census.filterSuppliers().size() + "
RagRetrievalFilterSupplier beans ("
- + census.filterSuppliers().stream().map(bean ->
bean.getBeanClass().toString())
- .collect(Collectors.joining(", "))
- + "). Exactly one is allowed: an ambiguous lookup
would leave every retrieval "
- + "unfiltered instead of failing.")));
- return;
- }
-
- BeanInfo supplier = census.filterSuppliers().get(0);
- if (DEPENDENT_DOTNAME.equals(supplier.getScope().getDotName())) {
- validationErrors.produce(new ValidationErrorBuildItem(new
ConfigurationException(
- "RagRetrievalFilterSupplier bean " +
supplier.getBeanClass()
- + " is @Dependent. Use @ApplicationScoped,
@RequestScoped or @Singleton: the supplier "
- + "is resolved once per augmentor, so a @Dependent
instance would never be destroyed.")));
- return;
- }
-
- if (!census.producesAugmentor(ragBridgeConfig, new
EasyRagPresent().getAsBoolean())) {
- LOG.warnf("RagRetrievalFilterSupplier bean %s will never be
consulted: it filters only the "
- + "RetrievalAugmentors produced by this extension, and
none is produced here (Easy RAG or an "
- + "application-provided RetrievalAugmentor takes over).
Retrieval is NOT filtered.",
- supplier.getBeanClass());
- }
- }
-
- // A store declared only for use from a Camel route is never injected
anywhere in Java,
- // so ArC would remove it as unused and registerNamedEmbeddingStores would
find nothing.
- @BuildStep
- UnremovableBeanBuildItem retainNamedEmbeddingStores() {
- return new UnremovableBeanBuildItem(bean -> bean.getQualifiers()
- .stream()
- .anyMatch(qualifier ->
qualifier.name().equals(EMBEDDING_STORE_NAME_DOTNAME)));
- }
-
- // Bridges @EmbeddingStoreName-qualified CDI beans into the Camel registry
so routes
- // can reference them by name (e.g. embeddingStore=#products) without
manual binding.
- // Stores declared via Quarkus LangChain4j configuration (pgvector, redis,
...) are
- // runtime-init synthetic beans, so the scan must not run before ArC
initializes them.
- @BuildStep
- @Record(ExecutionTime.RUNTIME_INIT)
- @Consume(SyntheticBeansRuntimeInitBuildItem.class)
- CamelRuntimeTaskBuildItem registerNamedEmbeddingStores(
- QuarkusLangchain4jRecorder recorder,
- CamelRegistryBuildItem registry) {
- recorder.registerNamedEmbeddingStores(registry.getRegistry());
- return new CamelRuntimeTaskBuildItem("named-embedding-stores");
- }
-
@BuildStep
void validateAndRegisterAiServices(
CombinedIndexBuildItem indexBuildItem,
@@ -249,187 +149,4 @@ class SupportQuarkusLangchain4jProcessor {
}
}
- // RagAugmentorName carries @Qualifier, but it lives in the support
runtime artifact, which
- // ships no Jandex index. Without this registration ArC does not know the
annotation at all
- // and the synthetic bean below fails the build with
- // "Annotation class not available: @RagAugmentorName".
- @BuildStep
- QualifierRegistrarBuildItem registerRagAugmentorNameQualifier() {
- return new QualifierRegistrarBuildItem(
- () ->
Map.of(DotName.createSimple(RagAugmentorName.class.getName()), Set.of()));
- }
-
- /**
- * Produces {@link RetrievalAugmentor} CDI beans that bridge Camel
ingestion routes with
- * {@code @RegisterAiService} RAG.
- *
- * <p>
- * Two modes:
- * <ul>
- * <li><b>Explicit config</b> — each entry under {@code
quarkus.camel.langchain4j.rag.augmentors.<name>}
- * produces a {@code @Named("<name>")} RetrievalAugmentor backed by the
configured store. The entry
- * marked {@code default=true} also serves the unqualified lookup; with a
single entry that marking
- * is optional, with several it is required.</li>
- * <li><b>Auto-detection</b> — when no config entries exist, at least one
EmbeddingStore and one
- * EmbeddingModel are present, and no RetrievalAugmentor exists yet, a
default one is produced
- * backed by the {@code @Default} CDI bean.</li>
- * </ul>
- */
- @BuildStep(onlyIfNot = EasyRagPresent.class)
- @Record(ExecutionTime.RUNTIME_INIT)
- void registerDefaultRetrievalAugmentor(
- BeanDiscoveryFinishedBuildItem beanDiscovery,
- RagBridgeConfig ragBridgeConfig,
- QuarkusLangchain4jRecorder recorder,
- BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
-
- BeanCensus census = BeanCensus.of(beanDiscovery);
-
- // Effective augmentors: one per explicit config entry. Sorted, so
that a message naming
- // them reads the same on every build - SmallRye's map is not
declaration-ordered.
- Map<String, AugmentorDefinition> effective = new TreeMap<>();
- for (Map.Entry<String, AugmentorConfig> entry :
ragBridgeConfig.augmentors().entrySet()) {
- AugmentorConfig cfg = entry.getValue();
- effective.put(entry.getKey(), new AugmentorDefinition(
- cfg.embeddingStoreName(),
cfg.embeddingModelName().orElse(null), cfg.defaultAugmentor()));
- }
-
- if (!effective.isEmpty()) {
- String designatedDefault = resolveDesignatedDefault(effective,
census.retrievalAugmentor());
-
- for (Map.Entry<String, AugmentorDefinition> entry :
effective.entrySet()) {
- String name = entry.getKey();
- AugmentorDefinition def = entry.getValue();
-
- LOG.debugf("Registering named RetrievalAugmentor '%s' backed
by EmbeddingStore '%s'%s",
- name, def.embeddingStoreName(),
name.equals(designatedDefault) ? " (default)" : "");
-
- SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator =
SyntheticBeanBuildItem
- .configure(RetrievalAugmentor.class)
- .scope(ApplicationScoped.class)
-
.addQualifier().annotation(Named.class).addValue("value", name).done()
- .setRuntimeInit()
- .supplier(recorder.createRetrievalAugmentorSupplier(
- def.embeddingStoreName(),
def.embeddingModelName(), name));
-
- if (name.equals(designatedDefault)) {
- // keeps @Named only: the implicit @Default makes it the
one candidate the
- // unqualified Instance<RetrievalAugmentor> lookup of
Quarkus LangChain4j sees
- configurator.defaultBean();
- } else {
- // a real qualifier suppresses the implicit @Default (CDI
rule), so this bean
- // stays selectable by name without making the unqualified
lookup ambiguous
-
configurator.addQualifier().annotation(RagAugmentorName.class).addValue("value",
name).done();
- }
-
- syntheticBeans.produce(configurator.done());
- }
- return;
- }
-
- if (census.retrievalAugmentor()) {
- return;
- }
-
- if (census.embeddingStores() >= 1 && census.embeddingModels() >= 1) {
- LOG.debug("EmbeddingStore and EmbeddingModel CDI beans detected"
- + " - registering default RetrievalAugmentor backed by
@Default store");
- syntheticBeans.produce(SyntheticBeanBuildItem
- .configure(RetrievalAugmentor.class)
- .scope(ApplicationScoped.class)
- .defaultBean()
- .setRuntimeInit()
- .supplier(recorder.createRetrievalAugmentorSupplier(null,
null, null))
- .done());
- }
- }
-
- /**
- * Decides which augmentor is the unqualified default, or fails the build:
silence here would
- * mean an ambiguous CDI lookup and RAG silently switched off for every AI
service.
- */
- static String resolveDesignatedDefault(Map<String, AugmentorDefinition>
effective,
- boolean hasRetrievalAugmentor) {
- List<String> marked = effective.entrySet().stream()
- .filter(e -> e.getValue().markedDefault())
- .map(Map.Entry::getKey)
- .toList();
-
- if (marked.size() > 1) {
- throw new ConfigurationException(
- "Multiple retrieval augmentors are marked default: " +
marked + ". Mark exactly one with "
- +
"quarkus.camel.langchain4j.rag.augmentors.<name>.default=true");
- }
-
- if (hasRetrievalAugmentor) {
- // a user-provided RetrievalAugmentor bean already serves the
unqualified lookup
- if (marked.size() == 1) {
- throw new ConfigurationException(
- "Retrieval augmentor '" + marked.get(0) + "' is marked
default, but the application "
- + "already provides a RetrievalAugmentor bean.
Remove the default marking — "
- + "produced augmentors remain selectable by
name.");
- }
- return null;
- }
-
- if (marked.size() == 1) {
- return marked.get(0);
- }
-
- if (effective.size() == 1) {
- return effective.keySet().iterator().next();
- }
-
- throw new ConfigurationException(
- effective.size() + " retrieval augmentors are configured (" +
String.join(", ", effective.keySet())
- + ") but none is marked default. An unmarked ambiguity
would silently disable RAG for "
- + "every AI service, so the build stops instead. Mark
exactly one with "
- +
"quarkus.camel.langchain4j.rag.augmentors.<name>.default=true");
- }
-
- record AugmentorDefinition(String embeddingStoreName, String
embeddingModelName, boolean markedDefault) {
- }
-
- /** One pass over the discovered beans, answering everything the RAG
bridge decides on. */
- record BeanCensus(int embeddingStores, int embeddingModels, boolean
retrievalAugmentor,
- List<BeanInfo> filterSuppliers) {
-
- static BeanCensus of(BeanDiscoveryFinishedBuildItem beanDiscovery) {
- DotName embeddingStoreDN =
DotName.createSimple(EmbeddingStore.class.getName());
- DotName embeddingModelDN =
DotName.createSimple(EmbeddingModel.class.getName());
- DotName retrievalAugmentorDN =
DotName.createSimple(RetrievalAugmentor.class.getName());
-
- int embeddingStores = 0;
- int embeddingModels = 0;
- boolean retrievalAugmentor = false;
- List<BeanInfo> filterSuppliers = new ArrayList<>();
-
- for (BeanInfo bean :
beanDiscovery.beanStream().collect(Collectors.toList())) {
- for (Type type : bean.getTypes()) {
- DotName typeName = type.name();
- if (typeName.equals(embeddingStoreDN)) {
- embeddingStores++;
- } else if (typeName.equals(embeddingModelDN)) {
- embeddingModels++;
- } else if (typeName.equals(retrievalAugmentorDN)) {
- retrievalAugmentor = true;
- } else if
(typeName.equals(RAG_RETRIEVAL_FILTER_SUPPLIER_DOTNAME)) {
- filterSuppliers.add(bean);
- }
- }
- }
- return new BeanCensus(embeddingStores, embeddingModels,
retrievalAugmentor, filterSuppliers);
- }
-
- /** Whether {@link #registerDefaultRetrievalAugmentor} will produce an
augmentor to filter. */
- boolean producesAugmentor(RagBridgeConfig ragBridgeConfig, boolean
easyRagPresent) {
- if (easyRagPresent) {
- return false;
- }
- if (!ragBridgeConfig.augmentors().isEmpty()) {
- return true;
- }
- return !retrievalAugmentor && embeddingStores >= 1 &&
embeddingModels >= 1;
- }
- }
}
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
b/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
index cb8dbd37a9..af6301a6c9 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
+++
b/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
@@ -16,70 +16,16 @@
*/
package org.apache.camel.quarkus.component.support.langchain4j;
-import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
-import java.util.List;
-import java.util.function.Supplier;
-import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.guardrail.Guardrail;
-import dev.langchain4j.rag.RetrievalAugmentor;
-import dev.langchain4j.store.embedding.EmbeddingStore;
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.InstanceHandle;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
-import jakarta.enterprise.inject.Any;
-import jakarta.enterprise.inject.spi.Bean;
-import jakarta.enterprise.util.TypeLiteral;
-import org.apache.camel.spi.Registry;
import org.jboss.logging.Logger;
@Recorder
public class QuarkusLangchain4jRecorder {
- private static final Logger LOG =
Logger.getLogger(QuarkusLangchain4jRecorder.class);
- private static final String EMBEDDING_STORE_NAME_CLASS =
"io.quarkiverse.langchain4j.EmbeddingStoreName";
- @SuppressWarnings("serial")
- private static final TypeLiteral<EmbeddingStore<TextSegment>>
EMBEDDING_STORE_TYPE = new TypeLiteral<>() {
- };
-
- public void registerNamedEmbeddingStores(RuntimeValue<Registry>
camelRegistry) {
- List<InstanceHandle<EmbeddingStore<TextSegment>>> handles =
Arc.container()
- .listAll(EMBEDDING_STORE_TYPE, Any.Literal.INSTANCE);
-
- Registry registry = camelRegistry.getValue();
- for (InstanceHandle<EmbeddingStore<TextSegment>> handle : handles) {
- Bean<?> bean = handle.getBean();
- if (bean == null) {
- continue;
- }
- for (Annotation qualifier : bean.getQualifiers()) {
- if
(EMBEDDING_STORE_NAME_CLASS.equals(qualifier.annotationType().getName())) {
- try {
- String name = (String) qualifier.annotationType()
- .getMethod("value").invoke(qualifier);
- if (name != null && !name.isEmpty()) {
- // The identity check keeps a bean carrying both
@Named("x") and
- // @EmbeddingStoreName("x") from triggering a
spurious warning
- EmbeddingStore<?> existing =
registry.lookupByNameAndType(name, EmbeddingStore.class);
- if (existing != null && existing != handle.get()) {
- LOG.warnf(
- "The Camel registry already resolves a
different EmbeddingStore under the name \"%s\"; lookups by that name may not
return the @EmbeddingStoreName(\"%s\") bean",
- name, name);
- }
- // Bind via supplier so a store never referenced
by a route is not instantiated
- registry.bind(name, EmbeddingStore.class,
handle::get);
- LOG.debugf("Registered @EmbeddingStoreName(\"%s\")
in Camel registry", name);
- }
- } catch (ReflectiveOperationException e) {
- LOG.warnf(e, "Failed to extract name from
@EmbeddingStoreName qualifier");
- }
- }
- }
- }
- }
-
public RuntimeValue<Guardrail<?, ?>>
instantiateGuardrails(Class<Guardrail<?, ?>> guardrailClass) {
try {
return new
RuntimeValue<>(guardrailClass.getConstructor().newInstance());
@@ -90,10 +36,4 @@ public class QuarkusLangchain4jRecorder {
return null;
}
}
-
- /** @param augmentorName {@code null} for the auto-produced default
augmentor. */
- public Supplier<RetrievalAugmentor> createRetrievalAugmentorSupplier(
- String embeddingStoreName, String embeddingModelName, String
augmentorName) {
- return new DefaultRetrievalAugmentorSupplier(embeddingStoreName,
embeddingModelName, augmentorName);
- }
}
diff --git a/extensions/langchain4j-embeddingstore/deployment/pom.xml
b/extensions/langchain4j-embeddingstore/deployment/pom.xml
index fe57df2ffa..ea9b0fa447 100644
--- a/extensions/langchain4j-embeddingstore/deployment/pom.xml
+++ b/extensions/langchain4j-embeddingstore/deployment/pom.xml
@@ -42,6 +42,11 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-langchain4j-embeddingstore</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-junit-internal</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/EasyRagPresent.java
similarity index 94%
copy from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
copy to
extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/EasyRagPresent.java
index e462ed5785..797fde5767 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
+++
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/EasyRagPresent.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j.deployment;
+package
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment;
import java.util.function.BooleanSupplier;
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/Langchain4jEmbeddingstoreRagProcessor.java
similarity index 72%
copy from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
copy to
extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/Langchain4jEmbeddingstoreRagProcessor.java
index 25cadd2a55..db96d87c9f 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/SupportQuarkusLangchain4jProcessor.java
+++
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/Langchain4jEmbeddingstoreRagProcessor.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j.deployment;
+package
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment;
import java.util.ArrayList;
import java.util.List;
@@ -23,9 +23,6 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
-import dev.langchain4j.guardrail.Guardrail;
-import dev.langchain4j.guardrail.InputGuardrail;
-import dev.langchain4j.guardrail.OutputGuardrail;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.store.embedding.EmbeddingStore;
@@ -42,123 +39,35 @@ import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
-import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
-import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
-import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
import io.quarkus.runtime.configuration.ConfigurationException;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Named;
-import jakarta.inject.Singleton;
-import
org.apache.camel.quarkus.component.support.langchain4j.QuarkusLangchain4jRecorder;
-import org.apache.camel.quarkus.component.support.langchain4j.RagAugmentorName;
-import org.apache.camel.quarkus.component.support.langchain4j.RagBridgeConfig;
-import
org.apache.camel.quarkus.component.support.langchain4j.RagBridgeConfig.AugmentorConfig;
-import
org.apache.camel.quarkus.component.support.langchain4j.RagRetrievalFilterSupplier;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.Langchain4jEmbeddingstoreRecorder;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagAugmentorName;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagBridgeConfig;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagBridgeConfig.AugmentorConfig;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagRetrievalFilterSupplier;
import org.apache.camel.quarkus.core.deployment.spi.CamelRegistryBuildItem;
import org.apache.camel.quarkus.core.deployment.spi.CamelRuntimeTaskBuildItem;
-import org.jboss.jandex.AnnotationInstance;
-import org.jboss.jandex.AnnotationTarget;
-import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
-import org.jboss.jandex.IndexView;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
-import static
io.quarkus.arc.deployment.UnremovableBeanBuildItem.beanClassNames;
-
/**
- * Build steps required only when Quarkus LangChain4j is detected.
+ * Bridges Camel ingestion routes with Quarkus LangChain4j RAG. Registered
only when Quarkus
+ * LangChain4j is detected.
*/
@BuildSteps(onlyIf = QuarkusLangchain4jPresent.class)
-class SupportQuarkusLangchain4jProcessor {
+class Langchain4jEmbeddingstoreRagProcessor {
- public static final DotName REGISTER_AI_SERVICES_DOTNAME = DotName
- .createSimple("io.quarkiverse.langchain4j.RegisterAiService");
private static final DotName EMBEDDING_STORE_NAME_DOTNAME = DotName
.createSimple("io.quarkiverse.langchain4j.EmbeddingStoreName");
private static final DotName RAG_RETRIEVAL_FILTER_SUPPLIER_DOTNAME =
DotName
.createSimple(RagRetrievalFilterSupplier.class.getName());
private static final DotName DEPENDENT_DOTNAME =
DotName.createSimple(Dependent.class.getName());
- private static final Logger LOG =
Logger.getLogger(SupportQuarkusLangchain4jProcessor.class);
-
- @BuildStep
- SystemPropertyBuildItem enforceJaxRsHttpClient() {
- LOG.infof("Quarkus LangChain4j detected - enforcing JAX-RS HTTP client
factory");
- return new
SystemPropertyBuildItem("langchain4j.http.clientBuilderFactory",
-
"io.quarkiverse.langchain4j.jaxrsclient.JaxRsHttpClientBuilderFactory");
- }
-
- @BuildStep
- @SuppressWarnings("unchecked")
- @Record(ExecutionTime.STATIC_INIT)
- void registerLangChain4jAiServiceTypesForReflection(
- CombinedIndexBuildItem combinedIndex,
- BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
- QuarkusLangchain4jRecorder recorder) {
- IndexView index = combinedIndex.getIndex();
- // Guardrails are instantiated dynamically
- Set<DotName> guardrailTypes =
index.getAllKnownImplementations(InputGuardrail.class)
- .stream()
- .map(ClassInfo::name)
- .collect(Collectors.toSet());
-
- index.getAllKnownImplementations(OutputGuardrail.class)
- .stream()
- .map(ClassInfo::name)
- .forEach(guardrailTypes::add);
-
-
index.getAllKnownSubclasses(SupportLangchain4jProcessor.JSON_EXTRACTOR_OUTPUT_GUARDRAIL)
- .stream()
- .map(ClassInfo::name)
- .forEach(guardrailTypes::add);
-
- guardrailTypes.stream()
- .filter(s ->
!s.equals(SupportLangchain4jProcessor.JSON_EXTRACTOR_OUTPUT_GUARDRAIL))
- .forEach(s -> {
- try {
- Class<Guardrail<?, ?>> guardrailClass;
- guardrailClass = (Class<Guardrail<?, ?>>)
Thread.currentThread()
- .getContextClassLoader()
- .loadClass(s.toString());
- syntheticBeans
- .produce(SyntheticBeanBuildItem.configure(s)
- .scope(Singleton.class)
- .named("GuardrailSynthetic" +
s.local())
-
.runtimeValue(recorder.instantiateGuardrails(guardrailClass))
- .done());
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
- }
-
- @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
- void registerQuarkusLangchain4jNativeSupport(
- CombinedIndexBuildItem combinedIndex,
- BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {
-
- IndexView index = combinedIndex.getIndex();
-
- // Discover all inner classes of QuarkusJsonCodecFactory
- List<String> codecFactoryClasses = index.getKnownClasses()
- .stream()
- .map(classInfo -> classInfo.name().toString())
- .filter(n ->
n.startsWith("io.quarkiverse.langchain4j.QuarkusJsonCodecFactory"))
- .toList();
-
- LOG.infof("Registered %d QuarkusJsonCodecFactory-related classes for
native reflection",
- codecFactoryClasses.size());
-
- reflectiveClasses.produce(ReflectiveClassBuildItem.builder(
- codecFactoryClasses.toArray(new String[0]))
- .methods()
- .fields()
- .constructors()
- .build());
- }
+ private static final Logger LOG =
Logger.getLogger(Langchain4jEmbeddingstoreRagProcessor.class);
// The retrieval filter supplier is only resolved programmatically at
augmentor creation
// time, so ArC would remove a user's implementation as unused.
@@ -226,30 +135,13 @@ class SupportQuarkusLangchain4jProcessor {
@Record(ExecutionTime.RUNTIME_INIT)
@Consume(SyntheticBeansRuntimeInitBuildItem.class)
CamelRuntimeTaskBuildItem registerNamedEmbeddingStores(
- QuarkusLangchain4jRecorder recorder,
+ Langchain4jEmbeddingstoreRecorder recorder,
CamelRegistryBuildItem registry) {
recorder.registerNamedEmbeddingStores(registry.getRegistry());
return new CamelRuntimeTaskBuildItem("named-embedding-stores");
}
- @BuildStep
- void validateAndRegisterAiServices(
- CombinedIndexBuildItem indexBuildItem,
- BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
-
- LOG.debug("Discovering classes annotated with @RegisterAiService to
mark implementation beans as unremovable");
-
- for (AnnotationInstance instance :
indexBuildItem.getIndex().getAnnotations(REGISTER_AI_SERVICES_DOTNAME)) {
- if (instance.target().kind() == AnnotationTarget.Kind.CLASS) {
- String declarativeAiServiceClassName =
instance.target().asClass().name().toString();
- LOG.debugf("Marking Quarkus Ai service implementation class
for %s as unremovable",
- declarativeAiServiceClassName);
-
unremovableBeans.produce(beanClassNames(declarativeAiServiceClassName +
"$$QuarkusImpl"));
- }
- }
- }
-
- // RagAugmentorName carries @Qualifier, but it lives in the support
runtime artifact, which
+ // RagAugmentorName carries @Qualifier, but it lives in the extension
runtime artifact, which
// ships no Jandex index. Without this registration ArC does not know the
annotation at all
// and the synthetic bean below fails the build with
// "Annotation class not available: @RagAugmentorName".
@@ -280,7 +172,7 @@ class SupportQuarkusLangchain4jProcessor {
void registerDefaultRetrievalAugmentor(
BeanDiscoveryFinishedBuildItem beanDiscovery,
RagBridgeConfig ragBridgeConfig,
- QuarkusLangchain4jRecorder recorder,
+ Langchain4jEmbeddingstoreRecorder recorder,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
BeanCensus census = BeanCensus.of(beanDiscovery);
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/QuarkusLangchain4jPresent.java
similarity index 78%
rename from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
rename to
extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/QuarkusLangchain4jPresent.java
index e462ed5785..7dcbfc779c 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/EasyRagPresent.java
+++
b/extensions/langchain4j-embeddingstore/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/QuarkusLangchain4jPresent.java
@@ -14,17 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j.deployment;
+package
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment;
import java.util.function.BooleanSupplier;
-public class EasyRagPresent implements BooleanSupplier {
- private static final String EASY_RAG_CONFIG_CLASS =
"io.quarkiverse.langchain4j.easyrag.runtime.EasyRagConfig";
+public class QuarkusLangchain4jPresent implements BooleanSupplier {
+ private static final String REGISTER_AI_SERVICE_CLASS =
"io.quarkiverse.langchain4j.RegisterAiService";
@Override
public boolean getAsBoolean() {
try {
-
Thread.currentThread().getContextClassLoader().loadClass(EASY_RAG_CONFIG_CLASS);
+
Thread.currentThread().getContextClassLoader().loadClass(REGISTER_AI_SERVICE_CLASS);
return true;
} catch (ClassNotFoundException e) {
return false;
diff --git
a/extensions-support/langchain4j/deployment/src/test/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/RagAugmentorDefaultResolutionTest.java
b/extensions/langchain4j-embeddingstore/deployment/src/test/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/RagAugmentorDefaultResolutionTest.java
similarity index 91%
rename from
extensions-support/langchain4j/deployment/src/test/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/RagAugmentorDefaultResolutionTest.java
rename to
extensions/langchain4j-embeddingstore/deployment/src/test/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/RagAugmentorDefaultResolutionTest.java
index fbd3d3afe4..4b4bc3a804 100644
---
a/extensions-support/langchain4j/deployment/src/test/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/RagAugmentorDefaultResolutionTest.java
+++
b/extensions/langchain4j-embeddingstore/deployment/src/test/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/deployment/RagAugmentorDefaultResolutionTest.java
@@ -14,16 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j.deployment;
+package
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment;
import java.util.LinkedHashMap;
import java.util.Map;
import io.quarkus.runtime.configuration.ConfigurationException;
-import
org.apache.camel.quarkus.component.support.langchain4j.deployment.SupportQuarkusLangchain4jProcessor.AugmentorDefinition;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment.Langchain4jEmbeddingstoreRagProcessor.AugmentorDefinition;
import org.junit.jupiter.api.Test;
-import static
org.apache.camel.quarkus.component.support.langchain4j.deployment.SupportQuarkusLangchain4jProcessor.resolveDesignatedDefault;
+import static
org.apache.camel.quarkus.component.langchain4j.embeddingstore.deployment.Langchain4jEmbeddingstoreRagProcessor.resolveDesignatedDefault;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
diff --git a/extensions/langchain4j-embeddingstore/runtime/pom.xml
b/extensions/langchain4j-embeddingstore/runtime/pom.xml
index 83e00adbc5..7539728634 100644
--- a/extensions/langchain4j-embeddingstore/runtime/pom.xml
+++ b/extensions/langchain4j-embeddingstore/runtime/pom.xml
@@ -49,6 +49,19 @@
<groupId>org.apache.camel</groupId>
<artifactId>camel-langchain4j-embeddingstore</artifactId>
</dependency>
+ <dependency>
+ <groupId>dev.langchain4j</groupId>
+ <artifactId>langchain4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>dev.langchain4j</groupId>
+ <artifactId>langchain4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.quarkiverse.langchain4j</groupId>
+ <artifactId>quarkus-langchain4j-core</artifactId>
+ <optional>true</optional>
+ </dependency>
</dependencies>
<build>
diff --git
a/extensions/langchain4j-embeddingstore/runtime/src/main/doc/usage.adoc
b/extensions/langchain4j-embeddingstore/runtime/src/main/doc/usage.adoc
index daf2cf98c1..50d7c050d6 100644
--- a/extensions/langchain4j-embeddingstore/runtime/src/main/doc/usage.adoc
+++ b/extensions/langchain4j-embeddingstore/runtime/src/main/doc/usage.adoc
@@ -15,7 +15,7 @@ A store only referenced from routes does not need to be
injected anywhere in Jav
=== Retrieval augmentors for `@RegisterAiService`
-With Quarkus LangChain4j present, a `RetrievalAugmentor` is produced
automatically from the `@Default` `EmbeddingStore` and `EmbeddingModel` beans,
so an `@RegisterAiService` interface answers from the same store a Camel route
ingests into. Nothing is produced when the application declares its own
`RetrievalAugmentor`.
+With Quarkus LangChain4j present, a `RetrievalAugmentor` is produced
automatically from the `@Default` `EmbeddingStore` and `EmbeddingModel` beans,
so an `@RegisterAiService` interface answers from the same store a Camel route
ingests into. Nothing is produced when the application declares its own
`RetrievalAugmentor`. An AI service that should not answer from the store opts
out with `@RegisterAiService(retrievalAugmentor =
RegisterAiService.NoRetrievalAugmentorSupplier.class)`.
Augmentors can also be declared explicitly, one per store:
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/DefaultRetrievalAugmentorSupplier.java
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/DefaultRetrievalAugmentorSupplier.java
similarity index 98%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/DefaultRetrievalAugmentorSupplier.java
rename to
extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/DefaultRetrievalAugmentorSupplier.java
index 99c2455371..2f08f935c5 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/DefaultRetrievalAugmentorSupplier.java
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/DefaultRetrievalAugmentorSupplier.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j;
+package org.apache.camel.quarkus.component.langchain4j.embeddingstore;
import java.util.List;
import java.util.function.Supplier;
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/Langchain4jEmbeddingstoreRecorder.java
similarity index 83%
copy from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
copy to
extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/Langchain4jEmbeddingstoreRecorder.java
index cb8dbd37a9..41fc229a61 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/QuarkusLangchain4jRecorder.java
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/Langchain4jEmbeddingstoreRecorder.java
@@ -14,15 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j;
+package org.apache.camel.quarkus.component.langchain4j.embeddingstore;
import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.function.Supplier;
import dev.langchain4j.data.segment.TextSegment;
-import dev.langchain4j.guardrail.Guardrail;
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.store.embedding.EmbeddingStore;
import io.quarkus.arc.Arc;
@@ -36,9 +34,9 @@ import org.apache.camel.spi.Registry;
import org.jboss.logging.Logger;
@Recorder
-public class QuarkusLangchain4jRecorder {
+public class Langchain4jEmbeddingstoreRecorder {
- private static final Logger LOG =
Logger.getLogger(QuarkusLangchain4jRecorder.class);
+ private static final Logger LOG =
Logger.getLogger(Langchain4jEmbeddingstoreRecorder.class);
private static final String EMBEDDING_STORE_NAME_CLASS =
"io.quarkiverse.langchain4j.EmbeddingStoreName";
@SuppressWarnings("serial")
private static final TypeLiteral<EmbeddingStore<TextSegment>>
EMBEDDING_STORE_TYPE = new TypeLiteral<>() {
@@ -80,17 +78,6 @@ public class QuarkusLangchain4jRecorder {
}
}
- public RuntimeValue<Guardrail<?, ?>>
instantiateGuardrails(Class<Guardrail<?, ?>> guardrailClass) {
- try {
- return new
RuntimeValue<>(guardrailClass.getConstructor().newInstance());
- } catch (NoSuchMethodException | InvocationTargetException |
InstantiationException
- | IllegalAccessException e) {
- Logger.getLogger(QuarkusLangchain4jRecorder.class).debugf(e,
- "Can not instantiate guardrail of class %s",
guardrailClass.getName());
- return null;
- }
- }
-
/** @param augmentorName {@code null} for the auto-produced default
augmentor. */
public Supplier<RetrievalAugmentor> createRetrievalAugmentorSupplier(
String embeddingStoreName, String embeddingModelName, String
augmentorName) {
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagAugmentorName.java
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagAugmentorName.java
similarity index 91%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagAugmentorName.java
rename to
extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagAugmentorName.java
index 8d8ac05aef..e5306760b0 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagAugmentorName.java
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagAugmentorName.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j;
+package org.apache.camel.quarkus.component.langchain4j.embeddingstore;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@@ -29,7 +29,7 @@ import jakarta.inject.Qualifier;
*
* <p>
* Used only internally to mark RagAugmentors, which are not default. See
- * SupportQuarkusLangchain4jProcessor.registerDefaultRetrievalAugmentor
+ * Langchain4jEmbeddingstoreRagProcessor.registerDefaultRetrievalAugmentor
* Named annotation cannot be used, as it makes beans default.
*/
@Qualifier
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagBridgeConfig.java
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagBridgeConfig.java
similarity index 81%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagBridgeConfig.java
rename to
extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagBridgeConfig.java
index 9f81a76545..3eea8eace2 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagBridgeConfig.java
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagBridgeConfig.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j;
+package org.apache.camel.quarkus.component.langchain4j.embeddingstore;
import java.util.Map;
import java.util.Optional;
@@ -46,22 +46,28 @@ public interface RagBridgeConfig {
interface AugmentorConfig {
/**
- * CDI bean name of the {@code EmbeddingStore} to use.
- * Matches beans annotated with {@code @Named("name")} or {@code
@EmbeddingStoreName("name")}.
+ * CDI bean name of the `EmbeddingStore` to use.
+ * Matches beans annotated with `@Named("name")` or
`@EmbeddingStoreName("name")`.
+ *
+ * @asciidoclet
*/
String embeddingStoreName();
/**
- * CDI bean name of the {@code EmbeddingModel} to use.
- * When not set, the default (unnamed) EmbeddingModel is used.
+ * CDI bean name of the `EmbeddingModel` to use.
+ * When not set, the default (unnamed) `EmbeddingModel` is used.
+ *
+ * @asciidoclet
*/
Optional<String> embeddingModelName();
/**
- * Marks this augmentor as the one {@code @RegisterAiService} AI
services use when they
+ * Marks this augmentor as the one `@RegisterAiService` AI services
use when they
* do not select an augmentor explicitly. Exactly one augmentor must
be marked when more
* than one is configured — otherwise the build fails, because an
unmarked ambiguity
* would silently disable RAG for every AI service in the application.
+ *
+ * @asciidoclet
*/
@WithName("default")
@WithDefault("false")
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagRetrievalFilterSupplier.java
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagRetrievalFilterSupplier.java
similarity index 98%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagRetrievalFilterSupplier.java
rename to
extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagRetrievalFilterSupplier.java
index a8e403b034..cfe507bbc2 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/RagRetrievalFilterSupplier.java
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/embeddingstore/RagRetrievalFilterSupplier.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.support.langchain4j;
+package org.apache.camel.quarkus.component.langchain4j.embeddingstore;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.store.embedding.filter.Filter;
diff --git
a/extensions/langchain4j-embeddingstore/runtime/src/main/resources/META-INF/quarkus-extension.yaml
b/extensions/langchain4j-embeddingstore/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index efd34ab27f..35e9af8ae0 100644
---
a/extensions/langchain4j-embeddingstore/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++
b/extensions/langchain4j-embeddingstore/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -29,6 +29,8 @@ metadata:
guide:
"https://camel.apache.org/camel-quarkus/latest/reference/extensions/langchain4j-embeddingstore.html"
categories:
- "integration"
+ config:
+ - "quarkus.camel.langchain4j.rag"
status: "preview"
integrates:
- name: "Camel"
diff --git a/extensions/langchain4j-ingest/deployment/pom.xml
b/extensions/langchain4j-ingest/deployment/pom.xml
index 5a145cfbee..7974e80a61 100644
--- a/extensions/langchain4j-ingest/deployment/pom.xml
+++ b/extensions/langchain4j-ingest/deployment/pom.xml
@@ -46,6 +46,10 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-langchain4j-deployment</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+
<artifactId>camel-quarkus-langchain4j-embeddingstore-deployment</artifactId>
+ </dependency>
<!-- test dependencies -->
<dependency>
diff --git a/extensions/langchain4j-ingest/runtime/pom.xml
b/extensions/langchain4j-ingest/runtime/pom.xml
index 64b2757b3a..a9097e74f1 100644
--- a/extensions/langchain4j-ingest/runtime/pom.xml
+++ b/extensions/langchain4j-ingest/runtime/pom.xml
@@ -53,6 +53,10 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-langchain4j</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-langchain4j-embeddingstore</artifactId>
+ </dependency>
<!-- also transitive through the support module, but compiled against
directly
(EmbeddingStore, EmbeddingModel, DocumentSplitters), so declared
-->
<dependency>
diff --git
a/extensions/langchain4j-ingest/runtime/src/main/doc/quarkus-langchain4j.adoc
b/extensions/langchain4j-ingest/runtime/src/main/doc/quarkus-langchain4j.adoc
index 6ce6408812..e3378894f7 100644
---
a/extensions/langchain4j-ingest/runtime/src/main/doc/quarkus-langchain4j.adoc
+++
b/extensions/langchain4j-ingest/runtime/src/main/doc/quarkus-langchain4j.adoc
@@ -1 +1 @@
-This extension is designed and tested to work together with the
https://docs.quarkiverse.io/quarkus-langchain4j/dev/index.html[Quarkus
LangChain4j] extensions. The `EmbeddingStore` and `EmbeddingModel` beans a
pipeline writes through are ordinary CDI beans shared by both stacks —
including stores declared through Quarkus LangChain4j configuration — and with
the RAG augmentor bridge an `@RegisterAiService` interface answers from the
store a pipeline filled, without glue code. The integrat [...]
+This extension is designed and tested to work together with the
https://docs.quarkiverse.io/quarkus-langchain4j/dev/index.html[Quarkus
LangChain4j] extensions. The `EmbeddingStore` and `EmbeddingModel` beans a
pipeline writes through are ordinary CDI beans shared by both stacks —
including stores declared through Quarkus LangChain4j configuration — and with
the
xref:reference/extensions/langchain4j-embeddingstore.adoc#extensions-langchain4j-embeddingstore-usage-retrieval-augmentors-for-r
[...]
diff --git
a/integration-tests/langchain4j-rag-bridge-ql4j/src/main/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/TestTenantFilterSupplier.java
b/integration-tests/langchain4j-rag-bridge-ql4j/src/main/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/TestTenantFilterSupplier.java
index 8d737a3909..462b354250 100644
---
a/integration-tests/langchain4j-rag-bridge-ql4j/src/main/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/TestTenantFilterSupplier.java
+++
b/integration-tests/langchain4j-rag-bridge-ql4j/src/main/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/TestTenantFilterSupplier.java
@@ -20,7 +20,7 @@ import dev.langchain4j.rag.query.Query;
import dev.langchain4j.store.embedding.filter.Filter;
import dev.langchain4j.store.embedding.filter.MetadataFilterBuilder;
import jakarta.enterprise.context.ApplicationScoped;
-import
org.apache.camel.quarkus.component.support.langchain4j.RagRetrievalFilterSupplier;
+import
org.apache.camel.quarkus.component.langchain4j.embeddingstore.RagRetrievalFilterSupplier;
/**
* The retrieval-side isolation hook under test: every retrieval through a
produced augmentor is
diff --git
a/integration-tests/langchain4j-rag-bridge-ql4j/src/test/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/MultiAugmentorProfile.java
b/integration-tests/langchain4j-rag-bridge-ql4j/src/test/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/MultiAugmentorProfile.java
index 0f5cff5848..7c5e81f60f 100644
---
a/integration-tests/langchain4j-rag-bridge-ql4j/src/test/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/MultiAugmentorProfile.java
+++
b/integration-tests/langchain4j-rag-bridge-ql4j/src/test/java/org/apache/camel/quarkus/component/langchain4j/ragbridge/it/MultiAugmentorProfile.java
@@ -24,7 +24,7 @@ public class MultiAugmentorProfile implements
QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
// Two augmentors require exactly one designated default — without the
marking the build
- // fails (see
SupportQuarkusLangchain4jProcessor.resolveDesignatedDefault and its test)
+ // fails (see
Langchain4jEmbeddingstoreRagProcessor.resolveDesignatedDefault and its test)
return Map.of(
"quarkus.camel.langchain4j.rag.augmentors.products.embedding-store-name",
"products",
"quarkus.camel.langchain4j.rag.augmentors.products.default",
"true",