Tools & Function Calling
Learn how to extend AI capabilities by providing tools (also known as function calling) that the model can use to access external data and perform actions.
How Tool Calling Works
Tool calling allows the AI model to request execution of external functions when it needs additional capabilities.
Flow:
1. Client defines tools in request
2. Model decides if/when to call tools
3. API returns tool_use event with tool calls
4. Client executes tools locally
5. Client sends tool results in next request
6. Model processes results and responds
7. Repeat until model finishes (no more tool calls)
Tools are NOT executed server-side. The API only returns tool call requests for the client to execute. This design ensures security and flexibility.
Tool Definition Format
Each tool must be wrapped in a toolSchema object with three required components:
{
toolSchema: {
name: string; // Unique identifier (snake_case recommended)
description: string; // Clear description of what the tool does
parameters: object; // JSON Schema for parameters
}
}
toolSchema.name
Type: string
Description: Unique identifier for the tool. Use snake_case for consistency.
Examples: get_weather, search_database, send_email
toolSchema.description
Type: string
Description: Clear, concise description of what the tool does and when to use it. This helps the model understand when to call the tool.
Best practices:
- Be specific about functionality
- Include relevant use cases
- Mention any limitations
Example:
{
"toolSchema": {
"description": "Get current weather information for a specific location. Use this when the user asks about weather conditions, temperature, or forecasts."
}
}
toolSchema.parameters
Type: object (JSON Schema)
Description: JSON Schema definition of the tool's parameters.
Required fields:
type: Must be"object"properties: Object defining each parameterrequired: Array of required parameter names
Example:
{
"toolSchema": {
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name (e.g., 'San Francisco', 'London')"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
}
Complete Example Tool Definition
Here's a complete tool definition for a weather API:
{
"toolSchema": {
"name": "get_weather",
"description": "Get current weather information for a specific location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name (e.g., 'San Francisco')"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit (default: celsius)"
}
},
"required": ["location"]
}
}
}
Complete Tool Calling Workflow
Let's walk through a complete tool calling example.
Step 1: Define Tools and Make Initial Request
const tools = [
{
toolSchema: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name'
}
},
required: ['location']
}
}
}
];
const response = await fetch('https://app.bike4mind.com/api/ai/v1/completions', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'claude-3-5-sonnet',
messages: [
{
role: 'user',
content: 'What is the weather in San Francisco?'
}
],
options: {
tools: tools // Include tools in request
}
})
});
Step 2: Receive Tool Use Event
The model decides to use the weather tool:
{
"type": "tool_use",
"text": "I'll check the weather for you.",
"tools": [
{
"name": "get_weather",
"arguments": "{\"location\":\"San Francisco\"}",
"id": "toolu_01ABC123"
}
],
"usage": {
"inputTokens": 120,
"outputTokens": 65
}
}
Note: The arguments field is a JSON string that must be parsed. The id is required for pairing with tool_result.
Step 3: Execute Tool Locally
function executeToolLocally(toolName, toolArguments) {
// Parse the JSON string arguments
const toolInput = JSON.parse(toolArguments);
if (toolName === 'get_weather') {
// Call your weather API
const weatherData = callWeatherAPI(toolInput.location);
return {
location: toolInput.location,
temperature: 72,
condition: 'Sunny',
humidity: 45,
wind_speed: 5
};
}
throw new Error(`Unknown tool: ${toolName}`);
}
// Execute the tool
const toolResult = executeToolLocally('get_weather', { location: 'San Francisco' });
Step 4: Send Tool Result Back
const followUpResponse = await fetch('https://app.bike4mind.com/api/ai/v1/completions', {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'claude-3-5-sonnet',
messages: [
{
role: 'user',
content: 'What is the weather in San Francisco?'
},
{
role: 'assistant',
content: [
{
type: 'text',
text: "I'll check the weather for you."
},
{
type: 'tool_use',
id: 'toolu_01ABC123', // Use the ID from the tool_use response
name: 'get_weather',
input: { location: 'San Francisco' }
}
]
},
{
role: 'user',
content: [
{
type: 'tool_result',
tool_use_id: 'toolu_01ABC123', // Must match the tool_use id
content: JSON.stringify(toolResult)
}
]
}
],
options: {
tools: tools // Include same tools array
}
})
});
Step 5: Receive Final Response
{
"type": "content",
"text": "The weather in San Francisco is currently sunny with a temperature of 72°F. The humidity is at 45% with light winds of 5 mph.",
"usage": {
"inputTokens": 180,
"outputTokens": 42
}
}
Multiple Tools Example
You can define multiple tools for different capabilities:
const tools = [
{
toolSchema: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string', description: 'City name' }
},
required: ['location']
}
}
},
{
toolSchema: {
name: 'search_database',
description: 'Search the knowledge database for information',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max results', default: 10 }
},
required: ['query']
}
}
},
{
toolSchema: {
name: 'send_email',
description: 'Send an email to a recipient',
parameters: {
type: 'object',
properties: {
to: { type: 'string', description: 'Email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body' }
},
required: ['to', 'subject', 'body']
}
}
}
];
The model will choose the appropriate tool(s) based on the user's request.
Best Practices
1. Clear Tool Descriptions
Help the model understand when to use each tool:
❌ Bad:
{
"name": "get_data",
"description": "Gets data"
}
✅ Good:
{
"name": "get_weather",
"description": "Get current weather information for a specific location. Use this when the user asks about weather conditions, temperature, or forecasts for any city."
}
2. Validate Tool Inputs
Always validate parameters before execution:
function executeToolSafely(toolName, toolInput) {
// Validate tool name
const allowedTools = ['get_weather', 'search_database'];
if (!allowedTools.includes(toolName)) {
throw new Error(`Unknown tool: ${toolName}`);
}
// Validate input parameters
if (toolName === 'get_weather') {
if (typeof toolInput.location !== 'string') {
throw new Error('Invalid location parameter');
}
if (toolInput.location.length === 0) {
throw new Error('Location cannot be empty');
}
}
// Execute tool
return executeTool(toolName, toolInput);
}
3. Handle Tool Errors Gracefully
Return error messages to the model:
function executeToolWithErrorHandling(toolName, toolInput) {
try {
return executeTool(toolName, toolInput);
} catch (error) {
// Return error to model
return {
error: true,
message: `Tool execution failed: ${error.message}`,
toolName: toolName
};
}
}
The model will see the error and can respond appropriately (e.g., apologize, try alternative approach).
4. Set Timeouts
Don't let tool execution hang indefinitely:
async function executeToolWithTimeout(toolName, toolInput, timeoutMs = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Tool execution timeout')), timeoutMs);
});
const executionPromise = executeTool(toolName, toolInput);
try {
return await Promise.race([executionPromise, timeoutPromise]);
} catch (error) {
return {
error: true,
message: `Tool timed out after ${timeoutMs}ms`
};
}
}
5. Never Execute Untrusted Code
❌ NEVER DO THIS:
function executeTool(toolName, toolInput) {
eval(toolInput.code); // DANGEROUS! Never execute arbitrary code
}
✅ Use Whitelisted Tools:
function executeTool(toolName, toolInput) {
switch (toolName) {
case 'get_weather':
return getWeather(toolInput.location);
case 'search_database':
return searchDatabase(toolInput.query);
case 'send_email':
return sendEmail(toolInput.to, toolInput.subject, toolInput.body);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
6. Include Tools in Follow-up Requests
Always include the tools array in follow-up requests so the model has consistent tool availability:
// Initial request
{ options: { tools: [...] } }
// Follow-up with tool results
{ options: { tools: [...] } } // Same tools array
// Final request
{ options: { tools: [...] } } // Same tools array
7. Limit Tool Execution Time
Prevent long-running operations from blocking:
const MAX_EXECUTION_TIME = 10000; // 10 seconds
async function executeTool(toolName, toolInput) {
const startTime = Date.now();
const result = await performToolOperation(toolName, toolInput);
const duration = Date.now() - startTime;
if (duration > MAX_EXECUTION_TIME) {
console.warn(`Tool ${toolName} took ${duration}ms (exceeds ${MAX_EXECUTION_TIME}ms)`);
}
return result;
}
Tool Calling Patterns
Pattern 1: Single Tool Call
User asks a question → Model calls one tool → Model responds with answer
User: "What's the weather in Paris?"
↓
Model: tool_use (get_weather)
↓
Client: Executes tool
↓
Model: "The weather in Paris is..."
Pattern 2: Multiple Sequential Tool Calls
Model calls tools one at a time to gather information
User: "Compare weather in Paris and London"
↓
Model: tool_use (get_weather, Paris)
↓
Client: Returns Paris weather
↓
Model: tool_use (get_weather, London)
↓
Client: Returns London weather
↓
Model: "Paris is sunny at 72°F while London..."
Pattern 3: Parallel Tool Calls
Model calls multiple tools at once
User: "Get weather and news for San Francisco"
↓
Model: tool_use ([get_weather, get_news])
↓
Client: Executes both tools in parallel
↓
Model: "The weather is sunny and in the news..."
Error Handling
Tool Not Found
If the client doesn't recognize a tool:
function executeTool(toolName, toolInput) {
const handlers = {
'get_weather': getWeather,
'search_database': searchDatabase,
};
if (!handlers[toolName]) {
return {
error: true,
message: `Tool '${toolName}' not found. Available tools: ${Object.keys(handlers).join(', ')}`
};
}
return handlers[toolName](toolInput);
}
Tool Execution Failed
If a tool fails during execution:
async function executeTool(toolName, toolInput) {
try {
return await performTool(toolName, toolInput);
} catch (error) {
return {
error: true,
message: `Tool execution failed: ${error.message}`,
toolName: toolName,
input: toolInput
};
}
}
The model will see the error and can respond appropriately.
Security Considerations
1. Validate All Inputs
Never trust tool inputs from the model:
function validateWeatherInput(input) {
if (!input.location || typeof input.location !== 'string') {
throw new Error('Invalid location');
}
// Sanitize input
const location = input.location.trim();
if (location.length === 0 || location.length > 100) {
throw new Error('Invalid location length');
}
return location;
}
2. Whitelist Tools
Only allow execution of explicitly defined tools:
const ALLOWED_TOOLS = new Set(['get_weather', 'search_database']);
function executeTool(toolName, toolInput) {
if (!ALLOWED_TOOLS.has(toolName)) {
throw new Error(`Tool ${toolName} not allowed`);
}
// Execute tool
}
3. Rate Limit Tool Executions
Prevent abuse by rate limiting tool calls:
class ToolRateLimiter {
constructor(maxCallsPerMinute = 60) {
this.maxCalls = maxCallsPerMinute;
this.calls = [];
}
async checkLimit() {
const now = Date.now();
this.calls = this.calls.filter(time => time > now - 60000);
if (this.calls.length >= this.maxCalls) {
throw new Error('Tool rate limit exceeded');
}
this.calls.push(now);
}
}
const limiter = new ToolRateLimiter(60);
async function executeTool(toolName, toolInput) {
await limiter.checkLimit();
return performTool(toolName, toolInput);
}
4. Log Tool Usage
Monitor tool usage for security and debugging:
function executeTool(toolName, toolInput) {
console.log({
timestamp: new Date().toISOString(),
tool: toolName,
input: toolInput, // Be careful with sensitive data
});
const result = performTool(toolName, toolInput);
console.log({
timestamp: new Date().toISOString(),
tool: toolName,
success: true,
});
return result;
}
Testing Tools
Unit Test Example
describe('Weather Tool', () => {
it('should return weather for valid location', async () => {
const result = await executeTool('get_weather', {
location: 'San Francisco'
});
expect(result).toHaveProperty('temperature');
expect(result).toHaveProperty('condition');
expect(result.location).toBe('San Francisco');
});
it('should handle invalid location', async () => {
const result = await executeTool('get_weather', {
location: ''
});
expect(result).toHaveProperty('error');
expect(result.error).toBe(true);
});
it('should timeout long-running tools', async () => {
const result = await executeToolWithTimeout('slow_tool', {}, 1000);
expect(result).toHaveProperty('error');
expect(result.message).toContain('timeout');
});
});
Next Steps
- Extended Thinking - Use reasoning with tool calls
- Code Examples - See complete tool implementations
- Error Handling - Handle tool errors
- Best Practices - Production tool patterns