Skip to main content
Tool calling lets the LLM decide when and how to invoke your functions, then incorporates the results into its response — all running on-device for maximum privacy.

Basic Usage

import { RunAnywhere, ToolDefinition, ToolCallingResult } from '@runanywhere/core'

// 1. Define a tool
const weatherTool: ToolDefinition = {
  name: 'get_weather',
  description: 'Get the current weather for a given city',
  parameters: [
    {
      name: 'city',
      type: 'string',
      description: 'The city name, e.g. "San Francisco"',
      required: true,
    },
  ],
}

// 2. Register it with an executor
RunAnywhere.registerTool(weatherTool, async (args) => {
  return { temperature: 72, condition: 'sunny' }
})

// 3. Generate with tools
const result: ToolCallingResult = await RunAnywhere.generateWithTools(
  'What is the weather in San Francisco?',
  { tools: [weatherTool], autoExecute: true }
)

console.log(result.text) // "The weather in San Francisco is 72°F and sunny."
console.log(result.toolCalls) // [{ toolName: 'get_weather', arguments: { city: 'San Francisco' } }]
console.log(result.toolResults) // [{ toolName: 'get_weather', success: true, result: { ... } }]

Setup

Defining Tools

Each tool is described by a ToolDefinition that tells the LLM what the tool does and what arguments it accepts.
import { ToolDefinition } from '@runanywhere/core'

const tools: ToolDefinition[] = [
  {
    name: 'get_weather',
    description: 'Get the current weather for a given city',
    parameters: [
      {
        name: 'city',
        type: 'string',
        description: 'The city name, e.g. "San Francisco"',
        required: true,
      },
      {
        name: 'unit',
        type: 'string',
        description: 'Temperature unit: "celsius" or "fahrenheit"',
        required: false,
        defaultValue: 'celsius',
        enum: ['celsius', 'fahrenheit'],
      },
    ],
  },
  {
    name: 'calculate',
    description: 'Evaluate a mathematical expression',
    parameters: [
      {
        name: 'expression',
        type: 'string',
        description: 'Math expression, e.g. "2 + 3 * 4"',
        required: true,
      },
    ],
  },
]

Registering & Clearing Tools

// Clear any previously registered tools
RunAnywhere.clearTools()

// Register each tool with its executor function
RunAnywhere.registerTool(tools[0], async (args: Record<string, unknown>) => {
  const city = args.city as string
  const unit = (args.unit as string) ?? 'celsius'
  // Call a weather API or return mock data
  return { temperature: unit === 'celsius' ? 22 : 72, condition: 'sunny', city }
})

RunAnywhere.registerTool(tools[1], async (args: Record<string, unknown>) => {
  const expr = args.expression as string
  try {
    const result = Function(`"use strict"; return (${expr})`)()
    return { result: Number(result) }
  } catch {
    return { error: 'Invalid expression' }
  }
})
Always call RunAnywhere.clearTools() before re-registering tools to avoid duplicates — for example, when your component remounts.

API Reference

RunAnywhere.registerTool()

Register a tool definition with its executor function.
RunAnywhere.registerTool(
  definition: ToolDefinition,
  executor: (args: Record<string, unknown>) => Promise<Record<string, unknown>>
): void
ParameterTypeDescription
definitionToolDefinitionThe tool’s name, description, and parameters
executor(args: Record<string, unknown>) => Promise<Record<string, unknown>>Async function invoked when the LLM calls this tool

RunAnywhere.clearTools()

Remove all registered tools.
RunAnywhere.clearTools(): void

RunAnywhere.generateWithTools()

Run a full tool-calling loop: generate → parse → execute → re-generate until the LLM produces a final text response or the call limit is reached.
await RunAnywhere.generateWithTools(
  prompt: string,
  options?: ToolCallingOptions
): Promise<ToolCallingResult>
ParameterTypeDescription
promptstringThe user’s input prompt
optionsToolCallingOptions?Generation and tool options

RunAnywhere.parseToolCall()

Manually parse raw LLM output to extract a tool call. Useful when you want full control over the tool-calling loop instead of using autoExecute.
await RunAnywhere.parseToolCall(
  llmOutput: string
): Promise<ParsedToolCall>
ParameterTypeDescription
llmOutputstringRaw text output from the LLM

Types

ToolDefinition

interface ToolDefinition {
  name: string
  description: string
  parameters: ToolParameter[]
}

ToolParameter

interface ToolParameter {
  name: string
  type: 'string' | 'number' | 'boolean' | 'object' | 'array'
  description: string
  required?: boolean
  defaultValue?: string
  enum?: string[]
}
FieldTypeDescription
namestringParameter name
typestringOne of string, number, boolean, object, array
descriptionstringHuman-readable description for the LLM
requiredboolean?Whether the parameter is required (default: false)
defaultValuestring?Default value when the parameter is omitted
enumstring[]?Allowed values — constrains the LLM’s choices

ToolCallingOptions

