TnsAI

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()));

On this page