Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
"sourceType": "org.apache.camel.component.ai.tool.springboot.AiToolComponentConfiguration",
"defaultValue": false
},
{
"name": "camel.component.ai-tool.searchable",
"type": "java.lang.Boolean",
"description": "When true, this route tool is eligible for tool search and is omitted from the initial model request while the producer has tool search enabled. When false, the tool stays directly visible to the model. Searchability is a visibility policy, not an authorization policy, and has no effect when tool search is disabled.",
"sourceType": "org.apache.camel.component.ai.tool.springboot.AiToolComponentConfiguration",
"defaultValue": false
},
{
"name": "camel.component.ai-tool.tags",
"type": "java.lang.String",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ public class AiToolComponentConfiguration
* when the tool is exposed via camel-mcp-server.
*/
private Boolean returnDirect = false;
/**
* When true, this route tool is eligible for tool search and is omitted
* from the initial model request while the producer has tool search
* enabled. When false, the tool stays directly visible to the model.
* Searchability is a visibility policy, not an authorization policy, and
* has no effect when tool search is disabled.
*/
private Boolean searchable = false;
/**
* Comma-separated list of tags used to group tools. Producers filter the
* registry by these tags to select which tools to expose to the LLM. When
Expand Down Expand Up @@ -266,6 +274,14 @@ public void setReturnDirect(Boolean returnDirect) {
this.returnDirect = returnDirect;
}

public Boolean getSearchable() {
return searchable;
}

public void setSearchable(Boolean searchable) {
this.searchable = searchable;
}

public String getTags() {
return tags;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@
"type": "java.time.Duration",
"description": "The request timeout of the model (30s, 2m), when the model is created from the provider.",
"sourceType": "org.apache.camel.component.langchain4j.agent.springboot.LangChain4jAgentComponentConfiguration"
},
{
"name": "camel.component.langchain4j-agent.tool-search",
"type": "java.lang.Boolean",
"description": "When true, route tools marked searchable and remote MCP tools are omitted from the initial model request and exposed through one synthetic search tool. Directly visible route tools and customTools stay in the request. Search is a visibility policy, not authorization. Enabling it does not skip MCP tools\/list. A dynamic tool provider registered outside Camel is not searched. Only supported in inline agent creation mode.",
"sourceType": "org.apache.camel.component.langchain4j.agent.springboot.LangChain4jAgentComponentConfiguration",
"defaultValue": false
},
{
"name": "camel.component.langchain4j-agent.tool-search-embedding-model",
"type": "dev.langchain4j.model.embedding.EmbeddingModel",
"description": "LangChain4j embedding model used when toolSearchStrategy=vector. Required for vector search. The option is a dev.langchain4j.model.embedding.EmbeddingModel type.",
"sourceType": "org.apache.camel.component.langchain4j.agent.springboot.LangChain4jAgentComponentConfiguration"
},
{
"name": "camel.component.langchain4j-agent.tool-search-max-results",
"type": "java.lang.Integer",
"description": "Maximum number of tools returned by one tool search. Route and MCP tools are ranked together.",
"sourceType": "org.apache.camel.component.langchain4j.agent.springboot.LangChain4jAgentComponentConfiguration",
"defaultValue": 5
},
{
"name": "camel.component.langchain4j-agent.tool-search-strategy",
"type": "org.apache.camel.component.ai.tool.AiToolSearchStrategy",
"description": "How tool search ranks route and MCP tools. 'keyword' matches the tool name and description. 'vector' uses cosine similarity and requires toolSearchEmbeddingModel. Vector search does not fall back to keyword search.",
"sourceType": "org.apache.camel.component.langchain4j.agent.springboot.LangChain4jAgentComponentConfiguration",
"defaultValue": "keyword"
}
],
"hints": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import java.util.List;
import java.util.Map;
import dev.langchain4j.mcp.client.McpClient;
import dev.langchain4j.model.embedding.EmbeddingModel;
import org.apache.camel.component.ai.tool.AiToolSearchStrategy;
import org.apache.camel.component.langchain4j.agent.LangChain4jAgentComponent;
import org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration;
import org.apache.camel.component.langchain4j.agent.api.Agent;
import org.apache.camel.component.langchain4j.agent.api.AgentConfiguration;
Expand Down Expand Up @@ -121,6 +124,33 @@ public class LangChain4jAgentComponentConfiguration
* Tags for discovering and calling Camel route tools
*/
private String tags;
/**
* When true, route tools marked searchable and remote MCP tools are omitted
* from the initial model request and exposed through one synthetic search
* tool. Directly visible route tools and customTools stay in the request.
* Search is a visibility policy, not authorization. Enabling it does not
* skip MCP tools/list. A dynamic tool provider registered outside Camel is
* not searched. Only supported in inline agent creation mode.
*/
private Boolean toolSearch = false;
/**
* LangChain4j embedding model used when toolSearchStrategy=vector. Required
* for vector search. The option is a
* dev.langchain4j.model.embedding.EmbeddingModel type.
*/
private EmbeddingModel toolSearchEmbeddingModel;
/**
* Maximum number of tools returned by one tool search. Route and MCP tools
* are ranked together.
*/
private Integer toolSearchMaxResults = 5;
/**
* How tool search ranks route and MCP tools. 'keyword' matches the tool
* name and description. 'vector' uses cosine similarity and requires
* toolSearchEmbeddingModel. Vector search does not fall back to keyword
* search.
*/
private AiToolSearchStrategy toolSearchStrategy = AiToolSearchStrategy.KEYWORD;
/**
* Whether autowiring is enabled. This is used for automatic autowiring
* options (the option must be marked as autowired) by looking up in the
Expand Down Expand Up @@ -286,6 +316,39 @@ public void setTags(String tags) {
this.tags = tags;
}

public Boolean getToolSearch() {
return toolSearch;
}

public void setToolSearch(Boolean toolSearch) {
this.toolSearch = toolSearch;
}

public EmbeddingModel getToolSearchEmbeddingModel() {
return toolSearchEmbeddingModel;
}

public void setToolSearchEmbeddingModel(
EmbeddingModel toolSearchEmbeddingModel) {
this.toolSearchEmbeddingModel = toolSearchEmbeddingModel;
}

public Integer getToolSearchMaxResults() {
return toolSearchMaxResults;
}

public void setToolSearchMaxResults(Integer toolSearchMaxResults) {
this.toolSearchMaxResults = toolSearchMaxResults;
}

public AiToolSearchStrategy getToolSearchStrategy() {
return toolSearchStrategy;
}

public void setToolSearchStrategy(AiToolSearchStrategy toolSearchStrategy) {
this.toolSearchStrategy = toolSearchStrategy;
}

public Boolean getAutowiredEnabled() {
return autowiredEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public Set<ConvertiblePair> getConvertibleTypes() {
answer.add(new ConvertiblePair(String.class, org.apache.camel.component.langchain4j.agent.api.AgentConfiguration.class));
answer.add(new ConvertiblePair(String.class, org.apache.camel.component.langchain4j.agent.api.AgentFactory.class));
answer.add(new ConvertiblePair(String.class, org.apache.camel.component.langchain4j.agent.LangChain4jAgentConfiguration.class));
answer.add(new ConvertiblePair(String.class, dev.langchain4j.model.embedding.EmbeddingModel.class));
return answer;
}

Expand Down
3 changes: 2 additions & 1 deletion docs/spring-boot/modules/ROOT/pages/starters/ai-tool.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Please refer to the above links for usage and configuration details.

== Spring Boot Auto-Configuration

The starter supports 18 options, which are listed below.
The starter supports 19 options, which are listed below.

[width="100%",cols="2,5,^1,2",options="header"]
|===
Expand All @@ -44,6 +44,7 @@ The starter supports 18 options, which are listed below.
| camel.component.ai-tool.parameters | Tool input parameters. Format: parameter.NAME=TYPE, parameter.NAME.description=TEXT, parameter.NAME.required=true or false, parameter.NAME.enum=val1,val2. Supported types: string, integer, number, boolean. Mutually exclusive with argSchema. This is a multi-value option with prefix: parameter. | | String>
| camel.component.ai-tool.read-only-hint | MCP hint that the tool only reads data and does not modify state. Advisory for MCP clients; not enforced by Camel. | false | Boolean
| camel.component.ai-tool.return-direct | When true, AI producers that support agentic tool loops (such as camel-openai) return this tool's result directly to the caller without sending it back to the model. Also published as an MCP tool annotation when the tool is exposed via camel-mcp-server. | false | Boolean
| camel.component.ai-tool.searchable | When true, this route tool is eligible for tool search and is omitted from the initial model request while the producer has tool search enabled. When false, the tool stays directly visible to the model. Searchability is a visibility policy, not an authorization policy, and has no effect when tool search is disabled. | false | Boolean
| camel.component.ai-tool.tags | Comma-separated list of tags used to group tools. Producers filter the registry by these tags to select which tools to expose to the LLM. When omitted, the tool goes into a default pool available to all producers. | | String
| camel.component.ai-tool.title | Optional display title for MCP tool listings. Advisory hint for MCP clients only. | | String
|===
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ See the https://docs.langchain4j.dev/tutorials/spring-boot-integration[LangChain
integration documentation] for provider configuration, and the component documentation for
creating and configuring a Camel agent.

The starter supports 23 options, which are listed below.
The starter supports 27 options, which are listed below.

[width="100%",cols="2,5,^1,2",options="header"]
|===
Expand Down Expand Up @@ -62,4 +62,8 @@ The starter supports 23 options, which are listed below.
| camel.component.langchain4j-agent.tags | Tags for discovering and calling Camel route tools | | String
| camel.component.langchain4j-agent.temperature | The sampling temperature of the model, when the model is created from the provider. | | Double
| camel.component.langchain4j-agent.timeout | The request timeout of the model (30s, 2m), when the model is created from the provider. | | Duration
| camel.component.langchain4j-agent.tool-search | When true, route tools marked searchable and remote MCP tools are omitted from the initial model request and exposed through one synthetic search tool. Directly visible route tools and customTools stay in the request. Search is a visibility policy, not authorization. Enabling it does not skip MCP tools/list. A dynamic tool provider registered outside Camel is not searched. Only supported in inline agent creation mode. | false | Boolean
| camel.component.langchain4j-agent.tool-search-embedding-model | LangChain4j embedding model used when toolSearchStrategy=vector. Required for vector search. The option is a dev.langchain4j.model.embedding.EmbeddingModel type. | | EmbeddingModel
| camel.component.langchain4j-agent.tool-search-max-results | Maximum number of tools returned by one tool search. Route and MCP tools are ranked together. | 5 | Integer
| camel.component.langchain4j-agent.tool-search-strategy | How tool search ranks route and MCP tools. 'keyword' matches the tool name and description. 'vector' uses cosine similarity and requires toolSearchEmbeddingModel. Vector search does not fall back to keyword search. | keyword | AiToolSearchStrategy
|===
19 changes: 12 additions & 7 deletions tooling/camel-spring-boot-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -280,37 +280,37 @@
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-commons</artifactId>
<version>1.17.1</version>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>1.17.1</version>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-jmx</artifactId>
<version>1.17.1</version>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.17.1</version>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>1.7.1</version>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
<version>1.7.1</version>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<version>1.7.1</version>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>io.opentelemetry.proto</groupId>
Expand Down Expand Up @@ -3669,6 +3669,11 @@
<artifactId>camel-health</artifactId>
<version>4.24.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-hibernate</artifactId>
<version>4.24.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-hivemq</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ private void createEipModelConfigurationSource(String packageName, EipModel mode
prop.getField().setLiteralInitializer(option.getDefaultValue().toString());
} else if (!isBlank(option.getEnums())) {
String enumShortName = type.substring(type.lastIndexOf(".") + 1);
prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue());
prop.getField().setLiteralInitializer(enumShortName + "." + enumConstantName(type, option.getDefaultValue().toString()));
commonClass.addImport(model.getJavaType());
}
}
Expand Down Expand Up @@ -412,7 +412,7 @@ private void createRestConfigurationSource(String packageName, EipModel model, S
prop.getField().setLiteralInitializer(option.getDefaultValue().toString());
} else if (!isBlank(option.getEnums())) {
String enumShortName = type.substring(type.lastIndexOf(".") + 1);
prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue());
prop.getField().setLiteralInitializer(enumShortName + "." + enumConstantName(type, option.getDefaultValue().toString()));
javaClass.addImport(model.getJavaType());
}
}
Expand Down Expand Up @@ -773,7 +773,7 @@ private void createComponentConfigurationSource(String packageName, ComponentMod
prop.getField().setLiteralInitializer(option.getDefaultValue().toString());
} else if (!isBlank(option.getEnums())) {
String enumShortName = type.substring(type.lastIndexOf(".") + 1);
prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue());
prop.getField().setLiteralInitializer(enumShortName + "." + enumConstantName(type, option.getDefaultValue().toString()));
javaClass.addImport(model.getJavaType());
}
}
Expand Down Expand Up @@ -888,6 +888,24 @@ private GenericType loadType(String type) throws MojoFailureException {
}
}

// the enum constant matching the default value, as the documented enum values may use another case
private String enumConstantName(String enumType, String value) {
try {
Class<?> clazz = loadClass(enumType);
if (clazz.isEnum()) {
for (Object constant : clazz.getEnumConstants()) {
String name = ((Enum<?>) constant).name();
if (name.equalsIgnoreCase(value)) {
return name;
}
}
}
} catch (MojoFailureException e) {
// ignore and use the value as-is
}
return value;
}

// try loading class, looking for inner classes if needed
private Class loadClass(String loadClassName) throws MojoFailureException {
Class optionClass;
Expand Down Expand Up @@ -1238,7 +1256,7 @@ private void createDataFormatConfigurationSource(String packageName, DataFormatM
prop.getField().setLiteralInitializer(defaultValue.toString());
} else if (!isBlank(option.getEnums())) {
String enumShortName = type.substring(type.lastIndexOf(".") + 1);
prop.getField().setLiteralInitializer(enumShortName + "." + defaultValue);
prop.getField().setLiteralInitializer(enumShortName + "." + enumConstantName(type, defaultValue.toString()));
javaClass.addImport(model.getJavaType());
}
}
Expand Down Expand Up @@ -1368,7 +1386,7 @@ private void createLanguageConfigurationSource(String packageName, LanguageModel
prop.getField().setLiteralInitializer(option.getDefaultValue().toString());
} else if (isBlank(option.getEnums())) {
String enumShortName = type.substring(type.lastIndexOf(".") + 1);
prop.getField().setLiteralInitializer(enumShortName + "." + option.getDefaultValue());
prop.getField().setLiteralInitializer(enumShortName + "." + enumConstantName(type, option.getDefaultValue().toString()));
javaClass.addImport(model.getJavaType());
}
}
Expand Down
Loading