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 intnsai-toolsAgentBuilder.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();
});| Action | Behavior |
|---|---|
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);
}
});Custom Tools
A custom tool in TnsAI is a plain Java class with public methods annotated @Tool. The framework discovers them reflectively and exposes each method as a function the LLM can call. There is no base class to extend, no SPI to register, no Tool interface to implement — just an instance you hand to AgentBuilder.toolPojos(...).
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.