Skip to main content

Tool Calling Quickstart

This guide shows how to give your voice assistant custom capabilities through tool calling. By the end, you'll have an assistant that can check the weather, look up information, or perform any action you define.


What You'll Build

Tools let your assistant:

  • Fetch real-time data (weather, stocks, news)
  • Interact with your systems (databases, APIs)
  • Perform calculations
  • Control devices

Prerequisites


Step 1: Define a Tool

Create a tool using the defineTool helper:

import { defineTool } from '@llmrtc/llmrtc-core';

// Define the weather tool
const getWeatherTool = defineTool({
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name, e.g., "San Francisco" or "Tokyo"'
},
units: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature units (default: celsius)'
}
},
required: ['location']
}
}, async ({ location, units = 'celsius' }) => {
// Your implementation here
// This could call a real weather API
const weather = {
location,
temperature: units === 'celsius' ? 22 : 72,
units,
condition: 'sunny',
humidity: 65
};

return weather;
});

Tool Definition Structure

FieldRequiredDescription
nameYesUnique identifier (lowercase, underscores)
descriptionYesWhat the tool does (helps LLM decide when to use it)
parametersYesJSON Schema for the input parameters
handlerYesAsync function that executes the tool

Step 2: Register Tools

Create a ToolRegistry and register your tools:

import { ToolRegistry, defineTool } from '@llmrtc/llmrtc-core';

// Create registry
const registry = new ToolRegistry();

// Register weather tool
registry.register(getWeatherTool);

// Register more tools
registry.register(defineTool({
name: 'get_time',
description: 'Get the current time in a timezone',
parameters: {
type: 'object',
properties: {
timezone: {
type: 'string',
description: 'Timezone, e.g., "America/New_York" or "Asia/Tokyo"'
}
},
required: ['timezone']
}
}, async ({ timezone }) => {
const time = new Date().toLocaleString('en-US', { timeZone: timezone });
return { timezone, time };
}));

Step 3: Add Tools to Server

Update your server to include tools:

import 'dotenv/config';
import {
LLMRTCServer,
OpenAILLMProvider,
OpenAIWhisperProvider,
OpenAITTSProvider
} from '@llmrtc/llmrtc-backend';
import { ToolRegistry, defineTool } from '@llmrtc/llmrtc-core';

// Create tool registry
const registry = new ToolRegistry();

// Register weather tool
registry.register(defineTool({
name: 'get_weather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: { type: 'string', description: 'City name' },
units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location']
}
}, async ({ location, units = 'celsius' }) => {
// Simulated weather data
return {
location,
temperature: units === 'celsius' ? 22 : 72,
units,
condition: 'sunny'
};
}));

// Register time tool
registry.register(defineTool({
name: 'get_time',
description: 'Get the current time in a timezone',
parameters: {
type: 'object',
properties: {
timezone: { type: 'string', description: 'Timezone name' }
},
required: ['timezone']
}
}, async ({ timezone }) => {
try {
const time = new Date().toLocaleString('en-US', { timeZone: timezone });
return { timezone, time };
} catch {
return { error: `Unknown timezone: ${timezone}` };
}
}));

// Create server with tools
const server = new LLMRTCServer({
providers: {
llm: new OpenAILLMProvider({ apiKey: process.env.OPENAI_API_KEY }),
stt: new OpenAIWhisperProvider({ apiKey: process.env.OPENAI_API_KEY }),
tts: new OpenAITTSProvider({ apiKey: process.env.OPENAI_API_KEY })
},
toolRegistry: registry,
systemPrompt: `You are a helpful voice assistant with access to tools.
Use the get_weather tool when asked about weather.
Use the get_time tool when asked about time in different locations.
Always use tools when you have relevant ones available.`,
port: 8787
});

server.on('connection', ({ id }) => {
console.log(`Connected: ${id}`);
});

await server.start();
console.log('Server with tools running on ws://localhost:8787');

Step 4: Test Your Tools

Start your server and connect with the web client. Try asking:

  • "What's the weather in Tokyo?"
  • "What time is it in New York?"
  • "Is it warmer in London or Paris?"

The assistant will call the appropriate tools and respond with the results.


Tool Execution Flow

When a user asks something that requires a tool:


Real-World Tool Examples

Database Lookup