interface ToolCallingOptions {
  tools?: ToolDefinition[]
  maxToolCalls?: number
  autoExecute?: boolean
  temperature?: number
  maxTokens?: number
}
FieldTypeDefaultDescription
toolsToolDefinition[]?Override registered tools for this call
maxToolCallsnumber?5Maximum tool invocations per generation
autoExecuteboolean?trueAutomatically execute parsed tool calls
temperaturenumber?0.7Sampling temperature (0.0–2.0)
maxTokensnumber?256Maximum tokens to generate

ToolCallingResult

interface ToolCallingResult {
  text: string
  toolCalls: Array<{
    toolName: string
    arguments: Record<string, unknown>
  }>
  toolResults: Array<{
    toolName: string
    success: boolean
    result?: unknown
    error?: string
  }>
}
FieldTypeDescription
textstringFinal text response from the LLM
toolCallsArrayAll tool calls the LLM made during generation
toolResultsArrayExecution results for each tool call

ParsedToolCall

interface ParsedToolCall {
  toolCall?: {
    toolName: string
    arguments: Record<string, unknown>
  }
  text: string
}
FieldTypeDescription
toolCallobject?Extracted tool call, if the LLM invoked one
textstringRemaining text after tool-call extraction

Examples

Complete React Native Component

A full example with weather, calculator, and time tools wired into a chat-style UI.
ToolCallingDemo.tsx
import React, { useState, useCallback, useEffect } from 'react'
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  ActivityIndicator,
  StyleSheet,
} from 'react-native'
import {
  RunAnywhere,
  ToolDefinition,
  ToolCallingResult,
} from '@runanywhere/core'

const TOOLS: ToolDefinition[] = [
  {
    name: 'get_weather',
    description: 'Get the current weather for a given city',
    parameters: [
      { name: 'city', type: 'string', description: 'City name', required: true },
      {
        name: 'unit',
        type: 'string',
        description: 'Temperature unit',
        required: false,
        defaultValue: 'fahrenheit',
        enum: ['celsius', 'fahrenheit'],
      },
    ],
  },
  {
    name: 'calculate',
    description: 'Evaluate a mathematical expression',
    parameters: [
      { name: 'expression', type: 'string', description: 'Math expression', required: true },
    ],
  },
  {
    name: 'get_time',
    description: 'Get the current time in a given timezone',
    parameters: [
      {
        name: 'timezone',
        type: 'string',
        description: 'IANA timezone, e.g. "America/New_York"',
        required: true,
      },
    ],
  },
]

function registerAllTools(): void {
  RunAnywhere.clearTools()

  RunAnywhere.registerTool(TOOLS[0], async (args) => {
    const city = args.city as string
    const unit = (args.unit as string) ?? 'fahrenheit'
    const temp = unit === 'celsius' ? 22 : 72
    return { city, temperature: temp, unit, condition: 'partly cloudy' }
  })

  RunAnywhere.registerTool(TOOLS[1], async (args) => {
    const expr = args.expression as string
    try {
      const value = Function(`"use strict"; return (${expr})`)()
      return { expression: expr, result: Number(value) }
    } catch {
      return { expression: expr, error: 'Could not evaluate expression' }
    }
  })

  RunAnywhere.registerTool(TOOLS[2], async (args) => {
    const tz = args.timezone as string
    try {
      const time = new Date().toLocaleTimeString('en-US', { timeZone: tz })
      return { timezone: tz, currentTime: time }
    } catch {
      return { timezone: tz, error: 'Invalid timezone' }
    }
  })
}

interface Message {
  role: 'user' | 'assistant'
  text: string
  toolCalls?: ToolCallingResult['toolCalls']
}

