Advanced Tool Features
Beyond the basic registration paths covered in Tool Integration, the dispatcher layer exposes a small number of advanced surfaces: filter / listener composition, tool introspection, and direct invocation of tools outside the LLM loop.
Composing filters
setToolCallFilter accepts a single filter, but it's straightforward to compose multiple concerns by chaining decisions inside the lambda. Filters run before each tool call.
agent.setToolCallFilter((toolName, arguments) -> {
// Always block destructive tools
if (Set.of("file_write", "sql_query").contains(toolName)) {
return ToolCallAction.block();
}
// Guide the LLM away from expensive providers when the cheap one will do
if (toolName.equals("brave_search") && cacheHasRecent(arguments)) {
return ToolCallAction.guide("Result is in cache; call cache_lookup instead");
}
return ToolCallAction.allow();
});For more on filter actions (allow, block, guide, fallback), see Tool Integration / Tool Call Filters.
Composing listeners
setToolCallListener accepts a single listener. To run multiple observers (latency metrics + audit log + dashboard hook), implement a fan-out listener:
public class CompositeListener implements ToolCallListener {
private final List<ToolCallListener> delegates;
public CompositeListener(ToolCallListener... delegates) {
this.delegates = List.of(delegates);
}
@Override
public void onToolCallStart(String toolName, Map<String, Object> arguments) {
for (var l : delegates) l.onToolCallStart(toolName, arguments);
}
@Override
public void onToolCallComplete(String toolName, Object result, boolean success, long latencyMs) {
for (var l : delegates) l.onToolCallComplete(toolName, result, success, latencyMs);
}
}
agent.setToolCallListener(new CompositeListener(
metricsListener,
auditListener,
dashboardListener
));Inspecting the registry
Every agent built with AgentBuilder has a ToolMethodDispatcher; its registry() method exposes the full set of registered @Tool methods. Useful for snapshot tests, dynamic prompt construction (e.g. listing available tools in the system prompt), or operational dashboards.
import com.tnsai.tools.method.ToolMethodDispatcher;
import com.tnsai.tools.method.ToolMethodRegistry;
ToolMethodDispatcher dispatcher = agent.getToolMethodDispatcher();
ToolMethodRegistry registry = dispatcher.registry();
for (var tool : registry.allMethods()) {
System.out.println(tool.name() + " — " + tool.description());
}Invoking a tool outside the LLM loop
ToolMethodDispatcher.dispatch(name, args) is the same call path the LLM tool-call loop uses. Call it directly when you want to invoke a tool from non-LLM code — tests, batch jobs, smoke scripts — without spinning up the chat path:
Object result = dispatcher.dispatch(
"csv_summary",
Map.of("path", "/data/sales.csv")
);Argument types are coerced via Jackson — pass a Map<String, Object> whose entries match the method's @ToolParam parameter names. Unknown tool names throw IllegalArgumentException; argument-coercion failures throw ToolMethodInvocationException.
Looking up a tool by name
Use lookup(name) when you want metadata (description, schema) without invoking the tool:
import com.tnsai.tools.method.ToolMethod;
Optional<ToolMethod> tool = dispatcher.lookup("csv_summary");
tool.ifPresent(t -> System.out.println(t.description()));Related Documentation
- Tool Integration — basic
builtInTools/toolPojos/dynamicToolpaths - Custom Tools — writing your own
@ToolPOJOs - Action System — how
@ActionSpec(type = LLM)actions wire into the dispatcher - SPI Reference — pluggable backends for the rest of the framework
Tool Integration
A "tool" in TnsAI is a Java method annotated @Tool. Methods are grouped on POJO classes (toolkits); the framework discovers them reflectively at agent build time and exposes each as a function the LLM can call. Two registration paths share the same underlying ToolMethodRegistry:
Tools — Advanced
The function-shape POJO model deliberately keeps the tool surface small: a method, an annotation, a registry. Most "advanced" features that older docs covered (manifest generators, contract validators, security enforcers, parameter validators, retry/cache wrappers) were retired together with the legacy Tool interface in v0.6.0 / v0.7.0. Cross-cutting concerns now live one layer up — on the @ActionSpec annotation, on the agent's setToolCallFilter / setToolCallListener hooks, or on the dispatcher itself.