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 aToolDefinition 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
| Parameter | Type | Description |
|---|---|---|
definition | ToolDefinition | The 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>
| Parameter | Type | Description |
|---|---|---|
prompt | string | The user’s input prompt |
options | ToolCallingOptions? | 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>
| Parameter | Type | Description |
|---|---|---|
llmOutput | string | Raw 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[]
}
| Field | Type | Description |
|---|---|---|
name | string | Parameter name |
type | string | One of string, number, boolean, object, array |
description | string | Human-readable description for the LLM |
required | boolean? | Whether the parameter is required (default: false) |
defaultValue | string? | Default value when the parameter is omitted |
enum | string[]? | Allowed values — constrains the LLM’s choices |
ToolCallingOptions
interface ToolCallingOptions {
tools?: ToolDefinition[]
maxToolCalls?: number
autoExecute?: boolean
temperature?: number
maxTokens?: number
}
| Field | Type | Default | Description |
|---|---|---|---|
tools | ToolDefinition[]? | — | Override registered tools for this call |
maxToolCalls | number? | 5 | Maximum tool invocations per generation |
autoExecute | boolean? | true | Automatically execute parsed tool calls |
temperature | number? | 0.7 | Sampling temperature (0.0–2.0) |
maxTokens | number? | 256 | Maximum 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
}>
}
| Field | Type | Description |
|---|---|---|
text | string | Final text response from the LLM |
toolCalls | Array | All tool calls the LLM made during generation |
toolResults | Array | Execution results for each tool call |
ParsedToolCall
interface ParsedToolCall {
toolCall?: {
toolName: string
arguments: Record<string, unknown>
}
text: string
}
| Field | Type | Description |
|---|---|---|
toolCall | object? | Extracted tool call, if the LLM invoked one |
text | string | Remaining 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
UseparseToolCall() 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 whenmaxToolCalls > 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
| Scenario | Cause | Resolution |
|---|---|---|
| Tool not found | Tool name in LLM output doesn’t match any registered tool | Verify ToolDefinition.name matches exactly |
| Argument type mismatch | LLM passes wrong type for a parameter | Cast defensively in the executor; provide clear description values |
| Max calls exceeded | LLM enters a tool-call loop | Lower maxToolCalls or refine tool descriptions |
| Executor timeout | External API call hangs | Add AbortController timeouts inside executors |
| No tool call parsed | Model doesn’t emit a tool-call token | Use 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.| Feature | React Native (@runanywhere/core) | Web (@runanywhere/web) |
|---|---|---|
| Entry point | RunAnywhere.registerTool() | ToolCalling.registerTool() |
| Executor argument type | Record<string, unknown> | Record<string, ToolValue> |
| Manual parsing | RunAnywhere.parseToolCall() | Not available |
Parameter enum field | Supported | enumValues (different name) |
Parameter defaultValue | Supported | Not available |
Related
LLM Generation
Full text generation API
Streaming
Real-time token streaming
System Prompts
Control AI behavior
Error Handling
SDK error reference