Skip to content

Function Calling

Advanced tool integration and function calling with the Robota SDK.

Overview

The Robota SDK provides a powerful, type-safe function calling system that allows AI agents to interact with external tools, APIs, and services. The system is built around JSON Schema validation and automatic schema conversion.

Key Features

  • Type-Safe Tool Creation: Built-in TypeScript safety
  • JSON Schema Validation: Automatic parameter validation
  • Cross-Provider Support: Works with OpenAI, Anthropic, and Google AI
  • Automatic Schema Conversion: JSON Schema → Function call schemas
  • Error Handling: Robust error handling and recovery

Basic Function Calling

Creating Your First Tool

typescript
import { Robota, createFunctionTool } from '@robota-sdk/agent-core';

// Create a simple calculator tool
const calculatorTool = createFunctionTool(
  'calculate',
  'Performs mathematical calculations',
  {
    type: 'object',
    properties: {
      operation: {
        type: 'string',
        enum: ['add', 'subtract', 'multiply', 'divide'],
        description: 'Mathematical operation to perform',
      },
      a: {
        type: 'number',
        description: 'First number',
      },
      b: {
        type: 'number',
        description: 'Second number',
      },
    },
    required: ['operation', 'a', 'b'],
  },
  async (params) => {
    const { operation, a, b } = params;

    switch (operation) {
      case 'add':
        return { result: a + b };
      case 'subtract':
        return { result: a - b };
      case 'multiply':
        return { result: a * b };
      case 'divide':
        if (b === 0) {
          return { error: 'Cannot divide by zero' };
        }
        return { result: a / b };
      default:
        return { error: 'Unknown operation' };
    }
  },
);

// Create agent with the tool
const agent = new Robota({
  name: 'CalculatorAgent',
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You are a helpful assistant with calculation abilities.',
  },
  tools: [calculatorTool],
});

// Use the agent - it will automatically call the tool when needed
const response = await agent.run('What is 25 multiplied by 7?');
console.log(response); // The AI will use the calculator tool

Advanced Tool Patterns

Weather Information Tool

typescript
const weatherTool = createFunctionTool(
  'getWeather',
  'Get current weather information for a location',
  {
    type: 'object',
    properties: {
      location: {
        type: 'string',
        description: 'City name or coordinates',
      },
      units: {
        type: 'string',
        enum: ['celsius', 'fahrenheit', 'kelvin'],
        default: 'celsius',
        description: 'Temperature units',
      },
    },
    required: ['location'],
  },
  async (params) => {
    // In a real implementation, you'd call a weather API
    const { location, units = 'celsius' } = params;

    try {
      // Simulate API call
      const weatherData = await fetchWeatherAPI(location, units);

      return {
        location,
        temperature: weatherData.temperature,
        condition: weatherData.condition,
        humidity: weatherData.humidity,
        units,
      };
    } catch (error) {
      return {
        error: `Failed to get weather for ${location}: ${error.message}`,
      };
    }
  },
);

Database Query Tool

typescript
const databaseTool = createFunctionTool(
  'queryDatabase',
  'Query the database for information',
  {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'SQL query to execute',
      },
      table: {
        type: 'string',
        enum: ['users', 'products', 'orders'],
        description: 'Table to query',
      },
      limit: {
        type: 'number',
        minimum: 1,
        maximum: 100,
        default: 10,
        description: 'Maximum number of results',
      },
    },
    required: ['query', 'table'],
  },
  async (params) => {
    const { query, table, limit = 10 } = params;

    // Validate query for security
    if (!isValidQuery(query, table)) {
      return {
        error: 'Invalid or unsafe query',
      };
    }

    try {
      const results = await executeQuery(query, { table, limit });
      return {
        results,
        count: results.length,
        table,
      };
    } catch (error) {
      return {
        error: `Database query failed: ${error.message}`,
      };
    }
  },
);

File System Tool

