Tools
Give your bots capabilities by defining tools. A tool is a function the LLM can call during processing, with typed parameters and automatic schema generation.
Defining tools
Use the tool() helper. It takes a name, description, Zod schema, and function:
import { Baleybot, tool } from '@baleybots/core';
import { z } from 'zod';
// 1. Define the input schema
const WeatherInput = z.object({
city: z.string().describe('City name'),
});
// 2. Define the tool
const weatherTool = tool(
'get_weather',
'Get current weather for a city',
WeatherInput,
async ({ city }) => {
// Your weather API call here
return { temp: 72, condition: 'sunny', city };
},
);
// 3. Create the agent
const weatherBot = Baleybot.create({
name: 'weather-assistant',
goal: 'Help users with weather information',
tools: { weather: weatherTool },
});
const answer = await weatherBot.process(
"What's the weather in Tokyo?"
);
Parameters are automatically typed from the Zod schema. No manual type annotations needed.
Complex parameters
Zod handles all parameter shapes naturally:
// Object parameters — extract nested schemas
const UserInput = z.object({
name: z.string().describe('User name'),
age: z.number().optional().describe('User age'),
});
const createUser = tool(
'create_user',
'Creates a new user',
z.object({ user: UserInput }),
async ({ user }) => `Created user ${user.name}`,
);
// Array parameters
const SumInput = z.object({
numbers: z.array(z.number()).describe('Numbers to sum'),
});
const sumArray = tool(
'sum_array',
'Sums an array of numbers',
SumInput,
async ({ numbers }) => numbers.reduce((a, b) => a + b, 0),
);
// Enum parameters
const Color = z.enum(['red', 'green', 'blue']);
const setColor = tool(
'set_color',
'Sets the color',
z.object({ color: Color.describe('The color to set') }),
async ({ color }) => `Color set to ${color}`,
);
v6 features
Tools support AI SDK v6 features via an optional fifth argument:
Approval gating
Require user approval before a tool executes:
const deleteTool = tool(
'delete_file',
'Delete a file from the system',
z.object({ path: z.string() }),
async ({ path }) => {
// delete logic
return { deleted: true };
},
{ needsApproval: true }
);
You can also gate conditionally:
const WriteFileInput = z.object({
path: z.string(),
content: z.string(),
});
const writeTool = tool(
'write_file',
'Write content to a file',
WriteFileInput,
async ({ path, content }) => {
// write logic
return { written: true };
},
{
needsApproval: (params) =>
params.path.startsWith('/etc/') || params.path.includes('.env'),
},
);
Strict schema mode
Force the provider to enforce strict adherence to the schema, preventing hallucinated parameters:
const strictTool = tool(
'query_db',
'Query the database',
z.object({ sql: z.string() }),
async ({ sql }) => { /* ... */ },
{ strict: true }
);
Output transformation
Control what the model sees after a tool runs:
const searchTool = tool(
'search',
'Search the knowledge base',
z.object({ query: z.string() }),
async ({ query }) => {
const results = await search(query);
return results; // full results returned to your code
},
{
// model only sees a summary, not the full payload
toModelOutput: ({ output }) => ({
type: 'text',
value: `Found ${output.length} results. Top: ${output[0]?.title}`,
}),
}
);
You can also omit tool output from the model entirely:
{
toModelOutput: () => ({ type: 'omit' })
}
Streaming tool events
Baleybot handles tool execution internally. To observe tool calls in real-time, use onToken:
const result = await bot.process('What is 5 + 3?', {
onToken: (_name, event) => {
switch (event.type) {
case 'tool_call_stream_start':
console.log('Calling tool:', event.toolName);
break;
case 'tool_execution_output':
console.log('Tool result:', event.result);
break;
}
},
});
See the Streaming guide for all event types.
Alternative APIs
For most use cases, tool() is all you need. Two alternative APIs exist for specific situations:
defineZodTool() -- object-based syntax
Identical to tool() but uses an object instead of positional arguments. Use this if you prefer named properties or need to spread options:
import { defineZodTool } from '@baleybots/core';
import { z } from 'zod';
const AddInput = z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
});
const addTool = defineZodTool({
name: 'add',
description: 'Adds two numbers',
inputSchema: AddInput,
function: (params) => params.a + params.b,
needsApproval: false,
strict: true,
});
Note:
tool()callsdefineZodTool()internally -- they produce identical results.
defineTool() -- raw JSON Schema
Use this only when you have a pre-existing JSON Schema and don't want to convert it to Zod:
import { defineTool } from '@baleybots/core';
const addTool = defineTool({
name: 'add',
description: 'Adds two numbers',
inputSchema: {
type: 'object',
properties: {
a: { type: 'number', description: 'First number' },
b: { type: 'number', description: 'Second number' },
},
required: ['a', 'b'],
},
function: ({ a, b }: { a: number; b: number }) => a + b,
});
Note:
defineTool()does not support v6 features (needsApproval,strict,toModelOutput). Usetool()ordefineZodTool()for those.
API reference
tool(name, description, inputSchema, fn, options?)
Quick inline tool definition using Zod schema.
name-- tool namedescription-- what the tool doesinputSchema-- Zod schema for parametersfn-- the function to execute (params automatically typed)options?-- v6 features:needsApproval,strict,toModelOutput
defineZodTool(definition)
Object-based tool definition with Zod schema.
name-- tool namedescription-- what the tool doesinputSchema-- Zod schema for parametersfunction-- the function to executeneedsApproval?-- boolean or function for approval gatingstrict?-- enable strict JSON schema modetoModelOutput?-- transform output before sending to model
defineTool(definition)
Tool definition with raw JSON Schema.
name-- tool namedescription-- what the tool doesinputSchema-- JSON Schema object for parametersfunction-- the function to execute
Config options
tools?-- record of tool definitionsoutputSchema?-- schema for structured output (when not using tools)