Skip to main content
Enable on-device LLMs to invoke your Dart functions by registering typed tool definitions. The SDK handles argument parsing, execution, and result formatting automatically.
Tool calling uses the RunAnywhereTools class, not RunAnywhere. All tool registration and generation methods live on RunAnywhereTools.

Basic Usage

import 'package:runanywhere/runanywhere.dart';

// 1. Clear any previously registered tools
RunAnywhereTools.clearTools();

// 2. Register a tool
RunAnywhereTools.registerTool(
  const ToolDefinition(
    name: 'get_weather',
    description: 'Gets the current weather for a location',
    parameters: [
      ToolParameter(
        name: 'location',
        type: ToolParameterType.string,
        description: 'City name, e.g. "San Francisco"',
      ),
    ],
    category: 'Utility',
  ),
  _fetchWeather,
);

// 3. Generate with tools
final result = await RunAnywhereTools.generateWithTools(
  'What is the weather in San Francisco?',
  options: const ToolCallingOptions(
    maxToolCalls: 3,
    autoExecute: true,
    temperature: 0.7,
    maxTokens: 512,
  ),
);

print(result.text);        // "The weather in San Francisco is 72°F and sunny."
print(result.toolCalls);   // [ToolCall(toolName: 'get_weather', ...)]
print(result.toolResults); // [ToolResult(success: true, ...)]

Setup

1. Import the SDK

import 'package:runanywhere/runanywhere.dart';

2. Define Tool Executors

Every tool executor must match the signature Future<Map<String, ToolValue>> Function(Map<String, ToolValue>):
Future<Map<String, ToolValue>> _fetchWeather(
  Map<String, ToolValue> args,
) async {
  final location = args['location']?.stringValue ?? 'Unknown';

  // Call your weather service, local DB, or any async Dart code
  return {
    'temperature': NumberToolValue(72.0),
    'condition': StringToolValue('sunny'),
    'location': StringToolValue(location),
  };
}

3. Register Tools

RunAnywhereTools.clearTools();

RunAnywhereTools.registerTool(
  const ToolDefinition(
    name: 'get_weather',
    description: 'Gets the current weather for a location',
    parameters: [
      ToolParameter(
        name: 'location',
        type: ToolParameterType.string,
        description: 'City name, e.g. "San Francisco"',
      ),
    ],
    category: 'Utility',
  ),
  _fetchWeather,
);
Always call RunAnywhereTools.clearTools() before registering tools to avoid duplicates when hot-reloading during development.

API Reference

RunAnywhereTools

MethodReturn TypeDescription
registerTool(definition, executor)voidRegister a tool with its definition and executor function
clearTools()voidRemove all registered tools
generateWithTools(prompt, {options})Future<ToolCallingResult>Run the full orchestration loop: generate → parse → execute → repeat

ToolDefinition

PropertyTypeRequiredDescription
nameStringYesUnique identifier for the tool
descriptionStringYesHuman-readable description the LLM uses to decide when to call the tool
parametersList<ToolParameter>YesTyped parameter definitions
categoryStringNoLogical grouping (e.g. 'Utility', 'Data')

ToolParameter

PropertyTypeRequiredDescription
nameStringYesParameter name
typeToolParameterTypeYesOne of string, number, boolean, object, array
descriptionStringYesWhat this parameter represents

ToolCallingOptions

PropertyTypeDefaultDescription
maxToolCallsint5Maximum tool invocations per generation
autoExecutebooltrueAutomatically execute tool calls and feed results back to the LLM
temperaturedouble0.8Sampling temperature (0.0–2.0)
maxTokensint256Maximum tokens in the final response

ToolCallingResult

PropertyTypeDescription
textStringFinal text response after all tool calls complete
toolCallsList<ToolCall>Every tool call the LLM requested; each has toolName (String) and arguments (Map<String, ToolValue>)
toolResultsList<ToolResult>Execution results; each has result (Map<String, ToolValue>?), error (String?), and success (bool)

ToolValue Types

ToolValue is a Dart 3 sealed class. Create instances with the concrete subtypes and read values with the stringValue getter or pattern matching.
SubtypeConstructorDescription
StringToolValueStringToolValue('hello')String value
NumberToolValueNumberToolValue(42.0)Numeric value (double)
BoolToolValueBoolToolValue(true)Boolean value
NullToolValueNullToolValue()Explicit null
ArrayToolValueArrayToolValue()Array / list value
ObjectToolValueObjectToolValue()Nested object value
Access any value as a string via the .stringValue getter:
final temp = args['temperature'];
print(temp?.stringValue); // "72.0"

Pattern Matching (Dart 3)