typescript
const fileTool = createFunctionTool(
  'fileOperations',
  'Perform file system operations',
  {
    type: 'object',
    properties: {
      operation: {
        type: 'string',
        enum: ['read', 'write', 'list', 'delete'],
        description: 'File operation to perform',
      },
      path: {
        type: 'string',
        description: 'File or directory path',
      },
      content: {
        type: 'string',
        description: 'Content to write (for write operation)',
      },
      encoding: {
        type: 'string',
        enum: ['utf8', 'base64', 'binary'],
        default: 'utf8',
        description: 'File encoding',
      },
    },
    required: ['operation', 'path'],
  },
  async (params) => {
    const { operation, path, content, encoding = 'utf8' } = params;

    // Security check - only allow operations in allowed directories
    if (!isAllowedPath(path)) {
      return {
        error: 'Access denied: path not allowed',
      };
    }

    try {
      switch (operation) {
        case 'read':
          const fileContent = await fs.readFile(path, encoding);
          return { content: fileContent, path, encoding };

        case 'write':
          if (!content) {
            return { error: 'Content required for write operation' };
          }
          await fs.writeFile(path, content, encoding);
          return { success: true, path, bytesWritten: content.length };

        case 'list':
          const files = await fs.readdir(path);
          return { files, path, count: files.length };

        case 'delete':
          await fs.unlink(path);
          return { success: true, path, deleted: true };

        default:
          return { error: `Unknown operation: ${operation}` };
      }
    } catch (error) {
      return {
        error: `File operation failed: ${error.message}`,
        operation,
        path,
      };
    }
  },
);

Multi-Tool Agents

Combining Multiple Tools

typescript
// Create a comprehensive agent with multiple tools
const multiToolAgent = new Robota({
  name: 'MultiToolAgent',
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-4',
    systemMessage: `You are a helpful assistant with access to multiple tools:
    - Calculator for mathematical operations
    - Weather information for any location
    - Database queries for data retrieval
    - File system operations for file management
    
    Use these tools when appropriate to help users with their requests.`,
  },
  tools: [calculatorTool, weatherTool, databaseTool, fileTool],
});

// Complex multi-step task
const response = await multiToolAgent.run(`
    Please help me with the following tasks:
    1. Calculate the average temperature if it's 23°C in London and 18°C in Paris
    2. Save this result to a file called "temp-analysis.txt"
    3. Query the database for all users in the "users" table
`);

Tool-Specific Error Handling

typescript
const robustTool = createFunctionTool(
  'robustOperation',
  'A tool with comprehensive error handling',
  {
    type: 'object',
    properties: {
      action: { type: 'string', description: 'Action to perform' },
      data: { type: 'object', description: 'Input data' },
    },
    required: ['action'],
  },
  async (params) => {
    const { action, data } = params;

    try {
      // Validate input
      if (!isValidAction(action)) {
        return {
          error: 'Invalid action',
          code: 'INVALID_ACTION',
          details: { validActions: getValidActions() },
        };
      }

      // Perform operation with timeout
      const result = await Promise.race([
        performOperation(action, data),
        new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timeout')), 30000)),
      ]);

      return {
        success: true,
        result,
        action,
        timestamp: new Date().toISOString(),
      };
    } catch (error) {
      return {
        error: error.message,
        code: 'OPERATION_FAILED',
        action,
        timestamp: new Date().toISOString(),
        details: {
          stack: process.env.NODE_ENV === 'development' ? error.stack : undefined,
        },
      };
    }
  },
);

Streaming with Tools

Real-time Tool Execution

typescript
const agent = new Robota({
  name: 'StreamingToolAgent',
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You are a helpful assistant. Use tools when needed.',
  },
  tools: [calculatorTool, weatherTool],
});

// Stream responses while tools are being executed
const stream = agent.runStream("What's the weather in Tokyo and what's 15 * 8?");

for await (const chunk of stream) {
  process.stdout.write(chunk);
}

Advanced Patterns

Tool Registry

typescript
import { ToolRegistry } from '@robota-sdk/agent-core';

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

// Register tools
toolRegistry.register('calculator', calculatorTool);
toolRegistry.register('weather', weatherTool);
toolRegistry.register('database', databaseTool);

// Create agent with registry
const agent = new Robota({
  name: 'RegistryAgent',
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You have access to various tools through the registry.',
  },
  tools: toolRegistry.getAllTools(),
});

// Dynamically add tools
toolRegistry.register('newTool', createNewTool());

Conditional Tools

typescript
// Tools that are only available under certain conditions
const conditionalAgent = new Robota({
  name: 'ConditionalAgent',
  aiProviders: [openaiProvider],
  defaultModel: {
    provider: 'openai',
    model: 'gpt-3.5-turbo',
    systemMessage: 'You are a helpful assistant with context-specific tools.',
  },
  tools: [
    // Always available
    calculatorTool,

    // Only available if user has permission
    ...(userHasPermission ? [databaseTool] : []),

    // Only available in development
    ...(process.env.NODE_ENV === 'development' ? [debugTool] : []),
  ],
});

Tool Composition

typescript
// Compose complex tools from simpler ones
const compositeWorkflowTool = createFunctionTool(
  'dataAnalysisWorkflow',
  'Perform complete data analysis workflow',
  {
    type: 'object',
    properties: {
      dataSource: { type: 'string', description: 'Data source identifier' },
      analysisType: {
        type: 'string',
        enum: ['summary', 'trend', 'comparison'],
        description: 'Type of analysis to perform',
      },
    },
    required: ['dataSource', 'analysisType'],
  },
  async (params) => {
    const { dataSource, analysisType } = params;

    try {
      // Step 1: Fetch data
      const data = await fetchData(dataSource);

      // Step 2: Process data
      const processedData = await processData(data, analysisType);

      // Step 3: Generate insights
      const insights = await generateInsights(processedData);

      // Step 4: Create visualization
      const visualization = await createVisualization(insights);

      return {
        success: true,
        dataSource,
        analysisType,
        insights,
        visualization,
        summary: generateSummary(insights),
      };
    } catch (error) {
      return {
        error: `Workflow failed: ${error.message}`,
        dataSource,
        analysisType,
        step: error.step || 'unknown',
      };
    }
  },
);

Best Practices

✅ Do

  1. Use descriptive names: Tool names should be clear and specific
  2. Validate inputs: Always validate parameters before processing
  3. Handle errors gracefully: Return error objects instead of throwing
  4. Provide detailed schemas: Include descriptions for all parameters
  5. Use enums for limited options: Constrain parameter values when possible
  6. Implement timeouts: Prevent tools from hanging indefinitely
  7. Log tool usage: Track tool calls for debugging and analytics

❌ Don't

  1. Expose sensitive operations: Don't allow dangerous file system operations
  2. Skip input validation: Always validate parameters
  3. Return undefined: Always return a result object
  4. Use vague error messages: Provide specific error information
  5. Ignore security: Validate permissions and sanitize inputs
  6. Create overly complex tools: Keep tools focused on single tasks

Security Considerations

typescript
// Example of secure tool implementation
const secureTool = createFunctionTool(
  'secureFileRead',
  'Securely read files with permission checks',
  {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'File path to read' },
    },
    required: ['path'],
  },
  async (params) => {
    const { path } = params;

    // 1. Validate path
    if (!isValidPath(path)) {
      return { error: 'Invalid file path' };
    }

    // 2. Check permissions
    if (!hasReadPermission(path)) {
      return { error: 'Access denied' };
    }

    // 3. Sanitize path
    const safePath = sanitizePath(path);

    // 4. Perform operation
    try {
      const content = await fs.readFile(safePath, 'utf8');
      return { content, path: safePath };
    } catch (error) {
      return { error: `Failed to read file: ${error.message}` };
    }
  },
);

Performance Optimization

Tool Caching

typescript
// Implement caching for expensive operations
const cache = new Map();

const cachedTool = createFunctionTool(
  'expensiveOperation',
  'Perform expensive operation with caching',
  {
    type: 'object',
    properties: {
      input: { type: 'string', description: 'Input data' },
    },
    required: ['input'],
  },
  async (params) => {
    const { input } = params;
    const cacheKey = `expensive-${input}`;

    // Check cache first
    if (cache.has(cacheKey)) {
      return {
        result: cache.get(cacheKey),
        cached: true,
        timestamp: new Date().toISOString(),
      };
    }

    // Perform expensive operation
    const result = await expensiveOperation(input);

    // Cache result
    cache.set(cacheKey, result);

    return {
      result,
      cached: false,
      timestamp: new Date().toISOString(),
    };
  },
);

Next Steps

Released under the MIT License.