Skip to main content

Function Calling Integration Guide

This guide explains how to set up and use the Function Calling capabilities integrated from Drupal Native AI into the llm-mcp npm project.

Overviewโ€‹

The Function Calling system provides a framework for registering, managing, and executing functions that can be called by AI models. This enables powerful integrations where AI models can trigger actions in your application.

Key features include:

  • Function definition and registration
  • Parameter validation
  • Secure function execution
  • Integration with AI providers
  • Function categorization and tagging

Installation and Setupโ€‹

Prerequisitesโ€‹

  • Node.js 20.x or later
  • TypeScript 5.x or later
  • Access to bfapi and bfllm services
  • JWT authentication tokens

Configurationโ€‹

Configure function calling in your application:

// In your configuration setup
configManager.set('function-calling', {
enabled: true,
defaultTimeout: 10000,
maxConcurrent: 5,
security: {
allowedFunctions: ['safe_*'],
blockedFunctions: ['dangerous_*'],
},
});

Basic Usageโ€‹

Defining and Registering Functionsโ€‹

import [FunctionManagerImpl] from '@bluefly/mcp';
import [createLogger] from '@bluefly/mcp';
import [ConfigManager] from '@bluefly/mcp';

// Initialize dependencies
const logger = createLogger({ component: 'function-example' });
const configManager = new ConfigManager();

// Create function manager
const functionManager = new FunctionManagerImpl(logger, configManager);

// Define a function
const weatherFunction = {
name: 'getWeather',
description: 'Get the current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'The unit of temperature to use. Defaults to fahrenheit.',
default: 'fahrenheit',
},
},
required: ['location'],
},
category: 'weather',
tags: ['external-api', 'location'],
};

// Register the function definition
functionManager.registerFunction(weatherFunction);

// Register the function implementation
functionManager.registerFunctionImplementation('getWeather', async (args) => {
const { location, unit = 'fahrenheit' } = args;

// In a real implementation, call a weather API
// This is a mock response
return {
location,
temperature: 72,
unit,
condition: 'sunny',
humidity: 45,
timestamp: new Date().toISOString(),
};
});

Executing Functionsโ€‹

// Execute the function directly
const result = await functionManager.executeFunction('getWeather', {
location: 'San Francisco, CA',
});

console.log('Weather result:', result);
// Output:
// Weather result: {
// status: 'success',
// result: {
// location: 'San Francisco, CA',
// temperature: 72,
// unit: 'fahrenheit',
// condition: 'sunny',
// humidity: 45,
// timestamp: '2025-06-03T12:34:56.789Z'
// },
// functionName: 'getWeather'
// }

Processing Function Calls from AI Modelsโ€‹

// Process a function call from an AI model
const functionCall = {
name: 'getWeather',
arguments: '{"location": "New York, NY", "unit": "celsius"}',
};

const callResult = await functionManager.processFunctionCall(functionCall);

console.log('Call result:', callResult);
// Output:
// Call result: {
// status: 'success',
// result: {
// location: 'New York, NY',
// temperature: 22,
// unit: 'celsius',
// condition: 'cloudy',
// humidity: 65,
// timestamp: '2025-06-03T12:34:56.789Z'
// },
// functionName: 'getWeather'
// }

Advanced Usageโ€‹

Function Categories and Filteringโ€‹

// Register functions with categories
functionManager.registerFunction({
name: 'translateText',
description: 'Translate text to another language',
parameters: {
type: 'object',
properties: {
text: { type: 'string' },
targetLanguage: { type: 'string' },
},
required: ['text', 'targetLanguage'],
},
category: 'language',
});

// Get functions by category
const languageFunctions = functionManager.getAllFunctionDefinitions(
def => def.category === 'language'
);

// Get functions by tag
const apiFunctions = functionManager.getAllFunctionDefinitions(
def => def.tags && def.tags.includes('external-api')
);

Integration with AI Streamingโ€‹

import [AiStreamingServiceImpl] from '@bluefly/mcp';

// Initialize streaming service
const streamingService = new AiStreamingServiceImpl(logger, stateManager, configManager);

// Use in an Express route with function calling
app.post('/stream-with-functions', (req, res) => {
const { prompt, functions } = req.body;

return streamingService.createStreamingResponse(async (chunkHandler) => {
try {
// Get available functions
const availableFunctions = functions.map(name =>
functionManager.getFunctionDefinition(name)
).filter(Boolean);

// First output is the thinking process
chunkHandler('Thinking about the request...', { tokensUsed: 3 });

// Execute a function if needed (simulate AI deciding to call a function)
if (prompt.toLowerCase().includes('weather') && availableFunctions.some(f => f.name === 'getWeather')) {
chunkHandler('\n\nI need to check the weather information. Calling getWeather function...', { tokensUsed: 8 });

const result = await functionManager.executeFunction('getWeather', {
location: 'San Francisco, CA',
});

if (result.status === 'success') {
const weather = result.result;
chunkHandler(`\n\nI found that the weather in [weather.location] is [weather.condition] with a temperature of [weather.temperature]ยฐ[weather.unit === 'celsius' ? 'C' : 'F'] and [weather.humidity]% humidity.`, {
tokensUsed: 20,
functionCalls: [name: 'getWeather', result: result.result],
});
} else {
chunkHandler(`\n\nI tried to check the weather, but encountered an error: [result.error]`, {
tokensUsed: 12,
errors: [result.error],
});
}
} else {
chunkHandler('\n\nI can provide information without calling any functions for this request.', { tokensUsed: 10 });
}

// Conclude the response
chunkHandler('\n\nIs there anything else you would like to know?', { tokensUsed: 8 });
} catch (error) {
chunkHandler(`\n\nAn error occurred: [error.message]`, { error: error.message });
}
});
});