registry.register(defineTool({
name: 'lookup_customer',
description: 'Look up customer information by email or phone',
parameters: {
type: 'object',
properties: {
email: { type: 'string', description: 'Customer email' },
phone: { type: 'string', description: 'Customer phone number' }
}
}
}, async ({ email, phone }) => {
// Query your database
const customer = await db.customers.findOne({
where: { OR: [{ email }, { phone }] }
});

if (!customer) {
return { found: false };
}

return {
found: true,
name: customer.name,
accountStatus: customer.status,
memberSince: customer.createdAt
};
}));

API Integration

registry.register(defineTool({
name: 'search_products',
description: 'Search for products in the catalog',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
category: { type: 'string', description: 'Product category' },
maxPrice: { type: 'number', description: 'Maximum price' }
},
required: ['query']
}
}, async ({ query, category, maxPrice }) => {
const response = await fetch('https://api.yourstore.com/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, category, maxPrice })
});

const products = await response.json();

return {
count: products.length,
products: products.slice(0, 5).map(p => ({
name: p.name,
price: p.price,
inStock: p.inventory > 0
}))
};
}));

External Service

registry.register(defineTool({
name: 'send_notification',
description: 'Send a notification to the user',
parameters: {
type: 'object',
properties: {
type: { type: 'string', enum: ['email', 'sms'] },
message: { type: 'string', description: 'Notification message' }
},
required: ['type', 'message']
}
}, async ({ type, message }, context) => {
// Use context.sessionId to get user info
const user = await getUserFromSession(context.sessionId);

if (type === 'email') {
await sendEmail(user.email, message);
} else {
await sendSMS(user.phone, message);
}

return { sent: true, type };
}));

Tool Best Practices

Clear Descriptions

Help the LLM understand when to use each tool:

// Good: Specific and helpful
description: 'Get the current weather forecast including temperature, conditions, and humidity for any city worldwide'

// Bad: Vague
description: 'Weather stuff'

Parameter Validation

Use JSON Schema constraints:

parameters: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'Valid email address'
},
age: {
type: 'integer',
minimum: 0,
maximum: 150,
description: 'Age in years'
},
status: {
type: 'string',
enum: ['active', 'inactive', 'pending'],
description: 'Account status'
}
},
required: ['email']
}

Error Handling

Return meaningful errors:

registry.register(defineTool({
name: 'book_appointment',
// ...
}, async ({ date, time }) => {
try {
const result = await bookingService.create({ date, time });
return { success: true, confirmationId: result.id };
} catch (error) {
if (error.code === 'SLOT_UNAVAILABLE') {
return {
success: false,
error: 'That time slot is no longer available',
suggestedTimes: await bookingService.getAvailable(date)
};
}
return { success: false, error: 'Unable to book appointment' };
}
}));

Show Tool Calls in UI

Update your web client to display tool activity:

client.on('toolCallStart', ({ name, callId, arguments: args }) => {
console.log(`Calling tool: ${name} (${callId})`);
showToolIndicator(name);
});

client.on('toolCallEnd', ({ callId, result, error, durationMs }) => {
console.log(`Tool completed in ${durationMs}ms:`, result ?? error);
hideToolIndicator();
});

Tool Execution Options

Configure the ToolExecutor for your needs:

import { ToolExecutor } from '@llmrtc/llmrtc-core';

const executor = new ToolExecutor(registry, {
// Maximum execution time per tool (ms)
timeout: 30000,

// How many tools can run concurrently
maxConcurrency: 5,

// Validate arguments against JSON Schema
validateArguments: true,

// Callbacks for monitoring
onToolStart: (toolName, callId, args) => {
console.log(`Starting ${toolName} (${callId})`, args);
},
onToolEnd: (result) => {
console.log(`${result.toolName} completed in ${result.durationMs}ms`);
},
onToolError: (toolName, callId, error) => {
console.error(`${toolName} (${callId}) failed:`, error);
}
});

Troubleshooting

Tool Not Being Called

  1. Check the tool description is clear about when to use it
  2. Ensure the tool name is in the system prompt
  3. Verify tools are registered before server starts:
console.log('Registered tools:', registry.getDefinitions().map(t => t.name));

Tool Errors

Add error handling and logging:

registry.register(defineTool({
name: 'my_tool',
// ...
}, async (args, context) => {
console.log('Tool called with:', args);
console.log('Context:', context);

try {
const result = await doSomething(args);
console.log('Tool result:', result);
return result;
} catch (error) {
console.error('Tool error:', error);
return { error: error.message };
}
}));

Timeout Issues

Increase timeout for slow operations:

const executor = new ToolExecutor(registry, {
timeout: 60000 // 60 seconds
});

Next Steps

With tools working, try building more complex flows: