Tool Integration
A `Tool` is an external capability that an agent can invoke during a conversation. Tools are registered via `AgentBuilder.tool()` and exposed to the LLM as function definitions. The LLM decides when and how to call them.
Registering Tools
Before an agent can use a tool, you need to register it during agent construction. You can add tools one at a time with .tool() or pass a list with .tools(). Once registered, the agent automatically exposes them to the LLM so it can decide when to call each one.
Agent agent = AgentBuilder.create()
.llm(llm)
.role(role)
.tool(new BraveSearchTool()) // Single tool
.tools(List.of( // Multiple tools
new CalculatorTool(),
new WikipediaTool()
))
.build();Registered tools are automatically included in getToolsForLLM() and presented to the LLM as callable functions.
Tool Interface
To create a tool, you implement the Tool interface. At minimum, a tool needs a name, a description (which the LLM reads to understand what the tool does), and an execute method that performs the actual work. The optional metadata methods let you declare behavior hints like whether the tool needs user confirmation before running.
Every tool implements the Tool interface:
public interface Tool {
String execute(String query) throws Exception;
String getName();
String getDescription();
// Optional metadata
default boolean requiresConfirmation() { return false; }
default boolean isIdempotent() { return true; }
default boolean isParallelizable() { return true; }
default ToolCategory getCategory() { return ToolCategory.GENERAL; }
default int getPriority() { return 50; }
}See the Tools Reference for the full tool catalog and the Custom Tools guide for creating your own.
Tool Call Filters
Sometimes you want to restrict what an agent can do at runtime. Tool call filters let you intercept every tool call before it executes and decide whether to allow, block, or redirect it. This is useful for enforcing safety policies, preventing destructive operations, or guiding the LLM toward better tool usage.
Control which tools the agent is allowed to call:
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 let you observe tool execution without affecting it. They fire callbacks when a tool call starts and when it completes, giving you the tool name, arguments, result, and how long it took. This is the right place to add logging, track latency metrics, or build debugging dashboards.
Monitor tool execution for logging, metrics, or debugging:
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);
}
});