Security Considerationsโ€‹

Function Validationโ€‹

// Validate function parameters
function validateParameters(params, schema) {
// Check required properties
if (schema.required) {
for (const required of schema.required) {
if (params[required] === undefined) {
return { valid: false, error: `Missing required parameter: [required]` };
}
}
}

// Check property types
for (const [key, value] of Object.entries(params)) {
const propSchema = schema.properties[key];
if (!propSchema) continue;

if (propSchema.type === 'number' && typeof value !== 'number') {
return { valid: false, error: `Parameter Key must be a number` };
}

if (propSchema.type === 'string' && typeof value !== 'string') {
return { valid: false, error: `Parameter Key must be a string` };
}

if (propSchema.type === 'boolean' && typeof value !== 'boolean') {
return { valid: false, error: `Parameter Key must be a boolean` };
}

if (propSchema.enum && !propSchema.enum.includes(value)) {
return { valid: false, error: `Parameter Key must be one of: [propSchema.enum.join(', ')]` };
}
}

return { valid: true };
}

Permissions and Scopesโ€‹

// Define function permissions
const functionPermissions = {
'getWeather': ['basic:read'],
'translateText': ['basic:read'],
'createUser': ['admin:write'],
'deleteRecord': ['admin:write'],
};

// Check if user has permission to execute a function
function canExecuteFunction(user, functionName) {
const requiredPermissions = functionPermissions[functionName] || [];

// Check if user has all required permissions
return requiredPermissions.every(permission => user.permissions.includes(permission));
}

// Wrap function manager with permission checks
class SecureFunctionManager {
constructor(functionManager, getUserPermissions) {
this.functionManager = functionManager;
this.getUserPermissions = getUserPermissions;
}

async executeFunction(name, parameters, user) {
// Check if user has permission
if (!canExecuteFunction(user, name)) {
return {
status: 'error',
error: 'Permission denied',
functionName: name,
};
}

// Execute the function
return this.functionManager.executeFunction(name, parameters);
}
}

Integration with Drupalโ€‹

Configuring Function Calling in Drupalโ€‹

// In Drupal services.yml
services:
drupal_native_ai.function_manager:
class: Drupal\drupal_native_ai\Service\FunctionManager
arguments: ['@logger.factory', '@config.factory']

drupal_native_ai.function_registry:
class: Drupal\drupal_native_ai\Service\FunctionRegistry
arguments: ['@drupal_native_ai.function_manager', '@plugin.manager.function_handler']

Implementing a Function in Drupalโ€‹

<?php

namespace Drupal\my_module\Plugin\FunctionHandler;

use Drupal\drupal_native_ai\Plugin\FunctionHandlerBase;

/**
* @FunctionHandler(
* id = "get_drupal_content",
* label = "Get Drupal Content",
* description = "Retrieve content from Drupal"
* )
*/
class GetDrupalContent extends FunctionHandlerBase [/**
* {@inheritdoc]
*/
public function getDefinition() {
return [
'name' => 'getDrupalContent',
'description' => 'Get content from Drupal by type and ID',
'parameters' => [
'type' => 'object',
'properties' => [
'contentType' => [
'type' => 'string',
'description' => 'The content type',
],
'contentId' => [
'type' => 'string',
'description' => 'The content ID',
],
],
'required' => ['contentType', 'contentId'],
],
];
}

/**
* [@inheritdoc]
*/
public function execute(array $parameters) {
// Implementation to get Drupal content
$contentType = $parameters['contentType'];
$contentId = $parameters['contentId'];

// Fetch content from Drupal
// ... implementation ...

return [
'status' => 'success',
'result' => [
'title' => 'Content Title',
'body' => 'Content Body',
'created' => '2025-06-03T12:34:56',
'type' => $contentType,
'id' => $contentId,
],
];
}
}

Error Handlingโ€‹

Handling Function Execution Errorsโ€‹

try {
const result = await functionManager.executeFunction('riskyFunction', parameters);

if (result.status === 'error') {
console.error('Function execution error:', result.error);
// Handle error gracefully
} else [// Process successful result]
} catch (unexpectedError) {
console.error('Unexpected error:', unexpectedError);
// Handle unexpected errors
}

Implementing Retry Logicโ€‹

async function executeWithRetry(functionName, parameters, maxRetries = 3) {
let lastError;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await functionManager.executeFunction(functionName, parameters);

if (result.status === 'success') {
return result;
} else {
lastError = new Error(result.error);
console.warn(`Attempt [attempt]/[maxRetries] failed: [result.error]`);
}
} catch (error) {
lastError = error;
console.warn(`Attempt [attempt]/[maxRetries] failed with exception: [error.message]`);
}

if (attempt < maxRetries) {
// Exponential backoff
const delay = 1000 * Math.pow(2, attempt - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}
}

throw lastError || new Error(`Failed after [maxRetries] attempts`);
}

Additional Resourcesโ€‹