Use exhaustive switch expressions on the sealed class:
String formatToolValue(ToolValue value) {
  return switch (value) {
    StringToolValue sv => sv.stringValue,
    NumberToolValue nv => nv.value.toString(),
    BoolToolValue bv   => bv.value.toString(),
    NullToolValue()    => 'null',
    ArrayToolValue()   => '[array]',
    ObjectToolValue()  => '{object}',
  };
}

Examples

Weather Tool

Future<Map<String, ToolValue>> _fetchWeather(
  Map<String, ToolValue> args,
) async {
  final location = args['location']?.stringValue ?? 'Unknown';
  // Simulate API call
  return {
    'temperature': NumberToolValue(72.0),
    'condition': StringToolValue('sunny'),
    'location': StringToolValue(location),
  };
}

RunAnywhereTools.registerTool(
  const ToolDefinition(
    name: 'get_weather',
    description: 'Gets the current weather for a location',
    parameters: [
      ToolParameter(
        name: 'location',
        type: ToolParameterType.string,
        description: 'City name',
      ),
    ],
    category: 'Utility',
  ),
  _fetchWeather,
);

Calculator Tool

Future<Map<String, ToolValue>> _calculate(
  Map<String, ToolValue> args,
) async {
  final a = (args['a'] as NumberToolValue?)?.value ?? 0;
  final b = (args['b'] as NumberToolValue?)?.value ?? 0;
  final op = args['operation']?.stringValue ?? 'add';

  final result = switch (op) {
    'add'      => a + b,
    'subtract' => a - b,
    'multiply' => a * b,
    'divide'   => b != 0 ? a / b : double.nan,
    _          => double.nan,
  };

  return {'result': NumberToolValue(result)};
}

RunAnywhereTools.registerTool(
  const ToolDefinition(
    name: 'calculate',
    description: 'Performs basic arithmetic on two numbers',
    parameters: [
      ToolParameter(
        name: 'a',
        type: ToolParameterType.number,
        description: 'First operand',
      ),
      ToolParameter(
        name: 'b',
        type: ToolParameterType.number,
        description: 'Second operand',
      ),
      ToolParameter(
        name: 'operation',
        type: ToolParameterType.string,
        description: 'One of: add, subtract, multiply, divide',
      ),
    ],
    category: 'Math',
  ),
  _calculate,
);

Current Time Tool

Future<Map<String, ToolValue>> _getTime(
  Map<String, ToolValue> args,
) async {
  final timezone = args['timezone']?.stringValue ?? 'UTC';
  final now = DateTime.now().toUtc();

  return {
    'time': StringToolValue(now.toIso8601String()),
    'timezone': StringToolValue(timezone),
  };
}

RunAnywhereTools.registerTool(
  const ToolDefinition(
    name: 'get_current_time',
    description: 'Returns the current date and time',
    parameters: [
      ToolParameter(
        name: 'timezone',
        type: ToolParameterType.string,
        description: 'IANA timezone, e.g. "America/New_York"',
      ),
    ],
    category: 'Utility',
  ),
  _getTime,
);

Complete Flutter Widget

A full-screen widget that registers weather, calculator, and time tools, then runs a multi-tool conversation:
import 'package:flutter/material.dart';
import 'package:runanywhere/runanywhere.dart';

class ToolCallingScreen extends StatefulWidget {
  const ToolCallingScreen({super.key});

  @override
  State<ToolCallingScreen> createState() => _ToolCallingScreenState();
}

