This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5ef1539bacbb CAMEL-22851: Implement native tool-search-tool for
langchain4j-tools component (#20996)
5ef1539bacbb is described below
commit 5ef1539bacbb4732ed2610db95896715a98e916b
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Feb 5 15:46:16 2026 +0100
CAMEL-22851: Implement native tool-search-tool for langchain4j-tools
component (#20996)
This commit implements a native tool-search-tool feature for the
camel-langchain4j-tools component that allows LLMs to discover and access tools
dynamically without consuming the entire context window.
---
.../catalog/components/langchain4j-tools.json | 17 +-
.../tools/LangChain4jToolsEndpointConfigurer.java | 3 +
.../tools/LangChain4jToolsEndpointUriFactory.java | 3 +-
.../langchain4j/tools/langchain4j-tools.json | 17 +-
.../src/main/docs/langchain4j-tools-component.adoc | 79 +++++++++
.../tools/LangChain4jToolsEndpoint.java | 54 ++++++-
.../tools/LangChain4jToolsProducer.java | 89 +++++++++++
.../langchain4j/tools/ToolSearchTool.java | 135 ++++++++++++++++
.../tools/spec/CamelToolExecutorCache.java | 54 ++++++-
.../tools/spec/CamelToolSpecification.java | 21 ++-
.../langchain4j/tools/LangChain4jToolTest.java | 85 ++++++++++
.../tools/ToolSearchToolFormatTest.java | 121 ++++++++++++++
.../langchain4j/tools/ToolSearchToolTest.java | 177 +++++++++++++++++++++
.../tools/spec/CamelToolExecutorCacheTest.java | 160 +++++++++++++++++++
.../LangChain4jToolsEndpointBuilderFactory.java | 34 ++++
15 files changed, 1024 insertions(+), 25 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
index ab847741672e..d108c3e84d72 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
@@ -34,13 +34,14 @@
"toolId": { "index": 0, "kind": "path", "displayName": "Tool Id", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tool id" },
"tags": { "index": 1, "kind": "parameter", "displayName": "Tags", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tags for the tools" },
"description": { "index": 2, "kind": "parameter", "displayName":
"Description", "group": "consumer", "label": "consumer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tool description" },
- "name": { "index": 3, "kind": "parameter", "displayName": "Name", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Tool name" },
- "parameters": { "index": 4, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"description": "List of Tool parameters in the form of parameter.=. This is a
multi-value option with prefix: parameter." },
- "bridgeErrorHandler": { "index": 5, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
- "camelToolParameter": { "index": 6, "kind": "parameter", "displayName":
"Camel Tool Parameter", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Tool's Camel Parameters, programmatically define Tool
description and parameters" },
- "exceptionHandler": { "index": 7, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By def [...]
- "exchangePattern": { "index": 8, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "chatModel": { "index": 10, "kind": "parameter", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.cha [...]
+ "exposed": { "index": 3, "kind": "parameter", "displayName": "Exposed",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": true, "description": "Whether the tool
is automatically exposed to the LLM. When false, the tool is added to a
searchable list and can be discovered via the tool-search-tool" },
+ "name": { "index": 4, "kind": "parameter", "displayName": "Name", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Tool name" },
+ "parameters": { "index": 5, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"description": "List of Tool parameters in the form of parameter.=. This is a
multi-value option with prefix: parameter." },
+ "bridgeErrorHandler": { "index": 6, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
+ "camelToolParameter": { "index": 7, "kind": "parameter", "displayName":
"Camel Tool Parameter", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Tool's Camel Parameters, programmatically define Tool
description and parameters" },
+ "exceptionHandler": { "index": 8, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By def [...]
+ "exchangePattern": { "index": 9, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
+ "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "chatModel": { "index": 11, "kind": "parameter", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.cha [...]
}
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java
b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java
index 1eb3697ccef6..65185ab644f1 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointConfigurer.java
@@ -34,6 +34,7 @@ public class LangChain4jToolsEndpointConfigurer extends
PropertyConfigurerSuppor
case "exceptionHandler":
target.setExceptionHandler(property(camelContext,
org.apache.camel.spi.ExceptionHandler.class, value)); return true;
case "exchangepattern":
case "exchangePattern":
target.setExchangePattern(property(camelContext,
org.apache.camel.ExchangePattern.class, value)); return true;
+ case "exposed": target.setExposed(property(camelContext,
boolean.class, value)); return true;
case "lazystartproducer":
case "lazyStartProducer":
target.setLazyStartProducer(property(camelContext, boolean.class, value));
return true;
case "name": target.setName(property(camelContext,
java.lang.String.class, value)); return true;
@@ -62,6 +63,7 @@ public class LangChain4jToolsEndpointConfigurer extends
PropertyConfigurerSuppor
case "exceptionHandler": return
org.apache.camel.spi.ExceptionHandler.class;
case "exchangepattern":
case "exchangePattern": return org.apache.camel.ExchangePattern.class;
+ case "exposed": return boolean.class;
case "lazystartproducer":
case "lazyStartProducer": return boolean.class;
case "name": return java.lang.String.class;
@@ -86,6 +88,7 @@ public class LangChain4jToolsEndpointConfigurer extends
PropertyConfigurerSuppor
case "exceptionHandler": return target.getExceptionHandler();
case "exchangepattern":
case "exchangePattern": return target.getExchangePattern();
+ case "exposed": return target.isExposed();
case "lazystartproducer":
case "lazyStartProducer": return target.isLazyStartProducer();
case "name": return target.getName();
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointUriFactory.java
b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointUriFactory.java
index f67e9c5fe8ce..ef88f458975f 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointUriFactory.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/generated/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpointUriFactory.java
@@ -23,13 +23,14 @@ public class LangChain4jToolsEndpointUriFactory extends
org.apache.camel.support
private static final Set<String> SECRET_PROPERTY_NAMES;
private static final Map<String, String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(11);
+ Set<String> props = new HashSet<>(12);
props.add("bridgeErrorHandler");
props.add("camelToolParameter");
props.add("chatModel");
props.add("description");
props.add("exceptionHandler");
props.add("exchangePattern");
+ props.add("exposed");
props.add("lazyStartProducer");
props.add("name");
props.add("parameters");
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
index ab847741672e..d108c3e84d72 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
+++
b/components/camel-ai/camel-langchain4j-tools/src/generated/resources/META-INF/org/apache/camel/component/langchain4j/tools/langchain4j-tools.json
@@ -34,13 +34,14 @@
"toolId": { "index": 0, "kind": "path", "displayName": "Tool Id", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tool id" },
"tags": { "index": 1, "kind": "parameter", "displayName": "Tags", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The tags for the tools" },
"description": { "index": 2, "kind": "parameter", "displayName":
"Description", "group": "consumer", "label": "consumer", "required": false,
"type": "string", "javaType": "java.lang.String", "deprecated": false,
"deprecationNote": "", "autowired": false, "secret": false, "description":
"Tool description" },
- "name": { "index": 3, "kind": "parameter", "displayName": "Name", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Tool name" },
- "parameters": { "index": 4, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"description": "List of Tool parameters in the form of parameter.=. This is a
multi-value option with prefix: parameter." },
- "bridgeErrorHandler": { "index": 5, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
- "camelToolParameter": { "index": 6, "kind": "parameter", "displayName":
"Camel Tool Parameter", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Tool's Camel Parameters, programmatically define Tool
description and parameters" },
- "exceptionHandler": { "index": 7, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By def [...]
- "exchangePattern": { "index": 8, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
- "lazyStartProducer": { "index": 9, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produc [...]
- "chatModel": { "index": 10, "kind": "parameter", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.cha [...]
+ "exposed": { "index": 3, "kind": "parameter", "displayName": "Exposed",
"group": "consumer", "label": "consumer", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "defaultValue": true, "description": "Whether the tool
is automatically exposed to the LLM. When false, the tool is added to a
searchable list and can be discovered via the tool-search-tool" },
+ "name": { "index": 4, "kind": "parameter", "displayName": "Name", "group":
"consumer", "label": "consumer", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "Tool name" },
+ "parameters": { "index": 5, "kind": "parameter", "displayName":
"Parameters", "group": "consumer", "label": "consumer", "required": false,
"type": "object", "javaType": "java.util.Map<java.lang.String,
java.lang.String>", "prefix": "parameter.", "multiValue": true, "deprecated":
false, "deprecationNote": "", "autowired": false, "secret": false,
"description": "List of Tool parameters in the form of parameter.=. This is a
multi-value option with prefix: parameter." },
+ "bridgeErrorHandler": { "index": 6, "kind": "parameter", "displayName":
"Bridge Error Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Allows for bridging the consumer to the
Camel routing Error Handler, which mean any exceptions (if possible) occurred
while the Camel consumer is trying to pickup incoming [...]
+ "camelToolParameter": { "index": 7, "kind": "parameter", "displayName":
"Camel Tool Parameter", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "Tool's Camel Parameters, programmatically define Tool
description and parameters" },
+ "exceptionHandler": { "index": 8, "kind": "parameter", "displayName":
"Exception Handler", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By def [...]
+ "exchangePattern": { "index": 9, "kind": "parameter", "displayName":
"Exchange Pattern", "group": "consumer (advanced)", "label":
"consumer,advanced", "required": false, "type": "enum", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ],
"deprecated": false, "autowired": false, "secret": false, "description": "Sets
the exchange pattern when the consumer creates an exchange." },
+ "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName":
"Lazy Start Producer", "group": "producer (advanced)", "label":
"producer,advanced", "required": false, "type": "boolean", "javaType":
"boolean", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": false, "description": "Whether the producer should be started
lazy (on the first message). By starting lazy you can use this to allow
CamelContext and routes to startup in situations where a produ [...]
+ "chatModel": { "index": 11, "kind": "parameter", "displayName": "Chat
Model", "group": "advanced", "label": "advanced", "required": false, "type":
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration",
"configurationField": "configuration", "description": "Chat Model of type
dev.langchain4j.model.cha [...]
}
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/docs/langchain4j-tools-component.adoc
b/components/camel-ai/camel-langchain4j-tools/src/main/docs/langchain4j-tools-component.adoc
index 517b96194389..093e376d1edc 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/docs/langchain4j-tools-component.adoc
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/docs/langchain4j-tools-component.adoc
@@ -181,3 +181,82 @@ To switch to another Large Language Model and its
corresponding dependency, repl
In some circumstances, the LLM may decide not to call a tool.
This is a valid scenario that needs to be handled by application developers.
To do so, developers can get the `LangChain4jTools.NO_TOOLS_CALLED_HEADER`
from the exchange.
+
+=== Tool Search Tool
+
+The Tool Search Tool is a native feature that allows LLMs to discover and
access tools dynamically without consuming the entire context window with tool
definitions.
+
+==== Overview
+
+When you have many tools available, exposing all of them to the LLM in every
request can:
+
+* Consume significant context window space
+* Reduce the space available for actual conversation
+* Potentially confuse the LLM with too many options
+
+The Tool Search Tool solves this by allowing you to mark certain tools as
"searchable" (non-exposed). These tools are not automatically sent to the LLM
but can be discovered when needed.
+
+==== Using the `exposed` Parameter
+
+By default, all tools are exposed to the LLM (`exposed=true`). To make a tool
searchable instead:
+
+[source, java]
+----
+from("langchain4j-tools:queryBySSN?tags=users&description=Query user database
by social security number¶meter.ssn=string&exposed=false")
+ .to("sql:SELECT name FROM users WHERE ssn = :#ssn");
+----
+
+==== How It Works
+
+1. When you define tools with `exposed=false`, they are added to a searchable
tool registry
+2. A native `toolSearchTool` is automatically exposed to the LLM when
searchable tools exist
+3. The LLM can invoke `toolSearchTool` with tags to discover available tools
+4. The search results are returned to the LLM, which can then decide which
tools to use
+
+==== Example: Mixed Exposed and Searchable Tools
+
+[source, java]
+----
+// This tool is immediately available to the LLM
+from("langchain4j-tools:queryById?tags=users&description=Query user database
by user ID¶meter.userId=integer")
+ .to("sql:SELECT name FROM users WHERE id = :#userId");
+
+// This tool is searchable but not immediately exposed
+from("langchain4j-tools:queryBySSN?tags=users&description=Query user database
by social security number¶meter.ssn=string&exposed=false")
+ .to("sql:SELECT name FROM users WHERE ssn = :#ssn");
+
+// Another searchable tool with different tags
+from("langchain4j-tools:sendEmail?tags=users,email&description=Send email to a
user¶meter.email=string¶meter.message=string&exposed=false")
+ .to("smtp://mailserver");
+----
+
+In this example:
+
+* The `queryById` tool is immediately available to the LLM
+* The `queryBySSN` and `sendEmail` tools can be discovered by searching for
tags like "users" or "email"
+* The LLM can use the `toolSearchTool` to find these additional capabilities
when needed
+
+==== Benefits
+
+* *Reduced Context Usage*: Only expose the most commonly used tools initially
+* *Scalability*: Support hundreds or thousands of tools without overwhelming
the LLM
+* *Dynamic Discovery*: Let the LLM discover tools as needed based on the
conversation
+* *Better Organization*: Group related tools by tags for easier discovery
+
+==== Best Practices
+
+When using the Tool Search Tool feature, consider the following best practices:
+
+* *Tag Strategy*: Use meaningful, hierarchical tags (e.g., "users",
"users.admin", "database.users") to organize tools logically
+* *Expose Common Tools*: Keep frequently used tools exposed (`exposed=true`)
and make specialized tools searchable (`exposed=false`)
+* *Performance Considerations*: While the search is efficient, having
thousands of searchable tools may impact search performance. Consider grouping
tools by functional area
+* *LLM Guidance*: In your system message, inform the LLM about the
availability of the `toolSearchTool` and when to use it
+* *Tag Naming*: Use consistent, lowercase tag names without special characters
for best compatibility
+* *Tool Descriptions*: Write clear, descriptive tool descriptions as they are
returned in search results to help the LLM choose the right tool
+* *Testing*: Test your tool organization with real queries to ensure the LLM
can discover and use tools effectively
+
+==== Limitations
+
+* The LLM must be instructed to use the `toolSearchTool` - it won't
automatically know to search for tools
+* Search is based on exact tag matching - fuzzy matching or semantic search is
not currently supported
+* The feature requires an LLM with good function-calling capabilities to work
effectively
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
index c125e6b3088b..3ab33610503a 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsEndpoint.java
@@ -40,6 +40,8 @@ import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.support.DefaultEndpoint;
import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import static
org.apache.camel.component.langchain4j.tools.LangChain4jTools.SCHEME;
@@ -49,6 +51,8 @@ import static
org.apache.camel.component.langchain4j.tools.LangChain4jTools.SCHE
category = { Category.AI })
public class LangChain4jToolsEndpoint extends DefaultEndpoint {
+ private static final Logger LOG =
LoggerFactory.getLogger(LangChain4jToolsEndpoint.class);
+
@Metadata(required = true)
@UriPath(description = "The tool id")
private final String toolId;
@@ -77,6 +81,14 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
@UriParam(description = "Tool's Camel Parameters, programmatically define
Tool description and parameters")
private CamelSimpleToolParameter camelToolParameter;
+ @Metadata(label = "consumer", defaultValue = "true")
+ @UriParam(description = "Whether the tool is automatically exposed to the
LLM. When false, the tool is added to a searchable list and can be discovered
via the tool-search-tool",
+ defaultValue = "true")
+ private boolean exposed = true;
+
+ // Track the tool specification created by this endpoint for proper cleanup
+ private CamelToolSpecification camelToolSpecification;
+
public LangChain4jToolsEndpoint(String uri, LangChain4jToolsComponent
component, String toolId, String tags,
LangChain4jToolsConfiguration
configuration) {
super(uri, component);
@@ -143,13 +155,18 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
final LangChain4jToolsConsumer langChain4jToolsConsumer = new
LangChain4jToolsConsumer(this, processor);
configureConsumer(langChain4jToolsConsumer);
- CamelToolSpecification camelToolSpecification
- = new CamelToolSpecification(toolSpecification,
langChain4jToolsConsumer);
+ camelToolSpecification = new CamelToolSpecification(toolSpecification,
langChain4jToolsConsumer, exposed);
final CamelToolExecutorCache executorCache =
CamelToolExecutorCache.getInstance();
String[] splitTags = TagsHelper.splitTags(tags);
for (String tag : splitTags) {
- executorCache.put(tag, camelToolSpecification);
+ if (exposed) {
+ LOG.debug("Registering exposed tool: {} with tag: {}",
toolSpecification.name(), tag);
+ executorCache.put(tag, camelToolSpecification);
+ } else {
+ LOG.debug("Registering searchable tool: {} with tag: {}",
toolSpecification.name(), tag);
+ executorCache.putSearchable(tag, camelToolSpecification);
+ }
}
return camelToolSpecification.getConsumer();
@@ -233,11 +250,40 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
return tags;
}
+ /**
+ * Whether the tool is automatically exposed to the LLM
+ *
+ * @return true if the tool is exposed, false if searchable only
+ */
+ public boolean isExposed() {
+ return exposed;
+ }
+
+ public void setExposed(boolean exposed) {
+ this.exposed = exposed;
+ }
+
@Override
protected void doStop() throws Exception {
super.doStop();
- CamelToolExecutorCache.getInstance().getTools().clear();
+ // Only remove tools registered by this endpoint, not all tools
+ if (camelToolSpecification != null) {
+ final CamelToolExecutorCache executorCache =
CamelToolExecutorCache.getInstance();
+ String[] splitTags = TagsHelper.splitTags(tags);
+
+ for (String tag : splitTags) {
+ if (exposed) {
+ LOG.debug("Removing exposed tool: {} with tag: {}",
+
camelToolSpecification.getToolSpecification().name(), tag);
+ executorCache.remove(tag, camelToolSpecification);
+ } else {
+ LOG.debug("Removing searchable tool: {} with tag: {}",
+
camelToolSpecification.getToolSpecification().name(), tag);
+ executorCache.removeSearchable(tag,
camelToolSpecification);
+ }
+ }
+ }
}
/**
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
index 1fd5da30e1ef..522609ee9424 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
@@ -35,6 +35,8 @@ import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
+import dev.langchain4j.model.chat.request.json.JsonStringSchema;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.model.output.Response;
@@ -57,6 +59,8 @@ public class LangChain4jToolsProducer extends DefaultProducer
{
private final ObjectMapper objectMapper = new ObjectMapper();
+ private ToolSearchTool toolSearchTool;
+
public LangChain4jToolsProducer(LangChain4jToolsEndpoint endpoint) {
super(endpoint);
this.endpoint = endpoint;
@@ -80,6 +84,11 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
super.doStart();
this.chatModel = this.endpoint.getConfiguration().getChatModel();
ObjectHelper.notNull(chatModel, "chatModel");
+
+ // Initialize the tool search tool
+ final CamelToolExecutorCache toolCache =
CamelToolExecutorCache.getInstance();
+ String[] tags = TagsHelper.splitTags(endpoint.getTags());
+ this.toolSearchTool = new ToolSearchTool(toolCache, tags);
}
private void populateResponse(String response, Exchange exchange) {
@@ -151,6 +160,13 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
String toolName = toolExecutionRequest.name();
LOG.info("Invoking tool {} ({}) of {}", i, toolName,
toolExecutionRequests.size());
+ // Check if this is the ToolSearchTool
+ if (ToolSearchTool.TOOL_NAME.equals(toolName)) {
+ handleToolSearchToolInvocation(toolExecutionRequest,
chatMessages, exchange);
+ i++;
+ continue;
+ }
+
final CamelToolSpecification camelToolSpecification =
toolPair.callableTools().stream()
.filter(c ->
c.getToolSpecification().name().equals(toolName)).findFirst().get();
@@ -199,6 +215,55 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
}
}
+ /**
+ * Handles the invocation of the ToolSearchTool
+ *
+ * @param toolExecutionRequest the tool execution request
+ * @param chatMessages the chat messages
+ * @param exchange the exchange
+ */
+ private void handleToolSearchToolInvocation(
+ ToolExecutionRequest toolExecutionRequest, List<ChatMessage>
chatMessages, Exchange exchange) {
+ try {
+ // Validate arguments
+ String arguments = toolExecutionRequest.arguments();
+ if (arguments == null || arguments.trim().isEmpty()) {
+ LOG.warn("ToolSearchTool invoked with null or empty
arguments");
+ chatMessages.add(new ToolExecutionResultMessage(
+ toolExecutionRequest.id(),
+ toolExecutionRequest.name(),
+ "No search criteria provided. Please specify tags to
search for tools."));
+ return;
+ }
+
+ // Parse the arguments
+ JsonNode jsonNode = objectMapper.readValue(arguments,
JsonNode.class);
+ String tags = jsonNode.has("tags") ? jsonNode.get("tags").asText()
: "";
+
+ LOG.debug("ToolSearchTool searching for tags: {}", tags);
+
+ // Search for tools
+ List<CamelToolSpecification> matchingTools =
toolSearchTool.searchTools(tags);
+
+ // Format the result for the LLM
+ String result = ToolSearchTool.formatToolsForLLM(matchingTools);
+
+ // Add the result to chat messages
+ chatMessages.add(new ToolExecutionResultMessage(
+ toolExecutionRequest.id(),
+ toolExecutionRequest.name(),
+ result));
+
+ LOG.info("ToolSearchTool found {} matching tools for tags: {}",
matchingTools.size(), tags);
+ } catch (Exception e) {
+ LOG.error("Error executing ToolSearchTool", e);
+ chatMessages.add(new ToolExecutionResultMessage(
+ toolExecutionRequest.id(),
+ toolExecutionRequest.name(),
+ "Error searching for tools: " + e.getMessage()));
+ }
+ }
+
/**
* This talks with the LLM to, passing the list of tools, and expects a
response listing one ore more tools to be
* called
@@ -264,6 +329,12 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
}
}
+ // Add the ToolSearchTool if there are searchable tools
+ if (toolCache.hasSearchableTools()) {
+ ToolSpecification searchToolSpec =
createToolSearchToolSpecification();
+ toolSpecifications.add(searchToolSpec);
+ }
+
if (toolSpecifications.isEmpty()) {
exchange.getMessage().setHeader(LangChain4jTools.NO_TOOLS_CALLED_HEADER,
Boolean.TRUE);
return null;
@@ -272,6 +343,24 @@ public class LangChain4jToolsProducer extends
DefaultProducer {
return new ToolPair(toolSpecifications, callableTools);
}
+ /**
+ * Creates the ToolSpecification for the native ToolSearchTool
+ *
+ * @return the tool specification
+ */
+ private ToolSpecification createToolSearchToolSpecification() {
+ return ToolSpecification.builder()
+ .name(ToolSearchTool.TOOL_NAME)
+ .description(ToolSearchTool.TOOL_DESCRIPTION)
+ .parameters(JsonObjectSchema.builder()
+ .addProperty("tags", JsonStringSchema.builder()
+ .description(
+ "Comma-separated list of tags to
search for tools. Examples: 'users', 'email,users', 'database'. Leave empty to
see all available searchable tools.")
+ .build())
+ .build())
+ .build();
+ }
+
/**
* The pair of tools specifications that the Camel tools (i.e.: routes)
that can be called for that set
*
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/ToolSearchTool.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/ToolSearchTool.java
new file mode 100644
index 000000000000..af616ed277c6
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/ToolSearchTool.java
@@ -0,0 +1,135 @@
+/*
+ * 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.component.langchain4j.tools;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import dev.langchain4j.agent.tool.ToolSpecification;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
+
+/**
+ * Native tool that allows LLMs to search for available tools without
consuming the context window. This tool is
+ * automatically exposed when there are searchable (non-exposed) tools
registered.
+ */
+public class ToolSearchTool {
+
+ public static final String TOOL_NAME = "toolSearchTool";
+ public static final String TOOL_DESCRIPTION
+ = "Search for available tools by tags or keywords. Use this to
discover tools that can help accomplish specific tasks.";
+
+ private final CamelToolExecutorCache toolCache;
+ private final String[] producerTags;
+
+ public ToolSearchTool(CamelToolExecutorCache toolCache, String[]
producerTags) {
+ this.toolCache = toolCache;
+ this.producerTags = producerTags;
+ }
+
+ /**
+ * Searches for tools matching the given tags. The search looks for tools
in the searchable registry that match any
+ * of the provided tags.
+ *
+ * @param tags comma-separated list of tags to search for
+ * @return list of matching tool specifications (without duplicates)
+ */
+ public List<CamelToolSpecification> searchTools(String tags) {
+ // Use LinkedHashSet to maintain insertion order while preventing
duplicates
+ Set<CamelToolSpecification> matchingTools = new LinkedHashSet<>();
+
+ if (tags == null || tags.trim().isEmpty()) {
+ // Return all searchable tools if no tags specified
+ return getAllSearchableTools();
+ }
+
+ String[] searchTags = TagsHelper.splitTags(tags);
+ Map<String, Set<CamelToolSpecification>> searchableTools =
toolCache.getSearchableTools();
+
+ // Search for tools matching any of the search tags
+ // Note: We don't filter by producer tags here - the search should
find all tools
+ // with matching tags in the searchable registry, regardless of
producer tags
+ for (String searchTag : searchTags) {
+ Set<CamelToolSpecification> toolsForTag =
searchableTools.get(searchTag);
+ if (toolsForTag != null) {
+ matchingTools.addAll(toolsForTag);
+ }
+ }
+
+ return new ArrayList<>(matchingTools);
+ }
+
+ /**
+ * Gets all searchable tools for the producer's tags
+ *
+ * @return list of all searchable tool specifications
+ */
+ public List<CamelToolSpecification> getAllSearchableTools() {
+ List<CamelToolSpecification> allTools = new ArrayList<>();
+ Map<String, Set<CamelToolSpecification>> searchableTools =
toolCache.getSearchableTools();
+
+ for (String tag : producerTags) {
+ Set<CamelToolSpecification> toolsForTag = searchableTools.get(tag);
+ if (toolsForTag != null) {
+ allTools.addAll(toolsForTag);
+ }
+ }
+
+ return allTools;
+ }
+
+ /**
+ * Formats tool specifications as a readable string for the LLM
+ *
+ * @param tools list of tool specifications
+ * @return formatted string describing the tools
+ */
+ public static String formatToolsForLLM(List<CamelToolSpecification> tools)
{
+ if (tools.isEmpty()) {
+ return "No tools found matching the search criteria.";
+ }
+
+ StringBuilder result = new StringBuilder();
+ result.append("Found ").append(tools.size()).append(" tool(s):\n\n");
+
+ for (int i = 0; i < tools.size(); i++) {
+ CamelToolSpecification tool = tools.get(i);
+ ToolSpecification spec = tool.getToolSpecification();
+
+ result.append(i + 1).append(". ").append(spec.name()).append("\n");
+ result.append(" Description:
").append(spec.description()).append("\n");
+
+ if (spec.parameters() != null && spec.parameters().properties() !=
null
+ && !spec.parameters().properties().isEmpty()) {
+ result.append(" Parameters: ");
+ spec.parameters().properties().forEach((name, schema) -> {
+ result.append(name).append(", ");
+ });
+ // Remove trailing comma and space
+ result.setLength(result.length() - 2);
+ result.append("\n");
+ }
+
+ result.append("\n");
+ }
+
+ return result.toString();
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCache.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCache.java
index 6c00e31b7473..102cbdfb0972 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCache.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCache.java
@@ -23,14 +23,16 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Caches Tools Specification and Consumer route reference by the chatId, so
that different chats can have different
- * Tool implementation
+ * Tool implementation. Also maintains a separate cache for searchable
(non-exposed) tools.
*/
public final class CamelToolExecutorCache {
private Map<String, Set<CamelToolSpecification>> tools;
+ private Map<String, Set<CamelToolSpecification>> searchableTools;
private CamelToolExecutorCache() {
tools = new ConcurrentHashMap<>();
+ searchableTools = new ConcurrentHashMap<>();
}
private static final class SingletonHolder {
@@ -51,7 +53,57 @@ public final class CamelToolExecutorCache {
}
}
+ public void putSearchable(String chatId, CamelToolSpecification
specification) {
+ if (searchableTools.get(chatId) != null) {
+ searchableTools.get(chatId).add(specification);
+ } else {
+ Set<CamelToolSpecification> camelToolSpecifications = new
LinkedHashSet<>();
+ camelToolSpecifications.add(specification);
+ searchableTools.put(chatId, camelToolSpecifications);
+ }
+ }
+
+ /**
+ * Removes a specific tool specification from the exposed tools cache
+ *
+ * @param chatId the chat/tag identifier
+ * @param specification the tool specification to remove
+ */
+ public void remove(String chatId, CamelToolSpecification specification) {
+ Set<CamelToolSpecification> toolsForTag = tools.get(chatId);
+ if (toolsForTag != null) {
+ toolsForTag.remove(specification);
+ if (toolsForTag.isEmpty()) {
+ tools.remove(chatId);
+ }
+ }
+ }
+
+ /**
+ * Removes a specific tool specification from the searchable tools cache
+ *
+ * @param chatId the chat/tag identifier
+ * @param specification the tool specification to remove
+ */
+ public void removeSearchable(String chatId, CamelToolSpecification
specification) {
+ Set<CamelToolSpecification> toolsForTag = searchableTools.get(chatId);
+ if (toolsForTag != null) {
+ toolsForTag.remove(specification);
+ if (toolsForTag.isEmpty()) {
+ searchableTools.remove(chatId);
+ }
+ }
+ }
+
public Map<String, Set<CamelToolSpecification>> getTools() {
return tools;
}
+
+ public Map<String, Set<CamelToolSpecification>> getSearchableTools() {
+ return searchableTools;
+ }
+
+ public boolean hasSearchableTools() {
+ return !searchableTools.isEmpty();
+ }
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolSpecification.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolSpecification.java
index 7cda5f076ae1..678240fa18f9 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolSpecification.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolSpecification.java
@@ -29,10 +29,16 @@ public class CamelToolSpecification {
private ToolSpecification toolSpecification;
private LangChain4jToolsConsumer consumer;
+ private boolean exposed;
public CamelToolSpecification(ToolSpecification toolSpecification,
LangChain4jToolsConsumer consumer) {
+ this(toolSpecification, consumer, true);
+ }
+
+ public CamelToolSpecification(ToolSpecification toolSpecification,
LangChain4jToolsConsumer consumer, boolean exposed) {
this.toolSpecification = toolSpecification;
this.consumer = consumer;
+ this.exposed = exposed;
}
public ToolSpecification getToolSpecification() {
@@ -51,6 +57,14 @@ public class CamelToolSpecification {
this.consumer = consumer;
}
+ public boolean isExposed() {
+ return exposed;
+ }
+
+ public void setExposed(boolean exposed) {
+ this.exposed = exposed;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -60,13 +74,13 @@ public class CamelToolSpecification {
return false;
}
CamelToolSpecification that = (CamelToolSpecification) o;
- return Objects.equals(toolSpecification, that.toolSpecification) &&
Objects.equals(consumer,
- that.consumer);
+ return exposed == that.exposed && Objects.equals(toolSpecification,
that.toolSpecification)
+ && Objects.equals(consumer, that.consumer);
}
@Override
public int hashCode() {
- return Objects.hash(toolSpecification, consumer);
+ return Objects.hash(toolSpecification, consumer, exposed);
}
@Override
@@ -74,6 +88,7 @@ public class CamelToolSpecification {
return "CamelToolSpecification{" +
"toolSpecification=" + toolSpecification +
", consumer=" + consumer +
+ ", exposed=" + exposed +
'}';
}
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java
index 6894fe5bef2f..06ca58cd8f14 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolTest.java
@@ -43,6 +43,19 @@ public class LangChain4jToolTest extends CamelTestSupport {
.when("What is the name of the user 1?\n")
.invokeTool("QueryUserByNumber")
.withParam("number", 1)
+ .end()
+ .when("What tools are available for working with users?\n")
+ .replyWith("The following tools are available for working with
users: queryUserById, queryUserBySSN, sendEmail")
+ .end()
+ .when("Get the user with ID 42.\n")
+ .invokeTool("queryUserById")
+ .withParam("userId", 42)
+ .end()
+ .when("I need to query a user by their social security number.
First, find the appropriate tool, then use it to query SSN 123-45-6789.\n")
+ .invokeTool("toolSearchTool")
+ .withParam("tags", "users")
+ .andThenInvokeTool("queryUserBySSN")
+ .withParam("ssn", "123-45-6789")
.build();
@Override
@@ -82,6 +95,25 @@ public class LangChain4jToolTest extends CamelTestSupport {
from("langchain4j-tools:test1?tags=user&name=DoesNothing&description=Also does
not really do anything, but has a name")
.setBody(constant("Hello World"));
+ // Search tool test routes
+ from("direct:searchToolTest")
+ .to("langchain4j-tools:test1?tags=users,products");
+
+ // Exposed tool - should be immediately available to LLM
+
from("langchain4j-tools:queryUserById?tags=users&description=Query user
database by user ID¶meter.userId=integer")
+ .setBody(simple("{\"name\": \"John Doe\", \"id\":
${header.userId}}"));
+
+ // Non-exposed (searchable) tool - only available via tool
search
+
from("langchain4j-tools:queryUserBySSN?tags=users&description=Query user
database by social security number¶meter.ssn=string&exposed=false")
+ .setBody(simple("{\"name\": \"Jane Smith\", \"ssn\":
\"${header.ssn}\"}"));
+
+ // Another non-exposed tool with different tag
+
from("langchain4j-tools:queryProductById?tags=products&description=Query
product database by product ID¶meter.productId=integer&exposed=false")
+ .setBody(simple("{\"product\": \"Widget\", \"id\":
${header.productId}}"));
+
+ // Non-exposed tool for email operations
+ from("langchain4j-tools:sendEmail?tags=users&description=Send
email to a user¶meter.email=string¶meter.message=string&exposed=false")
+ .setBody(simple("Email sent to ${header.email}:
${header.message}"));
}
};
}
@@ -104,4 +136,57 @@ public class LangChain4jToolTest extends CamelTestSupport {
Assertions.assertThat(message.getBody(String.class)).containsIgnoringCase(nameFromDB);
Assertions.assertThat(message.getHeader("number")).isInstanceOf(Integer.class);
}
+
+ @Test
+ public void testToolSearchForUsers() throws Exception {
+ List<ChatMessage> messages = new ArrayList<>();
+ messages.add(new SystemMessage("""
+ You are a helpful assistant with access to various tools.
+ If you need to find available tools, use the toolSearchTool to
search by tags.
+ """));
+ messages.add(new UserMessage("""
+ What tools are available for working with users?
+ """));
+
+ Exchange result =
fluentTemplate.to("direct:searchToolTest").withBody(messages).request(Exchange.class);
+
+ Assertions.assertThat(result).isNotNull();
+ String response = result.getMessage().getBody(String.class);
+ Assertions.assertThat(response).isNotNull();
+ }
+
+ @Test
+ public void testExposedToolDirectAccess() throws Exception {
+ List<ChatMessage> messages = new ArrayList<>();
+ messages.add(new SystemMessage("""
+ You are a helpful assistant that can query user information.
+ """));
+ messages.add(new UserMessage("""
+ Get the user with ID 42.
+ """));
+
+ Exchange result =
fluentTemplate.to("direct:searchToolTest").withBody(messages).request(Exchange.class);
+
+ Assertions.assertThat(result).isNotNull();
+ String response = result.getMessage().getBody(String.class);
+ Assertions.assertThat(response).isNotNull();
+ }
+
+ @Test
+ public void testSearchAndUseNonExposedTool() throws Exception {
+ List<ChatMessage> messages = new ArrayList<>();
+ messages.add(new SystemMessage("""
+ You are a helpful assistant. If you need to find tools, search
for them first using the toolSearchTool.
+ """));
+ messages.add(new UserMessage(
+ """
+ I need to query a user by their social security
number. First, find the appropriate tool, then use it to query SSN 123-45-6789.
+ """));
+
+ Exchange result =
fluentTemplate.to("direct:searchToolTest").withBody(messages).request(Exchange.class);
+
+ Assertions.assertThat(result).isNotNull();
+ String response = result.getMessage().getBody(String.class);
+ Assertions.assertThat(response).isNotNull();
+ }
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolFormatTest.java
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolFormatTest.java
new file mode 100644
index 000000000000..deb3f4dbb32c
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolFormatTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.component.langchain4j.tools;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.langchain4j.agent.tool.ToolSpecification;
+import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
+import dev.langchain4j.model.chat.request.json.JsonStringSchema;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit tests for ToolSearchTool formatting functionality
+ */
+public class ToolSearchToolFormatTest {
+
+ @Test
+ public void testFormatEmptyToolList() {
+ List<CamelToolSpecification> tools = new ArrayList<>();
+ String result = ToolSearchTool.formatToolsForLLM(tools);
+
+ assertEquals("No tools found matching the search criteria.", result);
+ }
+
+ @Test
+ public void testFormatSingleTool() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("queryUser")
+ .description("Query user database by ID")
+ .parameters(JsonObjectSchema.builder()
+ .addProperty("userId",
JsonStringSchema.builder().description("User ID").build())
+ .build())
+ .build();
+
+ List<CamelToolSpecification> tools = new ArrayList<>();
+ tools.add(new CamelToolSpecification(spec, null, false));
+
+ String result = ToolSearchTool.formatToolsForLLM(tools);
+
+ assertTrue(result.contains("Found 1 tool(s)"));
+ assertTrue(result.contains("1. queryUser"));
+ assertTrue(result.contains("Description: Query user database by ID"));
+ assertTrue(result.contains("Parameters: userId"));
+ }
+
+ @Test
+ public void testFormatMultipleTools() {
+ ToolSpecification spec1 = ToolSpecification.builder()
+ .name("queryUser")
+ .description("Query user by ID")
+ .parameters(JsonObjectSchema.builder()
+ .addProperty("userId",
JsonStringSchema.builder().description("User ID").build())
+ .build())
+ .build();
+
+ ToolSpecification spec2 = ToolSpecification.builder()
+ .name("sendEmail")
+ .description("Send email to user")
+ .parameters(JsonObjectSchema.builder()
+ .addProperty("email",
JsonStringSchema.builder().description("Email address").build())
+ .addProperty("message",
JsonStringSchema.builder().description("Message content").build())
+ .build())
+ .build();
+
+ List<CamelToolSpecification> tools = new ArrayList<>();
+ tools.add(new CamelToolSpecification(spec1, null, false));
+ tools.add(new CamelToolSpecification(spec2, null, false));
+
+ String result = ToolSearchTool.formatToolsForLLM(tools);
+
+ assertTrue(result.contains("Found 2 tool(s)"));
+ assertTrue(result.contains("1. queryUser"));
+ assertTrue(result.contains("2. sendEmail"));
+ assertTrue(result.contains("Description: Query user by ID"));
+ assertTrue(result.contains("Description: Send email to user"));
+ assertTrue(result.contains("Parameters: userId"));
+ assertTrue(result.contains("Parameters: email, message") ||
result.contains("Parameters: message, email"));
+ }
+
+ @Test
+ public void testFormatToolWithoutParameters() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("healthCheck")
+ .description("Check system health")
+ .build();
+
+ List<CamelToolSpecification> tools = new ArrayList<>();
+ tools.add(new CamelToolSpecification(spec, null, false));
+
+ String result = ToolSearchTool.formatToolsForLLM(tools);
+
+ assertTrue(result.contains("Found 1 tool(s)"));
+ assertTrue(result.contains("1. healthCheck"));
+ assertTrue(result.contains("Description: Check system health"));
+ // Should not contain "Parameters:" line
+ assertFalse(result.contains("Parameters:"));
+ }
+
+ private void assertFalse(boolean condition) {
+ assertTrue(!condition);
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolTest.java
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolTest.java
new file mode 100644
index 000000000000..4d1d2570b300
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/ToolSearchToolTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.component.langchain4j.tools;
+
+import java.util.List;
+
+import dev.langchain4j.agent.tool.ToolSpecification;
+import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
+import dev.langchain4j.model.chat.request.json.JsonStringSchema;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit tests for ToolSearchTool
+ */
+public class ToolSearchToolTest {
+
+ private CamelToolExecutorCache toolCache;
+ private ToolSearchTool toolSearchTool;
+
+ @BeforeEach
+ public void setUp() {
+ toolCache = CamelToolExecutorCache.getInstance();
+ // Clear any existing tools
+ toolCache.getSearchableTools().clear();
+ toolCache.getTools().clear();
+
+ // Create tool search tool with producer tags
+ String[] producerTags = new String[] { "users", "products" };
+ toolSearchTool = new ToolSearchTool(toolCache, producerTags);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ toolCache.getSearchableTools().clear();
+ toolCache.getTools().clear();
+ }
+
+ @Test
+ public void testSearchToolsWithMatchingTag() {
+ // Add a searchable tool
+ ToolSpecification spec = createToolSpec("queryUser", "Query user by
ID");
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, false);
+ toolCache.putSearchable("users", camelSpec);
+
+ // Search for the tool
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools("users");
+
+ assertNotNull(results);
+ assertEquals(1, results.size());
+ assertEquals("queryUser",
results.get(0).getToolSpecification().name());
+ }
+
+ @Test
+ public void testSearchToolsWithMultipleTags() {
+ // Add tools with different tags
+ ToolSpecification spec1 = createToolSpec("queryUser", "Query user");
+ ToolSpecification spec2 = createToolSpec("sendEmail", "Send email");
+ ToolSpecification spec3 = createToolSpec("queryProduct", "Query
product");
+
+ toolCache.putSearchable("users", new CamelToolSpecification(spec1,
null, false));
+ toolCache.putSearchable("email", new CamelToolSpecification(spec2,
null, false));
+ toolCache.putSearchable("products", new CamelToolSpecification(spec3,
null, false));
+
+ // Search for multiple tags
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools("users,email");
+
+ assertNotNull(results);
+ assertEquals(2, results.size());
+ }
+
+ @Test
+ public void testSearchToolsNoDuplicates() {
+ // Add a tool with multiple tags
+ ToolSpecification spec = createToolSpec("sendEmail", "Send email to
user");
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, false);
+
+ toolCache.putSearchable("users", camelSpec);
+ toolCache.putSearchable("email", camelSpec);
+
+ // Search for both tags - should return only one result
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools("users,email");
+
+ assertNotNull(results);
+ assertEquals(1, results.size());
+ assertEquals("sendEmail",
results.get(0).getToolSpecification().name());
+ }
+
+ @Test
+ public void testSearchToolsWithEmptyTags() {
+ // Add some tools
+ toolCache.putSearchable("users", new
CamelToolSpecification(createToolSpec("tool1", "Tool 1"), null, false));
+ toolCache.putSearchable("products", new
CamelToolSpecification(createToolSpec("tool2", "Tool 2"), null, false));
+
+ // Search with empty tags should return all searchable tools for
producer tags
+ List<CamelToolSpecification> results = toolSearchTool.searchTools("");
+
+ assertNotNull(results);
+ assertEquals(2, results.size());
+ }
+
+ @Test
+ public void testSearchToolsWithNullTags() {
+ // Add some tools
+ toolCache.putSearchable("users", new
CamelToolSpecification(createToolSpec("tool1", "Tool 1"), null, false));
+
+ // Search with null tags should return all searchable tools
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools(null);
+
+ assertNotNull(results);
+ assertEquals(1, results.size());
+ }
+
+ @Test
+ public void testSearchToolsNoMatches() {
+ // Add a tool with a different tag
+ toolCache.putSearchable("admin", new
CamelToolSpecification(createToolSpec("adminTool", "Admin tool"), null, false));
+
+ // Search for non-existent tag
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools("users");
+
+ assertNotNull(results);
+ assertTrue(results.isEmpty());
+ }
+
+ @Test
+ public void testSearchNonExistentTagReturnsHelpfulMessage() {
+ // Add some tools with specific tags
+ toolCache.putSearchable("users",
+ new CamelToolSpecification(createToolSpec("queryUser", "Query
user by ID"), null, false));
+ toolCache.putSearchable("products",
+ new CamelToolSpecification(createToolSpec("queryProduct",
"Query product by ID"), null, false));
+
+ // Search for a non-existent tag
+ List<CamelToolSpecification> results =
toolSearchTool.searchTools("nonexistent");
+
+ // Verify empty results
+ assertNotNull(results);
+ assertTrue(results.isEmpty());
+
+ // Verify the formatted message is helpful for an LLM
+ String formattedMessage = ToolSearchTool.formatToolsForLLM(results);
+ assertNotNull(formattedMessage);
+ assertEquals("No tools found matching the search criteria.",
formattedMessage);
+ }
+
+ private ToolSpecification createToolSpec(String name, String description) {
+ return ToolSpecification.builder()
+ .name(name)
+ .description(description)
+ .parameters(JsonObjectSchema.builder()
+ .addProperty("param1",
JsonStringSchema.builder().description("Parameter 1").build())
+ .build())
+ .build();
+ }
+}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCacheTest.java
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCacheTest.java
new file mode 100644
index 000000000000..7037b34d6e32
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/spec/CamelToolExecutorCacheTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.component.langchain4j.tools.spec;
+
+import java.util.Set;
+
+import dev.langchain4j.agent.tool.ToolSpecification;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Unit tests for CamelToolExecutorCache
+ */
+public class CamelToolExecutorCacheTest {
+
+ private CamelToolExecutorCache cache;
+
+ @BeforeEach
+ public void setUp() {
+ cache = CamelToolExecutorCache.getInstance();
+ // Clear any existing tools
+ cache.getTools().clear();
+ cache.getSearchableTools().clear();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ cache.getTools().clear();
+ cache.getSearchableTools().clear();
+ }
+
+ @Test
+ public void testPutAndGetExposedTool() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("testTool")
+ .description("Test tool")
+ .build();
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, true);
+
+ cache.put("users", camelSpec);
+
+ Set<CamelToolSpecification> tools = cache.getTools().get("users");
+ assertNotNull(tools);
+ assertEquals(1, tools.size());
+ assertTrue(tools.contains(camelSpec));
+ }
+
+ @Test
+ public void testPutAndGetSearchableTool() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("searchableTool")
+ .description("Searchable tool")
+ .build();
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, false);
+
+ cache.putSearchable("products", camelSpec);
+
+ Set<CamelToolSpecification> tools =
cache.getSearchableTools().get("products");
+ assertNotNull(tools);
+ assertEquals(1, tools.size());
+ assertTrue(tools.contains(camelSpec));
+ }
+
+ @Test
+ public void testRemoveExposedTool() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("testTool")
+ .description("Test tool")
+ .build();
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, true);
+
+ cache.put("users", camelSpec);
+ assertEquals(1, cache.getTools().get("users").size());
+
+ cache.remove("users", camelSpec);
+ assertNull(cache.getTools().get("users"));
+ }
+
+ @Test
+ public void testRemoveSearchableTool() {
+ ToolSpecification spec = ToolSpecification.builder()
+ .name("searchableTool")
+ .description("Searchable tool")
+ .build();
+ CamelToolSpecification camelSpec = new CamelToolSpecification(spec,
null, false);
+
+ cache.putSearchable("products", camelSpec);
+ assertEquals(1, cache.getSearchableTools().get("products").size());
+
+ cache.removeSearchable("products", camelSpec);
+ assertNull(cache.getSearchableTools().get("products"));
+ }
+
+ @Test
+ public void testMultipleToolsWithSameTag() {
+ ToolSpecification spec1 =
ToolSpecification.builder().name("tool1").description("Tool 1").build();
+ ToolSpecification spec2 =
ToolSpecification.builder().name("tool2").description("Tool 2").build();
+
+ CamelToolSpecification camelSpec1 = new CamelToolSpecification(spec1,
null, true);
+ CamelToolSpecification camelSpec2 = new CamelToolSpecification(spec2,
null, true);
+
+ cache.put("users", camelSpec1);
+ cache.put("users", camelSpec2);
+
+ Set<CamelToolSpecification> tools = cache.getTools().get("users");
+ assertNotNull(tools);
+ assertEquals(2, tools.size());
+ }
+
+ @Test
+ public void testRemoveOneOfMultipleTools() {
+ ToolSpecification spec1 =
ToolSpecification.builder().name("tool1").description("Tool 1").build();
+ ToolSpecification spec2 =
ToolSpecification.builder().name("tool2").description("Tool 2").build();
+
+ CamelToolSpecification camelSpec1 = new CamelToolSpecification(spec1,
null, true);
+ CamelToolSpecification camelSpec2 = new CamelToolSpecification(spec2,
null, true);
+
+ cache.put("users", camelSpec1);
+ cache.put("users", camelSpec2);
+
+ cache.remove("users", camelSpec1);
+
+ Set<CamelToolSpecification> tools = cache.getTools().get("users");
+ assertNotNull(tools);
+ assertEquals(1, tools.size());
+ assertTrue(tools.contains(camelSpec2));
+ assertFalse(tools.contains(camelSpec1));
+ }
+
+ @Test
+ public void testHasSearchableTools() {
+ assertFalse(cache.hasSearchableTools());
+
+ ToolSpecification spec =
ToolSpecification.builder().name("tool").description("Tool").build();
+ cache.putSearchable("users", new CamelToolSpecification(spec, null,
false));
+
+ assertTrue(cache.hasSearchableTools());
+ }
+}
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
index 0e6cf85e50da..b3ef3d1f00c3 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
@@ -72,6 +72,40 @@ public interface LangChain4jToolsEndpointBuilderFactory {
doSetProperty("description", description);
return this;
}
+ /**
+ * Whether the tool is automatically exposed to the LLM. When false,
the
+ * tool is added to a searchable list and can be discovered via the
+ * tool-search-tool.
+ *
+ * The option is a: <code>boolean</code> type.
+ *
+ * Default: true
+ * Group: consumer
+ *
+ * @param exposed the value to set
+ * @return the dsl builder
+ */
+ default LangChain4jToolsEndpointConsumerBuilder exposed(boolean
exposed) {
+ doSetProperty("exposed", exposed);
+ return this;
+ }
+ /**
+ * Whether the tool is automatically exposed to the LLM. When false,
the
+ * tool is added to a searchable list and can be discovered via the
+ * tool-search-tool.
+ *
+ * The option will be converted to a <code>boolean</code> type.
+ *
+ * Default: true
+ * Group: consumer
+ *
+ * @param exposed the value to set
+ * @return the dsl builder
+ */
+ default LangChain4jToolsEndpointConsumerBuilder exposed(String
exposed) {
+ doSetProperty("exposed", exposed);
+ return this;
+ }
/**
* Tool name.
*