ignitionstack.pro v1.0 is out! Read the announcement →
Skip to Content

Function Calling & Tools

Function calling enables LLMs to execute actions beyond text generation. ignitionstack.pro implements a secure, extensible tool system that works across all providers supporting function calling.

How Function Calling Works

┌─────────────────────────────────────────────────────────────────┐ │ Function Calling Flow │ └─────────────────────────────────────────────────────────────────┘ User: "What's the weather in Tokyo?" ┌─────────────────────────┐ │ LLM analyzes intent │ │ Decides to call tool │ └───────────┬─────────────┘ ┌─────────────────────────┐ │ Tool Call Request │ │ get_weather(tokyo) │ └───────────┬─────────────┘ ┌─────────────────────────┐ │ Tool Executor runs │ │ Validates & executes │ └───────────┬─────────────┘ ┌─────────────────────────┐ │ Result returned │ │ {"temp": 22, ...} │ └───────────┬─────────────┘ ┌─────────────────────────┐ │ LLM incorporates │ │ result into response │ └───────────┬─────────────┘ "The current temperature in Tokyo is 22°C..."

Architecture

Core Components

ComponentLocationPurpose
Tool Executorlib/ai/tools/tool-executor.tsExecutes tool calls securely
Tool DefinitionsDatabase ai_tools tableStores tool schemas
Tool ExecutionsDatabase ai_tool_executions tableAudit log

Database Schema

-- Tool definitions CREATE TABLE ai_tools ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT UNIQUE NOT NULL, description TEXT NOT NULL, parameters JSONB NOT NULL, -- JSON Schema handler TEXT NOT NULL, -- Handler function name is_enabled BOOLEAN DEFAULT true, requires_confirmation BOOLEAN DEFAULT false, rate_limit INTEGER, -- Max calls per minute created_at TIMESTAMPTZ DEFAULT NOW() ); -- Execution audit log CREATE TABLE ai_tool_executions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tool_id UUID REFERENCES ai_tools(id), conversation_id UUID REFERENCES ai_conversations(id), user_id UUID REFERENCES auth.users(id), input JSONB NOT NULL, output JSONB, error TEXT, latency_ms INTEGER, created_at TIMESTAMPTZ DEFAULT NOW() );

Built-in Tools

get_current_time

Returns current time in specified timezone.

{ name: 'get_current_time', description: 'Get the current date and time', parameters: { type: 'object', properties: { timezone: { type: 'string', description: 'IANA timezone (e.g., America/New_York)', default: 'UTC', }, }, }, } // Usage: "What time is it in Tokyo?" // Call: get_current_time({ timezone: 'Asia/Tokyo' }) // Result: { time: '2025-01-20T15:30:00+09:00', timezone: 'Asia/Tokyo' }

calculate

Safely evaluates mathematical expressions.

{ name: 'calculate', description: 'Evaluate a mathematical expression', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'Math expression (e.g., "2 + 2 * 3")', }, }, required: ['expression'], }, } // Usage: "What's 15% of 230?" // Call: calculate({ expression: '230 * 0.15' }) // Result: { result: 34.5, expression: '230 * 0.15' }

search_web

Searches the web for information (placeholder for real implementation).