class _ToolCallingScreenState extends State<ToolCallingScreen> {
  String _response = '';
  List<String> _toolLog = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _registerTools();
  }

  void _registerTools() {
    RunAnywhereTools.clearTools();

    RunAnywhereTools.registerTool(
      const ToolDefinition(
        name: 'get_weather',
        description: 'Gets the current weather for a location',
        parameters: [
          ToolParameter(
            name: 'location',
            type: ToolParameterType.string,
            description: 'City name, e.g. "San Francisco"',
          ),
        ],
        category: 'Utility',
      ),
      _fetchWeather,
    );

    RunAnywhereTools.registerTool(
      const ToolDefinition(
        name: 'calculate',
        description: 'Performs basic arithmetic on two numbers',
        parameters: [
          ToolParameter(
            name: 'a',
            type: ToolParameterType.number,
            description: 'First operand',
          ),
          ToolParameter(
            name: 'b',
            type: ToolParameterType.number,
            description: 'Second operand',
          ),
          ToolParameter(
            name: 'operation',
            type: ToolParameterType.string,
            description: 'One of: add, subtract, multiply, divide',
          ),
        ],
        category: 'Math',
      ),
      _calculate,
    );

    RunAnywhereTools.registerTool(
      const ToolDefinition(
        name: 'get_current_time',
        description: 'Returns the current date and time',
        parameters: [
          ToolParameter(
            name: 'timezone',
            type: ToolParameterType.string,
            description: 'IANA timezone, e.g. "America/New_York"',
          ),
        ],
        category: 'Utility',
      ),
      _getTime,
    );
  }

  Future<Map<String, ToolValue>> _fetchWeather(
    Map<String, ToolValue> args,
  ) async {
    final location = args['location']?.stringValue ?? 'Unknown';
    return {
      'temperature': NumberToolValue(72.0),
      'condition': StringToolValue('sunny'),
      'location': StringToolValue(location),
    };
  }

  Future<Map<String, ToolValue>> _calculate(
    Map<String, ToolValue> args,
  ) async {
    final a = (args['a'] as NumberToolValue?)?.value ?? 0;
    final b = (args['b'] as NumberToolValue?)?.value ?? 0;
    final op = args['operation']?.stringValue ?? 'add';

    final result = switch (op) {
      'add'      => a + b,
      'subtract' => a - b,
      'multiply' => a * b,
      'divide'   => b != 0 ? a / b : double.nan,
      _          => double.nan,
    };

    return {'result': NumberToolValue(result)};
  }

  Future<Map<String, ToolValue>> _getTime(
    Map<String, ToolValue> args,
  ) async {
    final timezone = args['timezone']?.stringValue ?? 'UTC';
    final now = DateTime.now().toUtc();
    return {
      'time': StringToolValue(now.toIso8601String()),
      'timezone': StringToolValue(timezone),
    };
  }

  Future<void> _runPrompt(String prompt) async {
    setState(() {
      _isLoading = true;
      _response = '';
      _toolLog = [];
    });

    try {
      final result = await RunAnywhereTools.generateWithTools(
        prompt,
        options: const ToolCallingOptions(
          maxToolCalls: 3,
          autoExecute: true,
          temperature: 0.7,
          maxTokens: 512,
        ),
      );

      setState(() {
        _response = result.text;
        _toolLog = [
          for (final call in result.toolCalls)
            '→ ${call.toolName}(${call.arguments.map(
              (k, v) => MapEntry(k, v.stringValue),
            )})',
          for (final res in result.toolResults)
            res.success
                ? '✓ ${res.result}'
                : '✗ ${res.error}',
        ];
      });
    } catch (e) {
      setState(() => _response = 'Error: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Tool Calling Demo')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton(
                  onPressed: _isLoading
                      ? null
                      : () => _runPrompt(
                            'What is the weather in San Francisco?',
                          ),
                  child: const Text('Weather'),
                ),
                ElevatedButton(
                  onPressed: _isLoading
                      ? null
                      : () => _runPrompt('What is 42 * 17?'),
                  child: const Text('Calculate'),
                ),
                ElevatedButton(
                  onPressed: _isLoading
                      ? null
                      : () => _runPrompt('What time is it in UTC?'),
                  child: const Text('Time'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            if (_isLoading) const LinearProgressIndicator(),
            const SizedBox(height: 16),
            if (_toolLog.isNotEmpty) ...[
              Text(
                'Tool Activity',
                style: Theme.of(context).textTheme.titleSmall,
              ),
              const SizedBox(height: 4),
              for (final log in _toolLog)
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 2),
                  child: Text(log, style: const TextStyle(fontFamily: 'monospace', fontSize: 13)),
                ),
              const Divider(height: 24),
            ],
            if (_response.isNotEmpty)
              Expanded(
                child: SingleChildScrollView(
                  child: Text(_response, style: const TextStyle(fontSize: 16)),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Error Handling

Wrap generateWithTools in a try-catch and inspect individual ToolResult entries for per-tool errors:
try {
  final result = await RunAnywhereTools.generateWithTools(
    prompt,
    options: const ToolCallingOptions(maxToolCalls: 3, autoExecute: true),
  );

  for (final res in result.toolResults) {
    if (!res.success) {
      debugPrint('Tool failed: ${res.error}');
    }
  }
} on RunAnywhereException catch (e) {
  debugPrint('SDK error: ${e.message}');
} catch (e) {
  debugPrint('Unexpected error: $e');
}
If a tool executor throws, the SDK catches the exception and records it in the corresponding ToolResult.error field. The LLM receives the error message and can retry or respond gracefully.

Common Errors

ErrorCauseFix
ToolNotFoundLLM requested an unregistered toolVerify tool names match exactly
MaxToolCallsExceededHit the maxToolCalls limitIncrease the limit or simplify the prompt
ModelNotLoadedNo LLM model loadedCall RunAnywhere.loadModel(...) before generating
InvalidArgumentsArgument types don’t match the definitionCheck ToolParameterType in your ToolDefinition

See Also