CactusCactus

Function Calling

Enable structured outputs and tool use with on-device language models

Defining Tools

Tools are defined as JSON schemas that describe available functions:

const tools = [
  {
    name: 'get_weather',
    description: 'Get weather for a location',
    parameters: {
      type: 'object',
      properties: {
        location: { type: 'string', description: 'City name' }
      },
      required: ['location']
    }
  }
];
final tools = [
  {
    'name': 'get_weather',
    'description': 'Get weather for a location',
    'parameters': {
      'type': 'object',
      'properties': {
        'location': {'type': 'string', 'description': 'City name'}
      },
      'required': ['location']
    }
  }
];
val tools = listOf(
    mapOf(
        "name" to "get_weather",
        "description" to "Get weather for a location",
        "parameters" to mapOf(
            "type" to "object",
            "properties" to mapOf(
                "location" to mapOf("type" to "string", "description" to "City name")
            ),
            "required" to listOf("location")
        )
    )
)
const char* tools = R"([
    {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string"}
            },
            "required": ["location"]
        }
    }
])";

Calling with Tools

Pass tools to the completion call and parse the structured response:

import { CactusLM } from 'cactus-react-native';

const cactusLM = new CactusLM();
await cactusLM.download();
await cactusLM.init();

const result = await cactusLM.complete({
  messages: [{ role: 'user', content: "What's the weather in SF?" }],
  tools
});

console.log(result.functionCalls);
// [{ name: 'get_weather', arguments: { location: 'San Francisco' } }]
final result = model.completeMessages(
  [Message.user("What's the weather in Paris?")],
  tools: tools,
);

if (result.functionCalls != null) {
  for (final call in result.functionCalls!) {
    print('Function: ${call['name']}');
    print('Arguments: ${call['arguments']}');
  }
}
val result = model.complete(
    messages = listOf(Message.user("What's the weather in Paris?")),
    tools = tools
)

result.functionCalls?.forEach { call ->
    println("Function: ${call["name"]}")
    println("Arguments: ${call["arguments"]}")
}
char response[4096];
cactus_complete(
    model, messages, response, sizeof(response),
    nullptr,  // use default options
    tools,    // pass tools JSON
    nullptr,
    nullptr
);

The response JSON includes parsed function calls:

{
    "success": true,
    "function_calls": [{
        "name": "get_weather",
        "arguments": {"location": "Paris"}
    }],
    "response": null,
    "confidence": 0.91
}

Multi-Tool Example

You can define multiple tools and the model will select the appropriate one:

const tools = [
  {
    name: 'get_weather',
    description: 'Get weather for a location',
    parameters: {
      type: 'object',
      properties: {
        location: { type: 'string', description: 'City name' }
      },
      required: ['location']
    }
  },
  {
    name: 'search_web',
    description: 'Search the web for information',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' }
      },
      required: ['query']
    }
  }
];

const result = await cactusLM.complete({
  messages: [{ role: 'user', content: 'Look up the latest news about AI' }],
  tools
});

// Model picks the right tool
// [{ name: 'search_web', arguments: { query: 'latest AI news' } }]

Tool Schema Reference

interface Tool {
  name: string;
  description: string;
  parameters: {
    type: 'object';
    properties: Record<string, {
      type: string;          // 'string', 'number', 'boolean', 'array', 'object'
      description?: string;
    }>;
    required?: string[];
  };
}

Tips

  • Keep descriptions clear — The model uses tool descriptions to decide which function to call
  • Use required fields — Mark parameters as required when they are always needed
  • Smaller models may struggle with complex multi-tool scenarios; use larger models for reliability
  • Cloud handoff — If the model confidence is low on a tool call, consider routing to Cactus Cloud for better accuracy