export default function ToolCallingDemo(): React.JSX.Element {
  const [prompt, setPrompt] = useState('')
  const [messages, setMessages] = useState<Message[]>([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    registerAllTools()
  }, [])

  const handleSend = useCallback(async () => {
    const trimmed = prompt.trim()
    if (!trimmed || loading) return

    setMessages((prev) => [...prev, { role: 'user', text: trimmed }])
    setPrompt('')
    setLoading(true)

    try {
      const result = await RunAnywhere.generateWithTools(trimmed, {
        tools: TOOLS,
        maxToolCalls: 3,
        autoExecute: true,
        temperature: 0.7,
        maxTokens: 512,
      })

      setMessages((prev) => [
        ...prev,
        { role: 'assistant', text: result.text, toolCalls: result.toolCalls },
      ])
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Tool calling failed'
      setMessages((prev) => [...prev, { role: 'assistant', text: `Error: ${message}` }])
    } finally {
      setLoading(false)
    }
  }, [prompt, loading])

  return (
    <View style={styles.container}>
      <ScrollView style={styles.messages}>
        {messages.map((msg, i) => (
          <View key={i} style={msg.role === 'user' ? styles.userBubble : styles.aiBubble}>
            <Text style={styles.messageText}>{msg.text}</Text>
            {msg.toolCalls?.map((tc, j) => (
              <Text key={j} style={styles.toolCallText}>
                🔧 {tc.toolName}({JSON.stringify(tc.arguments)})
              </Text>
            ))}
          </View>
        ))}
        {loading && <ActivityIndicator style={styles.loader} />}
      </ScrollView>
      <View style={styles.inputRow}>
        <TextInput
          style={styles.input}
          value={prompt}
          onChangeText={setPrompt}
          placeholder="Ask about weather, math, or time..."
        />
        <TouchableOpacity style={styles.sendButton} onPress={handleSend} disabled={loading}>
          <Text style={styles.sendText}>Send</Text>
        </TouchableOpacity>
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#f5f5f5' },
  messages: { flex: 1, padding: 16 },
  userBubble: {
    alignSelf: 'flex-end', backgroundColor: '#007AFF', borderRadius: 16,
    padding: 12, marginBottom: 8, maxWidth: '80%',
  },
  aiBubble: {
    alignSelf: 'flex-start', backgroundColor: '#fff', borderRadius: 16,
    padding: 12, marginBottom: 8, maxWidth: '80%',
    shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 4, elevation: 1,
  },
  messageText: { fontSize: 15, color: '#1a1a1a' },
  toolCallText: { fontSize: 12, color: '#888', marginTop: 4, fontFamily: 'monospace' },
  loader: { marginVertical: 12 },
  inputRow: { flexDirection: 'row', padding: 12, backgroundColor: '#fff' },
  input: {
    flex: 1, backgroundColor: '#f0f0f0', borderRadius: 20,
    paddingHorizontal: 16, paddingVertical: 10, fontSize: 15,
  },
  sendButton: {
    marginLeft: 8, backgroundColor: '#007AFF', borderRadius: 20,
    paddingHorizontal: 20, justifyContent: 'center',
  },
  sendText: { color: '#fff', fontWeight: '600' },
})

Manual Tool Call Parsing

Use parseToolCall() when you need to inspect or modify tool calls before execution.
const llmOutput = await RunAnywhere.generate('What is the weather in Tokyo?', {
  maxTokens: 256,
})

const parsed = await RunAnywhere.parseToolCall(llmOutput.text)

if (parsed.toolCall) {
  console.log('Tool:', parsed.toolCall.toolName) // "get_weather"
  console.log('Args:', parsed.toolCall.arguments) // { city: "Tokyo" }
  console.log('Remaining text:', parsed.text)

  // Execute manually, apply rate limits, log, etc.
} else {
  console.log('No tool call found — plain text response')
}

Multi-Step Tool Chain

The LLM can chain multiple tool calls in a single generation when maxToolCalls > 1.
const result = await RunAnywhere.generateWithTools(
  'What is 15% of the temperature in New York right now?',
  {
    tools: TOOLS,
    maxToolCalls: 3,
    autoExecute: true,
  }
)

// The LLM will:
// 1. Call get_weather({ city: "New York" })  → { temperature: 72 }
// 2. Call calculate({ expression: "72 * 0.15" }) → { result: 10.8 }
// 3. Return a final text answer

console.log(result.text)
// "15% of the current temperature in New York (72°F) is 10.8°F."
console.log(result.toolCalls.length) // 2

Error Handling

Tool executors should always return a result object — never throw. If a tool fails, return an object with an error field so the LLM can recover gracefully.
RunAnywhere.registerTool(weatherTool, async (args) => {
  const city = args.city as string

  try {
    const response = await fetch(`https://api.weather.example/v1?city=${encodeURIComponent(city)}`)
    if (!response.ok) {
      return { error: `Weather API returned ${response.status}` }
    }
    const data = await response.json()
    return { temperature: data.temp, condition: data.condition }
  } catch (err) {
    return { error: err instanceof Error ? err.message : 'Network request failed' }
  }
})

Common Error Scenarios

ScenarioCauseResolution
Tool not foundTool name in LLM output doesn’t match any registered toolVerify ToolDefinition.name matches exactly
Argument type mismatchLLM passes wrong type for a parameterCast defensively in the executor; provide clear description values
Max calls exceededLLM enters a tool-call loopLower maxToolCalls or refine tool descriptions
Executor timeoutExternal API call hangsAdd AbortController timeouts inside executors
No tool call parsedModel doesn’t emit a tool-call tokenUse a model that supports tool calling; check prompt clarity

Platform Differences

The React Native SDK uses RunAnywhere directly for tool calling — there is no separate ToolCalling class like the Web SDK.
FeatureReact Native (@runanywhere/core)Web (@runanywhere/web)
Entry pointRunAnywhere.registerTool()ToolCalling.registerTool()
Executor argument typeRecord<string, unknown>Record<string, ToolValue>
Manual parsingRunAnywhere.parseToolCall()Not available
Parameter enum fieldSupportedenumValues (different name)
Parameter defaultValueSupportedNot available