{ name: 'search_web', description: 'Search the web for current information', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query', }, num_results: { type: 'number', description: 'Number of results (1-10)', default: 5, }, }, required: ['query'], }, }

get_weather

Returns weather information (placeholder for real API integration).

{ name: 'get_weather', description: 'Get current weather for a location', parameters: { type: 'object', properties: { location: { type: 'string', description: 'City name or coordinates', }, units: { type: 'string', enum: ['celsius', 'fahrenheit'], default: 'celsius', }, }, required: ['location'], }, }

Tool Executor

Basic Usage

// src/app/lib/ai/tools/tool-executor.ts import { ToolExecutor } from '@/lib/ai/tools/tool-executor' const executor = new ToolExecutor({ timeout: 10000, // 10 second timeout maxConcurrent: 5, // Max parallel executions enableLogging: true, // Log to database }) // Execute a tool call from LLM const result = await executor.execute({ name: 'get_current_time', arguments: { timezone: 'America/New_York' }, conversationId: 'conv-123', userId: 'user-456', }) // Result: { time: '2025-01-20T10:30:00-05:00', timezone: 'America/New_York' }

Handling Tool Calls in Chat

// During streaming, intercept tool calls for await (const chunk of provider.chatStream(messages, options)) { if (chunk.type === 'tool_call') { // Execute the tool const result = await executor.execute({ name: chunk.toolCall.name, arguments: chunk.toolCall.arguments, conversationId, userId, }) // Feed result back to LLM messages.push({ role: 'tool', toolCallId: chunk.toolCall.id, content: JSON.stringify(result), }) // Continue generation with tool result const continuation = provider.chatStream(messages, options) // ... } }

Creating Custom Tools

1. Define the Tool Schema

// src/app/lib/ai/tools/definitions/my-tool.ts export const myToolDefinition = { name: 'lookup_customer', description: 'Look up customer information by email or ID', parameters: { type: 'object', properties: { identifier: { type: 'string', description: 'Customer email or ID', }, fields: { type: 'array', items: { type: 'string' }, description: 'Fields to retrieve', default: ['name', 'email', 'plan'], }, }, required: ['identifier'], }, }

2. Implement the Handler

// src/app/lib/ai/tools/handlers/lookup-customer.ts import { ToolHandler } from '@/lib/ai/tools/types' export const lookupCustomerHandler: ToolHandler = async (args, context) => { const { identifier, fields = ['name', 'email', 'plan'] } = args // Validate access (tools run with user's permissions) if (!context.userId) { throw new Error('Authentication required') } // Query database const customer = await db .from('customers') .select(fields.join(',')) .or(`email.eq.${identifier},id.eq.${identifier}`) .single() if (!customer) { return { error: 'Customer not found' } } return { customer: customer.data, retrievedAt: new Date().toISOString(), } }

3. Register the Tool

// src/app/lib/ai/tools/registry.ts import { myToolDefinition } from './definitions/my-tool' import { lookupCustomerHandler } from './handlers/lookup-customer' export const toolRegistry = { // Built-in tools get_current_time: getCurrentTimeHandler, calculate: calculateHandler, search_web: searchWebHandler, get_weather: getWeatherHandler, // Custom tools lookup_customer: lookupCustomerHandler, } export const toolDefinitions = [ // Built-in getCurrentTimeDefinition, calculateDefinition, searchWebDefinition, getWeatherDefinition, // Custom myToolDefinition, ]

4. Or Store in Database

INSERT INTO ai_tools (name, description, parameters, handler) VALUES ( 'lookup_customer', 'Look up customer information by email or ID', '{ "type": "object", "properties": { "identifier": {"type": "string", "description": "Customer email or ID"}, "fields": {"type": "array", "items": {"type": "string"}, "default": ["name", "email", "plan"]} }, "required": ["identifier"] }', 'lookup_customer' );

Provider-Specific Formats

OpenAI Format

// OpenAI tool format const tools = [ { type: 'function', function: { name: 'get_weather', description: 'Get current weather', parameters: { type: 'object', properties: { location: { type: 'string' }, }, required: ['location'], }, }, }, ] // Response contains tool_calls { role: 'assistant', tool_calls: [{ id: 'call_abc123', type: 'function', function: { name: 'get_weather', arguments: '{"location": "Tokyo"}', }, }], }

Gemini Format

// Gemini function declarations const tools = [{ functionDeclarations: [{ name: 'get_weather', description: 'Get current weather', parameters: { type: 'object', properties: { location: { type: 'string' }, }, required: ['location'], }, }], }] // Response contains function call { candidates: [{ content: { parts: [{ functionCall: { name: 'get_weather', args: { location: 'Tokyo' }, }, }], }, }], }

Format Conversion

The adapters handle format conversion automatically:

// Adapter converts to provider-specific format const openaiTools = adapter.formatTools(toolDefinitions) const geminiTools = adapter.formatTools(toolDefinitions) // And normalizes responses back const normalizedCall = adapter.parseToolCall(providerResponse) // Always returns: { name, arguments, id }

Security

Sandboxed Execution

// Tools execute in a restricted context const context = { userId: authenticatedUser.id, conversationId, permissions: user.permissions, timeout: 10000, } // Handler receives context for access control const result = await handler(args, context)

Input Validation

// Validate arguments against JSON Schema import Ajv from 'ajv' const ajv = new Ajv() const validate = ajv.compile(tool.parameters) if (!validate(args)) { throw new ValidationError(validate.errors) }

Rate Limiting

// Per-tool rate limits const tool = await db.from('ai_tools').select().eq('name', name).single() if (tool.rate_limit) { const recentCalls = await db .from('ai_tool_executions') .select('id') .eq('tool_id', tool.id) .eq('user_id', userId) .gte('created_at', oneMinuteAgo) .count() if (recentCalls >= tool.rate_limit) { throw new RateLimitError('Tool rate limit exceeded') } }

Confirmation for Sensitive Tools

// Some tools require user confirmation { name: 'delete_account', requires_confirmation: true, // ... } // In the UI, show confirmation before executing if (tool.requires_confirmation) { const confirmed = await showConfirmationDialog({ title: 'Confirm Action', message: `Allow AI to execute: ${tool.name}?`, details: args, }) if (!confirmed) { return { cancelled: true } } }

UI Components

Tool Execution Display

// src/app/components/ai/ToolExecution.tsx export function ToolExecution({ execution }) { return ( <div className="tool-execution"> <div className="tool-header"> <ToolIcon name={execution.tool} /> <span>{execution.tool}</span> <StatusBadge status={execution.status} /> </div> <div className="tool-input"> <code>{JSON.stringify(execution.input, null, 2)}</code> </div> {execution.output && ( <div className="tool-output"> <code>{JSON.stringify(execution.output, null, 2)}</code> </div> )} {execution.error && ( <div className="tool-error">{execution.error}</div> )} </div> ) }

In Chat Message

// Display tool calls inline in chat {message.toolCalls?.map((call) => ( <ToolExecution key={call.id} execution={{ tool: call.name, input: call.arguments, output: call.result, status: call.status, }} /> ))}

Best Practices

1. Clear Descriptions

// BAD - Vague description { name: 'search', description: 'Search stuff' } // GOOD - Specific and helpful { name: 'search_knowledge_base', description: 'Search the company knowledge base for articles, FAQs, and documentation. Returns relevant excerpts with source links.', }

2. Minimal Parameters

// BAD - Too many optional params { properties: { query: { type: 'string' }, limit: { type: 'number' }, offset: { type: 'number' }, sortBy: { type: 'string' }, sortOrder: { type: 'string' }, filters: { type: 'object' }, // ... many more }, } // GOOD - Only what's needed { properties: { query: { type: 'string', description: 'Search query', }, limit: { type: 'number', description: 'Max results (1-20)', default: 5, }, }, required: ['query'], }

3. Graceful Error Handling

// Return informative errors const handler = async (args) => { try { const result = await riskyOperation(args) return { success: true, data: result } } catch (error) { return { success: false, error: error.message, suggestion: 'Try a different search term', } } }

4. Timeout Protection

// Wrap external calls with timeout const result = await Promise.race([ externalApiCall(args), timeout(10000, 'External API timeout'), ])