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 d222d7a708 Move AI tool specific code into the ai-tool extension
d222d7a708 is described below
commit d222d7a7086679229f4fad179317c635f581cc82
Author: James Netherton <[email protected]>
AuthorDate: Tue Sep 8 10:29:40 2026 +0100
Move AI tool specific code into the ai-tool extension
* Move AI tool specific code into the ai-tool extension
* Register the AI tool bridge only via the ai-tool extension, and say so
The bridge exposing ai-tool: routes to Quarkus LangChain4j AI services now
lives in camel-quarkus-ai-tool, so it is registered only when that extension
is a dependency. integration-tests/langchain4j-agent-ql4j was getting it
transitively via camel-quarkus-support-langchain4j and silently lost its
Camel tools, failing Langchain4jAgentTest.customAiService.
* Declare camel-quarkus-ai-tool in langchain4j-agent-ql4j, which uses
ai-tool: routes directly
* Warn at Camel context start when tools are registered but the bridge is
absent, so the misconfiguration is not silent
* Warn when @CamelAiTools is used without Quarkus LangChain4j on the
classpath, where the annotation has no effect
* Reject a blank @CamelAiTools tag at build time: it exposed every
registered tool to the service instead of the intended subset
* Make quarkus-jackson optional, since only the bridge uses an ObjectMapper
and Quarkus LangChain4j depends on quarkus-jackson itself
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 | 19 ++++
.../ROOT/pages/reference/extensions/ai-tool.adoc | 14 ++-
extensions-support/langchain4j/deployment/pom.xml | 5 -
.../SupportQuarkusLangchain4jProcessor.java | 107 -------------------
extensions-support/langchain4j/runtime/pom.xml | 5 -
.../langchain4j/QuarkusLangchain4jRecorder.java | 5 -
.../ai/tool}/deployment/AiToolPresent.java | 11 +-
.../ai/tool/deployment/AiToolProcessor.java | 118 +++++++++++++++++++++
.../tool/deployment/QuarkusLangchain4jPresent.java | 11 +-
extensions/ai-tool/runtime/pom.xml | 15 +++
extensions/ai-tool/runtime/src/main/doc/usage.adoc | 14 ++-
.../quarkus/component/ai/tool/AiToolRecorder.java} | 15 ++-
.../component/ai/tool}/AiToolSpecConverter.java | 2 +-
.../component/ai/tool/AiToolSpecConverterImpl.java | 18 ++--
.../component/ai/tool}/CamelAiToolProvider.java | 10 +-
.../quarkus/component/ai/tool}/CamelAiTools.java | 2 +-
.../ai/tool}/CamelAiToolsInterceptor.java | 2 +-
.../agent/deployment/CamelAiToolBridgeAbsent.java | 19 ++--
.../deployment/Langchain4jAgentProcessor.java | 13 +++
extensions/langchain4j-agent/runtime/pom.xml | 4 +
.../agent/Langchain4jAgentRecorder.java | 60 +++++++++++
integration-tests/ai-tool-langchain4j/pom.xml | 11 +-
.../langchain4j/it/service/AdminAiService.java | 2 +-
.../langchain4j/it/service/WeatherAiService.java | 2 +-
.../it/service/WeatherAiServiceOllama.java | 2 +-
.../it/service/WeatherAiServiceOllamaWrongTag.java | 2 +-
.../it/service/WeatherAiServiceWrongTag.java | 2 +-
integration-tests/langchain4j-agent-ql4j/pom.xml | 17 +++
28 files changed, 327 insertions(+), 180 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 099532dae0..d1f7b80309 100644
--- a/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
+++ b/docs/modules/ROOT/pages/migration-guide/3.40.0.adoc
@@ -19,6 +19,25 @@ To keep the previous behaviour for a context that relied on
the default, set it
quarkus.camel.ldap.dir-contexts."my-context".security-authentication=none
----
+== AI Tool extension changes
+
+=== The LangChain4j tool bridge moved to the AI Tool extension
+
+The bridge that exposes `ai-tool:` routes to LangChain4j AI services was part
of `camel-quarkus-support-langchain4j`, an internal artifact that applications
had to declare by hand alongside `camel-quarkus-ai-tool`. It is now part of
`camel-quarkus-ai-tool` itself, and that dependency can be removed.
+
+The `@CamelAiTools` annotation moved with it. Update the import.
+
+[source,java]
+----
+// Was: import
org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
+----
+
+The bridge is registered when `camel-quarkus-ai-tool` is a dependency and
Quarkus LangChain4j and
+`camel-langchain4j-agent` are on the classpath. Applications that used
`ai-tool:` routes without
+declaring `camel-quarkus-ai-tool` must now add it. A warning naming the
affected tools is logged at
+startup when they are registered without it. Its behaviour is otherwise
unchanged.
+
== JTA extension changes
=== Rollback and resume failures are no longer swallowed
diff --git a/docs/modules/ROOT/pages/reference/extensions/ai-tool.adoc
b/docs/modules/ROOT/pages/reference/extensions/ai-tool.adoc
index 86ff0a1f5f..cc1bf8a1af 100644
--- a/docs/modules/ROOT/pages/reference/extensions/ai-tool.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/ai-tool.adoc
@@ -100,5 +100,17 @@ endpoint's `tags` parameter — an agent without `tags`
receives no registry too
the agent's other sources, such as MCP clients or custom tool objects, are
unaffected).
Quarkus LangChain4j AI services (`@RegisterAiService` interfaces and
programmatically built
`AiServices`) receive every registered tool by default; restrict a service to
a subset with
-the `@CamelAiTools` annotation.
+the `org.apache.camel.quarkus.component.ai.tool.CamelAiTools` annotation.
+
+[source,java]
+----
+@RegisterAiService
+@CamelAiTools("support")
+public interface SupportAgent {
+ String chat(@UserMessage String message);
+}
+----
+
+The tool provider backing this is registered automatically when Quarkus
LangChain4j and
+`camel-langchain4j-agent` are on the classpath.
diff --git a/extensions-support/langchain4j/deployment/pom.xml
b/extensions-support/langchain4j/deployment/pom.xml
index cb956f0a65..0ca00589e8 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>org.apache.camel</groupId>
- <artifactId>camel-ai-tool</artifactId>
- <optional>true</optional>
- </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit-internal</artifactId>
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 815265a93f..25cadd2a55 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
@@ -17,8 +17,6 @@
package org.apache.camel.quarkus.component.support.langchain4j.deployment;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -31,10 +29,7 @@ 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.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
-import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
-import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.QualifierRegistrarBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
@@ -51,18 +46,11 @@ 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.gizmo.ClassCreator;
-import io.quarkus.gizmo.MethodCreator;
-import io.quarkus.gizmo.MethodDescriptor;
-import io.quarkus.gizmo.ResultHandle;
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.AiToolSpecConverter;
-import
org.apache.camel.quarkus.component.support.langchain4j.CamelAiToolProvider;
-import
org.apache.camel.quarkus.component.support.langchain4j.CamelAiToolsInterceptor;
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;
@@ -88,8 +76,6 @@ class SupportQuarkusLangchain4jProcessor {
public static final DotName REGISTER_AI_SERVICES_DOTNAME = DotName
.createSimple("io.quarkiverse.langchain4j.RegisterAiService");
- private static final DotName CAMEL_AI_TOOLS_DOTNAME = DotName
-
.createSimple("org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools");
private static final DotName EMBEDDING_STORE_NAME_DOTNAME = DotName
.createSimple("io.quarkiverse.langchain4j.EmbeddingStoreName");
private static final DotName RAG_RETRIEVAL_FILTER_SUPPLIER_DOTNAME =
DotName
@@ -174,85 +160,6 @@ class SupportQuarkusLangchain4jProcessor {
.build());
}
- @BuildStep(onlyIf = AiToolPresent.class)
- AdditionalBeanBuildItem registerCamelAiToolProvider() {
- LOG.info("Camel AI Tool detected - registering CamelAiToolProvider as
CDI bean for ToolProvider auto-discovery");
- return
AdditionalBeanBuildItem.unremovableOf(CamelAiToolProvider.class);
- }
-
- @BuildStep(onlyIf = AiToolPresent.class)
- void generateAiToolSpecConverter(BuildProducer<GeneratedBeanBuildItem>
generatedBeans,
- BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
-
- String generatedClassName =
"org.apache.camel.quarkus.component.support.langchain4j.AiToolSpecConverterImpl";
-
- try (ClassCreator cc = ClassCreator.builder()
- .classOutput(new GeneratedBeanGizmoAdaptor(generatedBeans))
- .className(generatedClassName)
- .interfaces(AiToolSpecConverter.class)
- .build()) {
-
- cc.addAnnotation(Singleton.class);
-
- String toolSpecClass =
"dev.langchain4j.agent.tool.ToolSpecification";
- String aiToolSpecClass =
"org.apache.camel.component.ai.tool.AiToolSpec";
-
- try (MethodCreator mc = cc.getMethodCreator("toToolSpecification",
- toolSpecClass, aiToolSpecClass)) {
-
- ResultHandle spec = mc.getMethodParam(0);
- ResultHandle result = mc.invokeStaticMethod(
- MethodDescriptor.ofMethod(
-
"org.apache.camel.component.langchain4j.agent.AiToolSpecToLangChain4j",
- "toToolSpecification",
- toolSpecClass,
- aiToolSpecClass),
- spec);
- mc.returnValue(result);
- }
- }
-
- unremovableBeans.produce(beanClassNames(generatedClassName));
- }
-
- @BuildStep(onlyIf = AiToolPresent.class)
- @Record(ExecutionTime.STATIC_INIT)
- void configureCamelAiToolTags(
- CombinedIndexBuildItem combinedIndex,
- BuildProducer<AdditionalBeanBuildItem> additionalBeans,
- QuarkusLangchain4jRecorder recorder) {
-
- IndexView index = combinedIndex.getIndex();
- Map<String, String> tagMap = new HashMap<>();
- for (AnnotationInstance annotation :
index.getAnnotations(CAMEL_AI_TOOLS_DOTNAME)) {
- if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
- String className =
annotation.target().asClass().name().toString();
- if (annotation.value() == null) {
- LOG.warnf("@CamelAiTools on %s has no value — skipping",
className);
- continue;
- }
- String tagValue = annotation.value().asString();
- if (tagValue.isBlank()) {
- LOG.warnf("@CamelAiTools on %s has blank value —
skipping", className);
- continue;
- }
- tagMap.put(className, tagValue);
- LOG.infof("Discovered @CamelAiTools(\"%s\") on %s", tagValue,
className);
- }
- }
-
- if (tagMap.isEmpty()) {
- return;
- }
-
- recorder.setCamelAiToolTagMap(tagMap);
-
- additionalBeans.produce(AdditionalBeanBuildItem.builder()
- .addBeanClasses(CamelAiToolsInterceptor.class)
- .setUnremovable()
- .build());
- }
-
// The retrieval filter supplier is only resolved programmatically at
augmentor creation
// time, so ArC would remove a user's implementation as unused.
@BuildStep
@@ -330,20 +237,6 @@ class SupportQuarkusLangchain4jProcessor {
CombinedIndexBuildItem indexBuildItem,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
- if (!new AiToolPresent().getAsBoolean()) {
- Collection<AnnotationInstance> aiToolsAnnotations =
indexBuildItem.getIndex()
- .getAnnotations(CAMEL_AI_TOOLS_DOTNAME);
- if (!aiToolsAnnotations.isEmpty()) {
- LOG.warnf("@CamelAiTools annotations found but
camel-langchain4j-agent is not on the classpath. "
- + "Add camel-langchain4j-agent dependency to enable
the Camel AI tool bridge. "
- + "Affected classes: %s",
- aiToolsAnnotations.stream()
- .filter(a -> a.target().kind() ==
AnnotationTarget.Kind.CLASS)
- .map(a ->
a.target().asClass().name().toString())
- .collect(Collectors.joining(", ")));
- }
- }
-
LOG.debug("Discovering classes annotated with @RegisterAiService to
mark implementation beans as unremovable");
for (AnnotationInstance instance :
indexBuildItem.getIndex().getAnnotations(REGISTER_AI_SERVICES_DOTNAME)) {
diff --git a/extensions-support/langchain4j/runtime/pom.xml
b/extensions-support/langchain4j/runtime/pom.xml
index 15a1effe91..2569f94a27 100644
--- a/extensions-support/langchain4j/runtime/pom.xml
+++ b/extensions-support/langchain4j/runtime/pom.xml
@@ -55,11 +55,6 @@
<artifactId>quarkus-langchain4j-core</artifactId>
<optional>true</optional>
</dependency>
- <dependency>
- <groupId>org.apache.camel</groupId>
- <artifactId>camel-ai-tool</artifactId>
- <optional>true</optional>
- </dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-langchain4j-agent</artifactId>
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 7b80f57df8..cb8dbd37a9 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
@@ -19,7 +19,6 @@ 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.Map;
import java.util.function.Supplier;
import dev.langchain4j.data.segment.TextSegment;
@@ -45,10 +44,6 @@ public class QuarkusLangchain4jRecorder {
private static final TypeLiteral<EmbeddingStore<TextSegment>>
EMBEDDING_STORE_TYPE = new TypeLiteral<>() {
};
- public void setCamelAiToolTagMap(Map<String, String> tagMap) {
- CamelAiToolProvider.TAG_MAP.putAll(tagMap);
- }
-
public void registerNamedEmbeddingStores(RuntimeValue<Registry>
camelRegistry) {
List<InstanceHandle<EmbeddingStore<TextSegment>>> handles =
Arc.container()
.listAll(EMBEDDING_STORE_TYPE, Any.Literal.INSTANCE);
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolPresent.java
similarity index 76%
copy from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
copy to
extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolPresent.java
index 7168ec2d43..f85d13c852 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
+++
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolPresent.java
@@ -14,20 +14,21 @@
* 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.ai.tool.deployment;
import java.util.function.BooleanSupplier;
+/**
+ * Whether {@code camel-langchain4j-agent} is on the classpath, providing the
+ * {@code AiToolSpec} to {@code ToolSpecification} conversion the bridge is
generated against.
+ */
public class AiToolPresent implements BooleanSupplier {
- private static final String AI_TOOL_REGISTRY_CLASS =
"org.apache.camel.component.ai.tool.AiToolRegistry";
private static final String AI_TOOL_SPEC_CONVERTER_CLASS =
"org.apache.camel.component.langchain4j.agent.AiToolSpecToLangChain4j";
@Override
public boolean getAsBoolean() {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
- cl.loadClass(AI_TOOL_REGISTRY_CLASS);
- cl.loadClass(AI_TOOL_SPEC_CONVERTER_CLASS);
+
Thread.currentThread().getContextClassLoader().loadClass(AI_TOOL_SPEC_CONVERTER_CLASS);
return true;
} catch (ClassNotFoundException e) {
return false;
diff --git
a/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
index e132611dd1..579ebab368 100644
---
a/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
+++
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
@@ -16,15 +16,133 @@
*/
package org.apache.camel.quarkus.component.ai.tool.deployment;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import
io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.runtime.configuration.ConfigurationException;
+import org.apache.camel.quarkus.component.ai.tool.AiToolRecorder;
+import org.apache.camel.quarkus.component.ai.tool.AiToolSpecConverterImpl;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiToolProvider;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiToolsInterceptor;
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
+import org.jboss.jandex.DotName;
+import org.jboss.jandex.IndexView;
+import org.jboss.logging.Logger;
class AiToolProcessor {
private static final String FEATURE = "camel-ai-tool";
+ private static final DotName CAMEL_AI_TOOLS_DOTNAME = DotName
+
.createSimple("org.apache.camel.quarkus.component.ai.tool.CamelAiTools");
+
+ private static final Logger LOG = Logger.getLogger(AiToolProcessor.class);
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
+
+ /**
+ * Diagnostics for {@code @CamelAiTools} usage. Runs unconditionally: the
pieces the annotation needs are exactly
+ * what may be missing, so gating this on their presence would silence the
report.
+ */
+ @BuildStep
+ void validateCamelAiToolsUsage(
+ CombinedIndexBuildItem combinedIndex,
+ BuildProducer<ValidationErrorBuildItem> validationErrors) {
+
+ List<AnnotationInstance> annotations = combinedIndex.getIndex()
+ .getAnnotations(CAMEL_AI_TOOLS_DOTNAME)
+ .stream()
+ .filter(annotation -> annotation.target().kind() ==
AnnotationTarget.Kind.CLASS)
+ .toList();
+
+ if (annotations.isEmpty()) {
+ return;
+ }
+
+ String affected = annotations.stream()
+ .map(annotation ->
annotation.target().asClass().name().toString())
+ .collect(Collectors.joining(", "));
+
+ if (!new QuarkusLangchain4jPresent().getAsBoolean()) {
+ LOG.warnf("@CamelAiTools annotations found but Quarkus LangChain4j
is not on the classpath. "
+ + "The annotation only filters tools provided to Quarkus
LangChain4j AI services, so it has no "
+ + "effect here. Affected classes: %s", affected);
+ return;
+ }
+
+ if (!new AiToolPresent().getAsBoolean()) {
+ LOG.warnf("@CamelAiTools annotations found but
camel-langchain4j-agent is not on the classpath. "
+ + "Add camel-langchain4j-agent dependency to enable the
Camel AI tool bridge. "
+ + "Affected classes: %s", affected);
+ return;
+ }
+
+ for (AnnotationInstance annotation : annotations) {
+ if (annotation.value() == null ||
annotation.value().asString().isBlank()) {
+ validationErrors.produce(new ValidationErrorBuildItem(new
ConfigurationException(
+ "@CamelAiTools on " +
annotation.target().asClass().name() + " has no tag. An empty tag "
+ + "would expose every registered Camel AI tool
to the service instead of the "
+ + "intended subset. Give it the tag used by
the ai-tool: routes it should see, or "
+ + "remove the annotation to receive all tools
deliberately.")));
+ }
+ }
+ }
+
+ @BuildStep(onlyIf = { QuarkusLangchain4jPresent.class, AiToolPresent.class
})
+ AdditionalBeanBuildItem registerCamelAiToolProvider() {
+ LOG.info("Camel AI Tool detected - registering CamelAiToolProvider as
CDI bean for ToolProvider auto-discovery");
+ return
AdditionalBeanBuildItem.unremovableOf(CamelAiToolProvider.class);
+ }
+
+ @BuildStep(onlyIf = { QuarkusLangchain4jPresent.class, AiToolPresent.class
})
+ AdditionalBeanBuildItem registerAiToolSpecConverter() {
+ return
AdditionalBeanBuildItem.unremovableOf(AiToolSpecConverterImpl.class);
+ }
+
+ @BuildStep(onlyIf = { QuarkusLangchain4jPresent.class, AiToolPresent.class
})
+ @Record(ExecutionTime.STATIC_INIT)
+ void configureCamelAiToolTags(
+ CombinedIndexBuildItem combinedIndex,
+ BuildProducer<AdditionalBeanBuildItem> additionalBeans,
+ AiToolRecorder recorder) {
+
+ IndexView index = combinedIndex.getIndex();
+ Map<String, String> tagMap = new HashMap<>();
+ for (AnnotationInstance annotation :
index.getAnnotations(CAMEL_AI_TOOLS_DOTNAME)) {
+ if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) {
+ String className =
annotation.target().asClass().name().toString();
+ // Blank tags are rejected by validateCamelAiToolsUsage
+ if (annotation.value() == null ||
annotation.value().asString().isBlank()) {
+ continue;
+ }
+ String tagValue = annotation.value().asString();
+ tagMap.put(className, tagValue);
+ LOG.infof("Discovered @CamelAiTools(\"%s\") on %s", tagValue,
className);
+ }
+ }
+
+ if (tagMap.isEmpty()) {
+ return;
+ }
+
+ recorder.setCamelAiToolTagMap(tagMap);
+
+ additionalBeans.produce(AdditionalBeanBuildItem.builder()
+ .addBeanClasses(CamelAiToolsInterceptor.class)
+ .setUnremovable()
+ .build());
+ }
}
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/QuarkusLangchain4jPresent.java
similarity index 64%
copy from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
copy to
extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/QuarkusLangchain4jPresent.java
index 7168ec2d43..0338954600 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
+++
b/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/QuarkusLangchain4jPresent.java
@@ -14,20 +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.ai.tool.deployment;
import java.util.function.BooleanSupplier;
-public class AiToolPresent implements BooleanSupplier {
- private static final String AI_TOOL_REGISTRY_CLASS =
"org.apache.camel.component.ai.tool.AiToolRegistry";
- private static final String AI_TOOL_SPEC_CONVERTER_CLASS =
"org.apache.camel.component.langchain4j.agent.AiToolSpecToLangChain4j";
+public class QuarkusLangchain4jPresent implements BooleanSupplier {
+ private static final String REGISTER_AI_SERVICE_CLASS =
"io.quarkiverse.langchain4j.RegisterAiService";
@Override
public boolean getAsBoolean() {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
- cl.loadClass(AI_TOOL_REGISTRY_CLASS);
- cl.loadClass(AI_TOOL_SPEC_CONVERTER_CLASS);
+
Thread.currentThread().getContextClassLoader().loadClass(REGISTER_AI_SERVICE_CLASS);
return true;
} catch (ClassNotFoundException e) {
return false;
diff --git a/extensions/ai-tool/runtime/pom.xml
b/extensions/ai-tool/runtime/pom.xml
index b2ae74c9b4..53a5da1b59 100644
--- a/extensions/ai-tool/runtime/pom.xml
+++ b/extensions/ai-tool/runtime/pom.xml
@@ -45,6 +45,21 @@
<groupId>org.apache.camel</groupId>
<artifactId>camel-ai-tool</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-jackson</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>dev.langchain4j</groupId>
+ <artifactId>langchain4j</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-langchain4j-agent</artifactId>
+ <optional>true</optional>
+ </dependency>
</dependencies>
<build>
diff --git a/extensions/ai-tool/runtime/src/main/doc/usage.adoc
b/extensions/ai-tool/runtime/src/main/doc/usage.adoc
index 555525dda4..cec5fd7f4c 100644
--- a/extensions/ai-tool/runtime/src/main/doc/usage.adoc
+++ b/extensions/ai-tool/runtime/src/main/doc/usage.adoc
@@ -49,4 +49,16 @@ endpoint's `tags` parameter — an agent without `tags`
receives no registry too
the agent's other sources, such as MCP clients or custom tool objects, are
unaffected).
Quarkus LangChain4j AI services (`@RegisterAiService` interfaces and
programmatically built
`AiServices`) receive every registered tool by default; restrict a service to
a subset with
-the `@CamelAiTools` annotation.
+the `org.apache.camel.quarkus.component.ai.tool.CamelAiTools` annotation.
+
+[source,java]
+----
+@RegisterAiService
+@CamelAiTools("support")
+public interface SupportAgent {
+ String chat(@UserMessage String message);
+}
+----
+
+The tool provider backing this is registered automatically when Quarkus
LangChain4j and
+`camel-langchain4j-agent` are on the classpath.
diff --git
a/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolRecorder.java
similarity index 70%
copy from
extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
copy to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolRecorder.java
index e132611dd1..be4b8d4172 100644
---
a/extensions/ai-tool/deployment/src/main/java/org/apache/camel/quarkus/component/ai/tool/deployment/AiToolProcessor.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolRecorder.java
@@ -14,17 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.quarkus.component.ai.tool.deployment;
+package org.apache.camel.quarkus.component.ai.tool;
-import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
+import java.util.Map;
-class AiToolProcessor {
+import io.quarkus.runtime.annotations.Recorder;
- private static final String FEATURE = "camel-ai-tool";
+@Recorder
+public class AiToolRecorder {
- @BuildStep
- FeatureBuildItem feature() {
- return new FeatureBuildItem(FEATURE);
+ public void setCamelAiToolTagMap(Map<String, String> tagMap) {
+ CamelAiToolProvider.TAG_MAP.putAll(tagMap);
}
}
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverter.java
similarity index 95%
copy from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
copy to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverter.java
index adf443d9fc..9f749df0b1 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverter.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.ai.tool;
import dev.langchain4j.agent.tool.ToolSpecification;
import org.apache.camel.component.ai.tool.AiToolSpec;
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverterImpl.java
similarity index 62%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
rename to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverterImpl.java
index adf443d9fc..adc8afb42c 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/AiToolSpecConverter.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/AiToolSpecConverterImpl.java
@@ -14,16 +14,22 @@
* 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.ai.tool;
import dev.langchain4j.agent.tool.ToolSpecification;
+import jakarta.inject.Singleton;
import org.apache.camel.component.ai.tool.AiToolSpec;
+import org.apache.camel.component.langchain4j.agent.AiToolSpecToLangChain4j;
/**
- * Converts a Camel {@link AiToolSpec} to a langchain4j {@link
ToolSpecification}.
- * The implementation is generated at build time via Gizmo to avoid a
compile-time
- * dependency on {@code camel-langchain4j-agent}.
+ * Resolves {@code camel-langchain4j-agent} types, so it is only registered as
a bean when that optional dependency is
+ * present. See {@code AiToolProcessor}.
*/
-public interface AiToolSpecConverter {
- ToolSpecification toToolSpecification(AiToolSpec spec);
+@Singleton
+public class AiToolSpecConverterImpl implements AiToolSpecConverter {
+
+ @Override
+ public ToolSpecification toToolSpecification(AiToolSpec spec) {
+ return AiToolSpecToLangChain4j.toToolSpecification(spec);
+ }
}
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolProvider.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolProvider.java
similarity index 94%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolProvider.java
rename to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolProvider.java
index 2c8c093f69..b21f27a699 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolProvider.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolProvider.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.ai.tool;
import java.util.Map;
import java.util.Set;
@@ -39,10 +39,10 @@ import org.apache.camel.support.DefaultExchange;
import org.jboss.logging.Logger;
/**
- * Bridges Camel's {@link AiToolRegistry} to langchain4j's {@link
ToolProvider} SPI. When registered as a CDI bean (done
- * automatically by the deployment processor when both {@code camel-ai-tool}
and quarkus-langchain4j are on the
- * classpath), all Camel routes registered via {@code ai-tool:} endpoints
become available to
- * {@code @RegisterAiService} AI services without explicit configuration.
+ * Bridges Camel's {@link AiToolRegistry} to langchain4j's {@link
ToolProvider} SPI. The deployment processor registers
+ * it as a CDI bean when Quarkus LangChain4j and {@code
camel-langchain4j-agent} are on the classpath, at which point
+ * all Camel routes registered via {@code ai-tool:} endpoints become available
to {@code @RegisterAiService} AI
+ * services without explicit configuration.
*/
public class CamelAiToolProvider implements ToolProvider {
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiTools.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiTools.java
similarity index 96%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiTools.java
rename to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiTools.java
index 8be0eeacbf..0e2efde5ff 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiTools.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiTools.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.ai.tool;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
diff --git
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolsInterceptor.java
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolsInterceptor.java
similarity index 97%
rename from
extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolsInterceptor.java
rename to
extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolsInterceptor.java
index 239ab26976..8ccd7ec0ff 100644
---
a/extensions-support/langchain4j/runtime/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/CamelAiToolsInterceptor.java
+++
b/extensions/ai-tool/runtime/src/main/java/org/apache/camel/quarkus/component/ai/tool/CamelAiToolsInterceptor.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.ai.tool;
import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
diff --git
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/CamelAiToolBridgeAbsent.java
similarity index 64%
rename from
extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
rename to
extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/CamelAiToolBridgeAbsent.java
index 7168ec2d43..2e2dcab46d 100644
---
a/extensions-support/langchain4j/deployment/src/main/java/org/apache/camel/quarkus/component/support/langchain4j/deployment/AiToolPresent.java
+++
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/CamelAiToolBridgeAbsent.java
@@ -14,23 +14,24 @@
* 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.agent.deployment;
import java.util.function.BooleanSupplier;
-public class AiToolPresent implements BooleanSupplier {
- private static final String AI_TOOL_REGISTRY_CLASS =
"org.apache.camel.component.ai.tool.AiToolRegistry";
- private static final String AI_TOOL_SPEC_CONVERTER_CLASS =
"org.apache.camel.component.langchain4j.agent.AiToolSpecToLangChain4j";
+/**
+ * Whether the LangChain4j tool bridge from {@code camel-quarkus-ai-tool} is
missing. Without it, tools registered in
+ * the {@code AiToolRegistry} are not exposed to Quarkus LangChain4j AI
services.
+ */
+public class CamelAiToolBridgeAbsent implements BooleanSupplier {
+ private static final String CAMEL_AI_TOOL_PROVIDER_CLASS =
"org.apache.camel.quarkus.component.ai.tool.CamelAiToolProvider";
@Override
public boolean getAsBoolean() {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
- cl.loadClass(AI_TOOL_REGISTRY_CLASS);
- cl.loadClass(AI_TOOL_SPEC_CONVERTER_CLASS);
- return true;
- } catch (ClassNotFoundException e) {
+
Thread.currentThread().getContextClassLoader().loadClass(CAMEL_AI_TOOL_PROVIDER_CLASS);
return false;
+ } catch (ClassNotFoundException e) {
+ return true;
}
}
}
diff --git
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
index 8498e4c5e8..716e51a6bb 100644
---
a/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
+++
b/extensions/langchain4j-agent/deployment/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/deployment/Langchain4jAgentProcessor.java
@@ -20,10 +20,15 @@ import java.util.Set;
import java.util.stream.Collectors;
import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.ExecutionTime;
+import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
+import
org.apache.camel.quarkus.component.langchain4j.agent.Langchain4jAgentRecorder;
+import
org.apache.camel.quarkus.component.support.langchain4j.deployment.QuarkusLangchain4jPresent;
+import
org.apache.camel.quarkus.core.deployment.spi.RuntimeCamelContextCustomizerBuildItem;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
@@ -51,4 +56,12 @@ class Langchain4jAgentProcessor {
.methods(true)
.build();
}
+
+ // The bridge that exposes registry tools to Quarkus LangChain4j AI
services ships with camel-quarkus-ai-tool.
+ // Without that extension the tools are registered but silently
unreachable, so flag it at startup.
+ @BuildStep(onlyIf = { QuarkusLangchain4jPresent.class,
CamelAiToolBridgeAbsent.class })
+ @Record(ExecutionTime.RUNTIME_INIT)
+ RuntimeCamelContextCustomizerBuildItem
warnAboutMissingAiToolBridge(Langchain4jAgentRecorder recorder) {
+ return new
RuntimeCamelContextCustomizerBuildItem(recorder.createMissingAiToolBridgeWarning());
+ }
}
diff --git a/extensions/langchain4j-agent/runtime/pom.xml
b/extensions/langchain4j-agent/runtime/pom.xml
index 17e0a4f2af..deb3d53cc0 100644
--- a/extensions/langchain4j-agent/runtime/pom.xml
+++ b/extensions/langchain4j-agent/runtime/pom.xml
@@ -49,6 +49,10 @@
<groupId>org.apache.camel</groupId>
<artifactId>camel-langchain4j-agent</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-ai-tool</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git
a/extensions/langchain4j-agent/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/Langchain4jAgentRecorder.java
b/extensions/langchain4j-agent/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/Langchain4jAgentRecorder.java
new file mode 100644
index 0000000000..3eb26166d8
--- /dev/null
+++
b/extensions/langchain4j-agent/runtime/src/main/java/org/apache/camel/quarkus/component/langchain4j/agent/Langchain4jAgentRecorder.java
@@ -0,0 +1,60 @@
+/*
+ * 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.apache.camel.quarkus.component.langchain4j.agent;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.ai.tool.AiToolRegistry;
+import org.apache.camel.component.ai.tool.AiToolSpec;
+import org.apache.camel.spi.CamelContextCustomizer;
+import org.apache.camel.support.LifecycleStrategySupport;
+import org.jboss.logging.Logger;
+
+@Recorder
+public class Langchain4jAgentRecorder {
+
+ private static final Logger LOG =
Logger.getLogger(Langchain4jAgentRecorder.class);
+
+ /**
+ * Creates a customizer that warns when Camel AI tools are registered but
the tool bridge shipped with
+ * {@code camel-quarkus-ai-tool} is absent, in which case Quarkus
LangChain4j AI services receive no Camel tools.
+ * Tools are registered as {@code ai-tool:} routes start, so the check
runs once the Camel context has started.
+ */
+ public RuntimeValue<CamelContextCustomizer>
createMissingAiToolBridgeWarning() {
+ CamelContextCustomizer customizer = camelContext ->
camelContext.addLifecycleStrategy(
+ LifecycleStrategySupport.adapt(
+
LifecycleStrategySupport.onCamelContextStarted(Langchain4jAgentRecorder::warnIfToolsRegistered)));
+ return new RuntimeValue<>(customizer);
+ }
+
+ private static void warnIfToolsRegistered(CamelContext camelContext) {
+ Set<AiToolSpec> tools =
AiToolRegistry.getOrCreate(camelContext).getAllTools();
+ if (tools.isEmpty()) {
+ return;
+ }
+
+ LOG.warnf("Camel AI tools are registered but camel-quarkus-ai-tool is
not on the classpath. "
+ + "Quarkus LangChain4j AI services will not receive them. "
+ + "Add the camel-quarkus-ai-tool dependency to enable the
Camel AI tool bridge. "
+ + "Affected tools: %s",
+
tools.stream().map(AiToolSpec::getName).sorted().collect(Collectors.joining(",
")));
+ }
+}
diff --git a/integration-tests/ai-tool-langchain4j/pom.xml
b/integration-tests/ai-tool-langchain4j/pom.xml
index f1a297c9f6..e26b0b399f 100644
--- a/integration-tests/ai-tool-langchain4j/pom.xml
+++ b/integration-tests/ai-tool-langchain4j/pom.xml
@@ -60,15 +60,10 @@
<artifactId>quarkus-rest</artifactId>
</dependency>
- <!-- Camel Quarkus LangChain4j support (provides the AiToolRegistry →
ToolProvider bridge) -->
+ <!-- Required at runtime for AiToolSpec → ToolSpecification conversion
(optional in the ai-tool extension) -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
- <artifactId>camel-quarkus-support-langchain4j</artifactId>
- </dependency>
- <!-- Required at runtime for AiToolSpec → ToolSpecification conversion
(optional in support module) -->
- <dependency>
- <groupId>org.apache.camel</groupId>
- <artifactId>camel-langchain4j-agent</artifactId>
+ <artifactId>camel-quarkus-langchain4j-agent</artifactId>
</dependency>
<!-- Quarkus LangChain4j -->
<dependency>
@@ -192,7 +187,7 @@
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
-
<artifactId>camel-quarkus-support-langchain4j-deployment</artifactId>
+
<artifactId>camel-quarkus-langchain4j-agent-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
diff --git
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/AdminAiService.java
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/AdminAiService.java
index b54e0d4d12..9943aa964d 100644
---
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/AdminAiService.java
+++
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/AdminAiService.java
@@ -19,8 +19,8 @@ package
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.service;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
import
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.model.AdminToolCallingChatModel;
-import org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
@ApplicationScoped
@RegisterAiService(chatLanguageModelSupplier = AdminToolCallingChatModel.class)
diff --git
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiService.java
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiService.java
index 9a1e8501a1..8a0679c09a 100644
---
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiService.java
+++
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiService.java
@@ -19,8 +19,8 @@ package
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.service;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
import
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.model.WeatherToolCallingChatModel;
-import org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
@ApplicationScoped
@RegisterAiService(chatLanguageModelSupplier =
WeatherToolCallingChatModel.class)
diff --git
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllama.java
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllama.java
index 76829813cb..5fbb4415c2 100644
---
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllama.java
+++
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllama.java
@@ -19,7 +19,7 @@ package
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.service;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
-import org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
@ApplicationScoped
@RegisterAiService
diff --git
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllamaWrongTag.java
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllamaWrongTag.java
index 1545589a33..b69596224f 100644
---
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllamaWrongTag.java
+++
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceOllamaWrongTag.java
@@ -19,7 +19,7 @@ package
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.service;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
-import org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
@ApplicationScoped
@RegisterAiService
diff --git
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceWrongTag.java
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceWrongTag.java
index 7e4a78d7b6..929a950021 100644
---
a/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceWrongTag.java
+++
b/integration-tests/ai-tool-langchain4j/src/main/java/org/apache/camel/quarkus/component/ai/tool/langchain4j/it/service/WeatherAiServiceWrongTag.java
@@ -19,8 +19,8 @@ package
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.service;
import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.quarkus.component.ai.tool.CamelAiTools;
import
org.apache.camel.quarkus.component.ai.tool.langchain4j.it.model.WrongTagToolCallingChatModel;
-import org.apache.camel.quarkus.component.support.langchain4j.CamelAiTools;
@ApplicationScoped
@RegisterAiService(chatLanguageModelSupplier =
WrongTagToolCallingChatModel.class)
diff --git a/integration-tests/langchain4j-agent-ql4j/pom.xml
b/integration-tests/langchain4j-agent-ql4j/pom.xml
index c58cc7e135..634ebcd4b9 100644
--- a/integration-tests/langchain4j-agent-ql4j/pom.xml
+++ b/integration-tests/langchain4j-agent-ql4j/pom.xml
@@ -43,6 +43,10 @@
</dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-ai-tool</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct</artifactId>
@@ -191,6 +195,19 @@
</activation>
<dependencies>
<!-- The following dependencies guarantee that this module is
built after them. You can update them by running `mvn process-resources
-Pformat -N` from the source tree root directory -->
+ <dependency>
+ <groupId>org.apache.camel.quarkus</groupId>
+ <artifactId>camel-quarkus-ai-tool-deployment</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-direct-deployment</artifactId>