[
https://issues.apache.org/jira/browse/CAMEL-24767?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18115936#comment-18115936
]
Federico Mariani commented on CAMEL-24767:
------------------------------------------
h3. Usage examples
Option names below are illustrative, to make the proposal concrete — see open
question 1 in the description. They assume {{searchable=true}} on the tool
consumer and {{toolSearch=true}} on the producer.
h4. The situation today
{code:java}
// 150 tool routes across the estate
from("ai-tool:getOrder?tags=erp&description=Get an order by
id¶meter.orderId=string").to("sql:...");
from("ai-tool:getInvoice?tags=erp&description=Get an invoice by
id¶meter.invoiceId=string").to("sql:...");
// ... 148 more
// every one of them is serialised into every single request
from("direct:ask")
.to("langchain4j-agent:ops?agentConfiguration=#cfg&tags=erp");
{code}
h4. 1. Mark tools searchable (consumer side, {{camel-ai-tool}})
{code:java}
// Entry-point tool: always visible, so the model knows the search space exists
from("ai-tool:listDomains?tags=erp&description=List the available business
domains")
.setBody(constant("orders, invoices, shipments, customers"));
// The long tail: indexed, not sent to the model until found
from("ai-tool:getOrder?tags=erp&searchable=true"
+ "&description=Get an order by id¶meter.orderId=string")
.to("sql:select * from orders where id = :#orderId");
from("ai-tool:getInvoice?tags=erp&searchable=true"
+ "&description=Get an invoice by id¶meter.invoiceId=string")
.to("sql:select * from invoices where id = :#invoiceId");
{code}
Tools without {{searchable=true}} keep today's behaviour and stay
always-visible, so the change is opt-in and existing routes are unaffected.
h4. 2. camel-langchain4j-agent
{code:java}
from("direct:ask")
.to("langchain4j-agent:ops?agentConfiguration=#cfg&tags=erp"
+ "&toolSearch=true"
+ "&toolSearchMaxResults=5");
{code}
Semantic search instead of keyword matching:
{code:java}
from("direct:ask")
.to("langchain4j-agent:ops?agentConfiguration=#cfg&tags=erp"
+ "&toolSearch=true"
+ "&toolSearchStrategy=VECTOR"
+ "&embeddingModel=#embeddingModel");
{code}
This is already reachable today in the Java DSL with no Camel change, through
the existing {{AgentConfiguration#withAiServicesCustomizer}} escape hatch —
useful for validating the behaviour before settling on an API:
{code:java}
AgentConfiguration config = new AgentConfiguration()
.withAiServicesCustomizer(ai ->
ai.toolSearchStrategy(new VectorToolSearchStrategy(embeddingModel)));
{code}
h4. 3. camel-spring-ai-chat
{code:java}
from("direct:ask")
.to("spring-ai-chat:ops?chatModel=#chatModel&tags=erp"
+ "&toolSearch=true"
+ "&toolSearchMaxResults=5");
{code}
Spring AI scopes the index per session and evicts it. The existing
{{CamelSpringAiChatConversationId}} header is the natural session key, so a
multi-turn conversation keeps the tools it discovered:
{code:java}
from("direct:ask")
.setHeader("CamelSpringAiChatConversationId", header("userId"))
.to("spring-ai-chat:ops?chatModel=#chatModel&chatMemory=#chatMemory&tags=erp"
+ "&toolSearch=true");
{code}
h4. 4. camel-openai
{code:java}
from("direct:ask")
.to("openai:chat-completion?model=gpt-4&tags=erp"
+ "&toolSearch=true"
+ "&toolSearchMaxResults=5");
{code}
h4. 5. Route tools and MCP tools in one index
Both are indexed together, so they rank against each other rather than one
family being always-visible (see the MCP comment above):
{code:java}
from("direct:ask")
.to("langchain4j-agent:ops?agentConfiguration=#cfg&tags=erp"
+ "&mcpClients=#githubMcp,#jiraMcp"
+ "&toolSearch=true");
{code}
h4. 6. What happens at runtime
With {{toolSearch=true}}, a turn looks like this:
# The model receives the always-visible tools plus one {{tool_search_tool}},
instead of all 150.
# User asks: _"what is the status of order 4711?"_
# The model calls {{tool_search_tool(query="look up an order")}}.
# Camel returns the matching tool names, e.g. {{getOrder}}, {{getOrderStatus}}.
# Those tools are added to the next request, and the model calls
{{getOrder(orderId=4711)}}.
# The route runs and the answer comes back.
The cost is one extra model round trip per turn, against a much smaller prompt
on every request. That trade favours search once the tool count is high; below
roughly 15-20 tools it is likely a net loss, which is why this should stay
opt-in and be documented as such.
h4. YAML
{code:yaml}
- route:
from:
uri: direct:ask
steps:
- to:
uri: langchain4j-agent:ops
parameters:
agentConfiguration: "#cfg"
tags: erp
toolSearch: true
toolSearchMaxResults: 5
{code}
_Claude Code on behalf of Croway_
> camel-ai-tool - reinstate tool search, shared by langchain4j-agent,
> spring-ai-chat and openai
> ---------------------------------------------------------------------------------------------
>
> Key: CAMEL-24767
> URL: https://issues.apache.org/jira/browse/CAMEL-24767
> Project: Camel
> Issue Type: Improvement
> Reporter: Federico Mariani
> Priority: Major
>
> h2. Context
> CAMEL-22851 added a native tool-search-tool to {{camel-langchain4j-tools}}
> (shipped in 4.18.0). It added an {{exposed}} URI option: with
> {{exposed=false}} a tool was not sent to the LLM on every request but placed
> in a searchable registry, and a {{toolSearchTool}} was auto-exposed so the
> LLM could discover it on demand.
> {{camel-langchain4j-tools}} was deprecated in 4.22 and removed in 4.23
> (commit 2a2b0e1bb71c), replaced by {{camel-ai-tool}} for tool definition and
> {{camel-langchain4j-agent}} for tool calling. The tool-search capability was
> *not* ported: {{AiToolConfiguration}} has no {{exposed}}/searchable
> equivalent, and none of the three AI producers that consume
> {{AiToolRegistry}} support tool search today.
> So the feature regressed out of the project as a side effect of the
> migration. The 4.22 and 4.23 upgrade guides document the
> {{langchain4j-tools:}} -> {{ai-tool:}} route migration but do not mention
> that {{exposed=false}} has no equivalent.
> h2. Why it matters
> All three producers select tools by {{tags}} and serialise every matching
> {{ai-tool:}} route into every request:
> * {{camel-langchain4j-agent}} - via {{AiToolSpecToLangChain4j}} /
> {{ToolProvider}}
> * {{camel-spring-ai-chat}} - {{SpringAiChatProducer#applyRequestOptions}}
> puts all tag-matched callbacks into {{ToolCallingChatOptions}}
> * {{camel-openai}} - {{OpenAIEndpoint}} lines 283/366
> With a broad tag over a large route catalogue this consumes a significant
> part of the context window on every call, and tool-selection accuracy
> degrades well before the context limit is reached.
> h2. Proposal
> Reinstate tool search in {{camel-ai-tool}}, which is the shared abstraction
> ({{AiToolRegistry}} / {{AiToolSpec}}), so the three consumers behave
> consistently rather than each inheriting whatever its upstream framework does:
> # Consumer side: an option on {{ai-tool:}} marking a tool searchable rather
> than always-visible (the {{exposed}} option from CAMEL-22851, or a clearer
> name).
> # Producer side: a {{toolSearch}} option on {{langchain4j-agent}},
> {{spring-ai-chat}} and {{openai}} enabling the search tool for that endpoint.
> # A scoring/index implementation over {{AiToolSpec}} in {{camel-ai-tool}}, so
> {{tags}} semantics and ranking are identical across the three.
> # Upgrade-guide entry noting the capability was absent between the removal
> and this change.
> h2. Framework support now available
> This did not exist when CAMEL-22851 was implemented (it was hand-rolled in
> Camel). Both frameworks now ship it, at versions Camel already depends on:
> * *LangChain4j 1.20.0* - {{dev.langchain4j.service.tool.search}}
> ({{@Experimental}}, since 1.12.0): {{ToolSearchStrategy}} with
> {{SimpleToolSearchStrategy}} (keyword) and {{VectorToolSearchStrategy}}
> (embeddings, needs only an {{EmbeddingModel}}), wired with
> {{AiServices.toolSearchStrategy(...)}}. Tools supplied through a
> {{ToolProvider}} - which is how Camel passes route tools - are searchable:
> {{ToolService#createContext}} builds {{availableTools}} from static tools and
> providers first, then applies the search service.
> {{AbstractAgent#configureBuilder}} already sets sibling options
> ({{maxToolCallingRoundTrips}}, {{hallucinatedToolNameStrategy}}), so this is
> a few lines. It is also reachable today without any Camel change through the
> existing {{AgentConfiguration#withAiServicesCustomizer}} escape hatch, which
> makes it easy to validate the behaviour before committing to an API.
> * *Spring AI 2.0.1* - {{spring-ai-tool-search-tool}} and
> {{spring-ai-tool-search-advisor}}: {{ToolIndex}} (regex, Lucene, vector
> store), {{ToolSearchToolCallingAdvisor}} with {{maxResults}} and
> session-scoped LRU/TTL eviction. Session scoping maps onto the existing
> {{CamelSpringAiChatConversationId}} header. Note this advisor extends
> {{ToolCallingAdvisor}}, which replaces the model-internal tool-calling loop -
> that is the main design consideration on this side.
> * *camel-openai* - no equivalent in the OpenAI Java SDK, but Camel owns the
> agentic loop already ({{maxToolIterations}}), so the search tool has to be
> driven directly. This is the strongest argument for putting the index in
> {{camel-ai-tool}}: it gives the OpenAI component an implementation to reuse
> instead of a bespoke one.
> h2. Open questions
> * Option naming, and whether searchable is opt-in on the tool
> ({{exposed=false}}, as in CAMEL-22851) or opt-in on the producer
> ({{toolSearch=true}}), or both.
> * Keyword matching (as the original implementation did, by tag) versus
> embedding-based semantic search, which both frameworks now offer.
> * Whether to delegate to each framework's native implementation, or keep a
> single Camel-owned index for consistent behaviour across the three
> components. The frameworks differ in how discovered tools persist:
> LangChain4j accumulates them through chat-memory message attributes, Spring
> AI through a session-scoped index with eviction.
> Prior art for the design is in commit 5ef1539bacbb (CAMEL-22851), in
> particular {{ToolSearchTool}} and the searchable registry in
> {{CamelToolExecutorCache}}.
> _Claude Code on behalf of Croway_
--
This message was sent by Atlassian Jira
(v8.20.10#820010)