This is an automated email from the ASF dual-hosted git repository.
fmariani 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 a58804f5c116 CAMEL-23074 - camel-langchain4j-tools: Add tool parameter
metadata support (required, enum)
a58804f5c116 is described below
commit a58804f5c116ba8b962e147c3561cf58dc914f57
Author: Croway <[email protected]>
AuthorDate: Wed Feb 25 10:47:37 2026 +0100
CAMEL-23074 - camel-langchain4j-tools: Add tool parameter metadata support
(required, enum)
---
.../catalog/components/langchain4j-tools.json | 2 +-
.../langchain4j/tools/langchain4j-tools.json | 2 +-
.../src/main/docs/langchain4j-tools-component.adoc | 41 ++
.../tools/LangChain4jToolsEndpoint.java | 146 ++++++-
.../tools/spec/CamelSimpleToolParameter.java | 33 ++
.../LangChain4jToolRequiredParameterTest.java | 426 +++++++++++++++++++++
.../LangChain4jToolsEndpointBuilderFactory.java | 18 +-
7 files changed, 647 insertions(+), 21 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 3180eaf555e9..34b31f066148 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
@@ -36,7 +36,7 @@
"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" },
"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." },
+ "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 with optional metadata. Format:
parameter.=, parameter..description=, parameter..required=, 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 [...]
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 3180eaf555e9..34b31f066148 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
@@ -36,7 +36,7 @@
"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" },
"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." },
+ "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 with optional metadata. Format:
parameter.=, parameter..description=, parameter..required=, 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 [...]
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 093e376d1edc..cfe27505d5a2 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
@@ -103,6 +103,13 @@ or via the endpoint option `camelToolParameter` for a
programmatic approach.
The parameters can be found as headers in the consumer route, in particular,
if you define `parameter.userId=5`,
in the consumer route `${header.userId}` can be used.
+Parameters support optional metadata:
+
+* `parameter.<name>=<type>` — defines the parameter type (`string`, `integer`,
`number`, `boolean`)
+* `parameter.<name>.description=<text>` — defines a description passed to the
LLM
+* `parameter.<name>.required=<true|false>` — marks the parameter as required
(default: `false`)
+* `parameter.<name>.enum=<value1,value2>` — restricts the parameter to a set
of allowed values (comma-separated)
+
.Producer and consumer example:
[source, java]
----
@@ -113,6 +120,40 @@ from("langchain4j-tools:test1?tags=users&description=Query
user database by user
.to("sql:SELECT name FROM users WHERE id = :#userId");
----
+.Example with required parameters and descriptions:
+[source, java]
+----
+from("langchain4j-tools:weather?tags=weather&description=Get weather forecast"
+ + "¶meter.location=string"
+ + "¶meter.location.description=The city and state"
+ + "¶meter.location.required=true"
+ + "¶meter.unit=string"
+ + "¶meter.unit.description=Temperature unit"
+ + "¶meter.unit.enum=celsius,fahrenheit")
+ .process(exchange -> {
+ String location = exchange.getIn().getHeader("location", String.class);
+ // ...
+ });
+----
+
+When using the programmatic `CamelSimpleToolParameter` approach, required
parameters can be specified as a list of parameter names.
+The full constructor also accepts `additionalProperties` (to restrict extra
fields) and `definitions` (for recursive schemas using `JsonReferenceSchema`):
+
+[source, java]
+----
+CamelSimpleToolParameter toolParameter = new CamelSimpleToolParameter(
+ "Get weather forecast",
+ List.of(
+ new NamedJsonSchemaProperty("location",
+ JsonStringSchema.builder().description("The city and
state").build()),
+ new NamedJsonSchemaProperty("unit",
+ JsonEnumSchema.builder().enumValues("celsius", "fahrenheit")
+ .description("Temperature unit").build())),
+ List.of("location"), // required parameter names
+ false, // additionalProperties
+ null); // definitions (for recursive schemas)
+----
+
.Usage example:
[source, java]
----
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 3ab33610503a..e2e4cbd68146 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
@@ -16,11 +16,14 @@
*/
package org.apache.camel.component.langchain4j.tools;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.model.chat.request.json.JsonBooleanSchema;
+import dev.langchain4j.model.chat.request.json.JsonEnumSchema;
import dev.langchain4j.model.chat.request.json.JsonIntegerSchema;
import dev.langchain4j.model.chat.request.json.JsonNumberSchema;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
@@ -73,8 +76,12 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
private String name;
@Metadata(label = "consumer")
- @UriParam(description = "List of Tool parameters in the form of
parameter.<name>=<type>", prefix = "parameter.",
- multiValue = true)
+ @UriParam(description = "List of Tool parameters with optional metadata. "
+ + "Format: parameter.<name>=<type>,
parameter.<name>.description=<text>, "
+ + "parameter.<name>.required=<true|false>,
parameter.<name>.enum=<value1,value2>. "
+ + "Example: parameter.location=string,
parameter.location.description=The city and state, "
+ + "parameter.location.required=true,
parameter.unit.enum=celsius,fahrenheit",
+ prefix = "parameter.", multiValue = true)
private Map<String, String> parameters;
@Metadata(label = "consumer,advanced")
@@ -119,18 +126,25 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
namedProperty.getProperties());
}
+ if (camelToolParameter.getRequired() != null &&
!camelToolParameter.getRequired().isEmpty()) {
+ parametersBuilder.required(camelToolParameter.getRequired());
+ }
+
+ if (camelToolParameter.getAdditionalProperties() != null) {
+
parametersBuilder.additionalProperties(camelToolParameter.getAdditionalProperties());
+ }
+
+ if (camelToolParameter.getDefinitions() != null &&
!camelToolParameter.getDefinitions().isEmpty()) {
+
parametersBuilder.definitions(camelToolParameter.getDefinitions());
+ }
+
toolSpecificationBuilder.parameters(parametersBuilder.build());
} else if (description != null) {
toolSpecificationBuilder.description(description);
if (parameters != null) {
-
- JsonObjectSchema.Builder parametersBuilder =
JsonObjectSchema.builder();
-
- parameters.forEach((name, type) ->
parametersBuilder.addProperty(name, createJsonSchema(type)));
-
- toolSpecificationBuilder.parameters(parametersBuilder.build());
+
toolSpecificationBuilder.parameters(buildParameterSchema(parameters));
}
} else {
// Consumer without toolParameter or description
@@ -287,19 +301,121 @@ public class LangChain4jToolsEndpoint extends
DefaultEndpoint {
}
/**
- * Creates a JsonScheùaElement based on a String type
+ * Builds a {@link JsonObjectSchema} from the flat parameter map by
parsing parameter metadata and constructing the
+ * appropriate schema elements.
+ *
+ * @param parameters the flat parameter map
+ * @return the built JsonObjectSchema
+ */
+ private JsonObjectSchema buildParameterSchema(Map<String, String>
parameters) {
+ Map<String, ParameterMetadata> paramMetadata =
parseParameterMetadata(parameters);
+
+ JsonObjectSchema.Builder builder = JsonObjectSchema.builder();
+ List<String> requiredParams = new ArrayList<>();
+
+ for (Map.Entry<String, ParameterMetadata> entry :
paramMetadata.entrySet()) {
+ String paramName = entry.getKey();
+ ParameterMetadata metadata = entry.getValue();
+
+ JsonSchemaElement schema;
+ if (metadata.enumValues != null && !metadata.enumValues.isEmpty())
{
+ schema = JsonEnumSchema.builder()
+ .enumValues(metadata.enumValues)
+ .description(metadata.description)
+ .build();
+ } else {
+ schema = createJsonSchema(metadata.type, metadata.description);
+ }
+
+ builder.addProperty(paramName, schema);
+
+ if (metadata.required) {
+ requiredParams.add(paramName);
+ }
+ }
+
+ if (!requiredParams.isEmpty()) {
+ builder.required(requiredParams);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a JsonSchemaElement based on a String type with an optional
description.
*
* @param type
+ * @param description optional description, may be null
* @return
*/
- private JsonSchemaElement createJsonSchema(String type) {
+ private JsonSchemaElement createJsonSchema(String type, String
description) {
return switch (type.toLowerCase()) {
- case "string" -> JsonStringSchema.builder().build();
- case "integer" -> JsonIntegerSchema.builder().build();
- case "number" -> JsonNumberSchema.builder().build();
- case "boolean" -> JsonBooleanSchema.builder().build();
- default -> JsonStringSchema.builder().build(); // fallback for
unkown types
+ case "string" ->
JsonStringSchema.builder().description(description).build();
+ case "integer" ->
JsonIntegerSchema.builder().description(description).build();
+ case "number" ->
JsonNumberSchema.builder().description(description).build();
+ case "boolean" ->
JsonBooleanSchema.builder().description(description).build();
+ default ->
JsonStringSchema.builder().description(description).build(); // fallback for
unknown types
};
}
+ /**
+ * Parses the flat parameter map into structured metadata.
+ * <p>
+ * Handles parameter configurations like:
+ * <ul>
+ * <li>parameter.location=string</li>
+ * <li>parameter.location.description=The city and state</li>
+ * <li>parameter.location.required=true</li>
+ * <li>parameter.unit.enum=celsius,fahrenheit</li>
+ * </ul>
+ *
+ * @param parameters the flat parameter map
+ * @return map of parameter names to their metadata
+ */
+ private Map<String, ParameterMetadata> parseParameterMetadata(Map<String,
String> parameters) {
+ Map<String, ParameterMetadata> metadata = new HashMap<>();
+
+ for (Map.Entry<String, String> entry : parameters.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+
+ if (key.contains(".")) {
+ String[] parts = key.split("\\.", 2);
+ String paramName = parts[0];
+ String propertyName = parts[1];
+
+ ParameterMetadata meta = metadata.computeIfAbsent(paramName, k
-> new ParameterMetadata());
+
+ switch (propertyName) {
+ case "description":
+ meta.description = value;
+ break;
+ case "required":
+ meta.required = Boolean.parseBoolean(value);
+ break;
+ case "enum":
+ meta.enumValues = List.of(value.split(","));
+ break;
+ default:
+ break;
+ }
+ } else {
+ ParameterMetadata meta = metadata.computeIfAbsent(key, k ->
new ParameterMetadata());
+ meta.type = value;
+ }
+ }
+
+ return metadata;
+ }
+
+ /**
+ * Internal class to hold parameter metadata.
+ */
+ private static class ParameterMetadata {
+ String type = "string";
+ String description;
+ boolean required = false;
+ List<String> enumValues;
+ }
+
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelSimpleToolParameter.java
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelSimpleToolParameter.java
index 074b774e0b95..c54ee45394ec 100644
---
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelSimpleToolParameter.java
+++
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/spec/CamelSimpleToolParameter.java
@@ -16,7 +16,11 @@
*/
package org.apache.camel.component.langchain4j.tools.spec;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+
+import dev.langchain4j.model.chat.request.json.JsonSchemaElement;
/**
* langchain4j Simple Tool parameter implementation, this class can be used to
provide multiple properties/input
@@ -26,10 +30,27 @@ public class CamelSimpleToolParameter {
private final String description;
private final List<NamedJsonSchemaProperty> properties;
+ private final List<String> required;
+ private final Boolean additionalProperties;
+ private final Map<String, JsonSchemaElement> definitions;
public CamelSimpleToolParameter(String description,
List<NamedJsonSchemaProperty> properties) {
+ this(description, properties, Collections.emptyList());
+ }
+
+ public CamelSimpleToolParameter(String description,
List<NamedJsonSchemaProperty> properties,
+ List<String> required) {
+ this(description, properties, required, null, null);
+ }
+
+ public CamelSimpleToolParameter(String description,
List<NamedJsonSchemaProperty> properties,
+ List<String> required, Boolean
additionalProperties,
+ Map<String, JsonSchemaElement>
definitions) {
this.description = description;
this.properties = properties;
+ this.required = required;
+ this.additionalProperties = additionalProperties;
+ this.definitions = definitions;
}
public List<NamedJsonSchemaProperty> getProperties() {
@@ -39,4 +60,16 @@ public class CamelSimpleToolParameter {
public String getDescription() {
return description;
}
+
+ public List<String> getRequired() {
+ return required;
+ }
+
+ public Boolean getAdditionalProperties() {
+ return additionalProperties;
+ }
+
+ public Map<String, JsonSchemaElement> getDefinitions() {
+ return definitions;
+ }
}
diff --git
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolRequiredParameterTest.java
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolRequiredParameterTest.java
new file mode 100644
index 000000000000..d2abdb1a5d8a
--- /dev/null
+++
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolRequiredParameterTest.java
@@ -0,0 +1,426 @@
+/*
+ * 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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import dev.langchain4j.model.chat.request.json.JsonEnumSchema;
+import dev.langchain4j.model.chat.request.json.JsonIntegerSchema;
+import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
+import dev.langchain4j.model.chat.request.json.JsonReferenceSchema;
+import dev.langchain4j.model.chat.request.json.JsonSchemaElement;
+import dev.langchain4j.model.chat.request.json.JsonStringSchema;
+import org.apache.camel.Consumer;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolExecutorCache;
+import
org.apache.camel.component.langchain4j.tools.spec.CamelToolSpecification;
+import
org.apache.camel.component.langchain4j.tools.spec.NamedJsonSchemaProperty;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+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;
+
+public class LangChain4jToolRequiredParameterTest extends CamelTestSupport {
+
+ @AfterEach
+ void clearCache() {
+ CamelToolExecutorCache.getInstance().getTools().clear();
+ }
+
+ @Test
+ public void testUriParameterWithRequired() throws Exception {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("location", "string");
+ parameters.put("location.description", "The city and state");
+ parameters.put("location.required", "true");
+ parameters.put("unit", "string");
+ parameters.put("unit.required", "false");
+ parameters.put("count", "integer");
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setParameters(parameters);
+ endpoint.setDescription("Test tool with required params");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ // Retrieve the tool specification from the cache
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+ assertEquals(1, tools.size());
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ // Verify properties exist
+ assertNotNull(schema.properties().get("location"));
+ assertNotNull(schema.properties().get("unit"));
+ assertNotNull(schema.properties().get("count"));
+
+ // Verify required list contains only "location"
+ assertNotNull(schema.required());
+ assertEquals(1, schema.required().size());
+ assertTrue(schema.required().contains("location"));
+
+ // Verify description is set on location
+ assertEquals("The city and state",
schema.properties().get("location").description());
+ }
+
+ @Test
+ public void testUriParameterWithNoRequired() throws Exception {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("name", "string");
+ parameters.put("age", "integer");
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setParameters(parameters);
+ endpoint.setDescription("Test tool without required params");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+ assertNotNull(schema.properties().get("name"));
+ assertNotNull(schema.properties().get("age"));
+
+ // No required parameters should be set
+ assertTrue(schema.required() == null || schema.required().isEmpty());
+ }
+
+ @Test
+ public void testProgrammaticWithRequired() throws Exception {
+ CamelSimpleToolParameter toolParameter = new CamelSimpleToolParameter(
+ "Query user by location and name",
+ List.of(
+ new NamedJsonSchemaProperty(
+ "location",
+ JsonStringSchema.builder().description("The
city").build()),
+ new NamedJsonSchemaProperty(
+ "name",
+ JsonStringSchema.builder().build()),
+ new NamedJsonSchemaProperty(
+ "age",
+ JsonIntegerSchema.builder().build())),
+ List.of("location", "name"));
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setCamelToolParameter(toolParameter);
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ // Verify required list
+ assertNotNull(schema.required());
+ assertEquals(2, schema.required().size());
+ assertTrue(schema.required().contains("location"));
+ assertTrue(schema.required().contains("name"));
+
+ // Verify description is preserved
+ assertEquals("The city",
schema.properties().get("location").description());
+ }
+
+ @Test
+ public void testProgrammaticWithoutRequired() throws Exception {
+ CamelSimpleToolParameter toolParameter = new CamelSimpleToolParameter(
+ "Simple tool",
+ List.of(
+ new NamedJsonSchemaProperty("name",
JsonStringSchema.builder().build())));
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setCamelToolParameter(toolParameter);
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ // No required parameters
+ assertTrue(schema.required() == null || schema.required().isEmpty());
+ }
+
+ @Test
+ public void testUriParameterWithDescription() throws Exception {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("city", "string");
+ parameters.put("city.description", "The name of the city");
+ parameters.put("temperature", "number");
+ parameters.put("temperature.description", "Temperature threshold");
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setParameters(parameters);
+ endpoint.setDescription("Weather tool");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ assertEquals("The name of the city",
schema.properties().get("city").description());
+ assertEquals("Temperature threshold",
schema.properties().get("temperature").description());
+ }
+
+ @Test
+ public void testUriParameterWithEnum() throws Exception {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("unit", "string");
+ parameters.put("unit.enum", "celsius,fahrenheit");
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setParameters(parameters);
+ endpoint.setDescription("Tool with enum param");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ // Verify the parameter is a JsonEnumSchema
+ assertTrue(schema.properties().get("unit") instanceof JsonEnumSchema);
+ JsonEnumSchema enumSchema = (JsonEnumSchema)
schema.properties().get("unit");
+ assertEquals(2, enumSchema.enumValues().size());
+ assertTrue(enumSchema.enumValues().contains("celsius"));
+ assertTrue(enumSchema.enumValues().contains("fahrenheit"));
+ }
+
+ @Test
+ public void testUriParameterWithEnumAndDescription() throws Exception {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("unit", "string");
+ parameters.put("unit.enum", "C,F");
+ parameters.put("unit.description", "Temperature unit");
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setParameters(parameters);
+ endpoint.setDescription("Tool with enum and description");
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ assertTrue(schema.properties().get("unit") instanceof JsonEnumSchema);
+ JsonEnumSchema enumSchema = (JsonEnumSchema)
schema.properties().get("unit");
+ assertEquals(2, enumSchema.enumValues().size());
+ assertTrue(enumSchema.enumValues().contains("C"));
+ assertTrue(enumSchema.enumValues().contains("F"));
+ assertEquals("Temperature unit", enumSchema.description());
+ }
+
+ @Test
+ public void testProgrammaticWithAdditionalProperties() throws Exception {
+ CamelSimpleToolParameter toolParameter = new CamelSimpleToolParameter(
+ "Strict tool",
+ List.of(
+ new NamedJsonSchemaProperty("name",
JsonStringSchema.builder().build())),
+ List.of("name"),
+ false,
+ null);
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setCamelToolParameter(toolParameter);
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+ assertEquals(false, schema.additionalProperties());
+ }
+
+ @Test
+ public void testProgrammaticWithDefinitions() throws Exception {
+ // Create a recursive schema: a "node" with a name and optional
children (list of node references)
+ Map<String, JsonSchemaElement> definitions = Map.of(
+ "node", JsonObjectSchema.builder()
+ .addStringProperty("name")
+ .addProperty("parent",
JsonReferenceSchema.builder().reference("node").build())
+ .build());
+
+ CamelSimpleToolParameter toolParameter = new CamelSimpleToolParameter(
+ "Tool with recursive schema",
+ List.of(
+ new NamedJsonSchemaProperty(
+ "root",
+
JsonReferenceSchema.builder().reference("node").build())),
+ List.of("root"),
+ null,
+ definitions);
+
+ LangChain4jToolsComponent component
+ = context.getComponent(LangChain4jTools.SCHEME,
LangChain4jToolsComponent.class);
+
+ LangChain4jToolsEndpoint endpoint = new LangChain4jToolsEndpoint(
+ "langchain4j-tools:test",
+ component,
+ "test-tool",
+ "test-tag",
+ new LangChain4jToolsConfiguration());
+
+ endpoint.setCamelContext(context);
+ endpoint.setCamelToolParameter(toolParameter);
+
+ Consumer consumer = endpoint.createConsumer(exchange -> {
+ });
+
+ assertNotNull(consumer);
+
+ Set<CamelToolSpecification> tools =
CamelToolExecutorCache.getInstance().getTools().get("test-tag");
+ assertNotNull(tools);
+
+ JsonObjectSchema schema = (JsonObjectSchema)
tools.iterator().next().getToolSpecification().parameters();
+ assertNotNull(schema);
+
+ // Verify definitions are set
+ assertNotNull(schema.definitions());
+ assertEquals(1, schema.definitions().size());
+ assertTrue(schema.definitions().containsKey("node"));
+
+ // Verify the root property is a reference
+ assertTrue(schema.properties().get("root") instanceof
JsonReferenceSchema);
+ }
+}
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 b3ef3d1f00c3..969d5c8d49bd 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
@@ -121,8 +121,13 @@ public interface LangChain4jToolsEndpointBuilderFactory {
return this;
}
/**
- * List of Tool parameters in the form of parameter.=. This is a
- * multi-value option with prefix: parameter.
+ * List of Tool parameters with optional metadata. Format: parameter.=,
+ * parameter..description=, parameter..required=, parameter..enum=.
+ * Example: parameter.location=string,
+ * parameter.location.description=The city and state,
+ * parameter.location.required=true,
+ * parameter.unit.enum=celsius,fahrenheit. This is a multi-value option
+ * with prefix: parameter.
*
* The option is a: <code>java.util.Map<java.lang.String,
* java.lang.String></code> type.
@@ -141,8 +146,13 @@ public interface LangChain4jToolsEndpointBuilderFactory {
return this;
}
/**
- * List of Tool parameters in the form of parameter.=. This is a
- * multi-value option with prefix: parameter.
+ * List of Tool parameters with optional metadata. Format: parameter.=,
+ * parameter..description=, parameter..required=, parameter..enum=.
+ * Example: parameter.location=string,
+ * parameter.location.description=The city and state,
+ * parameter.location.required=true,
+ * parameter.unit.enum=celsius,fahrenheit. This is a multi-value option
+ * with prefix: parameter.
*
* The option is a: <code>java.util.Map<java.lang.String,
* java.lang.String></code> type.