TnsAI

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:

  • AgentBuilder.builtInTools(BuiltInTool...) — the shipped toolkits in tnsai-tools
  • AgentBuilder.toolPojos(Object...) — your own annotated POJOs

For runtime-defined tools (e.g. MCP proxies), see dynamicTool(...) further down.

Registering Tools

import com.tnsai.agents.AgentBuilder;
import com.tnsai.enums.BuiltInTool;

Agent agent = AgentBuilder.create()
    .llm(llm)
    .role(role)
    .builtInTools(BuiltInTool.WEB_SEARCH_TOOLS, BuiltInTool.UTILITY_TOOLS)
    .toolPojos(new MyDomainTools(), new MyAnalyticsTools())
    .build();

Both calls accumulate into a single ToolMethodRegistry. Duplicate @Tool(name=...) values across the entire registration set fail fast at build() time.

Defining a @Tool method

import com.tnsai.annotations.Tool;
import com.tnsai.annotations.ToolParam;

public class MyDomainTools {

    @Tool(name = "find_customer", description = "Look up a customer by email")
    public Customer findCustomer(
        @ToolParam(description = "The customer's email address") String email
    ) {
        return customerRepo.findByEmail(email);
    }
}

The method name (or explicit @Tool(name = ...) if you prefer it different from the Java identifier) is what the LLM calls. @ToolParam descriptions populate the JSON-Schema sent to the model — write them like API parameter docs.

For a deeper walk-through, see Custom Tools. For the shipped catalog, see Catalog.

Runtime-Defined Tools

When a tool's identity is only known at runtime — typically an MCP proxy fronting a remote server's catalog — register it with dynamicTool(...) instead of an annotated POJO:

import com.tnsai.tools.method.DynamicToolMethod;

DynamicToolMethod proxy = DynamicToolMethod.builder()
    .name("remote_search")
    .description("Search the remote knowledge base")
    .parameter("query", "string", "Search term")
    .handler(args -> remoteClient.search((String) args.get("query")))
    .build();

AgentBuilder.create()
    .llm(llm)
    .role(role)
    .dynamicTool(proxy)
    .build();

DynamicToolMethod and POJO @Tool methods share the same registry and dispatcher; the LLM can't distinguish them.

Tool Call Filters

Sometimes you want to restrict what an agent can do at runtime. Tool call filters intercept every tool call before it executes and decide whether to allow, block, or redirect it. Useful for enforcing safety policies, preventing destructive operations, or guiding the LLM toward better tool usage.

agent.setToolCallFilter((toolName, arguments) -> {
    // Block dangerous tools
    if (toolName.equals("file_write")) return false;

    // Allow everything else
    return true;
});

For more nuanced control, use ToolCallAction:

agent.setToolCallFilter((toolName, arguments) -> {
    if (toolName.equals("sql_query")) {
        String query = (String) arguments.get("query");
        if (query.toUpperCase(Locale.ROOT).contains("DROP")) {
            return ToolCallAction.block();
        }
        if (query.toUpperCase(Locale.ROOT).contains("DELETE")) {
            return ToolCallAction.guide("Use soft deletes instead of DELETE statements");
        }
    }
    return ToolCallAction.allow();
});
ActionBehavior
ToolCallAction.allow()Proceed as-is
ToolCallAction.block()Reject the call
ToolCallAction.guide(message)Provide corrective feedback to the LLM
ToolCallAction.fallback(suggestion)Suggest an alternative tool or approach

Tool Call Listeners

Tool call listeners observe execution without affecting it. Callbacks fire when a tool call starts and completes, exposing the tool name, arguments, result, and latency. The right place to add logging, latency metrics, or debugging dashboards.

agent.setToolCallListener(new ToolCallListener() {
    @Override
    public void onToolCallStart(String toolName, Map<String, Object> arguments) {
        log.info("Calling tool: {} with args: {}", toolName, arguments);
    }

    @Override
    public void onToolCallComplete(String toolName, Object result, boolean success, long latencyMs) {
        log.info("Tool {} completed in {}ms (success={})", toolName, latencyMs, success);
    }
});

On this page